Tại sao hộp thoại FolderBrowserDialog không cuộn đến thư mục đã chọn?


76

Như hiển thị trong ảnh chụp màn hình này, thư mục đã chọn không có trong dạng xem. Nó cần được cuộn xuống để xem thư mục đã chọn.

nhập mô tả hình ảnh ở đây

Hộp thoại tương tự hiển thị thư mục đã chọn hiển thị trên máy tính khác nhau

nhập mô tả hình ảnh ở đây

Tôi đã chạy nó trên hai máy tính đều có cửa sổ 7. Nó hoạt động chính xác trên một nhưng không hoạt động trên thứ hai. Có vẻ gì đó với môi trường windows thay vì một số vấn đề về mã? Bất cứ ai có thể đề nghị bất kỳ sửa chữa?

Không có thay đổi trong mã. Tôi đã sử dụng các đường dẫn dài hơn từ các ổ đĩa khác nhau nhưng kết quả giống nhau.

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 ();
            }

        }

Vâng, đó là môi trường. Hộp thoại được thực hiện trong Windows, không phải trong Silverlight. Có thể là một lỗi Windows, tôi cá rằng hộp văn bản "Thư mục" thường vắng mặt là nguyên nhân gốc rễ. Nếu không có nó, thư mục "Sự cố" sẽ hiển thị. Liên hệ với bộ phận Hỗ trợ của Microsoft nếu bạn muốn theo đuổi việc này.
Hans Passant

Câu trả lời:


86

Vấn đề cơ bản là một quyết định thiết kế kém trong FolderBrowserDialog. Trước tiên, chúng ta cần nhận ra rằng FolderBrowserDialogđiều khiển không phải là .NET, mà là Common Dialogvà là một phần của Windows. Người thiết kế hộp thoại này đã chọn không gửi cho điều khiển TreeView một TVM_ENSUREVISIBLEthông báo sau khi hộp thoại được hiển thị và một thư mục ban đầu được chọn. Thông báo này khiến điều khiển TreeView cuộn để mục hiện được chọn hiển thị trong cửa sổ.

Vì vậy, tất cả chúng ta cần phải làm gì để khắc phục điều này là để gửi các TreeView đó là một phần của FolderBrowserDialogcác TVM_ENSUREVISIBLEthông điệp và mọi thứ sẽ tuyệt vời. Đúng? Chà, không nhanh như vậy. Đây thực sự là câu trả lời, nhưng có một số điều cản trở chúng ta.

  • Đầu tiên, bởi vì FolderBrowserDialognó không thực sự là một điều khiển .NET, nó không có bộ Controlssưu tập nội bộ. Điều này có nghĩa là chúng ta không thể chỉ tìm và truy cập điều khiển con TreeView từ .NET.

  • Thứ hai, các nhà thiết kế của FolderBrowserDialoglớp .NET quyết định niêm phong lớp này. Quyết định đáng tiếc này ngăn chúng tôi xuất phát từ nó và ghi đè trình xử lý thông báo cửa sổ. Nếu chúng tôi có thể làm điều này, chúng tôi có thể đã cố gắng đăng TVM_ENSUREVISIBLEtin nhắn khi chúng tôi nhận được WM_SHOWWINDOWtin nhắn trong trình xử lý tin nhắn.

  • Vấn đề thứ ba là chúng tôi không thể gửi TVM_ENSUREVISIBLEthông báo cho đến khi điều khiển Chế độ xem dạng cây thực sự tồn tại dưới dạng cửa sổ thực và nó không tồn tại cho đến khi chúng tôi gọi ShowDialogphương thức. Tuy nhiên, phương thức này chặn, vì vậy chúng tôi sẽ không có cơ hội đăng thông báo của mình khi phương thức này được gọi.

Để giải quyết những vấn đề này, tôi đã tạo một lớp trợ giúp tĩnh với một phương thức duy nhất có thể được sử dụng để hiển thị a FolderBrowserDialogvà sẽ khiến nó cuộn đến thư mục đã chọn. Tôi quản lý điều này bằng cách bắt đầu một đoạn ngắn Timerngay trước khi gọi ShowDialogphương thức của đối thoại , sau đó theo dõi xử lý của TreeViewđiều khiển trong Timertrình xử lý (tức là sau khi đối thoại được hiển thị) và gửi TVM_ENSUREVISIBLEtin nhắn của chúng tôi .

Giải pháp này không phải là hoàn hảo vì nó phụ thuộc vào một số kiến ​​thức trước đó về FolderBrowserDialog. Cụ thể, tôi tìm thấy cuộc đối thoại bằng cách sử dụng tiêu đề cửa sổ của nó. Điều này sẽ phá vỡ với các cài đặt không phải tiếng Anh. Tôi theo dõi các điều khiển con trong cuộc hội thoại bằng cách sử dụng Mã mục đối thoại của chúng, thay vì văn bản tiêu đề hoặc tên lớp, vì tôi cảm thấy điều này sẽ đáng tin cậy hơn theo thời gian.

