Zwar hat das .NET-Framework mit den von der CommonDialog-Klassen ebernden Dialog-Klassen (dazu gehören OpenFileDialog, FolderBrowserDialog) schon Dialoge, welche die Funktion bieten Daten zu öffnen, zu speichern oder das Verzeichnis zu wählen. Diese sind aber leider nicht erweiterbar und daher nicht für sämtliche Zwecke zu gebrauchen.

In diesem Beitrag werde ich deshalb eine Möglichkeit aufzeigen, einen eigenen Dialog zu erstellen für verschiedene Zwecke zu erstellen.

Einfacher Dialog zum Öffnen von Dateien

Zum Erstellen eines einfachen OpenFileDialogs benötigen wir erstmal eine Form. In der Form platzieren wir ein TreeView, sowie zwei Buttons. Zusätzlich benötigen wir eine ImageList, welche die eigentlichen Icons der Dateien und Verzeichnisse zwischenspeichert und aus welcher wir die benötigten Icons beziehen werden.

Nachdem wir die benötigten Controls hinzugefügt haben, benötigen wir eine Methode, die uns alle Laufwerke in die TreeView lädt. Diese bilden nämlich jeweils den Stammknotenpunkt (RootTreeNode).

Die Methode sieht wir folgt aus und soll bei dem Aufruf der Form durchlaufen werden:

private void GetAllDrives()
{
	DriveInfo[] drives = DriveInfo.GetDrives();
	foreach (var drive in drives)
	{
		TreeNode rootTreeNode = new TreeNode();
		rootTreeNode.Text = drive.Name;
		rootTreeNode.Tag = drive.Name;
		rootTreeNode.ImageIndex = GetIconOfFile_Folder(drive.Name);
		rootTreeNode.SelectedImageIndex = rootTreeNode.ImageIndex;
		rootTreeNode.Nodes.Add(" "); //Placeholder to enable expanding (+)
		FolderAndFiles_treeView.Nodes.Add(rootTreeNode);
	}
}

Die Methode GetIconOfFile_Folder bezieht das Icon des Elements, wie es der Windows Explorer auch anzeigt.
Zum Beziehen wird der Namespace System.Runtime.InteropServices benötigt. Diesen binden wir per

using System.Runtime.InteropServices;

ein.
Folgender Code wird zum Beziehen der Icons benötigt:

[StructLayout(LayoutKind.Sequential)]
public struct SHFILEINFO
{
	public IntPtr hIcon;
	public IntPtr iIcon;
	public uint dwAttributes;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
	public string szDisplayName;
	[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
	public string szTypeName;
};

class Win32
{
	public const uint SHGFI_ICON = 0x100;
	public const uint SHGFI_LARGEICON = 0x0;    // 'Large icon
	public const uint SHGFI_SMALLICON = 0x1;    // 'Small icon

	[DllImport("shell32.dll")]
	public static extern IntPtr SHGetFileInfo(string pszPath,
								uint dwFileAttributes,
								ref SHFILEINFO psfi,
								uint cbSizeFileInfo,
								uint uFlags);
}

private int GetIconOfFile_Folder(string Path)
{
	IntPtr hImgSmall;    //the handle to the system image list
	SHFILEINFO shinfo = new SHFILEINFO();

	hImgSmall = Win32.SHGetFileInfo(Path, 0, ref shinfo,
								   (uint)Marshal.SizeOf(shinfo),
									Win32.SHGFI_ICON |
									Win32.SHGFI_SMALLICON);

	System.Drawing.Icon myIcon =
		   System.Drawing.Icon.FromHandle(shinfo.hIcon);

	FolderAndFiles_imageList.Images.Add(myIcon);

	return FolderAndFiles_imageList.Images.Count - 1; //Ab 0 (zero) wird angefangen, somit ist die Gesamtzahl n+1
}

Bisher werden nur die aufklappbaren Laufwerke angezeigt. Sie besitzen jedoch noch keine weiteren Knoten. Diese füllen wir indem wir einen EventHandler für das BeforeExpand-Event mittels

FolderAndFiles_treeView.BeforeExpand += FolderAndFiles_treeView_BeforeExpand;

erstellen.

Die dazugehörige Methode FolderAndFiles_treeView_BeforeExpand sieht wie folgt aus:

private void FolderAndFiles_treeView_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
	e.Node.Nodes.Clear();
	GetFilesAndFolder(e.Node, (string)e.Node.Tag);
}

