Warum wird im Dialogfeld "FolderBrowserDialog" nicht zum ausgewählten Ordner gescrollt?


76

Wie in diesem Screenshot gezeigt, befindet sich der ausgewählte Ordner nicht in der Ansicht. Es muss nach unten gescrollt werden, um den ausgewählten Ordner anzuzeigen.

Geben Sie hier die Bildbeschreibung ein

Im selben Dialogfeld wird der ausgewählte Ordner auf einem anderen Computer angezeigt

Geben Sie hier die Bildbeschreibung ein

Ich habe es auf zwei Computern mit Windows 7 ausgeführt. Es funktioniert auf einem Computer korrekt, auf dem zweiten jedoch nicht. Es sieht etwas mit Windows-Umgebung statt ein Code-Problem? Kann jemand eine Lösung vorschlagen?

Es gibt keine Änderung im Code. Ich habe längere Pfade von verschiedenen Laufwerken verwendet, aber die Ergebnisse sind gleich.

private void TestDialog_Click ( object sender, EventArgs e )
        {
            //Last path store the selected path, to show the same directory as selected on next application launch.
            //Properties.Settings.Default.LastPath

            FolderBrowserDialog dlgFolder = new FolderBrowserDialog ();

            dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

            dlgFolder.SelectedPath = Properties.Settings.Default.LastPath;

            if (dlgFolder.ShowDialog () == System.Windows.Forms.DialogResult.OK)
            {

                Properties.Settings.Default.LastPath = dlgFolder.SelectedPath;               

                Properties.Settings.Default.Save ();
            }

        }

Ja, es ist umweltbedingt. Der Dialog ist in Windows implementiert, nicht in Silverlight. Könnte ein Windows-Fehler sein, ich wette, dass das normalerweise fehlende Textfeld "Ordner" die Hauptursache ist. Ohne sie wäre der Ordner "Issues" sichtbar. Wenden Sie sich an den Microsoft-Support, wenn Sie dies weiterverfolgen möchten.
Hans Passant

Antworten:


85

Das grundsätzliche Problem ist eine schlechte Entwurfsentscheidung in der FolderBrowserDialog. Zunächst müssen wir erkennen, dass das FolderBrowserDialogkein .NET-Steuerelement ist, sondern das Common Dialogund Teil von Windows. Der Designer dieses Dialogfelds hat beschlossen, dem TreeView-Steuerelement keine TVM_ENSUREVISIBLENachricht 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 FolderBrowserDialogder TVM_ENSUREVISIBLENachricht 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 FolderBrowserDialoges sich nicht wirklich um ein .NET-Steuerelement handelt, verfügt es nicht über eine interne ControlsSammlung. Dies bedeutet, dass wir das untergeordnete TreeView-Steuerelement nicht einfach über .NET finden und darauf zugreifen können.

  • Zweitens haben die Designer der .NET- FolderBrowserDialogKlasse 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_ENSUREVISIBLENachricht zu veröffentlichen, als wir die WM_SHOWWINDOWNachricht im Nachrichtenhandler erhalten haben.

  • Das dritte Problem ist, dass wir die TVM_ENSUREVISIBLENachricht erst senden können, wenn das Tree View-Steuerelement tatsächlich als reales Fenster vorhanden ist, und erst, wenn wir die ShowDialogMethode 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 Timerkurz vor dem Aufrufen der Dialogmethode einen Kurzstart beginne ShowDialogund dann das Handle des TreeViewSteuerelements im TimerHandler aufspüre (dh nachdem der Dialog angezeigt wird) und unsere TVM_ENSUREVISIBLENachricht 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
{
    /// <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;
    }
}

6
Dies sollte als Antwort markiert werden. Ich bin gerade auf dasselbe Problem gestoßen, und dieser Code hat perfekt funktioniert. Es ist auch eine sehr detaillierte und gut geschriebene Erklärung.
Dan

Was sollte das zweite Argument der Methode sein ShowFolderBrowser? Die IWin32Window...?
Syspect

