Browse Source

Win32 implementation of folder browse dialog.

pull/427/head
danwalmsley 11 years ago
parent
commit
b87c4a8d66
  1. 2
      src/Perspex.Controls/SystemDialog.cs
  2. 153
      src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs
  3. 12
      src/Windows/Perspex.Win32/Perspex.Win32.csproj
  4. 70
      src/Windows/Perspex.Win32/SystemDialogImpl.cs
  5. 2
      src/Windows/Perspex.Win32/packages.config

2
src/Perspex.Controls/SystemDialog.cs

@ -36,6 +36,8 @@ namespace Perspex.Controls
public class OpenFolderDialog : FileSystemDialog public class OpenFolderDialog : FileSystemDialog
{ {
public string DefaultDirectory { get; set; }
public Task<string> ShowAsync(Window window = null) public Task<string> ShowAsync(Window window = null)
=> PerspexLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, window?.PlatformImpl); => PerspexLocator.Current.GetService<ISystemDialogImpl>().ShowFolderDialogAsync(this, window?.PlatformImpl);
} }

153
src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -703,6 +704,131 @@ namespace Perspex.Win32.Interop
return SetClassLong64(hWnd, nIndex, dwNewLong); 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)] [DllImport("user32.dll", SetLastError = true)]
public static extern bool OpenClipboard(IntPtr hWndOwner); 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)] [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct OpenFileName public struct OpenFileName
{ {
@ -899,6 +1050,6 @@ namespace Perspex.Win32.Interop
public IntPtr reservedPtr; public IntPtr reservedPtr;
public int reservedInt; public int reservedInt;
public int flagsEx; public int flagsEx;
} }
} }
} }

12
src/Windows/Perspex.Win32/Perspex.Win32.csproj

@ -38,18 +38,6 @@
<NoWarn>CS1591</NoWarn> <NoWarn>CS1591</NoWarn>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.WindowsAPICodePack-Core.1.1.0.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.WindowsAPICodePack.ShellExtensions, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />

70
src/Windows/Perspex.Win32/SystemDialogImpl.cs

@ -10,10 +10,10 @@ using Perspex.Controls;
using Perspex.Controls.Platform; using Perspex.Controls.Platform;
using Perspex.Platform; using Perspex.Platform;
using Perspex.Win32.Interop; using Perspex.Win32.Interop;
using Microsoft.WindowsAPICodePack.Dialogs;
namespace Perspex.Win32 namespace Perspex.Win32
{ {
class SystemDialogImpl : ISystemDialogImpl class SystemDialogImpl : ISystemDialogImpl
{ {
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
@ -45,16 +45,15 @@ namespace Perspex.Win32
dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length); dialog.InitialFileName?.CopyTo(0, fileBuffer, 0, dialog.InitialFileName.Length);
string userSelectedExt = null; string userSelectedExt = null;
fixed (char* pFileBuffer = fileBuffer) fixed (char* pFileBuffer = fileBuffer)
fixed (char* pFilterBuffer = filterBuffer) fixed (char* pFilterBuffer = filterBuffer)
fixed (char* pDefExt = defExt) fixed (char* pDefExt = defExt)
fixed (char* pInitDir = dialog.InitialDirectory) fixed (char* pInitDir = dialog.InitialDirectory)
fixed (char* pTitle = dialog.Title) fixed (char* pTitle = dialog.Title)
{ {
var ofn = new UnmanagedMethods.OpenFileName() var ofn = new UnmanagedMethods.OpenFileName()
{ {
hwndOwner = hWnd, hwndOwner = hWnd,
hInstance = IntPtr.Zero, hInstance = IntPtr.Zero,
lCustData = IntPtr.Zero, lCustData = IntPtr.Zero,
@ -134,27 +133,56 @@ namespace Perspex.Win32
{ {
return Task.Factory.StartNew(() => return Task.Factory.StartNew(() =>
{ {
var dlg = new CommonOpenFileDialog(); string result = string.Empty;
dlg.Title = dialog.Title;
dlg.IsFolderPicker = true;
dlg.InitialDirectory = dialog.InitialDirectory;
dlg.AddToMostRecentlyUsedList = false; var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero;
dlg.AllowNonFileSystemItems = false; var frm = (UnmanagedMethods.IFileDialog)(new UnmanagedMethods.FileOpenDialogRCW());
uint options;
dlg.EnsureFileExists = true; frm.GetOptions(out options);
dlg.EnsurePathExists = true; options |= UnmanagedMethods.FOS_PICKFOLDERS | UnmanagedMethods.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS_NOVALIDATE | UnmanagedMethods.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS_DONTADDTORECENT;
dlg.EnsureReadOnly = false; frm.SetOptions(options);
dlg.EnsureValidNames = true;
dlg.Multiselect = false;
dlg.ShowPlacesList = true;
dlg.DefaultFileName = "";
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; return result;

2
src/Windows/Perspex.Win32/packages.config

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="Microsoft.WindowsAPICodePack-Core" version="1.1.0.2" targetFramework="net45" />
<package id="Microsoft.WindowsAPICodePack-Shell" version="1.1.0.0" targetFramework="net45" />
<package id="Rx-Core" version="2.2.5" targetFramework="net45" /> <package id="Rx-Core" version="2.2.5" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" /> <package id="Rx-Interfaces" version="2.2.5" targetFramework="net45" />
<package id="Rx-Linq" version="2.2.5" targetFramework="net45" /> <package id="Rx-Linq" version="2.2.5" targetFramework="net45" />

Loading…
Cancel
Save