Iterate through WINAPI TreeView

97 views Asked by At

To classify the question:

Basically, it's all about a user dialog!!! I work with WPF and don't want to use the FolderBrowserDialog because the Forms NameSpace can only be integrated poorly in a .NETCore WPF project. So I chose the SHBrowseForFolder dialog natively under the API32 in the ShellNameSpace. The FolderBrowserDialog is just a wrapper around this Windows dialog. (I'm building an MVVM-enabled WPF wrapper, for a directory selection dialog.)

No problem, the dialog is displayed, a root directory can be defined ... the only problem is the initial select directory. (Selecting the start directory in the dialog window/TreeView))

For this I use a launcher that is started before the directory dialog is called and (via a timer) "gets" the dialog window and accesses the directory tree view.

Now I want to iterate through the treeview (of the dialog window) and select the treeview item that matches the select directory.

It is precisely at this point that the problem arises that I cannot "access" subordinate items.

The details ...

I'm working with C# and I want to iterate through the TreeView of a Windows FolderDialogBrowser (WINAPI).

I get the pointer to the TreeView via the Windows dialog window (user32.dll -> FindWindow).

IntPtr hwndDlg = FindWindow(null, _topLevelSearchString);  // "get" the handle of the dlg box window

if (hwndDlg != IntPtr.Zero)                                // Has the Dlg box window found?
{
    IntPtr hwndFolderCtrl = GetDlgItem(hwndDlg, _dlgControl); // "get" the dlg box control in the dlg box window

    if (hwndFolderCtrl != IntPtr.Zero) // Has the dlg box control found in the dlg box window?
    {
        IntPtr hwndTV = GetDlgItem(hwndFolderCtrl, _dlgTreeView);  // "Get" the handle of the TreeView of the Dlg box
   ...

Now I want to iterate through this one... as an example with fixed references:

IntPtr RootItem = IntPtr.Zero;                  // Initialize the handle of the root entry
IntPtr AktItem = IntPtr.Zero;                   // handle of the current Initialize TreeView items
string AktVerzeichnis = RootVerzeichnis;        // Initialize current directory

RootItem = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_ROOT), IntPtr.Zero);   // Get root entry
AktItem = RootItem;                             // Pointer to the current update entry

if (!SelectVerzeichnis.Equals(AktVerzeichnis))  // Select Directory Is Not Root Directory?
{
    if (AktItem != IntPtr.Zero)                 // Is there a root item?
    {
        IntPtr Item1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem);   // Item1 entry = get child entry from root entry [level 1]
        System.Diagnostics.Debug.Print("1. Eintrag: " + GetTVItem(pTV, Item1));        // Determine the designation of the 1st entry

        IntPtr Item1_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item1);    // Get 1st sub-item from Item1 [level 2].
        System.Diagnostics.Debug.Print("1.1 Eintrag: " + GetTVItem(pTV, Item1_1));     // Designation of 1.1. Determine entry

        IntPtr Item2 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), Item1);       // Get 2nd entry [level 1].
        System.Diagnostics.Debug.Print("2. Eintrag: " + GetTVItem(pTV, Item2));        // Determine the designation of the 2nd entry

        IntPtr Item3 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_NEXT), Item2);       // Get 3rd entry [level 1].
        System.Diagnostics.Debug.Print("3. Eintrag: " + GetTVItem(pTV, Item3));        // Determine the designation of the 3rd entry

        IntPtr Item3_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item3);    // Get 1st sub-item from Item3 [level 2].
        System.Diagnostics.Debug.Print("3.1 Eintrag: " + GetTVItem(pTV, Item3_1));     // Designation of 3.1. Determine entry

The above sequence gives: Item1: Anruf_Info Item1_1: Item2: Anruf_Info REPORT22 Item3: Druck Item3_1:

My problem is that I always don't get the sub-items (both Item1_1 and Item3_1) (=> IntPtr.Zerro).

TVGN_CHILD is (except sub-entry from root entry => Item1 !!!) not work!!!

What can that be???

image

Different calls from:

SendMessage(pTV, TVM_GETNEXTIM, new(TVGN_CHILD), Item1)

I select the entry before retrieving the sub-entry so that (internally through the TreeView) the sub-entries are loaded dynamically.

IntPtr Item1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), RootItem);   // 1. Eintrag = Untergeordneter Eintrag vom Root-Eintrag holen [Ebene 1]
SendMessage(pTV, TVM_SELECTITEM, new IntPtr(TVGN_CARET), Item1);               // 1. Eintrag selektieren
SendMessage(pTV, TVGN_FIRSTVISIBLE, IntPtr.Zero, Item1);                       // 1. Eintrag in den sichtbaren Bereich bringen

IntPtr Item1_1 = SendMessage(pTV, TVM_GETNEXTITEM, new(TVGN_CHILD), Item1);    // 1. Unter-Eintrag vom 1. Eintrag [Ebene 2] holen

1

There are 1 answers

10
Charlieface On

I think you are beating down the wrong path. There is a documented method to change the path to a specific selected path, and this is used in the official FolderBrowserDialog also.

Use a callback function which receives messages from the dialog. In this case you want to catch the INITIALIZED message, and send a SETSELECTION message back.

[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
public static extern IntPtr SendMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);

delegate int BrowseCallback(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData);

string _selectedPath;
BrowseCallback _callbackKeepAlive = BrowseCallbackProc;

private int BrowseCallbackProc(IntPtr hwnd, int msg, IntPtr lParam, IntPtr lpData)
{
    // Indicates the browse dialog box has finished initializing. The lpData value is zero. 
    const int BFFM_INITIALIZED = 1;
    const int BFFM_SETSELECTIONW = 0x400 + 103,

    switch (msg)
    {
        case BFFM_INITIALIZED: 
            if (_selectedPath?.Length > 0) 
            {
                SendMessage(hwnd, BFFM_SETSELECTIONW, (IntPtr)1, _selectedPath);
            }
        break;
    }
    return 0;
}

Pass the _callbackKeepAlive value in the lpfn field of your BROWSEINFOW struct. Note the use of a keep alive: you need to make sure the runtime does not dispose your callback before you're finished PInvoke.