@Syspect - Das IWin32Window-Argument ist einfach das übergeordnete Formular, von dem aus die Ordnerauswahl gestartet wird. Wenn Sie dies direkt aus Ihrem Formularcode aufrufen, können Sie einfach das Schlüsselwort 'this' als Parameter verwenden. (Technisch gesehen ist ein IWin32Window tatsächlich ein Wrapper um das hWnd hinter dem Formular, aber C # verbirgt all die hässlichen Dinge, die damit verbunden sind, vor Ihnen!)
Brad Oestreicher

2
Unter Win7 stellte ich fest, dass das Scrollen stattfand und dann zurückgesetzt wurde, als Systemordner wie Bibliotheken usw. zum Baum hinzugefügt wurden, nachdem der Dialog ursprünglich angezeigt wurde. Das Einstellen eines anfänglichen Intervalls von 1000 ms war ausreichend, um dies zu überwinden, obwohl es nur noch eine Karte oben ist!
Jonathan Mitchell

1
Auf Win10 gibt es, wie @Jonathan Mitchell bemerkte, ein Zeitproblem. Einstellung t.Interval = 100; war genug, um dies auf meiner Maschine zu lösen (länger für langsamere Maschinen?).
Avenmore

10

Ich weiß, dass dieser Thread sehr alt ist, aber mit Erweiterungsmethoden kann dieser zur FolderBrowserDialog.ShowDialog-Methode hinzugefügt und bei Bedarf wiederholt verwendet werden.

Das Beispiel (unten) verwendet nur die einfache SendKeys-Methode (was ich nicht gerne mache, aber in diesem Fall funktioniert es gut). Wenn Sie mit der SendKeys-Methode zum ausgewählten Ordner im Dialogfeld springen und dies in Visual Studio debuggen, gilt der SendKeys-Aufruf für das aktuelle Fenster, das das aktive VS-Fenster ist. Um kinderleichter zu sein und zu verhindern, dass das falsche Fenster die SendKeys-Nachricht erhält, enthält die Erweiterungsmethode die externen Methodenaufrufe, um Nachrichten an das spezifische Fenster zu senden, ähnlich dem, was Marc F gepostet hat, aber in C # übersetzt.

internal static class FolderBrowserDialogExtension
{
    public static DialogResult ShowDialog(this FolderBrowserDialog dialog, bool scrollIntoView)
    {
        return ShowDialog(dialog, null, scrollIntoView);
    }

    public static DialogResult ShowDialog(this FolderBrowserDialog dialog, IWin32Window owner, bool scrollIntoView)
    {
        if (scrollIntoView)
        {
            SendKeys.Send("{TAB}{TAB}{RIGHT}");
        }

        return dialog.ShowDialog(owner);
    }
}

1
Dies war für mich unter einem x64 Windows 8-Betriebssystem am hilfreichsten. Ich habe es jedoch erweitert, indem ich die Ausführung der sendkeys in einem Timer_Tick-Ereignis nach 500 Millisekunden hinzugefügt habe, als es in den ausgewählten Ordner verschoben und dann auf das Stammlaufwerk dieses Ordners zurückgesetzt wurde. Es war also eine Verzögerung erforderlich.
Hynsey

8

Ich habe eine Problemumgehung von https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory- verwendet.

FolderBrowserDialog^ oFBD = gcnew FolderBrowserDialog;
oFBD->RootFolder = Environment::SpecialFolder::MyComputer;
oFBD->SelectedPath = i_sPathImport;
oFBD->ShowNewFolderButton = false;     // use if appropriate in your application
SendKeys::Send ("{TAB}{TAB}{RIGHT}");  // <<-- Workaround
::DialogResult oResult = oFBD->ShowDialog ();

Es ist nicht der schönste Weg, aber es funktioniert für mich.
Ohne das RootFolderfunktioniert es NICHT beim ersten Anruf, sondern beim zweiten und folgenden. Damit funktioniert es immer.

Wie andere festgestellt haben, hängt dieser Fehler vom Betriebssystem ab:
Ich verwende Win 7 Pro x64 SP1


1
Funktioniert bei mir. Es macht Spaß zu lernen, dass die Tastenfolge Tab-Tab-Rechtspfeil zum ausgewählten Verzeichnis blättert. In C #:SendKeys.Send("{TAB}{TAB}{RIGHT}");
Roland

1
"dieser Fehler": Ich nehme an, dass "dies" sich auf den SendKeys-Trick bezieht und dass "Fehler" "Feature" sein sollte.
Roland

7

Fügen Sie im VB.Net-Code einfach diese Codezeile ein, bevor Sie den Dialog anzeigen.

SendKeys.Send ("{TAB}{TAB}{RIGHT}")

Diese Antwort ist fantastisch! löst mein Problem vorerst .. Ich hasste es wirklich, zur Auswahl scrollen zu müssen ..: +1:. wäre meine akzeptierte Antwort, wenn es nach mir ginge: P
Dan Bradbury

3

Ich habe in verschiedenen Foren gelesen, dass es an RootFolder liegen könnte, da SelectedPath und RootFolder sich gegenseitig ausschließen. Das bedeutet, dass beide nicht nebeneinander existieren können, aber mit dem Standard-RootFolder (.Desktop) zumindest das Klettern im Baum (Navigieren auf dem Laufwerk) ermöglicht / Ordner).