Das Event ruft also die Methode GetFilesAndFolder auf, welche zuerst die unter dem erweiterten Knoten liegenden Knoten löscht und danach die Verzeichnisse und Dateien bezieht. Diese Methode benötigt zum einen den erweiterten Knoten, sowie das Verzeichnis aus welchem die Ordner und Dateien bezogen werden sollen (dies wurde im Tag gespeichert) als Parameter.
Die Methode selbst sieht wie folgt aus:

private void GetFilesAndFolder(TreeNode tn, string Path)
{
	try
	{
		string[] Directories = Directory.GetDirectories(Path);
		string[] Files = Directory.GetFiles(Path);

		foreach (string dir in Directories)
		{
			TreeNode dirTreeNode = new TreeNode();
			dirTreeNode.Tag = dir;
			dirTreeNode.Text = new DirectoryInfo(dir).Name;
			dirTreeNode.ImageIndex = GetIconOfFile_Folder(dir);
			dirTreeNode.SelectedImageIndex = dirTreeNode.ImageIndex;
			dirTreeNode.Nodes.Add(" ");
			tn.Nodes.Add(dirTreeNode);
		}

		foreach (string file in Files)
		{
			TreeNode fileTreeNode = new TreeNode();
			fileTreeNode.Tag = file;
			fileTreeNode.Text = new FileInfo(file).Name;
			fileTreeNode.ImageIndex = GetIconOfFile_Folder(file);
			fileTreeNode.SelectedImageIndex = fileTreeNode.ImageIndex;
			tn.Nodes.Add(fileTreeNode);
		}
	}
	catch (Exception ex)
	{
		MessageBox.Show(ex.Message, ex.Source, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
	}
}

Den schwierigsten Teil haben wir jetzt hinter uns, die Dateien und Verzeichnisse werden bezogen und in der TreeView angezeigt und die jeweiligen Icons bezogen.
Jetzt müssen wir lediglich noch die zwei Buttons belegen.
Ich habe dies so gelöst:
Abbrechen

private void Cancel_button_Click(object sender, EventArgs e)
{
	this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
	this.Close();
}

Annehmen

private void Accept_button_Click(object sender, EventArgs e)
{
	string filePath = (string)FolderAndFiles_treeView.SelectedNode.Tag;
	if (CheckIfPathIsFile(filePath) == true) // Sollte es sich um eine Datei handeln
	{
		this.FilePath = filePath; //Bei FilePath handelt es sich um ein public Property
		this.DialogResult = System.Windows.Forms.DialogResult.OK;
		this.Close();
	}
	else //Sollte es sich um ein Verzeichnis handeln
	{
		FolderAndFiles_treeView.SelectedNode.Expand(); //erweitere den aktuell gewählten Knoten
	}
}

In der Methode des AcceptButton-ClickEvents wird auch geprüft, ob es sich bei dem gewählten Element um eine Datei oder ein Verzeichnis handelt. Dies geschieht mit folgender Methode:

private bool CheckIfPathIsFile(string Path)
{
	FileAttributes attr = File.GetAttributes(Path);
	if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
		return false;
	else
		return true;
}

Es wird also Wahr zurückgegeben, wenn es sich um eine Datei handelt.

Zu guter Letzt müssen wir nur noch den FileDialog aufrufen, dies geschieht mit folgender Methode:

private void OpenSimpleFileDialog()
{
	SimpleOpenFileDialog simpleOpenFileDia = new SimpleOpenFileDialog();
	if(simpleOpenFileDia.ShowDialog() == System.Windows.Forms.DialogResult.OK)
		MessageBox.Show(simpleOpenFileDia.FilePath);
}

Das Ergebnis sieht wie folgt aus:

2013-12-02 14_43_34-SimpleOpenFileDialog