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

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

введите описание изображения здесь

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

введите описание изображения здесь

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

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

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

        }

person Munawar    schedule 04.08.2011    source источник
comment
Да, это экологическое. Диалог реализован в Windows, а не в Silverlight. Вполне может быть ошибкой Windows, я уверен, что обычно отсутствующее текстовое поле папки является основной причиной. Без него папка «Проблемы» была бы видна. Обратитесь в службу поддержки Microsoft, если вы хотите решить эту проблему.   -  person Hans Passant    schedule 05.08.2011


Ответы (14)


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

Итак, все, что нам нужно сделать, чтобы исправить это, - это отправить TreeView, который является частью сообщения FolderBrowserDialog the 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;
    }
}
person Brad Oestreicher    schedule 15.03.2013
comment
Это должно быть отмечено как ответ. Я только что столкнулся с той же проблемой, и этот код работал отлично. Это также очень подробное и хорошо написанное объяснение. - person Dan; 03.06.2013
comment
Каким должен быть второй аргумент метода ShowFolderBrowser? _2 _...? - person Syspect; 30.06.2013
comment
@Syspect - аргумент IWin32Window - это просто родительская форма, из которой запускается средство выбора папки. Если вы вызываете это прямо из кода формы, вы можете просто использовать ключевое слово this в качестве параметра. (Технически IWin32Window на самом деле является оберткой вокруг hWnd за формой, но C # скрывает от вас все уродливые вещи, связанные с этим!) - person Brad Oestreicher; 13.07.2013
comment
В Win7 я заметил, что прокрутка произошла, а затем была сброшена, поскольку системные папки, такие как библиотеки и т. Д., Были добавлены в дерево после того, как диалог был первоначально показан. Установка начального интервала в 1000 мс была достаточной, чтобы преодолеть это, хотя это всего лишь еще одна карта наверху! - person Jonathan Mitchell; 15.08.2013
comment
Я пробовал это, и у меня это отлично сработало. Но я бы хотел, чтобы рядом с текстовым полем была кнопка «По умолчанию», в которой всегда будет отображаться конкретная папка. Любая помощь будет отличной. Спасибо - person hima; 06.08.2014
comment
В Win10, как заметил @Jonathan Mitchell, есть проблема с синхронизацией. Установка t.Interval = 100; было достаточно, чтобы решить эту проблему на моей машине (дольше для более медленных машин?). - person avenmore; 02.11.2015
comment
Я пробовал это из кода mfc / c ++ (без таймера), и GetDlgItem не смог найти древовидное представление. Поэтому я использовал EnumChildWindows найти его. Я активировал его BFFM_SELCHANGED и потом отключил. В качестве примечания я обнаружил, что BFFM_SETSELECTION и BFFM_SETEXPANDED не работают с несуществующими путями, поэтому возвращение по пути до тех пор, пока не будет найден действительный, может быть решением. - person wonko realtime; 09.09.2016
comment
@Brad Oestreicher - Я тестировал ваш образец на Windows 10, но он не работает. Он вообще не перешел к выбранному каталогу. - person Munawar; 16.12.2016
comment
@Brad, он попадает в SendMessage при достижении повторных попыток до 9, но не прокручивается до выбранного каталога / примечания. - person Munawar; 16.12.2016
comment
@ Мунавар. Я заставил его работать в Windows 10, хотя пришлось скорректировать время. Как и вы, я видел, как он отправлял сообщение SendMessage (и возвращал успешный результат), но ничего не делал. Возможно, та же проблема, о которой упоминал выше Джонатан. Я добавил задержку, прежде чем он попытается начать поиск диалога, и теперь все значительно улучшилось (эту задержку можно сделать, добавив блок «if retries‹ 5 »ниже строки, где мы, например, уменьшаем счетчик повторов) - person PaulG; 24.02.2017
comment
Что касается настройки интервала времени, существует множество различных факторов, которые могут повлиять на последний результат. В частности, когда вы открываете сетевую папку, на инициализацию FolderBrowserDialog уходит много времени, вам нужно установить 2000 мс, даже 5000 мс. - person Wang Jijun; 14.12.2017
comment
Я считаю это прекрасным ответом. Думаю, стоит написать действительно хорошую статью! Живите и учитесь у программистов низкого уровня! Спасибо, Брэд и Пол. - person Alexander Chernosvitov; 06.12.2020

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

В примере (ниже) просто используется простой метод SendKeys (который я ненавижу, но в данном случае он работает хорошо). При использовании метода SendKeys для перехода к выбранной папке в диалоговом окне, если вы отлаживаете это в Visual Studio, тогда вызов SendKeys применяется к текущему окну, которое будет активным окном VS. Чтобы быть более надежным и избежать неправильного окна для получения сообщения SendKeys, тогда метод расширения будет содержать вызовы внешних методов для отправки сообщений в конкретное окно, подобное тому, что опубликовал Marc F, но переведенным на 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);
    }
}
person Brien Halstead    schedule 10.09.2016
comment
Это было наиболее полезно для меня в ОС Windows 8 x64. Однако я расширил его, добавив выполнение ключей sendkeys в событии Timer_Tick через 500 миллисекунд, когда он перемещался в выбранную папку, а затем возвращался к корневому диску этой папки. Так что нужна была задержка. - person hynsey; 23.06.2017

Я использовал обходной путь из 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

