Le problème fondamental est une mauvaise décision de conception dans la FolderBrowserDialog
. Tout d'abord, nous devons réaliser que le FolderBrowserDialog
n'est pas un contrôle .NET, mais plutôt l'interface utilisateur de l Common Dialog
et fait partie de Windows. Le concepteur de cette boîte de dialogue a choisi de ne pas envoyer au contrôle TreeView un message TVM_ENSUREVISIBLE
après l'affichage de la boîte de dialogue et la sélection d'un dossier initial. Ce message provoque le défilement d'un contrôle TreeView afin que l'élément actuellement sélectionné soit visible dans la fenêtre.
Donc, tout ce que nous devons faire pour résoudre ce problème est d'envoyer le TreeView qui fait partie de l'interface de l'utilisateur. FolderBrowserDialog
el TVM_ENSUREVISIBLE
et tout sera parfait. Pas vrai ? Eh bien, pas si vite. C'est en effet la réponse, mais il y a des choses qui se dressent sur notre chemin.
-
Premièrement, parce que le FolderBrowserDialog
n'est pas vraiment un contrôle .NET, il n'a pas d'interface interne. Controls
collection. Cela signifie que nous ne pouvons pas simplement trouver et accéder au contrôle enfant TreeView à partir de .NET.
-
Deuxièmement, les concepteurs du système .NET FolderBrowserDialog
La classe a décidé de joint cette classe. Cette décision malheureuse nous empêche de dériver de cette classe et de surcharger le gestionnaire de messages de la fenêtre. Si nous avions été en mesure de le faire, nous aurions pu essayer d'afficher la classe TVM_ENSUREVISIBLE
lorsque nous avons reçu le message WM_SHOWWINDOW
dans le gestionnaire de messages.
-
Le troisième problème est que nous ne pouvons pas envoyer la TVM_ENSUREVISIBLE
tant que le contrôle Tree View n'existe pas en tant que fenêtre réelle, et il n'existe pas tant que nous n'appelons pas la commande ShowDialog
méthode. Cependant, cette méthode se bloque, nous n'aurons donc pas la possibilité de poster notre message une fois que cette méthode sera appelée.
Pour contourner ces problèmes, j'ai créé une classe d'aide statique dotée d'une seule méthode qui peut être utilisée pour afficher un message de type FolderBrowserDialog
et le fera défiler jusqu'au dossier sélectionné. Je gère cela en lançant une courte Timer
juste avant d'appeler la fonction ShowDialog
puis en recherchant la poignée de la méthode TreeView
contrôle dans le Timer
(c'est-à-dire après l'affichage du dialogue) et en envoyant notre TVM_ENSUREVISIBLE
message.
Cette solution n'est pas parfaite car elle dépend de certaines connaissances préalables sur la FolderBrowserDialog
. Plus précisément, je trouve le dialogue en utilisant le titre de sa fenêtre. Cela ne fonctionnera pas avec les installations non anglaises. Je retrouve les contrôles enfants dans le dialogue en utilisant leur ID d'élément de dialogue, plutôt que le texte du titre ou le nom de la classe, car j'ai pensé que cela serait plus fiable dans le temps.
Ce code a été testé sur Windows 7 (64 bit), et Windows XP.
Voici le code : (Vous pouvez avoir besoin : 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;
}
}