From b87c4a8d662e15c035ef181f280b9edb5138dd9c Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 21:35:35 +0000 Subject: [PATCH] Win32 implementation of folder browse dialog. --- src/Perspex.Controls/SystemDialog.cs | 2 + .../Perspex.Win32/Interop/UnmanagedMethods.cs | 153 +++++++++++++++++- .../Perspex.Win32/Perspex.Win32.csproj | 12 -- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 70 +++++--- src/Windows/Perspex.Win32/packages.config | 2 - 5 files changed, 203 insertions(+), 36 deletions(-) diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index 2c4ec92147..bac791dd36 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -36,6 +36,8 @@ namespace Perspex.Controls public class OpenFolderDialog : FileSystemDialog { + public string DefaultDirectory { get; set; } + public Task ShowAsync(Window window = null) => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); } diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 5bb2038813..2a0a000226 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -703,6 +704,131 @@ namespace Perspex.Win32.Interop return SetClassLong64(hWnd, nIndex, dwNewLong); } + #region Constants + + public const uint FOS_PICKFOLDERS = 0x00000020; + public const uint FOS_FORCEFILESYSTEM = 0x00000040; + public const uint FOS_NOVALIDATE = 0x00000100; + public const uint FOS_NOTESTFILECREATE = 0x00010000; + public const uint FOS_DONTADDTORECENT = 0x02000000; + + public const uint S_OK = 0x0000; + + public const uint SIGDN_FILESYSPATH = 0x80058000; + + #endregion + + + #region COM + + [ComImport, ClassInterface(ClassInterfaceType.None), TypeLibType(TypeLibTypeFlags.FCanCreate), Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] + internal class FileOpenDialogRCW { } + + + [ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IFileDialog + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + [PreserveSig()] + uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow + + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypes([In] uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr rgFilterSpec); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileTypeIndex([In] uint iFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileTypeIndex(out uint piFileType); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Advise([In, MarshalAs(UnmanagedType.Interface)] IntPtr pfde, out uint pdwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Unadvise([In] uint dwCookie); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOptions([In] uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetOptions(out uint fos); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetDefaultFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetResult([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint AddPlace([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, uint fdap); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Close([MarshalAs(UnmanagedType.Error)] uint hr); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetClientGuid([In] ref Guid guid); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint ClearClientData(); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } + + + [ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + internal interface IShellItem + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint BindToHandler([In] IntPtr pbc, [In] ref Guid rbhid, [In] ref Guid riid, [Out, MarshalAs(UnmanagedType.Interface)] out IntPtr ppvOut); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetParent([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetDisplayName([In] uint sigdnName, out IntPtr ppszName); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint GetAttributes([In] uint sfgaoMask, out uint psfgaoAttribs); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } + + #endregion + + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv); + [DllImport("user32.dll", SetLastError = true)] public static extern bool OpenClipboard(IntPtr hWndOwner); @@ -873,6 +999,31 @@ namespace Perspex.Win32.Interop } + [Flags] + internal enum FOS : uint + { + FOS_OVERWRITEPROMPT = 0x00000002, + FOS_STRICTFILETYPES = 0x00000004, + FOS_NOCHANGEDIR = 0x00000008, + FOS_PICKFOLDERS = 0x00000020, + FOS_FORCEFILESYSTEM = 0x00000040, // Ensure that items returned are filesystem items. + FOS_ALLNONSTORAGEITEMS = 0x00000080, // Allow choosing items that have no storage. + FOS_NOVALIDATE = 0x00000100, + FOS_ALLOWMULTISELECT = 0x00000200, + FOS_PATHMUSTEXIST = 0x00000800, + FOS_FILEMUSTEXIST = 0x00001000, + FOS_CREATEPROMPT = 0x00002000, + FOS_SHAREAWARE = 0x00004000, + FOS_NOREADONLYRETURN = 0x00008000, + FOS_NOTESTFILECREATE = 0x00010000, + FOS_HIDEMRUPLACES = 0x00020000, + FOS_HIDEPINNEDPLACES = 0x00040000, + FOS_NODEREFERENCELINKS = 0x00100000, + FOS_DONTADDTORECENT = 0x02000000, + FOS_FORCESHOWHIDDEN = 0x10000000, + FOS_DEFAULTNOMINIMODE = 0x20000000 + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct OpenFileName { @@ -899,6 +1050,6 @@ namespace Perspex.Win32.Interop public IntPtr reservedPtr; public int reservedInt; public int flagsEx; - } + } } } diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 36b5cf098b..0cd573d7b8 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,18 +38,6 @@ CS1591 - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.2\lib\Microsoft.WindowsAPICodePack.dll - True - - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll - True - - - ..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll - True - diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index d870515de8..d8473f75c2 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,10 +10,10 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; -using Microsoft.WindowsAPICodePack.Dialogs; namespace Perspex.Win32 { + class SystemDialogImpl : ISystemDialogImpl { public unsafe Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) @@ -45,16 +45,15 @@ namespace Perspex.Win32 dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length); string userSelectedExt = null; - + fixed (char* pFileBuffer = fileBuffer) fixed (char* pFilterBuffer = filterBuffer) fixed (char* pDefExt = defExt) fixed (char* pInitDir = dialog.InitialDirectory) fixed (char* pTitle = dialog.Title) { - var ofn = new UnmanagedMethods.OpenFileName() - { + { hwndOwner = hWnd, hInstance = IntPtr.Zero, lCustData = IntPtr.Zero, @@ -134,27 +133,56 @@ namespace Perspex.Win32 { return Task.Factory.StartNew(() => { - var dlg = new CommonOpenFileDialog(); - dlg.Title = dialog.Title; - dlg.IsFolderPicker = true; - dlg.InitialDirectory = dialog.InitialDirectory; + string result = string.Empty; - dlg.AddToMostRecentlyUsedList = false; - dlg.AllowNonFileSystemItems = false; - - dlg.EnsureFileExists = true; - dlg.EnsurePathExists = true; - dlg.EnsureReadOnly = false; - dlg.EnsureValidNames = true; - dlg.Multiselect = false; - dlg.ShowPlacesList = true; - dlg.DefaultFileName = ""; + var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; + var frm = (UnmanagedMethods.IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW()); + uint options; + frm.GetOptions(out options); + options |= UnmanagedMethods.FOS_PICKFOLDERS | UnmanagedMethods.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS_NOVALIDATE | UnmanagedMethods.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS_DONTADDTORECENT; + frm.SetOptions(options); - string result = string.Empty; + if (dialog.InitialDirectory != null) + { + UnmanagedMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + { + frm.SetFolder(directoryShellItem); + } + } - if (dlg.ShowDialog() == CommonFileDialogResult.Ok) + if (dialog.DefaultDirectory != null) { - result = dlg.FileName; + UnmanagedMethods.IShellItem directoryShellItem; + var riid = new Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"); //IShellItem + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == UnmanagedMethods.S_OK) + { + frm.SetDefaultFolder(directoryShellItem); + } + } + + if (frm.Show(hWnd) == UnmanagedMethods.S_OK) + { + UnmanagedMethods.IShellItem shellItem; + if (frm.GetResult(out shellItem) == UnmanagedMethods.S_OK) + { + IntPtr pszString; + if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == UnmanagedMethods.S_OK) + { + if (pszString != IntPtr.Zero) + { + try + { + result = Marshal.PtrToStringAuto(pszString); + } + finally + { + Marshal.FreeCoTaskMem(pszString); + } + } + } + } } return result; diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 4f6923eb53..571e4fe023 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,7 +1,5 @@  - -