Mã này đã được thử nghiệm trên Windows 7 (64 bit) và Windows XP.

Đây là mã: (Bạn có thể cần 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
Đây nên được đánh dấu là câu trả lời. Tôi vừa gặp phải vấn đề tương tự và mã này hoạt động hoàn hảo. Nó cũng là một lời giải thích rất chi tiết và tốt bằng văn bản.
Dan

Đối số thứ hai của phương thức nên là ShowFolderBrowsergì? Cái IWin32Window...?
Syspect

@Syspect - Đối số IWin32Window chỉ đơn giản là biểu mẫu mẹ mà từ đó trình chọn thư mục đang được khởi chạy. Nếu bạn đang gọi điều này trực tiếp từ mã Biểu mẫu của mình, bạn chỉ có thể sử dụng từ khóa 'this' làm tham số. (Về mặt kỹ thuật, một IWin32Window thực sự là một wrapper quanh hWnd đằng sau những hình thức, nhưng C # da tất cả những thứ xấu xí tham gia với điều đó từ bạn!)
Brad Oestreicher

2
Trên Win7, tôi quan sát thấy rằng quá trình cuộn xảy ra và sau đó được đặt lại khi các thư mục hệ thống như Thư viện, v.v. được thêm vào cây sau khi hộp thoại được hiển thị ban đầu. Đặt khoảng thời gian ban đầu là 1000ms là đủ để vượt qua điều này, mặc dù nó chỉ đơn thuần là một thẻ nữa trên đầu!
Jonathan Mitchell

1
Trên Win10, như @Jonathan Mitchell đã lưu ý, có một vấn đề về thời gian. Đặt t.Interval = 100; đã đủ để giải quyết vấn đề này trên máy của tôi (lâu hơn đối với máy chậm hơn?).
avenmore

11

Tôi biết luồng này đã cũ, nhưng với các phương thức mở rộng, luồng này có thể được thêm vào phương thức FolderBrowserDialog.ShowDialog và sau đó được sử dụng nhiều lần nếu cần.

Ví dụ (bên dưới) chỉ sử dụng phương thức SendKeys dễ dàng (mà tôi không thích làm, nhưng trong trường hợp này, nó hoạt động tốt). Khi sử dụng phương thức SendKeys để chuyển đến thư mục đã chọn trong hộp thoại, nếu bạn đang gỡ lỗi điều này trong Visual Studio, thì lệnh gọi SendKeys áp dụng cho cửa sổ hiện tại, đó sẽ là cửa sổ VS đang hoạt động. Để an toàn hơn và để tránh cửa sổ nhận nhầm tin nhắn SendKeys, thì phương thức mở rộng sẽ chứa các lệnh gọi phương thức bên ngoài để gửi tin nhắn đến cửa sổ cụ thể tương tự như những gì Marc F đã đăng, nhưng được dịch sang C #.

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
Điều này hữu ích nhất đối với tôi trên HĐH Windows 8 x64. Tuy nhiên, tôi đã mở rộng nó bằng cách thêm thực thi các khóa gửi trong sự kiện Timer_Tick sau 500 mili giây, khi nó di chuyển đến thư mục đã chọn, sau đó hoàn nguyên về ổ đĩa gốc của thư mục đó. Vì vậy, một sự chậm trễ đã được yêu cầu.
hynsey

8

Tôi đã sử dụng giải pháp thay thế từ https://www.daniweb.com/software-development/csharp/threads/300578/folderbrowserdialog-expanding-the-selected-directory-

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 ();

Đó không phải là cách đẹp nhất, nhưng nó phù hợp với tôi.
Nếu không có RootFoldernó, nó KHÔNG hoạt động trong lần gọi đầu tiên, nhưng vào lần thứ 2 và sau đó. Với nó, nó luôn hoạt động.

Như những người khác đã quan sát thấy rằng lỗi này phụ thuộc vào hệ điều hành:
Tôi đang sử dụng Win 7 Pro x64 SP1


1
Làm việc cho tôi. Thật thú vị khi biết rằng trình tự bàn phím mũi tên phải cuộn đến thư mục đã chọn. Trong C #:SendKeys.Send("{TAB}{TAB}{RIGHT}");
Roland

1
"this fail": Tôi cho rằng "this" đề cập đến thủ thuật SendKeys và "fail" phải là "feature".
Roland

8

trên mã VB.Net, chỉ cần đặt dòng mã này ngay trước khi hiển thị hộp thoại.

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

câu trả lời này là tuyệt vời! giải quyết vấn đề của tôi trong lúc này .. thực sự ghét phải cuộn xuống lựa chọn ..: +1 :. sẽ là câu trả lời được chấp nhận của tôi nếu điều đó tùy thuộc vào tôi: P
Dan Bradbury,

Đáng buồn thay, điều này không hoạt động trên hệ thống của tôi (Windows 10);
Phil Rogers

3

Tôi đọc tại các diễn đàn khác nhau rằng có thể là do RootFolder vì SelectedPath và RootFolder loại trừ lẫn nhau, điều đó có nghĩa là cả hai không thể cùng tồn tại nhưng với RootFolder mặc định (.Desktop), nó cho phép, ít nhất, leo lên Cây (điều hướng ổ đĩa /thư mục).

Tuy nhiên, nếu RootFolder được thay đổi thành khác với Desktop, bạn sẽ không thể điều hướng đến các đường dẫn UNC.

Trả lời cho Hans Passant: Tôi đã thử Phần mở rộng hộp thoại này, có TextBox, nhưng không may mắn.

Tùy chỉnh hộp thoại duyệt thư mục để hiển thị đường dẫn


3

cái này phù hợp với tôi

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

nhưng chỉ sau lần sử dụng hộp thoại thứ hai


3

Tôi đã thấy rằng:

  1. Nếu .SelectedPathkết thúc bằng "\", Hộp thoại sẽ cuộn xuống để hiển thị đường dẫn.
  2. Nếu .SelectedPathkhông kết thúc bằng "\", đường dẫn vẫn được chọn, nhưng không đảm bảo hiển thị.

Xin lỗi: giải pháp này chỉ hoạt động một nửa số lần. Có vẻ như có một số điều kiện chủng tộc bên trong. Lưu ý: thư mục nên tồn tại.
Aleksandr

Tôi vẫn chưa thấy tác phẩm này. Sự lựa chọn luôn nằm ở gốc.
Phil Rogers

2

Tôi đã tính toán một cái gì đó trong VB.NET, vì vậy sẽ dễ dàng chuyển đổi nó thành C #. Tôi là người Pháp và tôi mới bắt đầu học VB. Dù sao, bạn có thể thử giải pháp của tôi.

Ý tưởng của tôi là khởi chạy một tác vụ không đồng bộ ngay trước khi hiển thị folderBrowserDialog.

Tôi tự tìm thấy điều này, nhưng tôi đã lấy cảm hứng từ bài đăng của Brad. Đây là mã của tôi:

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

Tôi đang chờ những gợi ý của bạn. Và ai đó có thể dịch nó sang C # vì tôi không biết C #.


Câu hỏi khá cũ, vì vậy tôi không đặt nhiều hy vọng rằng ai đó sẽ sớm trả lời. Cảm ơn các đầu vào!
ZygD

1
Điều này nên được hỏi trong câu hỏi của riêng nó. Nó không có khả năng được nhìn thấy ở đây.
Stuart Siegler

2

Tôi gặp phải vấn đề tương tự trong c ++ / mfc. Tôi thích sử dụng :: PostMessage hơn là :: SendMessage trong lệnh gọi lại BFFM_INITIALIZED để đặt tin nhắn TVM_ENSUREVISIBLE

    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

Tôi đã đọc các cuộc thảo luận ở trên và các giải pháp. Riêng Brat Oestreicher đã đưa tôi đi đúng hướng. Về bản chất, trước tiên chúng ta phải tìm điều khiển TreeView trong SHBrowseForFolderhộp thoại và gửi thông báo đến cửa sổ đó TVM_ENSUREVISIBLE. Điều sau đây thực hiện điều này trong 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);
} 