Wenn RootFolder jedoch in einen anderen als Desktop geändert wird, können Sie nicht zu UNC-Pfaden navigieren.

Antwort an Hans Passant: Ich habe diese Dialog-Erweiterung mit TextBox ausprobiert, aber kein Glück.

Anpassen des Dialogfelds "Ordner suchen", um den Pfad anzuzeigen


3

das funktioniert bei mir

folderBrowserDialog1.Reset();  
folderBrowserDialog1.RootFolder = Environment.SpecialFolder.MyComputer;
folderBrowserDialog1.SelectedPath = WorkingFolder;

aber erst nach der zweiten Verwendung des Dialogs


3

Ich habe das gefunden:

  1. Wenn .SelectedPathmit "\" endet, wird der Dialog nach unten gescrollt, um den Pfad sichtbar zu machen.
  2. Wenn .SelectedPathnicht mit "\" endet, ist der Pfad weiterhin ausgewählt, aber nicht sichtbar.

Entschuldigung: Diese Lösung funktioniert nur halb so oft. Es scheint, als ob es im Inneren eine gewisse Rennbedingung gibt. Hinweis: Das Verzeichnis sollte vorhanden sein.
Aleksandr

2

Ich habe etwas in VB.NET berechnet, daher wäre es einfach, es in C # umzuwandeln. Ich bin Franzose und ich bin Anfänger in VB. Wie auch immer, Sie können meine Lösung ausprobieren.

Meine Idee ist es, eine asynchrone Aufgabe kurz vor dem Anzeigen der zu starten folderBrowserDialog.

Ich habe das selbst gefunden, aber ich wurde von Brad Post inspiriert. Hier ist mein Code:

Imports System.Threading.Tasks
Imports Microsoft.VisualBasic.FileIO.FileSystem

Public Enum GW
    HWNDFIRST = 0
    HWNDLAST = 1
    HWNDNEXT = 2
    HWNDPREV = 3
    OWNER = 4
    CHILD = 5
    ENABLEDPOPUP = 6
End Enum

Public Declare Function SendMessageW Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal wParam As Integer, <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
Public Declare Function FindWindowExW Lib "user32.dll" (ByVal hWndParent As IntPtr, ByVal hWndChildAfter As IntPtr, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszClass As String, <MarshalAs(UnmanagedType.LPWStr)> ByVal lpszWindow As String) As IntPtr
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal wCmd As Long) As Long
Public Declare Function GetDesktopWindow Lib "user32" () As IntPtr
Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As System.Text.StringBuilder, ByVal nMaxCount As Integer) As Integer

