diff --git a/src/Perspex.Controls/CommonDialog.cs b/src/Perspex.Controls/CommonDialog.cs new file mode 100644 index 0000000000..cc81ee0122 --- /dev/null +++ b/src/Perspex.Controls/CommonDialog.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Perspex.Controls.Platform; +using Splat; + +namespace Perspex.Controls +{ + public class CommonDialog + { + public string Title { get; set; } + public List Filters { get; set; } = new List(); + public string InitialFileName { get; set; } + public string InitialDirectory { get; set; } + public bool AllowMultiple { get; set; } + public string DefaultExtension { get; set; } + public CommonDialogAction Action { get; set; } + + + public Task ShowAsync(Window window = null) + => Locator.Current.GetService().ShowAsync(this, window?.PlatformImpl); + } + + public enum CommonDialogAction + { + OpenFile, + SaveFile + } + + public class CommonDialogFilter + { + public string Name { get; set; } + public List Extensions { get; set; } = new List(); + } +} diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 38d5486118..117a683330 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -41,6 +41,7 @@ Properties\SharedAssemblyInfo.cs + @@ -64,6 +65,7 @@ + diff --git a/src/Perspex.Controls/Platform/ICommonDialogImpl.cs b/src/Perspex.Controls/Platform/ICommonDialogImpl.cs new file mode 100644 index 0000000000..89f817a675 --- /dev/null +++ b/src/Perspex.Controls/Platform/ICommonDialogImpl.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Perspex.Platform; + +namespace Perspex.Controls.Platform +{ + public interface ICommonDialogImpl + { + Task ShowAsync(CommonDialog dialog, IWindowImpl parent); + } +} diff --git a/src/Windows/Perspex.Win32/CommonDialogImpl.cs b/src/Windows/Perspex.Win32/CommonDialogImpl.cs new file mode 100644 index 0000000000..d0cce7be95 --- /dev/null +++ b/src/Windows/Perspex.Win32/CommonDialogImpl.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Perspex.Controls; +using Perspex.Controls.Platform; +using Perspex.Platform; +using Perspex.Win32.Interop; + +namespace Perspex.Win32 +{ + class CommonDialogImpl : ICommonDialogImpl + { + public unsafe Task ShowAsync(CommonDialog dialog, IWindowImpl parent) + { + var hWnd = parent?.Handle?.Handle ?? IntPtr.Zero; + return Task.Factory.StartNew(() => + { + var filters = new StringBuilder(); + foreach (var filter in dialog.Filters) + { + var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e)); + filters.Append(filter.Name); + filters.Append(" ("); + filters.Append(extMask); + filters.Append(")"); + filters.Append('\0'); + filters.Append(extMask); + filters.Append('\0'); + } + if (filters.Length == 0) + filters.Append("All files\0*.*\0"); + filters.Append('\0'); + + var filterBuffer = new char[filters.Length]; + filters.CopyTo(0, filterBuffer, 0, filterBuffer.Length); + + + var buffer = new char[256]; + fixed (char* pBuffer = buffer) + fixed (char* pFilterBuffer = filterBuffer) + fixed (char* pDefExt = dialog.DefaultExtension) + fixed (char* pInitDir = dialog.InitialDirectory) + fixed (char* pTitle = dialog.Title) + { + + var ofn = new UnmanagedMethods.OpenFileName() + { + hwndOwner = hWnd, + hInstance = IntPtr.Zero, + lCustData = IntPtr.Zero, + nFilterIndex = 0, + Flags = + UnmanagedMethods.OpenFileNameFlags.OFN_EXPLORER | + UnmanagedMethods.OpenFileNameFlags.OFN_HIDEREADONLY, + nMaxCustFilter = 0, + nMaxFile = buffer.Length - 1, + nMaxFileTitle = 0, + lpTemplateName = IntPtr.Zero, + lpfnHook = IntPtr.Zero, + lpstrCustomFilter = IntPtr.Zero, + lpstrDefExt = new IntPtr(pDefExt), + lpstrFile = new IntPtr(pBuffer), + lpstrFileTitle = IntPtr.Zero, + lpstrFilter = new IntPtr(pFilterBuffer), + lpstrInitialDir = new IntPtr(pInitDir), + lpstrTitle = new IntPtr(pTitle), + + }; + ofn.lStructSize = Marshal.SizeOf(ofn); + if (dialog.AllowMultiple && dialog.Action == CommonDialogAction.OpenFile) + ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_ALLOWMULTISELECT; + + if (dialog.Action == CommonDialogAction.SaveFile) + ofn.Flags |= UnmanagedMethods.OpenFileNameFlags.OFN_NOREADONLYRETURN | + UnmanagedMethods.OpenFileNameFlags.OFN_OVERWRITEPROMPT; + + var pofn = &ofn; + + var res = dialog.Action == CommonDialogAction.OpenFile + ? UnmanagedMethods.GetOpenFileName(new IntPtr(pofn)) + : UnmanagedMethods.GetSaveFileName(new IntPtr(pofn)); + if (!res) + return null; + + } + var cStart = 0; + string dir = null; + var files = new List(); + for (var c = 0; c < buffer.Length; c++) + { + if (buffer[c] == 0) + { + //Encountered double zero char + if (cStart == c) + break; + + var s = new string(buffer, cStart, c - cStart); + if (dir == null) + dir = s; + else + files.Add(s); + cStart = c + 1; + } + } + if (files.Count == 0) + return new[] {dir}; + + return files.Select(f => Path.Combine(dir, f)).ToArray(); + }); + } + } +} diff --git a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs index feb033005d..8b5f0223ba 100644 --- a/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Text; // ReSharper disable InconsistentNaming +#pragma warning disable 169 namespace Perspex.Win32.Interop { @@ -677,7 +678,14 @@ namespace Perspex.Win32.Interop [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)] public static extern IntPtr GlobalFree(IntPtr hMem); + [DllImport("comdlg32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetSaveFileNameW")] + public static extern bool GetSaveFileName(IntPtr lpofn); + [DllImport("comdlg32.dll", CharSet = CharSet.Unicode, EntryPoint = "GetOpenFileNameW")] + public static extern bool GetOpenFileName(IntPtr lpofn); + + [DllImport("comdlg32.dll")] + public static extern int CommDlgExtendedError(); public enum ClipboardFormat { @@ -745,5 +753,49 @@ namespace Perspex.Win32.Interop public string lpszClassName; public IntPtr hIconSm; } + + [Flags] + public enum OpenFileNameFlags + { + + OFN_ALLOWMULTISELECT = 0x00000200, + + OFN_EXPLORER = 0x00080000, + + OFN_HIDEREADONLY = 0x00000004, + + OFN_NOREADONLYRETURN = 0x00008000, + + OFN_OVERWRITEPROMPT = 0x00000002 + + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + public struct OpenFileName + { + public int lStructSize; + public IntPtr hwndOwner; + public IntPtr hInstance; + public IntPtr lpstrFilter; + public IntPtr lpstrCustomFilter; + public int nMaxCustFilter; + public int nFilterIndex; + public IntPtr lpstrFile; + public int nMaxFile; + public IntPtr lpstrFileTitle; + public int nMaxFileTitle; + public IntPtr lpstrInitialDir; + public IntPtr lpstrTitle; + public OpenFileNameFlags Flags; + private ushort Unused; + private ushort Unused2; + public IntPtr lpstrDefExt; + public IntPtr lCustData; + public IntPtr lpfnHook; + public IntPtr lpTemplateName; + 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 ce70610233..befa17488a 100644 --- a/src/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/src/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -23,6 +23,7 @@ prompt 4 bin\Debug\Perspex.Win32.XML + true pdbonly @@ -32,6 +33,7 @@ prompt 4 bin\Release\Perspex.Win32.XML + true @@ -65,6 +67,7 @@ Properties\SharedAssemblyInfo.cs + diff --git a/src/Windows/Perspex.Win32/Win32Platform.cs b/src/Windows/Perspex.Win32/Win32Platform.cs index e0fa7d9d5b..0eb0c80bd9 100644 --- a/src/Windows/Perspex.Win32/Win32Platform.cs +++ b/src/Windows/Perspex.Win32/Win32Platform.cs @@ -8,6 +8,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; using System.Runtime.InteropServices; +using Perspex.Controls.Platform; using Perspex.Input; using Perspex.Platform; using Perspex.Shared.PlatformSupport; @@ -41,6 +42,7 @@ namespace Perspex.Win32 private static void InitializeInternal() { var locator = Locator.CurrentMutable; + locator.Register(() => new CommonDialogImpl(), typeof (ICommonDialogImpl)); locator.Register(() => new PopupImpl(), typeof(IPopupImpl)); locator.Register(() => new ClipboardImpl(), typeof(IClipboard)); locator.Register(() => WindowsKeyboardDevice.Instance, typeof(IKeyboardDevice));