Rất cám ơn Gary Beene .


0

dlgFolder.RootFolder = Môi trường.SpecialFolder.DesktopDirectory;

không giống như

dlgFolder.RootFolder = Môi trường.SpecialFolder.Desktop;

Sự khác biệt giữa SpecialFolder.Desktop và SpecialFolder.DesktopDirectory là gì?

Chuỗi được liên kết chỉ ra rằng dưới dạng một đường dẫn, chúng nhận được cùng một kết quả. Nhưng chúng không giống nhau, vì một là đường logic và đường kia là đường vật lý.

Tôi đã tìm thấy khi một trong hai được gán cho RootFolder của hộp thoại thư mục mở, hành vi kết quả có thể khác nhau.

Như một phép gán .RootFolder, một số phiên bản của cửa sổ, như win7, coi một trong hai phiên bản là "Máy tính để bàn". Đó là, bạn có thể thấy mục nhập phụ "Máy tính" và mở mục đó để xem các ký tự ổ đĩa riêng lẻ. .SelectedPath được chọn theo cả hai cách, nhưng đường dẫn đã chọn chỉ hiển thị khi đường dẫn logic của màn hình được gán cho .RootFolder.

Tệ hơn nữa, khi sử dụng hộp thoại duyệt thư mục trong bản phát hành trước win10, nó xuất hiện "DesktopDirectory", chỉ là nội dung của Thư mục màn hình, không có liên kết nào đến thư mục màn hình hợp lý. Và không liệt kê bất kỳ mục phụ nào dưới nó. Rất khó chịu nếu một ứng dụng được viết cho win7 đang cố gắng được sử dụng với win10.

