由于WPF超过10年后仍然没有文件夹浏览器对话框,我不想仅为此对话框设置对System.Windows.Forms的引用,我搜索了一下,找到了一个很好的来源(我在想)符合我的需要。
以下是代码:
原生的东西:
Class NativeMethods
<DllImport("shell32.dll")>
Public Shared Function SHCreateShellItem(pidlParent As IntPtr, psfParent As IntPtr, pidl As IntPtr, ByRef ppsi As IShellItem) As Integer
End Function
<DllImport("shell32.dll")>
Public Shared Function SHParseDisplayName(<MarshalAs(UnmanagedType.LPWStr)> ByVal pszPath As String, ByVal bindingContext As IntPtr, <Out()> ByRef pidl As IntPtr, ByVal sfgaoIn As UInteger, <Out()> ByRef sfgaoOut As UInteger) As Integer
End Function
End Class
<ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Friend Interface IShellItem
Sub BindToHandler(<[In], MarshalAs(UnmanagedType.[Interface])> pbc As IntPtr, <[In]> ByRef bhid As Guid, <[In]> ByRef riid As Guid, ByRef ppv As IntPtr)
Sub GetParent(<MarshalAs(UnmanagedType.[Interface])> ByRef ppsi As IShellItem)
Sub GetDisplayName(<[In]> sigdnName As SIGDN, <MarshalAs(UnmanagedType.LPWStr)> ByRef ppszName As String)
Sub GetAttributes(<[In]> sfgaoMask As UInteger, ByRef psfgaoAttribs As UInteger)
Sub Compare(<[In], MarshalAs(UnmanagedType.[Interface])> psi As IShellItem, <[In]> hint As UInteger, ByRef piOrder As Integer)
End Interface
<ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Friend Interface IShellItemArray
Sub BindToHandler(<[In], MarshalAs(UnmanagedType.[Interface])> pbc As IntPtr, <[In]> ByRef rbhid As Guid, <[In]> ByRef riid As Guid, ByRef ppvOut As IntPtr)
Sub GetPropertyStore(<[In]> Flags As Integer, <[In]> ByRef riid As Guid, ByRef ppv As IntPtr)
Sub GetPropertyDescriptionList(<[In], MarshalAs(UnmanagedType.Struct)> ByRef keyType As IntPtr, <[In]> ByRef riid As Guid, ByRef ppv As IntPtr)
Sub GetAttributes(<[In], MarshalAs(UnmanagedType.I4)> dwAttribFlags As IntPtr, <[In]> sfgaoMask As UInteger, ByRef psfgaoAttribs As UInteger)
Sub GetCount(ByRef pdwNumItems As UInteger)
Sub GetItemAt(<[In]> dwIndex As UInteger, <MarshalAs(UnmanagedType.[Interface])> ByRef ppsi As IShellItem)
Sub EnumItems(<MarshalAs(UnmanagedType.[Interface])> ByRef ppenumShellItems As IntPtr)
End Interface
<ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), CoClass(GetType(FileOpenDialog))>
Friend Interface IFileOpenDialog
<PreserveSig>
Function Show(<[In]> parent As IntPtr) As Integer
Sub SetFileTypes(<[In]> cFileTypes As UInteger, <[In], MarshalAs(UnmanagedType.Struct)> ByRef rgFilterSpec As IntPtr)
Sub SetFileTypeIndex(<[In]> iFileType As UInteger)
Sub GetFileTypeIndex(ByRef piFileType As UInteger)
Sub Advise(<[In], MarshalAs(UnmanagedType.[Interface])> pfde As IntPtr, ByRef pdwCookie As UInteger)
Sub Unadvise(<[In]> dwCookie As UInteger)
Sub SetOptions(<[In]> fos As FOS)
Sub GetOptions(ByRef pfos As FOS)
Sub SetDefaultFolder(<[In], MarshalAs(UnmanagedType.[Interface])> psi As IShellItem)
Sub SetFolder(<[In], MarshalAs(UnmanagedType.[Interface])> psi As IShellItem)
Sub GetFolder(<MarshalAs(UnmanagedType.[Interface])> ByRef ppsi As IShellItem)
Sub GetCurrentSelection(<MarshalAs(UnmanagedType.[Interface])> ByRef ppsi As IShellItem)
Sub SetFileName(<[In], MarshalAs(UnmanagedType.LPWStr)> pszName As String)
Sub GetFileName(<MarshalAs(UnmanagedType.LPWStr)> ByRef pszName As String)
Sub SetTitle(<[In], MarshalAs(UnmanagedType.LPWStr)> pszTitle As String)
Sub SetOkButtonLabel(<[In], MarshalAs(UnmanagedType.LPWStr)> pszText As String)
Sub SetFileNameLabel(<[In], MarshalAs(UnmanagedType.LPWStr)> pszLabel As String)
Sub GetResult(<MarshalAs(UnmanagedType.[Interface])> ByRef ppsi As IShellItem)
Sub AddPlace(<[In], MarshalAs(UnmanagedType.[Interface])> psi As IShellItem, fdcp As Object)
Sub SetDefaultExtension(<[In], MarshalAs(UnmanagedType.LPWStr)> pszDefaultExtension As String)
Sub Close(<MarshalAs(UnmanagedType.[Error])> hr As Integer)
Sub SetClientGuid(<[In]> ByRef guid As Guid)
Sub ClearClientData()
Sub SetFilter(<MarshalAs(UnmanagedType.[Interface])> pFilter As IntPtr)
Sub GetResults(<MarshalAs(UnmanagedType.[Interface])> ByRef ppenum As IShellItemArray)
Sub GetSelectedItems(<MarshalAs(UnmanagedType.[Interface])> ByRef ppsai As IShellItemArray)
End Interface
<ComImport, Guid("DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7")>
Class FileOpenDialog
End Class
Enum SIGDN As UInteger
DESKTOPABSOLUTEEDITING = &H8004C000UI
DESKTOPABSOLUTEPARSING = &H80028000UI
FILESYSPATH = &H80058000UI
NORMALDISPLAY = 0
PARENTRELATIVE = &H80080001UI
PARENTRELATIVEEDITING = &H80031001UI
PARENTRELATIVEFORADDRESSBAR = &H8007C001UI
PARENTRELATIVEPARSING = &H80018001UI
URL = &H80068000UI
End Enum
<Flags>
Enum FOS
ALLNONSTORAGEITEMS = &H80
ALLOWMULTISELECT = &H200
CREATEPROMPT = &H2000
DEFAULTNOMINIMODE = &H20000000
DONTADDTORECENT = &H2000000
FILEMUSTEXIST = &H1000
FORCEFILESYSTEM = &H40
FORCESHOWHIDDEN = &H10000000
HIDEMRUPLACES = &H20000
HIDEPINNEDPLACES = &H40000
NOCHANGEDIR = 8
NODEREFERENCELINKS = &H100000
NOREADONLYRETURN = &H8000
NOTESTFILECREATE = &H10000
NOVALIDATE = &H100
OVERWRITEPROMPT = 2
PATHMUSTEXIST = &H800
PICKFOLDERS = &H20
SHAREAWARE = &H4000
STRICTFILETYPES = 4
End Enum
对话类:
Public NotInheritable Class FolderBrowserDialog
Private _selectedPath As String
Private _selectedElementName As String
Private _selectedPaths As String()
Private _selectedElementNames As String()
Private _allowNonStoragePlaces As Boolean
Private _multiSelect As Boolean
Public Property SelectedPath As String
Get
Return _selectedPath
End Get
Set(ByVal value As String)
_selectedPath = value
End Set
End Property
Public Property SelectedElementName As String
Get
Return _selectedElementName
End Get
Private Set(value As String)
_selectedElementName = value
End Set
End Property
Public Property SelectedPaths As String()
Get
Return _selectedPaths
End Get
Private Set(value As String())
_selectedPaths = value
End Set
End Property
Public Property SelectedElementNames As String()
Get
Return _selectedElementNames
End Get
Private Set(value As String())
_selectedElementNames = value
End Set
End Property
Public Property AllowNonStoragePlaces As Boolean
Get
Return _allowNonStoragePlaces
End Get
Set(ByVal value As Boolean)
_allowNonStoragePlaces = value
End Set
End Property
Public Property MultiSelect As Boolean
Get
Return _multiSelect
End Get
Set(ByVal value As Boolean)
_multiSelect = value
End Set
End Property
Public Function ShowDialog(ByVal owner As Window) As Boolean
Return Me.ShowDialog(If(owner Is Nothing, IntPtr.Zero, New Interop.WindowInteropHelper(owner).Handle))
End Function
Public Function ShowDialog(ByVal owner As IntPtr) As Boolean
Dim dialog As IFileOpenDialog
If (Environment.OSVersion.Version.Major < 6) Then
Throw New InvalidOperationException("The dialog needs at least Windows Vista to work.")
End If
dialog = Me.CreateNativeDialog
Try
Call Me.SetInitialFolder(dialog)
Call Me.SetOptions(dialog)
If (dialog.Show(owner) <> 0) Then
Return False
End If
Call Me.SetDialogResults(dialog)
Return True
Finally
Marshal.ReleaseComObject(dialog)
End Try
End Function
Private Function CreateNativeDialog() As IFileOpenDialog
Return DirectCast(New FileOpenDialog, IFileOpenDialog)
End Function
Private Sub SetInitialFolder(ByVal dialog As IFileOpenDialog)
Dim item As IShellItem = Nothing
Dim idl As IntPtr
Dim atts As UInteger
If Not String.IsNullOrEmpty(Me.SelectedPath) Then
If (NativeMethods.SHParseDisplayName(Me.SelectedPath, IntPtr.Zero, idl, 0, atts) = 0) AndAlso (NativeMethods.SHCreateShellItem(IntPtr.Zero, IntPtr.Zero, idl, item) = 0) Then
dialog.SetDefaultFolder(item)
End If
End If
End Sub
Private Sub SetOptions(ByVal dialog As IFileOpenDialog)
dialog.SetOptions(Me.GetDialogOptions)
End Sub
Private Function GetDialogOptions() As FOS
Dim options As FOS
options = FOS.PICKFOLDERS
If Me.MultiSelect Then
options = options Or FOS.ALLOWMULTISELECT
End If
If Not Me.AllowNonStoragePlaces Then
options = options Or FOS.FORCEFILESYSTEM
End If
Return options
End Function
Private Sub SetDialogResults(ByVal dialog As IFileOpenDialog)
Dim item As IShellItem = Nothing
Dim path As String = Nothing
Dim value As String = Nothing
If Not Me.MultiSelect Then
dialog.GetResult(item)
Call Me.GetPathAndElementName(item, path, value)
Me.SelectedPath = path
Me.SelectedPaths = New String() {path}
Me.SelectedElementName = value
Me.SelectedElementNames = New String() {value}
Else
Dim items As IShellItemArray = Nothing
Dim count As UInteger
dialog.GetResults(items)
items.GetCount(count)
Me.SelectedPaths = New String(count - 1) {}
Me.SelectedElementNames = New String(count - 1) {}
For i As UInteger = 0 To count - 1
items.GetItemAt(i, item)
Call Me.GetPathAndElementName(item, path, value)
Me.SelectedPaths(i) = path
Me.SelectedElementNames(i) = value
Next i
Me.SelectedPath = Nothing
Me.SelectedElementName = Nothing
End If
End Sub
Private Sub GetPathAndElementName(ByVal item As IShellItem, ByRef path As String, ByRef elementName As String)
item.GetDisplayName(SIGDN.PARENTRELATIVEFORADDRESSBAR, elementName)
Try
item.GetDisplayName(SIGDN.FILESYSPATH, path)
Catch ex As ArgumentException When (ex.HResult = -2147024809)
path = Nothing
End Try
End Sub
End Class
除了一个小缺陷之外,这可以像预期的那样工作。当我在WPF窗口中使用这个类时......
Dim dialog As FolderBrowserDialog
dialog = New FolderBrowserDialog
dialog.SelectedPath = "C:\Windows\System32"
If dialog.ShowDialog(Me) Then
MsgBox(String.Join(", ", dialog.SelectedPaths))
End If
...显示文件夹浏览器对话框,对话框顶部的文本字段设置为我的初始文件夹C:\Windows\System32
,但对话框左侧的树视图仅设置为drive {{ 1}}。因此,当我想通过鼠标选择我的初始文件夹的子文件夹时,我需要先点击几次导航到初始文件夹,然后才能选择我真正想要选择的文件夹。
有没有办法让文本字段和对话框的树视图保持同步?
我知道有第三方的东西,比如ookii.dialogs,但我想让我的大部分代码都在我的控制之下。