Почему диалоговое окно FolderBrowserDialog не прокручивается до выбранной папки?

Как показано на этом снимке экрана, выбранная папка отсутствует в представлении. Чтобы просмотреть выбранную папку, ее нужно прокрутить вниз.

enter image description here

В этом же диалоговом окне отображается выбранная папка, видимая на другом компьютере

enter image description here

Я запустил его на двух компьютерах, имеющих окна 7. Он работает правильно на одном, но не на втором. Что-то выглядит с окнами, а не с кодом? Может ли кто-нибудь предложить какое-либо исправление?

В коде нет изменений. Я использовал более длинные пути с разных дисков, но результаты одинаковы.

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

        }

Ответ 1

Основная проблема - это плохое дизайнерское решение в FolderBrowserDialog. Во-первых, нам нужно понять, что FolderBrowserDialog не является элементом управления .NET, а скорее является Common Dialog и является частью Windows. Дизайнер этого диалога решил не отправлять элемент управления TreeView a TVM_ENSUREVISIBLE после отображения диалогового окна и выбрать начальную папку. Это сообщение заставляет элемент управления TreeView прокручиваться так, чтобы текущий выбранный элемент был виден в окне.

Итак, все, что нам нужно сделать, чтобы исправить это, - это отправить TreeView, который является частью сообщения FolderBrowserDialog 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;
    }
}

Ответ 2

Я использовал обходное решение из 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 ();

Это не самый приятный способ, но он работает для меня.
Без RootFolder он НЕ работает при первом вызове, а на втором и следующем. При этом он работает всегда.

Как отмечают другие, этот отказ зависит от операционной системы:
Я использую Win 7 Pro x64 SP1

Ответ 3

Я читал на разных форумах, что это может быть связано с RootFolder, потому что SelectedPath и RootFolder являются взаимоисключающими, что означает, что оба не могут сосуществовать, но с по умолчанию RootFolder (.Desktop). Он позволяет, по крайней мере, подниматься по дереву ( перемещаться по диску/папкам).

Однако, если RootFolder изменен на другое, кроме рабочего, вы не сможете перейти к UNC-путям.

Ответа на вопрос Hans Passant: Я пробовал это расширение Dialog, которое имеет TextBox, но не повезло.

Настройка диалогового окна просмотра папки для отображения пути

Ответ 4

в коде VB.Net, просто поместите эту строку кода прямо перед отображением диалога.

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

Ответ 5

Я вычислил что-то в VB.NET, поэтому было бы легко преобразовать его в С#. Я французский, и я новичок в VB. В любом случае, вы можете попробовать мое решение.

Моя идея - запустить асинхронную задачу перед показом folderBrowserDialog.

Я нашел это сам, но меня вдохновил пост Брэда. Здесь мой код:

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

Я жду ваших предложений. И кто-то может перевести его на С#, потому что я не знаю С#.

Ответ 6

Я столкнулся с той же проблемой в С++/mfc. Это помогло мне использовать:: PostMessage, а не:: SendMessage в BFFM_INITIALIZED обратном вызове, чтобы разместить TVM_ENSUREVISIBLE msg

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

Ответ 7

Я знаю, что этот поток является WAY старым, но с помощью методов расширения это можно добавить к методу FolderBrowserDialog.ShowDialog, а затем повторно использовать там, где это необходимо.

Образец (ниже) просто использует простой метод SendKeys (который я ненавижу, но в этом случае он работает хорошо). При использовании метода SendKeys для перехода к выбранной папке в диалоговом окне, если вы отлаживаете это в Visual Studio, тогда вызов SendKeys применяется к текущему окну, который будет активным окном VS. Чтобы быть более надежным и избегать неправильного окна от получения сообщения SendKeys, метод расширения будет содержать вызовы внешних методов для отправки сообщений в конкретное окно, аналогичное тому, что было опубликовано Marc F, но переведено на С#.

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

Ответ 8

Я обнаружил, что:

  • Если .SelectedPath заканчивается на "\", Диалог будет прокручиваться вниз, чтобы сделать видимым путь.
  • Если .SelectedPath не заканчивается на "\", путь по-прежнему выбран, но не может быть видимым.

Ответ 10

dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

не совпадает с

dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;

В чем разница между SpecialFolder.Desktop и SpecialFolder.DesktopDirectory?

Связанный поток указывает, что в качестве пути они получают одинаковый результат. Но они не совпадают, поскольку один из них является логическим путем, а другой - физическим путем.

Я обнаружил, что когда один из них назначен RootFolder в диалоговом окне открытой папки, результирующее поведение может быть другим.

Как назначение .RootFolder, некоторые версии окон, например win7, рассматривают один из них как "Рабочий стол". То есть вы можете увидеть под-запись "Компьютер" и открыть ее, чтобы увидеть отдельные буквы диска. Выбор .SelectedPath выбирается в любом направлении, но выбранный путь становится видимым только тогда, когда логический путь рабочего стола назначается .RootFolder.

Хуже того, при использовании диалогового окна папки просмотра в предварительном выпуске win10, кажется, что "DesktopDirectory" как раз это, только содержимое каталога Desktop, без какой-либо ссылки на каталог логического рабочего стола. И не перечислять подделки под ним. Очень неприятно, если приложение, написанное для win7, пытается использоваться с win10.

Я думаю, что проблема, с которой сталкивается OP, заключается в том, что они использовали физический рабочий стол в качестве корня, когда они должны были использовать логический рабочий стол.

У меня нет объяснения, почему OP две разные машины реагируют по-разному. Я бы предположил, что у них установлены две разные версии .NET framework.

Тот факт, что предварительная предварительная версия win10 имеет проблему "Застрял на рабочем столе" с диалоговым окном папки просмотра, может быть связана с более поздней платформой .NET, поставляемой с предварительным выпуском win10. К сожалению, я по-прежнему не осведомлен о всех фактах в этом случае (win10), поскольку я еще не обновлялся.

P.S. Я обнаружил, что win8 также испытывает симптом "Stuck on Desktop":

https://superuser.com/questions/869928/windows-8-1-folder-selection-dialog-missing-my-computer-and-sub-items

Обходной путь состоял в том, чтобы выбрать альтернативный GUI в win8. Возможно, что-то подобное можно сделать в предварительном выпуске win10.

Ответ 11

это работает для меня

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

но только после второго использования диалога