Фундаментальная проблема - это плохое дизайнерское решение в FolderBrowserDialog
. Во-первых, нам нужно понять, что FolderBrowserDialog
не является элементом управления .NET, а, скорее, Common Dialog
и является частью Windows. Разработчик этого диалогового окна решил не отправлять элементу управления TreeView сообщение TVM_ENSUREVISIBLE
после отображения диалогового окна и выбора начальной папки. Это сообщение вызывает прокрутку элемента управления TreeView, чтобы в окне был виден текущий выбранный элемент.
Итак, все, что нам нужно сделать, чтобы исправить это, - это отправить TreeView, который является частью сообщения FolderBrowserDialog
the TVM_ENSUREVISIBLE
, и все будет отлично. Правильно? Ну не так быстро. Это действительно ответ, но на нашем пути есть кое-что.
Во-первых, поскольку FolderBrowserDialog
на самом деле не является элементом управления .NET, у него нет внутренней Controls
коллекции. Это означает, что мы не можем просто найти и получить доступ к дочернему элементу управления TreeView из .NET.
Во-вторых, разработчики класса .NET FolderBrowserDialog
решили запечатать этот класс. Это неудачное решение не позволяет нам получить от него и переопределить обработчик оконных сообщений. Если бы мы смогли это сделать, мы, возможно, попытались бы опубликовать сообщение 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
{
/// <summary>
/// Using title text to look for the top level dialog window is fragile.
/// In particular, this will fail in non-English applications.
/// </summary>
const string _topLevelSearchString = "Browse For Folder";
/// <summary>
/// These should be more robust. We find the correct child controls in the dialog
/// by using the GetDlgItem method, rather than the FindWindow(Ex) method,
/// because the dialog item IDs should be constant.
/// </summary>
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);
/// <summary>
/// Some of the messages that the Tree View control will respond to
/// </summary>
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);
/// <summary>
/// Constants used to identity specific items in the Tree View control
/// </summary>
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;
/// <summary>
/// Calling this method is identical to calling the ShowDialog method of the provided
/// FolderBrowserDialog, except that an attempt will be made to scroll the Tree View
/// to make the currently selected folder visible in the dialog window.
/// </summary>
/// <param name="dlg"></param>
/// <param name="parent"></param>
/// <returns></returns>
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
{
//
// We failed to find the Tree View control.
//
// As a fall back (and this is an UberUgly hack), we will send
// some fake keystrokes to the application in an attempt to force
// the Tree View to scroll to the selected item.
//
t.Stop();
SendKeys.Send("{TAB}{TAB}{DOWN}{DOWN}{UP}{UP}");
}
};
t.Interval = 10;
t.Start();
result = dlg.ShowDialog( parent );
}
return result;
}
}
person
Brad Oestreicher
schedule
15.03.2013