Private Sub FolderBrowserDialog_EnsureVisible(FB As FolderBrowserDialog, _Owner As IntPtr)
    Dim hwnd As IntPtr
    Dim sClassname As New System.Text.StringBuilder(256)
    Thread.Sleep(50)                                     'necessary to let FolderBrowserDialog construct its window
    hwnd = GetDesktopWindow()                            'Desktop window handle.
    hwnd = GetWindow(hwnd, GW.CHILD)                     'We will find all children.
    Do Until hwnd = 0
        If GetWindow(hwnd, GW.OWNER) = _Owner Then       'If one window is owned by our main window...
            GetClassName(hwnd, sClassname, 255)
            If sClassname.ToString = "#32770" Then       'Check if the class is FolderBrowserDialog.
                Exit Do                                  'Then we found it.
            End If
        End If
        hwnd = GetWindow(hwnd, GW.HWNDNEXT)              'Next window.
    Loop                                                 'If no found then exit.
    If hwnd = 0 Then Exit Sub
    Dim hChild As IntPtr = 0
    Dim hTreeView As IntPtr = 0
    Dim i As Integer = 0
    Do
        i += 1
        If i > 1000 Then Exit Sub                                       'Security to avoid infinite loop.
        hChild = FindWindowExW(hwnd, hChild, Nothing, Nothing)          'Look for children windows of FolderBrowserDialog.
        hTreeView = FindWindowExW(hChild, 0, "SysTreeView32", Nothing)  'Look for treeview of FolderBrowserDialog.
        Thread.Sleep(5)                                                 'delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
    Loop While hTreeView = 0
    If SendMessageW(hwnd, &H46A, 1, FB.SelectedPath) = 0 Then           'Send message BFFM_SETEXPANDED to FolderBrowserDialog.
        SendMessageW(hTreeView, &H7, 0, Nothing)                        'Send message WM_SETFOCUS to the treeeview.
    End If
End Sub


Dim My_save_dir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\My-Saves"

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim FolderBrowserDialog1 As New FolderBrowserDialog
    FolderBrowserDialog1.Description = "Choose your save files path."
    If Directory.Exists(My_save_dir) Then
        FolderBrowserDialog1.SelectedPath = My_save_dir
    Else
        FolderBrowserDialog1.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
    End If

    Dim Me_handle = Me.Handle         'Store the main handle to compare after with each windows owner.
    Task.Run(Sub() FolderBrowserDialog_EnsureVisible(FolderBrowserDialog1, Me_handle))      'Here's the trick, run an asynchronous task to modify the folderdialog.
    If FolderBrowserDialog1.ShowDialog(Me) = System.Windows.Forms.DialogResult.OK Then
        My_save_dir = FolderBrowserDialog1.SelectedPath
    End If
End Sub

Ich warte auf Ihre Vorschläge. Und jemand kann es in C # übersetzen, weil ich C # nicht kenne.


Die Frage ist ziemlich alt, daher würde ich nicht viel hoffen, dass jemand bald antworten würde. Danke für die Eingabe!
ZygD

1
Dies sollte in einer eigenen Frage gestellt werden. Es ist nicht wahrscheinlich, hier gesehen zu werden.
Stuart Siegler

2

Ich habe das gleiche Problem in c ++ / mfc. Es hat bei mir funktioniert, :: PostMessage anstelle von :: SendMessage im Rückruf BFFM_INITIALIZED zu verwenden, um die Nachricht TVM_ENSUREVISIBLE zu platzieren

    case BFFM_INITIALIZED: 
{
// select something
::SendMessage(m_hDialogBox, BFFM_SETSELECTION, TRUE, (LPARAM) pszSelection);


// find tree control
m_hTreeCtrl = 0;
HWND hchild = GetWindow(hWnd, GW_CHILD) ;
while (hchild != NULL)
{
  VS_TChar classname[200] ;
  GetClassName(hchild, classname, 200) ;

  if (VS_strcmp(classname, _T("SHBrowseForFolder ShellNameSpace Control")) == 0)
  {
    HWND hlistctrl = GetWindow(hchild, GW_CHILD) ;
    do
    { 
      GetClassName(hlistctrl, classname, 200) ;
      if (lstrcmp(classname, _T("SysTreeView32")) == 0)
      {
        m_hTreeCtrl = hlistctrl;
        break ;   
      }

      hlistctrl = GetWindow(hlistctrl, GW_HWNDNEXT) ;
    } while (hlistctrl != NULL);
  }      
  if (m_hTreeCtrl)
    break;
  hchild = GetWindow(hchild, GW_HWNDNEXT);      
}

if (m_hTreeCtrl)
{
  int item = ::SendMessage(m_hTreeCtrl, TVM_GETNEXTITEM, TVGN_CARET, 0);
  if (item != 0)             
    ::PostMessage(m_hTreeCtrl, TVM_ENSUREVISIBLE,0,item);
}
break;
}

