Das grundsätzliche Problem ist eine schlechte Entwurfsentscheidung in der FolderBrowserDialog. Zunächst müssen wir erkennen, dass das FolderBrowserDialogkein .NET-Steuerelement ist, sondern das Common Dialogund Teil von Windows. Der Designer dieses Dialogfelds hat beschlossen, dem TreeView-Steuerelement keine TVM_ENSUREVISIBLENachricht zu senden , nachdem das Dialogfeld angezeigt und ein erster Ordner ausgewählt wurde. Diese Nachricht bewirkt, dass ein TreeView-Steuerelement einen Bildlauf durchführt, sodass das aktuell ausgewählte Element im Fenster sichtbar ist.
Alles, was wir tun müssen, um dies zu beheben, ist das Senden der TreeView, die Teil FolderBrowserDialogder TVM_ENSUREVISIBLENachricht ist, und alles wird großartig. Richtig? Na ja, nicht so schnell. Dies ist zwar die Antwort, aber einige Dinge stehen uns im Weg.
Erstens, da FolderBrowserDialoges sich nicht wirklich um ein .NET-Steuerelement handelt, verfügt es nicht über eine interne ControlsSammlung. Dies bedeutet, dass wir das untergeordnete TreeView-Steuerelement nicht einfach über .NET finden und darauf zugreifen können.
Zweitens haben die Designer der .NET- FolderBrowserDialogKlasse beschlossen, diese Klasse zu versiegeln . Diese unglückliche Entscheidung hindert uns daran, daraus abzuleiten und den Fenstermeldungshandler zu überschreiben. Wären wir dazu in der Lage gewesen, hätten wir möglicherweise versucht, die TVM_ENSUREVISIBLENachricht zu veröffentlichen, als wir die WM_SHOWWINDOWNachricht im Nachrichtenhandler erhalten haben.
Das dritte Problem ist, dass wir die TVM_ENSUREVISIBLENachricht erst senden können, wenn das Tree View-Steuerelement tatsächlich als reales Fenster vorhanden ist, und erst, wenn wir die ShowDialogMethode aufrufen . Diese Methode wird jedoch blockiert, sodass wir nach dem Aufruf dieser Methode keine Möglichkeit haben, unsere Nachricht zu veröffentlichen.
Um diese Probleme zu umgehen, habe ich eine statische Hilfsklasse mit einer einzigen Methode erstellt, mit der a angezeigt werden kann FolderBrowserDialog, und veranlasst, dass zum ausgewählten Ordner gescrollt wird. Ich schaffe dies, indem ich Timerkurz vor dem Aufrufen der Dialogmethode einen Kurzstart beginne ShowDialogund dann das Handle des TreeViewSteuerelements im TimerHandler aufspüre (dh nachdem der Dialog angezeigt wird) und unsere TVM_ENSUREVISIBLENachricht sende .
Diese Lösung ist nicht perfekt, da sie von einigen Vorkenntnissen über die abhängt FolderBrowserDialog. Insbesondere finde ich den Dialog anhand seines Fenstertitels. Dies wird bei nicht englischen Installationen unterbrochen. Ich verfolge die untergeordneten Steuerelemente im Dialog anhand ihrer Dialog-Element-IDs anstelle des Titeltextes oder des Klassennamens, da ich der Meinung war, dass dies mit der Zeit zuverlässiger sein würde.
Dieser Code wurde unter Windows 7 (64 Bit) und Windows XP getestet.
Hier ist der Code: (Möglicherweise müssen: using System.Runtime.InteropServices;)
public static class FolderBrowserLauncher
{
const string _topLevelSearchString = "Browse For Folder";
const int _dlgItemBrowseControl = 0;
const int _dlgItemTreeView = 100;
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr GetDlgItem(IntPtr hDlg, int nIDDlgItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
private const int TV_FIRST = 0x1100;
private const int TVM_SELECTITEM = (TV_FIRST + 11);
private const int TVM_GETNEXTITEM = (TV_FIRST + 10);
private const int TVM_GETITEM = (TV_FIRST + 12);
private const int TVM_ENSUREVISIBLE = (TV_FIRST + 20);
private const int TVGN_ROOT = 0x0;
private const int TVGN_NEXT = 0x1;
private const int TVGN_CHILD = 0x4;
private const int TVGN_FIRSTVISIBLE = 0x5;
private const int TVGN_NEXTVISIBLE = 0x6;
private const int TVGN_CARET = 0x9;
public static DialogResult ShowFolderBrowser( FolderBrowserDialog dlg, IWin32Window parent = null )
{
DialogResult result = DialogResult.Cancel;
int retries = 10;
using (Timer t = new Timer())
{
t.Tick += (s, a) =>
{
if (retries > 0)
{
--retries;
IntPtr hwndDlg = FindWindow((string)null, _topLevelSearchString);
if (hwndDlg != IntPtr.Zero)
{
IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgItemBrowseControl);
if (hwndFolderCtrl != IntPtr.Zero)
{
IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgItemTreeView);
if (hwndTV != IntPtr.Zero)
{
IntPtr item = SendMessage(hwndTV, (uint)TVM_GETNEXTITEM, new IntPtr(TVGN_CARET), IntPtr.Zero);
if (item != IntPtr.Zero)
{
SendMessage(hwndTV, TVM_ENSUREVISIBLE, IntPtr.Zero, item);
retries = 0;
t.Stop();
}
}
}
}
}
else
{
t.Stop();
SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}");
}
};
t.Interval = 10;
t.Start();
result = dlg.ShowDialog( parent );
}
return result;
}
}