Tôi nghĩ vấn đề mà OP đang gặp phải là họ sử dụng máy tính để bàn vật lý làm gốc, trong khi lẽ ra họ phải sử dụng máy tính để bàn logic.

Tôi không có lời giải thích tại sao hai máy khác nhau của OP lại phản ứng khác nhau. Tôi sẽ suy đoán rằng họ đã cài đặt hai phiên bản .NET framework khác nhau.

Thực tế là win10 trước khi gặp sự cố "Bị kẹt trên máy tính để bàn" với hộp thoại thư mục duyệt có thể là do khuôn khổ .NET gần đây hơn được vận chuyển với win10 trước. Thật không may, tôi vẫn không biết tất cả các sự kiện trong trường hợp (win10) này, vì tôi chưa cập nhật.

PS Tôi thấy rằng win8 cũng gặp phải hiện tượng "Bị kẹt trên máy tính để bàn":

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

Cách giải quyết ở đó là chọn GUI thay thế trong win8. Có lẽ điều gì đó tương tự có thể được thực hiện trong win10 trước.


0

Đáp lại bài đăng của Marc F - Tôi đã chuyển đổi VB.Net sang C #

    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.
        }
    }

Đã kiểm tra điều này và nó hoạt động tốt. Đảm bảo bạn tham chiếu System.Runtime.InteropServices, System.Threading, một System.Threading.Tasks


0

Liên kết này có một câu trả lời đơn giản phù hợp với tôi (tôi có cửa sổ 8.1)

FolderBrowserDialog: Mở rộng thư mục đã chọn


1
Thao tác này sẽ mở rộng thư mục đã chọn, nhưng khiến con đầu tiên được chọn không chính xác và không giải quyết được vấn đề cuộn.
avenmore

Tại sao bạn liên kết đến một trang web khác mà không ít nhất là sao chép giải pháp ở đây?
yossico

-1

Cách tốt nhất, ít nhất là đáng tin cậy nhất là tạo hộp thoại lớp trình duyệt của riêng bạn. Vấn đề cuộn cây đã là một vấn đề nhức nhối trong nhiều năm - nó sẽ không bao giờ được khắc phục!

Nếu bạn biết làm thế nào để kết xuất trong sơn thì không có nhiều thứ bạn không thể làm .. nhanh trong sơn tốt đó là một câu chuyện khác.

Nơi đầu tiên tôi sẽ xem xét mã nguồn .Net nguồn mở trên GitHub, trong phiên bản .Net mà bạn chọn, cho lớp hộp thoại mà bạn muốn cải thiện. Bạn có thể ngạc nhiên về những gì bạn có thể đạt được với một chút nỗ lực và làm theo. Chỉ cần sao chép điều khiển và gỡ lỗi đến điểm xảy ra lỗi và vá lỗi - đó là những gì Microsoft làm, bạn cũng vậy!

Vì đây là một chủ đề cũ và các mẫu đăng có thể không bao giờ được đọc. Nó sẽ làm cho nhiều hơn kể từ khi đăng nếu được hỏi.

Tuy nhiên, đối với ai đó đang tìm cách giải quyết một vấn đề như cuộn cây đến thư mục "mong đợi", đây là một số lời khuyên chắc chắn. Nếu sự cố tồn tại với một điều khiển hoặc thư viện không có giải pháp ngay lập tức, hãy tạo phiên bản của riêng bạn, khi có thể, hãy mở rộng bản gốc và vá sự cố. Tôi đã cải tiến mọi thứ từ lớp Windows.Form.Control sang các thư viện Win32 với mục đích duy nhất là nhận được kết quả có thể đoán trước và chính xác.

Tin tốt là với C #, có rất nhiều điều khiển cấp thấp để đạt được hầu hết mọi mục tiêu hợp lý và C cũng vậy.

Trong quá khứ, tôi đã dành quá nhiều giờ để tìm kiếm giải pháp cho một vấn đề mà tôi vừa tạo lại những gì không hoạt động mà rất nhiều thời gian sẽ được cứu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.