person Tobias Knauss    schedule 17.04.2015
comment
Работает на меня. Интересно узнать, что последовательность клавиш табуляции, табуляции и правой стрелки прокручивает выбранный каталог. В C #: SendKeys.Send("{TAB}{TAB}{RIGHT}"); - person Roland; 15.02.2016
comment
этот сбой: я полагаю, что это относится к уловке SendKeys, и этот сбой должен быть особенностью. - person Roland; 15.02.2016

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

SendKeys.Send ("{TAB}{TAB}{RIGHT}")
person Loi Condes    schedule 08.08.2016
comment
этот ответ потрясающий! решает мою проблему на данный момент .. действительно ненавидел необходимость прокрутки вниз до выбора ..: +1 :. был бы мой принятый ответ, если бы это было до меня: P - person Dan Bradbury; 20.11.2018
comment
К сожалению, это не работает в моей системе (Windows 10); - person Phil Rogers; 24.11.2020
comment
@Phil, если у вас отображается кнопка New Folder, SendKeys.Send ("{RIGHT}") у меня сработало. - person Ste; 06.07.2021

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

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

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

person oferb    schedule 28.08.2014

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

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

Ответ Гансу Пассанту: Я пробовал это расширение диалога, в котором есть текстовое поле, но не повезло.

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

person Munawar    schedule 05.08.2011

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

  1. Если .SelectedPath заканчивается на "\", диалоговое окно будет прокручиваться вниз, чтобы сделать путь видимым.
  2. Если .SelectedPath не заканчивается на "\", путь по-прежнему выбран, но не обязательно видимый.
person René    schedule 12.01.2015
comment
Извините: это решение работает только половину раз. Похоже, внутри есть какое-то состояние гонки. Примечание: каталог должен существовать. - person Aleksandr; 20.03.2018
comment
Я еще не видел эту работу. Выбор всегда остается в корне. - person Phil Rogers; 24.11.2020

Я что-то вычислил в VB.NET, поэтому было бы легко преобразовать это в C #. Я француз и новичок в 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'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

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

person Marc F    schedule 11.07.2015
comment
Вопрос довольно старый, поэтому я не возлагаю больших надежд на то, что кто-то ответит в ближайшее время. Спасибо за вклад! - person ZygD; 11.07.2015
comment
Это следует задать в отдельном вопросе. Его здесь вряд ли можно увидеть. - person Stuart Siegler; 11.07.2015

Я столкнулся с той же проблемой в c ++ / mfc. У меня сработало использование :: PostMessage вместо :: SendMessage в обратном вызове BFFM_INITIALIZED для размещения сообщения 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;
}
person Stefan_l_01    schedule 29.01.2016

Я прочитал приведенное выше обсуждение и решения. В частности, Брат Острейхер направил меня в правильном направлении. По сути, мы должны сначала найти элемент управления TreeView в диалоговом окне SHBrowseForFolder и отправить этому окну сообщение TVM_ENSUREVISIBLE. Следующее делает это в 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);
} 

Большое спасибо Гэри Бин.

person Paul Ogilvie    schedule 18.01.2018

dlgFolder.RootFolder = Environment.SpecialFolder.DesktopDirectory;

это не то же самое, что

dlgFolder.RootFolder = Environment.SpecialFolder.Desktop;

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

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

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

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

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

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

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

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

P.S. Я обнаружил, что win8 также испытывает симптом «зависания на рабочем столе»:

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

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

person an odder guest    schedule 27.06.2015

В ответ на сообщение Марка Ф. - я преобразовал VB.Net в 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.
        }
    }

Протестировал это, и он отлично работает. Убедитесь, что вы ссылаетесь на System.Runtime.InteropServices, System.Threading, an System.Threading.Tasks

person MC9000    schedule 13.03.2018


Лучший подход, по крайней мере, самый надежный - создать собственное диалоговое окно класса браузера. Проблема с прокруткой дерева была проблемой в течение многих лет - ее никогда не исправить!

Если вы знаете, как рисовать краской, вы мало что можете сделать ... быстро рисовать хорошо, это отдельная история.

В первую очередь я хотел бы взглянуть на исходный код .Net с открытым исходным кодом на GitHub в выбранной вами версии .Net для класса диалога, который вы хотите улучшить. Вы можете быть удивлены тем, чего можно достичь, приложив немного усилий и доведя их до конца. Просто продублируйте элемент управления и отладьте до точки, где возникает ошибка, и исправьте - это то, что делает Microsoft, и вы тоже можете!

Поскольку это старая ветка, и образцы сообщений, возможно, никогда не будут прочитаны. Было бы больше, так как опубликовать, если бы спросили.

Тем не менее, для тех, кто хочет решить такую ​​проблему, как прокрутка дерева к «ожидаемому» каталогу, вот несколько надежных советов. Если существует проблема с элементом управления или библиотекой, у которой нет немедленного решения, создайте свою собственную версию, по возможности расширьте исходную и исправьте проблему. Я переработал все, от класса Windows.Form.Control до библиотек Win32, с единственной целью - получить предсказуемые и точные результаты.

Хорошая новость заключается в том, что в C # доступно множество низкоуровневых элементов управления для достижения практически любой разумной цели, в том числе и C.

Раньше я тратил слишком много часов на поиск решения проблемы, и если бы я только что воссоздал то, что не работало, можно было бы сэкономить много времени.

person Jamie    schedule 23.06.2018