1

Ich habe die obige Diskussion und Lösungen gelesen. Besonders Brat Oestreicher hat mich in die richtige Richtung gebracht. Im Wesentlichen müssen wir zuerst das TreeView-Steuerelement im SHBrowseForFolderDialogfeld finden und diesem Fenster die TVM_ENSUREVISIBLENachricht senden . Das Folgende macht dies in C.

#include <windows.h>
#include <objbase.h>
#include <objidl.h>
#include <Shlobj.h>
#include <Dsclient.h>
#include <wchar.h>
// 
//  EnumCallback - Callback function for EnumWindows 
// 
static BOOL CALLBACK EnumCallback(HWND hWndChild, LPARAM lParam)
{
   char szClass[MAX_PATH];
   HTREEITEM hNode;
   if (GetClassName(hWndChild, szClass, sizeof(szClass))
   &&  strcmp(szClass,"SysTreeView32")==0) {
      hNode = TreeView_GetSelection(hWndChild);    // found the tree view window
      TreeView_EnsureVisible (hWndChild, hNode);   // ensure its selection is visible
      return(FALSE);   // done; stop enumerating
   }
   return(TRUE);       // continue enumerating
}
// 
//  BrowseCallbackProc - Callback function for SHBrowseForFolder 
// 
static INT CALLBACK BrowseCallbackProc (HWND hWnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 
{
    switch (uMsg) 
    { 
        case BFFM_INITIALIZED:
            SendMessage (hWnd, BFFM_SETEXPANDED, TRUE, lpData);    // expand the tree view
            SendMessage (hWnd, BFFM_SETSELECTION, TRUE, lpData);   // select the item
            break;
        case BFFM_SELCHANGED:
            EnumChildWindows(hWnd, EnumCallback,0);
            break;
    } 
    return 0; 
} 
// 
//  SelectDirectory - User callable entry point 
// 
int SelectDirectory (HWND hWndParent, char *path, int pathSize) 
{ 
    BROWSEINFO bi = {0};
    LPITEMIDLIST pidl = NULL;
    wchar_t ws[MAX_PATH];

    CoInitialize(0);
    if (pathSize < MAX_PATH) return(FALSE);

    swprintf(ws, MAX_PATH, L"%hs", path);

    bi.hwndOwner = hWndParent; 
    bi.lpszTitle = "Select Directory"; 
    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
    bi.lpfn = BrowseCallbackProc;
    bi.lParam = (LPARAM) ws;

    pidl = SHBrowseForFolder (&bi); 
    if (pidl != NULL) 
    { 
        LPMALLOC pMalloc = NULL; 
        SHGetPathFromIDList (pidl, path);
        path[pathSize-1]= '\0';

        SHGetMalloc(&pMalloc);
        pMalloc->lpVtbl->Free(pMalloc,pidl);    // deallocate item 
        pMalloc->lpVtbl->Release(pMalloc);

        return (TRUE);
    } 
    return (FALSE);
} 

Vielen Dank an Gary Beene .


0

dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

ist nicht dasselbe wie

dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;

Was ist der Unterschied zwischen SpecialFolder.Desktop und SpecialFolder.DesktopDirectory?

Der verknüpfte Thread gibt an, dass sie als Pfad dasselbe Ergebnis erhalten. Sie sind jedoch nicht gleich, da einer ein logischer und der andere ein physischer Pfad ist.

Ich habe festgestellt, dass das resultierende Verhalten unterschiedlich sein kann, wenn einer der beiden dem RootFolder des Dialogfelds zum Öffnen von Ordnern zugewiesen ist.

Als .RootFolder-Zuweisung behandeln einige Windows-Versionen wie win7 eine der beiden Versionen als "Desktop". Das heißt, Sie können den Untereintrag "Computer" sehen und diesen öffnen, um die einzelnen Laufwerksbuchstaben anzuzeigen. Der .SelectedPath wird in beiden Fällen ausgewählt, der ausgewählte Pfad wird jedoch nur sichtbar, wenn der logische Pfad des Desktops dem .RootFolder zugewiesen ist.

Schlimmer noch, wenn Sie das Dialogfeld zum Durchsuchen von Ordnern in der Win10-Vorabversion verwenden, scheint "DesktopDirectory" genau das zu sein, nur der Inhalt des Desktop-Verzeichnisses, ohne jegliche Verknüpfung mit dem logischen Desktop-Verzeichnis. Und keine Unterelemente darunter aufzulisten. Sehr frustrierend, wenn eine für win7 geschriebene App versucht, mit win10 verwendet zu werden.

Ich denke, das Problem des OP ist, dass sie den physischen Desktop als Root verwendet haben, wenn sie den logischen Desktop hätten verwenden sollen.

Ich habe keine Erklärung dafür, warum die beiden verschiedenen Maschinen des OP unterschiedlich reagieren. Ich würde spekulieren, dass zwei verschiedene Versionen des .NET Frameworks installiert sind.

Die Tatsache, dass bei der Win10-Vorabversion das Problem "Auf dem Desktop hängen geblieben" im Dialogfeld "Ordner durchsuchen" auftritt, kann auf das neuere .NET-Framework zurückzuführen sein, das mit der Win10-Vorabversion ausgeliefert wurde. Leider kenne ich alle Fakten in diesem Fall (win10) nicht, da ich sie noch nicht aktualisiert habe.

PS Ich habe festgestellt, dass bei win8 auch das Symptom "Stuck on Desktop" auftritt:

/superuser/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items

Die Problemumgehung bestand darin, die alternative GUI in win8 auszuwählen. Vielleicht kann etwas Ähnliches in Win10 Prerelease gemacht werden.


0

Als Antwort auf Marc Fs Beitrag habe ich das VB.Net in C # konvertiert.

    public enum GW
    {
        HWNDFIRST = 0,
        HWNDLAST = 1,
        HWNDNEXT = 2,
        HWNDPREV = 3,
        OWNER = 4,
        CHILD = 5,
        ENABLEDPOPUP = 6
    }

    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessageW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr SendMessageW(IntPtr hWnd, uint msg, int wParam, [MarshalAs(UnmanagedType.LPWStr)] string lParam);
    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "FindWindowExW", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr FindWindowExW(IntPtr hWndParent, IntPtr hWndChildAfter, [MarshalAs(UnmanagedType.LPWStr)] string lpszClass, [MarshalAs(UnmanagedType.LPWStr)] string lpszWindow);
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern UInt32 GetWindow(IntPtr hwnd, UInt32 wCmd);
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetDesktopWindow", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern IntPtr GetDesktopWindow();
    [System.Runtime.InteropServices.DllImport("user32", EntryPoint = "GetClassNameA", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Ansi, SetLastError = true)]
    public static extern int GetClassName(IntPtr hwnd, System.Text.StringBuilder lpClassName, int nMaxCount);

    private void FolderBrowserDialog_EnsureVisible(FolderBrowserDialog FB, IntPtr _Owner)
    {
        IntPtr hwnd = System.IntPtr.Zero;
        System.Text.StringBuilder sClassname = new System.Text.StringBuilder(256);
        Thread.Sleep(50); //necessary to let FolderBrowserDialog construct its window
        hwnd = GetDesktopWindow(); //Desktop window handle.
        hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.CHILD); //We will find all children.
        while (!(hwnd == (System.IntPtr)0))
        {
            if (GetWindow(hwnd, (UInt32)GW.OWNER) == (UInt32)_Owner) //If one window is owned by our main window...
            {
                GetClassName(hwnd, sClassname, 255);
                if (sClassname.ToString() == "#32770") //Check if the class is FolderBrowserDialog.
                {
                    break; //Then we found it.
                }
            }
            hwnd = (System.IntPtr)GetWindow(hwnd, (UInt32)GW.HWNDNEXT); //Next window.
        } //If no found then exit.
        if (hwnd == (System.IntPtr)0)
        {
            return;
        }
        IntPtr hChild = (System.IntPtr)0;
        IntPtr hTreeView = (System.IntPtr)0;
        int i = 0;
        do
        {
            i += 1;
            if (i > 1000) //Security to avoid infinite loop.
            {
                return;
            }
            hChild = FindWindowExW(hwnd, hChild, null, null); //Look for children windows of FolderBrowserDialog.
            hTreeView = FindWindowExW(hChild, (System.IntPtr)0, "SysTreeView32", null); //Look for treeview of FolderBrowserDialog.
            Thread.Sleep(5); //delay necessary because FolderBrowserDialog is in construction, then treeview maybe not yet exist.
        } while (hTreeView == (System.IntPtr)0);
        if (SendMessageW(hwnd, 0x46A, 1, FB.SelectedPath) == (System.IntPtr)0) //Send message BFFM_SETEXPANDED to FolderBrowserDialog.
        {
            SendMessageW(hTreeView, 0x7, 0, null); //Send message WM_SETFOCUS to the treeeview.
        }
    }

