Принциповою проблемою є неправильне дизайнерське рішення в FolderBrowserDialog
. По-перше, нам слід усвідомити, що FolderBrowserDialog
це не є .NET-управлінням, а скоріше є Common Dialog
і є частиною Windows. Дизайнер цього діалогового вікна вирішив не надсилати елемент керування TreeView aTVM_ENSUREVISIBLE
повідомлення після того, як відобразиться діалогове вікно та буде вибрана початкова папка. Це повідомлення змушує прокручувати елемент керування TreeView так, щоб вибраний на даний момент елемент був видно у вікні.
Таким чином, все , що нам потрібно зробити , щоб виправити це , щоб відправити TreeView , який є частиною FolderBrowserDialog
в TVM_ENSUREVISIBLE
повідомленні , і все буде чудово. Правда? Ну, не так швидко. Це справді відповідь, але деякі речі перешкоджають нам.
По-перше, оскільки FolderBrowserDialog
насправді не є елементом керування .NET, він не має внутрішньої Controls
колекції. Це означає, що ми не можемо просто знайти та отримати доступ до дочірнього елемента управління TreeView із .NET.
По-друге, дизайнери FolderBrowserDialog
класу .NET вирішили опечатати цей клас. Це невдале рішення заважає нам виходити з нього та замінювати віконний обробник повідомлень. Якби ми змогли це зробити, ми могли б спробувати опублікувати TVM_ENSUREVISIBLE
повідомлення, коли отримали WM_SHOWWINDOW
повідомлення в обробнику повідомлень.
Третя проблема полягає в тому, що ми не можемо відправити TVM_ENSUREVISIBLE
повідомлення, поки елемент керування Tree View насправді не існує як справжнє вікно, і він не існує, поки ми не викликаємо ShowDialog
метод. Однак цей метод блокує, тому ми не матимемо можливості розміщувати своє повідомлення після виклику цього методу.
Щоб обійти ці проблеми, я створив статичний допоміжний клас з одним методом, який можна використовувати для показу а FolderBrowserDialog
, і змусить його прокручувати до вибраної папки. Я управляю цим, починаючи короткий шлях Timer
безпосередньо перед викликом ShowDialog
методу діалогу , а потім відстежуючи дескриптор елемента TreeView
керування в Timer
обробнику (тобто після відображення діалогу) і надсилаючи наше TVM_ENSUREVISIBLE
повідомлення.
Це рішення не є ідеальним, оскільки воно залежить від деяких попередніх знань про FolderBrowserDialog
. Зокрема, я знаходжу діалог, використовуючи назву його вікна. Це призведе до розриву з інсталяціями, що не належать англійській мові. Я відстежую дочірні елементи керування у діалозі, використовуючи їх ідентифікатори елементів діалогу, а не текст заголовка або назву класу, тому що я вважав, що це буде надійніше з часом.
Цей код протестовано на Windows 7 (64-розрядна версія) та Windows XP.
Ось код: (Вам може знадобитися 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;
}
}