Das grundsätzliche Problem ist eine schlechte Entwurfsentscheidung in der FolderBrowserDialog
. Zunächst müssen wir erkennen, dass das FolderBrowserDialog
kein .NET-Steuerelement ist, sondern das Common Dialog
und Teil von Windows. Der Designer dieses Dialogfelds hat beschlossen, dem TreeView-Steuerelement keine TVM_ENSUREVISIBLE
Nachricht 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 FolderBrowserDialog
der TVM_ENSUREVISIBLE
Nachricht 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 FolderBrowserDialog
es sich nicht wirklich um ein .NET-Steuerelement handelt, verfügt es nicht über eine interne Controls
Sammlung. Dies bedeutet, dass wir das untergeordnete TreeView-Steuerelement nicht einfach über .NET finden und darauf zugreifen können.
Zweitens haben die Designer der .NET- FolderBrowserDialog
Klasse 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_ENSUREVISIBLE
Nachricht zu veröffentlichen, als wir die WM_SHOWWINDOW
Nachricht im Nachrichtenhandler erhalten haben.
Das dritte Problem ist, dass wir die TVM_ENSUREVISIBLE
Nachricht erst senden können, wenn das Tree View-Steuerelement tatsächlich als reales Fenster vorhanden ist, und erst, wenn wir die ShowDialog
Methode 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 Timer
kurz vor dem Aufrufen der Dialogmethode einen Kurzstart beginne ShowDialog
und dann das Handle des TreeView
Steuerelements im Timer
Handler aufspüre (dh nachdem der Dialog angezeigt wird) und unsere TVM_ENSUREVISIBLE
Nachricht 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;
}
}