Getestet und es funktioniert gut. Stellen Sie sicher, dass Sie auf System.Runtime.InteropServices, System.Threading und System.Threading.Tasks verweisen



-2

Der beste und zumindest zuverlässigste Ansatz besteht darin, ein eigenes Dialogfeld für die Browserklasse zu erstellen. Das Problem mit dem Scrollen von Bäumen ist seit vielen Jahren ein Problem - es wird niemals behoben werden!

Wenn Sie wissen, wie man in Farbe rendert, können Sie nicht viel tun. Schnell in Farbe gut, das ist eine andere Geschichte.

Der erste Punkt, den ich mir ansehen würde, ist der Open-Source-.Net-Quellcode auf GitHub in Ihrer .Net-Version Ihrer Wahl für die Dialogklasse, die Sie verbessern möchten. Sie werden überrascht sein, was Sie mit ein wenig Aufwand erreichen und durchziehen können. Duplizieren Sie einfach das Steuerelement und debuggen Sie bis zu dem Punkt, an dem der Fehler auftritt, und patchen Sie - das ist es, was Microsoft auch tut!

Da dies ein alter Thread ist und Posting-Beispiele möglicherweise nie gelesen werden. Es würde mehr machen, da zu posten, wenn man gefragt wird.

Für jemanden, der ein Problem wie das Scrollen des Baums zum "erwarteten" Verzeichnis lösen möchte, gibt es hier einige solide Ratschläge. Wenn ein Problem mit einem Steuerelement oder einer Bibliothek vorliegt, für das keine sofortige Lösung gefunden werden kann, erstellen Sie eine eigene Version. Erweitern Sie nach Möglichkeit das Original und patchen Sie das Problem. Ich habe alles von der Windows.Form.Control-Klasse bis zu Win32-Bibliotheken überarbeitet, um vorhersehbare und genaue Ergebnisse zu erhalten.

Die gute Nachricht ist, dass mit C # eine Menge Low-Level-Steuerung verfügbar ist, um fast jedes vernünftige Ziel zu erreichen, und das ist auch C.

In der Vergangenheit habe ich viel zu viele Stunden damit verbracht, nach einer Lösung für ein Problem zu suchen, bei dem, wenn ich gerade neu erstellt hätte, was nicht funktioniert, viel Zeit gespart worden wäre.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.