From 9a3f08ce9d7efb8502ef56fee0a7a4c2e247bd52 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 20:03:55 +0000 Subject: [PATCH 1/5] Basic implementation of BrowseFolderDialog and Win32 Implementation. --- src/Perspex.Controls/Platform/ISystemDialogImpl.cs | 2 ++ src/Perspex.Controls/SystemDialog.cs | 9 +++++++-- src/Windows/Perspex.Win32/Perspex.Win32.csproj | 4 ++++ src/Windows/Perspex.Win32/SystemDialogImpl.cs | 13 +++++++++++++ src/Windows/Perspex.Win32/packages.config | 1 + 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs index d3af982f95..f45eec8f95 100644 --- a/src/Perspex.Controls/Platform/ISystemDialogImpl.cs +++ b/src/Perspex.Controls/Platform/ISystemDialogImpl.cs @@ -18,5 +18,7 @@ namespace Perspex.Controls.Platform /// The parent window. /// A task returning the selected filenames. Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent); + + Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent); } } diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index 6eae1e9e9d..b61833390b 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -12,10 +12,9 @@ namespace Perspex.Controls public string InitialDirectory { get; set; } } - public class SaveFileDialog : FileDialog { - public string DefaultExtension { get; set; } + public string DefaultExtension { get; set; } public async Task ShowAsync(Window window = null) => @@ -31,6 +30,12 @@ namespace Perspex.Controls => PerspexLocator.Current.GetService().ShowFileDialogAsync(this, window?.PlatformImpl); } + public class OpenFolderDialog : SystemDialog + { + public Task ShowAsync(Window window = null) + => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); + } + public abstract class SystemDialog { public string Title { get; set; } diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index 0cd573d7b8..d62e03783c 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,6 +38,10 @@ CS1591 + + ..\..\..\packages\Ookii.Dialogs.1.0\lib\net35\Ookii.Dialogs.Wpf.dll + True + diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index 69f394c75c..7ed1c83d34 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,6 +10,7 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; +using Ookii.Dialogs.Wpf; namespace Perspex.Win32 { @@ -128,5 +129,17 @@ namespace Perspex.Win32 return files.Select(f => Path.Combine(dir, f)).ToArray(); }); } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog(); + + return Task.Factory.StartNew(() => + { + fbd.ShowDialog(); + + return fbd.SelectedPath; + }); + } } } diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 571e4fe023..7ea589649f 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,5 +1,6 @@  + From 90f4672be0cbf12357fb3af07e02d4e3e45f2645 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 20:58:00 +0000 Subject: [PATCH 2/5] removed dependency from ookii dialogs and implemented using Windows Code pack. --- src/Perspex.Controls/SystemDialog.cs | 10 ++++-- .../Perspex.Win32/Perspex.Win32.csproj | 12 +++++-- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 31 +++++++++++++++---- src/Windows/Perspex.Win32/packages.config | 3 +- 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/src/Perspex.Controls/SystemDialog.cs b/src/Perspex.Controls/SystemDialog.cs index b61833390b..2c4ec92147 100644 --- a/src/Perspex.Controls/SystemDialog.cs +++ b/src/Perspex.Controls/SystemDialog.cs @@ -5,10 +5,14 @@ using Perspex.Controls.Platform; namespace Perspex.Controls { - public abstract class FileDialog : SystemDialog + public abstract class FileDialog : FileSystemDialog { public List Filters { get; set; } = new List(); - public string InitialFileName { get; set; } + public string InitialFileName { get; set; } + } + + public abstract class FileSystemDialog : SystemDialog + { public string InitialDirectory { get; set; } } @@ -30,7 +34,7 @@ namespace Perspex.Controls => PerspexLocator.Current.GetService().ShowFileDialogAsync(this, window?.PlatformImpl); } - public class OpenFolderDialog : SystemDialog + public class OpenFolderDialog : FileSystemDialog { public Task ShowAsync(Window window = null) => PerspexLocator.Current.GetService().ShowFolderDialogAsync(this, window?.PlatformImpl); diff --git a/src/Windows/Perspex.Win32/Perspex.Win32.csproj b/src/Windows/Perspex.Win32/Perspex.Win32.csproj index d62e03783c..36b5cf098b 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -38,8 +38,16 @@ CS1591 - - ..\..\..\packages\Ookii.Dialogs.1.0\lib\net35\Ookii.Dialogs.Wpf.dll + + ..\..\..\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 7ed1c83d34..d870515de8 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -10,7 +10,7 @@ using Perspex.Controls; using Perspex.Controls.Platform; using Perspex.Platform; using Perspex.Win32.Interop; -using Ookii.Dialogs.Wpf; +using Microsoft.WindowsAPICodePack.Dialogs; namespace Perspex.Win32 { @@ -131,14 +131,33 @@ namespace Perspex.Win32 } public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) - { - VistaFolderBrowserDialog fbd = new VistaFolderBrowserDialog(); - + { return Task.Factory.StartNew(() => { - fbd.ShowDialog(); + var dlg = new CommonOpenFileDialog(); + dlg.Title = dialog.Title; + dlg.IsFolderPicker = true; + dlg.InitialDirectory = dialog.InitialDirectory; + + 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 = ""; + + string result = string.Empty; + + if (dlg.ShowDialog() == CommonFileDialogResult.Ok) + { + result = dlg.FileName; + } - return fbd.SelectedPath; + return result; }); } } diff --git a/src/Windows/Perspex.Win32/packages.config b/src/Windows/Perspex.Win32/packages.config index 7ea589649f..4f6923eb53 100644 --- a/src/Windows/Perspex.Win32/packages.config +++ b/src/Windows/Perspex.Win32/packages.config @@ -1,6 +1,7 @@  - + + From b87c4a8d662e15c035ef181f280b9edb5138dd9c Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 21:35:35 +0000 Subject: [PATCH 3/5] 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 @@  - - From 0f0800958f0c2984bd2b4e60d365cf18c1798dce Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 21:56:36 +0000 Subject: [PATCH 4/5] tidied UnmanagedMethods and put Interfaces out side of class scope. --- .../Perspex.Win32/Interop/UnmanagedMethods.cs | 230 +++++++++--------- src/Windows/Perspex.Win32/SystemDialogImpl.cs | 20 +- 2 files changed, 120 insertions(+), 130 deletions(-) diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index 2a0a000226..4d87d0e033 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -703,129 +703,11 @@ 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); @@ -999,6 +881,16 @@ namespace Perspex.Win32.Interop } + public enum HRESULT : long + { + S_FALSE = 0x0001, + S_OK = 0x0000, + E_INVALIDARG = 0x80070057, + E_OUTOFMEMORY = 0x8007000E + } + + public const uint SIGDN_FILESYSPATH = 0x80058000; + [Flags] internal enum FOS : uint { @@ -1052,4 +944,102 @@ namespace Perspex.Win32.Interop public int flagsEx; } } + + [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); + } } diff --git a/src/Windows/Perspex.Win32/SystemDialogImpl.cs b/src/Windows/Perspex.Win32/SystemDialogImpl.cs index d8473f75c2..84aa78a2a7 100644 --- a/src/Windows/Perspex.Win32/SystemDialogImpl.cs +++ b/src/Windows/Perspex.Win32/SystemDialogImpl.cs @@ -136,17 +136,17 @@ namespace Perspex.Win32 string result = string.Empty; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; - var frm = (UnmanagedMethods.IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW()); + var frm = (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; + options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE | UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT); frm.SetOptions(options); if (dialog.InitialDirectory != null) { - UnmanagedMethods.IShellItem directoryShellItem; + 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) + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.InitialDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetFolder(directoryShellItem); } @@ -154,21 +154,21 @@ namespace Perspex.Win32 if (dialog.DefaultDirectory != null) { - UnmanagedMethods.IShellItem directoryShellItem; + 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) + if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.DefaultDirectory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { frm.SetDefaultFolder(directoryShellItem); } } - if (frm.Show(hWnd) == UnmanagedMethods.S_OK) + if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK) { - UnmanagedMethods.IShellItem shellItem; - if (frm.GetResult(out shellItem) == UnmanagedMethods.S_OK) + IShellItem shellItem; + if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK) { IntPtr pszString; - if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == UnmanagedMethods.S_OK) + if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK) { if (pszString != IntPtr.Zero) { From e7ae829e5cbf79f6aede02d8f1c79322242429a8 Mon Sep 17 00:00:00 2001 From: danwalmsley Date: Mon, 15 Feb 2016 22:20:33 +0000 Subject: [PATCH 5/5] added placeholder implementations for system dialogs. --- src/Android/Perspex.Android/SystemDialogImpl.cs | 5 +++++ src/Gtk/Perspex.Gtk/SystemDialogImpl.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Android/Perspex.Android/SystemDialogImpl.cs b/src/Android/Perspex.Android/SystemDialogImpl.cs index 60f9de914c..d60fad8230 100644 --- a/src/Android/Perspex.Android/SystemDialogImpl.cs +++ b/src/Android/Perspex.Android/SystemDialogImpl.cs @@ -12,5 +12,10 @@ namespace Perspex.Android { throw new NotImplementedException(); } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs index 869bd2eb70..304de86fc3 100644 --- a/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs +++ b/src/Gtk/Perspex.Gtk/SystemDialogImpl.cs @@ -53,5 +53,10 @@ namespace Perspex.Gtk dlg.Show(); return tcs.Task; } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + throw new NotImplementedException(); + } } }