Browse Source

Merge pull request #7028 from AvaloniaUI/dialog-microcom

Use MicroCOM for Win32 dialogs implementation + minor fixes
pull/7094/head
Jumar Macato 4 years ago
committed by GitHub
parent
commit
270725e577
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      samples/ControlCatalog/Pages/DialogsPage.xaml
  2. 47
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  3. 6
      src/Avalonia.Controls/Platform/ISystemDialogImpl.cs
  4. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  5. 268
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  6. 269
      src/Windows/Avalonia.Win32/SystemDialogImpl.cs
  7. 191
      src/Windows/Avalonia.Win32/Win32Com/win32.idl
  8. 2
      src/tools/MicroComGenerator/ParseException.cs

15
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -2,16 +2,31 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.DialogsPage">
<StackPanel Orientation="Vertical" Spacing="4" Margin="4">
<TextBlock Classes="h1"
Text="Picker dialogs" />
<CheckBox Name="UseFilters">Use filters</CheckBox>
<Button Name="OpenFile">_Open File</Button>
<Button Name="OpenMultipleFiles">Open _Multiple File</Button>
<Button Name="SaveFile">_Save File</Button>
<Button Name="SelectFolder">Select Fo_lder</Button>
<Button Name="OpenBoth">Select _Both</Button>
<TextBlock x:Name="PickerLastResultsVisible"
Classes="h2"
IsVisible="False"
Text="Last picker results:" />
<ItemsPresenter x:Name="PickerLastResults" />
<TextBlock Margin="0, 8, 0, 0"
Classes="h1"
Text="Window dialogs" />
<Button Name="DecoratedWindow">Decorated _window</Button>
<Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
<Button Name="Dialog">_Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
<Button Name="OwnedWindow">Own_ed window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>
</StackPanel>
</UserControl>

47
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Dialogs;
using Avalonia.Layout;
using Avalonia.Markup.Xaml;
@ -16,6 +17,11 @@ namespace ControlCatalog.Pages
{
this.InitializeComponent();
var results = this.FindControl<ItemsPresenter>("PickerLastResults");
var resultsVisible = this.FindControl<TextBlock>("PickerLastResultsVisible");
string lastSelectedDirectory = null;
List<FileDialogFilter> GetFilters()
{
if (this.FindControl<CheckBox>("UseFilters").IsChecked != true)
@ -34,44 +40,67 @@ namespace ControlCatalog.Pages
};
}
this.FindControl<Button>("OpenFile").Click += delegate
this.FindControl<Button>("OpenFile").Click += async delegate
{
new OpenFileDialog()
var result = await new OpenFileDialog()
{
Title = "Open file",
Filters = GetFilters(),
Directory = lastSelectedDirectory,
// Almost guaranteed to exist
InitialFileName = Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName
}.ShowAsync(GetWindow());
results.Items = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.FindControl<Button>("OpenMultipleFiles").Click += async delegate
{
var result = await new OpenFileDialog()
{
Title = "Open multiple files",
Filters = GetFilters(),
Directory = lastSelectedDirectory,
AllowMultiple = true
}.ShowAsync(GetWindow());
results.Items = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.FindControl<Button>("SaveFile").Click += delegate
this.FindControl<Button>("SaveFile").Click += async delegate
{
new SaveFileDialog()
var result = await new SaveFileDialog()
{
Title = "Save file",
Filters = GetFilters(),
Directory = lastSelectedDirectory,
InitialFileName = "test.txt"
}.ShowAsync(GetWindow());
results.Items = new[] { result };
resultsVisible.IsVisible = result != null;
};
this.FindControl<Button>("SelectFolder").Click += delegate
this.FindControl<Button>("SelectFolder").Click += async delegate
{
new OpenFolderDialog()
var result = await new OpenFolderDialog()
{
Title = "Select folder",
Directory = lastSelectedDirectory,
}.ShowAsync(GetWindow());
lastSelectedDirectory = result;
results.Items = new [] { result };
resultsVisible.IsVisible = result != null;
};
this.FindControl<Button>("OpenBoth").Click += async delegate
{
var res = await new OpenFileDialog()
var result = await new OpenFileDialog()
{
Title = "Select both",
Directory = lastSelectedDirectory,
AllowMultiple = true
}.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
{
AllowDirectorySelection = true
});
if (res != null)
Console.WriteLine("Selected: \n" + string.Join("\n", res));
results.Items = result;
resultsVisible.IsVisible = result?.Any() == true;
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{

6
src/Avalonia.Controls/Platform/ISystemDialogImpl.cs

@ -1,5 +1,7 @@
using System.Threading.Tasks;
#nullable enable
namespace Avalonia.Controls.Platform
{
/// <summary>
@ -13,8 +15,8 @@ namespace Avalonia.Controls.Platform
/// <param name="dialog">The details of the file dialog to show.</param>
/// <param name="parent">The parent window.</param>
/// <returns>A task returning the selected filenames.</returns>
Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent);
Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent);
Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent);
Task<string?> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent);
}
}

1
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -9,6 +9,7 @@
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvnComIdl Include="WinRT\winrt.idl" OutputFile="WinRT\WinRT.Generated.cs" />
<AvnComIdl Include="Win32Com\win32.idl" OutputFile="Win32Com\Win32.Generated.cs" />
</ItemGroup>
<Import Project="../../../build/MicroCom.targets" />
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />

268
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -6,6 +6,9 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using Avalonia.MicroCom;
using Avalonia.Win32.Win32Com;
// ReSharper disable InconsistentNaming
#pragma warning disable 169, 649
@ -1244,8 +1247,19 @@ namespace Avalonia.Win32.Interop
internal static extern int CoCreateInstance(ref Guid clsid,
IntPtr ignore1, int ignore2, ref Guid iid, [Out] out IntPtr pUnkOuter);
internal unsafe static T CreateInstance<T>(ref Guid clsid, ref Guid iid) where T : IUnknown
{
var hresult = CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out IntPtr pUnk);
if (hresult != 0)
{
throw new COMException("CreateInstance", hresult);
}
using var unk = MicroComRuntime.CreateProxyFor<IUnknown>(pUnk, true);
return MicroComRuntime.QueryInterface<T>(unk);
}
[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);
internal static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string pszPath, IntPtr pbc, ref Guid riid, out IntPtr ppv);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool OpenClipboard(IntPtr hWndOwner);
@ -1888,7 +1902,8 @@ namespace Avalonia.Win32.Interop
E_INVALIDARG = 0x80070057,
E_OUTOFMEMORY = 0x8007000E,
E_NOTIMPL = 0x80004001,
E_UNEXPECTED = 0x8000FFFF
E_UNEXPECTED = 0x8000FFFF,
E_CANCELLED = 0x800704C7,
}
public enum Icons
@ -1897,33 +1912,6 @@ namespace Avalonia.Win32.Interop
ICON_BIG = 1
}
public const uint SIGDN_FILESYSPATH = 0x80058000;
[Flags]
public 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
}
public static class ShellIds
{
public static readonly Guid OpenFileDialog = Guid.Parse("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7");
@ -1934,228 +1922,6 @@ namespace Avalonia.Win32.Interop
public static readonly Guid ITaskBarList2 = Guid.Parse("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf");
}
[ComImport(), Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileDialog
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
[PreserveSig()]
uint Show([In, Optional] IntPtr hwndOwner); //IModalWindow
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
uint SetFileTypes(uint cFileTypes, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] COMDLG_FILTERSPEC[] 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("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IFileOpenDialog
{
[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)]
void SetFileTypeIndex([In] uint iFileType);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void 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)]
void 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)]
void SetFolder([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetFolder([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCurrentSelection([MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFileName([In, MarshalAs(UnmanagedType.LPWStr)] string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetTitle([In, MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetOkButtonLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszText);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFileNameLabel([In, MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void 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)]
void SetDefaultExtension([In, MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void Close([MarshalAs(UnmanagedType.Error)] int hr);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetClientGuid([In] ref Guid guid);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void ClearClientData();
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetResults([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppenum);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetSelectedItems([MarshalAs(UnmanagedType.Interface)] out IShellItemArray ppsai);
}
[ComImport, Guid("B63EA76D-1F85-456F-A19C-48159EFA858B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IShellItemArray
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void BindToHandler([In, MarshalAs(UnmanagedType.Interface)] IntPtr pbc, [In] ref Guid rbhid,
[In] ref Guid riid, out IntPtr ppvOut);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyStore([In] int Flags, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetPropertyDescriptionList([In] ref PROPERTYKEY keyType, [In] ref Guid riid, out IntPtr ppv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAttributes([In] SIATTRIBFLAGS dwAttribFlags, [In] uint sfgaoMask, out uint psfgaoAttribs);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCount(out uint pdwNumItems);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetItemAt([In] uint dwIndex, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppsi);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void EnumItems([MarshalAs(UnmanagedType.Interface)] out IntPtr ppenumShellItems);
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
}
public enum SIATTRIBFLAGS
{
SIATTRIBFLAGS_AND = 1,
SIATTRIBFLAGS_APPCOMPAT = 3,
SIATTRIBFLAGS_OR = 2
}
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public 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);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct COMDLG_FILTERSPEC
{

269
src/Windows/Avalonia.Win32/SystemDialogImpl.cs

@ -1,170 +1,229 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.MicroCom;
using Avalonia.Win32.Interop;
using Avalonia.Win32.Win32Com;
namespace Avalonia.Win32
{
class SystemDialogImpl : ISystemDialogImpl
internal class SystemDialogImpl : ISystemDialogImpl
{
private const UnmanagedMethods.FOS DefaultDialogOptions = UnmanagedMethods.FOS.FOS_FORCEFILESYSTEM | UnmanagedMethods.FOS.FOS_NOVALIDATE |
UnmanagedMethods.FOS.FOS_NOTESTFILECREATE | UnmanagedMethods.FOS.FOS_DONTADDTORECENT;
private const uint SIGDN_FILESYSPATH = 0x80058000;
public unsafe Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
private const FILEOPENDIALOGOPTIONS DefaultDialogOptions = FILEOPENDIALOGOPTIONS.FOS_FORCEFILESYSTEM | FILEOPENDIALOGOPTIONS.FOS_NOVALIDATE |
FILEOPENDIALOGOPTIONS.FOS_NOTESTFILECREATE | FILEOPENDIALOGOPTIONS.FOS_DONTADDTORECENT;
public unsafe Task<string[]?> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
return Task.Factory.StartNew(() =>
return Task.Run(() =>
{
string[] result = default;
Guid clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
Guid iid = UnmanagedMethods.ShellIds.IFileDialog;
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk);
var frm = (UnmanagedMethods.IFileDialog)unk;
string[]? result = default;
try
{
var clsid = dialog is OpenFileDialog ? UnmanagedMethods.ShellIds.OpenFileDialog : UnmanagedMethods.ShellIds.SaveFileDialog;
var iid = UnmanagedMethods.ShellIds.IFileDialog;
var frm = UnmanagedMethods.CreateInstance<IFileDialog>(ref clsid, ref iid);
var openDialog = dialog as OpenFileDialog;
var openDialog = dialog as OpenFileDialog;
uint options;
frm.GetOptions(out options);
options |= (uint)(DefaultDialogOptions);
if (openDialog?.AllowMultiple == true)
options |= (uint)UnmanagedMethods.FOS.FOS_ALLOWMULTISELECT;
frm.SetOptions(options);
var options = frm.Options;
options |= DefaultDialogOptions;
if (openDialog?.AllowMultiple == true)
{
options |= FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT;
}
frm.SetOptions(options);
var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? "";
frm.SetDefaultExtension(defaultExtension);
frm.SetFileName(dialog.InitialFileName ?? "");
frm.SetTitle(dialog.Title ?? "");
var defaultExtension = (dialog as SaveFileDialog)?.DefaultExtension ?? "";
fixed (char* pExt = defaultExtension)
{
frm.SetDefaultExtension(pExt);
}
var filters = new List<UnmanagedMethods.COMDLG_FILTERSPEC>();
if (dialog.Filters != null)
{
foreach (var filter in dialog.Filters)
var initialFileName = dialog.InitialFileName ?? "";
fixed (char* fExt = initialFileName)
{
var extMask = string.Join(";", filter.Extensions.Select(e => "*." + e));
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = filter.Name, pszSpec = extMask });
frm.SetFileName(fExt);
}
}
if (filters.Count == 0)
filters.Add(new UnmanagedMethods.COMDLG_FILTERSPEC { pszName = "All files", pszSpec = "*.*" });
frm.SetFileTypes((uint)filters.Count, filters.ToArray());
frm.SetFileTypeIndex(0);
var title = dialog.Title ?? "";
fixed (char* tExt = title)
{
frm.SetTitle(tExt);
}
if (dialog.Directory != null)
{
UnmanagedMethods.IShellItem directoryShellItem;
Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
fixed (void* pFilters = FiltersToPointer(dialog.Filters, out var count))
{
frm.SetFolder(directoryShellItem);
frm.SetDefaultFolder(directoryShellItem);
frm.SetFileTypes((ushort)count, pFilters);
}
}
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK)
{
if (openDialog?.AllowMultiple == true)
frm.SetFileTypeIndex(0);
if (dialog.Directory != null)
{
UnmanagedMethods.IShellItemArray shellItemArray;
((UnmanagedMethods.IFileOpenDialog)frm).GetResults(out shellItemArray);
uint count;
shellItemArray.GetCount(out count);
result = new string[count];
for (uint i = 0; i < count; i++)
var riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem)
== (uint)UnmanagedMethods.HRESULT.S_OK)
{
UnmanagedMethods.IShellItem shellItem;
shellItemArray.GetItemAt(i, out shellItem);
result[i] = GetAbsoluteFilePath(shellItem);
var proxy = MicroComRuntime.CreateProxyFor<IShellItem>(directoryShellItem, true);
frm.SetFolder(proxy);
frm.SetDefaultFolder(proxy);
}
}
else
frm.Show(hWnd);
if (openDialog?.AllowMultiple == true)
{
UnmanagedMethods.IShellItem shellItem;
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
using var fileOpenDialog = frm.QueryInterface<IFileOpenDialog>();
var shellItemArray = fileOpenDialog.Results;
var count = shellItemArray.Count;
var results = new List<string>();
for (int i = 0; i < count; i++)
{
result = new string[] { GetAbsoluteFilePath(shellItem) };
var shellItem = shellItemArray.GetItemAt(i);
if (GetAbsoluteFilePath(shellItem) is { } selected)
{
results.Add(selected);
}
}
result = results.ToArray();
}
else if (frm.Result is { } shellItem
&& GetAbsoluteFilePath(shellItem) is { } singleResult)
{
result = new[] { singleResult };
}
}
return result;
});
return result;
}
catch (COMException ex)
{
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
return result;
}
throw new Win32Exception(ex.HResult);
}
})!;
}
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
public unsafe Task<string?> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{
return Task.Factory.StartNew(() =>
return Task.Run(() =>
{
string result = default;
string? result = default;
try
{
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
var clsid = UnmanagedMethods.ShellIds.OpenFileDialog;
var iid = UnmanagedMethods.ShellIds.IFileDialog;
var frm = UnmanagedMethods.CreateInstance<IFileDialog>(ref clsid, ref iid);
var hWnd = parent?.PlatformImpl?.Handle?.Handle ?? IntPtr.Zero;
Guid clsid = UnmanagedMethods.ShellIds.OpenFileDialog;
Guid iid = UnmanagedMethods.ShellIds.IFileDialog;
var options = frm.Options;
options = FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS | DefaultDialogOptions;
frm.SetOptions(options);
UnmanagedMethods.CoCreateInstance(ref clsid, IntPtr.Zero, 1, ref iid, out object unk);
var frm = (UnmanagedMethods.IFileDialog)unk;
uint options;
frm.GetOptions(out options);
options |= (uint)(UnmanagedMethods.FOS.FOS_PICKFOLDERS | DefaultDialogOptions);
frm.SetOptions(options);
frm.SetTitle(dialog.Title ?? "");
var title = dialog.Title ?? "";
fixed (char* tExt = title)
{
frm.SetTitle(tExt);
}
if (dialog.Directory != null)
{
UnmanagedMethods.IShellItem directoryShellItem;
Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
if (dialog.Directory != null)
{
frm.SetFolder(directoryShellItem);
var riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out var directoryShellItem)
== (uint)UnmanagedMethods.HRESULT.S_OK)
{
var proxy = MicroComRuntime.CreateProxyFor<IShellItem>(directoryShellItem, true);
frm.SetFolder(proxy);
frm.SetDefaultFolder(proxy);
}
}
}
if (dialog.Directory != null)
{
UnmanagedMethods.IShellItem directoryShellItem;
Guid riid = UnmanagedMethods.ShellIds.IShellItem;
if (UnmanagedMethods.SHCreateItemFromParsingName(dialog.Directory, IntPtr.Zero, ref riid, out directoryShellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
frm.Show(hWnd);
if (frm.Result is not null)
{
frm.SetDefaultFolder(directoryShellItem);
result = GetAbsoluteFilePath(frm.Result);
}
}
if (frm.Show(hWnd) == (uint)UnmanagedMethods.HRESULT.S_OK)
return result;
}
catch (COMException ex)
{
UnmanagedMethods.IShellItem shellItem;
if (frm.GetResult(out shellItem) == (uint)UnmanagedMethods.HRESULT.S_OK)
if ((uint)ex.HResult == (uint)UnmanagedMethods.HRESULT.E_CANCELLED)
{
result = GetAbsoluteFilePath(shellItem);
return result;
}
throw new Win32Exception(ex.HResult);
}
return result;
});
}
private string GetAbsoluteFilePath(UnmanagedMethods.IShellItem shellItem)
private unsafe string? GetAbsoluteFilePath(IShellItem shellItem)
{
IntPtr pszString;
if (shellItem.GetDisplayName(UnmanagedMethods.SIGDN_FILESYSPATH, out pszString) == (uint)UnmanagedMethods.HRESULT.S_OK)
var pszString = new IntPtr(shellItem.GetDisplayName(SIGDN_FILESYSPATH));
if (pszString != IntPtr.Zero)
{
if (pszString != IntPtr.Zero)
try
{
try
{
return Marshal.PtrToStringAuto(pszString);
}
finally
{
Marshal.FreeCoTaskMem(pszString);
}
return Marshal.PtrToStringUni(pszString);
}
finally
{
Marshal.FreeCoTaskMem(pszString);
}
}
return default;
}
private unsafe byte[] FiltersToPointer(List<FileDialogFilter>? filters, out int lenght)
{
if (filters == null || filters.Count == 0)
{
filters = new List<FileDialogFilter>
{
new FileDialogFilter { Name = "All files", Extensions = new List<string> { "*" } }
};
}
var size = Marshal.SizeOf<UnmanagedMethods.COMDLG_FILTERSPEC>();
var arr = new byte[size];
var resultArr = new byte[size * filters.Count];
for (int i = 0; i < filters.Count; i++)
{
var filter = filters[i];
var filterPtr = Marshal.AllocHGlobal(size);
try
{
var filterStr = new UnmanagedMethods.COMDLG_FILTERSPEC
{
pszName = filter.Name ?? string.Empty,
pszSpec = string.Join(";", filter.Extensions.Select(e => "*." + e))
};
Marshal.StructureToPtr(filterStr, filterPtr, false);
Marshal.Copy(filterPtr, resultArr, i * size, size);
}
finally
{
Marshal.FreeHGlobal(filterPtr);
}
}
lenght = filters.Count;
return resultArr;
}
}
}

191
src/Windows/Avalonia.Win32/Win32Com/win32.idl

@ -0,0 +1,191 @@
@clr-namespace Avalonia.Win32.Win32Com
@clr-access internal
@clr-map FLOAT float
@clr-map HSTRING IntPtr
@clr-map Vector2 System.Numerics.Vector2
@clr-map Vector3 System.Numerics.Vector3
@clr-map Quaternion System.Numerics.Quaternion
@clr-map Matrix4x4 System.Numerics.Matrix4x4
@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
@clr-map HWND IntPtr
@clr-map BOOL int
@clr-map DWORD int
@clr-map boolean int
@clr-map BYTE byte
@clr-map INT16 short
@clr-map INT32 int
@clr-map INT64 long
@clr-map UINT ushort
@clr-map UINT16 ushort
@clr-map ULONG uint
@clr-map UINT32 uint
@clr-map UINT64 ulong
@clr-map DOUBLE double
@clr-map GUID System.Guid
@clr-map REFGUID System.Guid*
@clr-map REFIID System.Guid*
@clr-map WCHAR System.Char
[flags]
enum FILEOPENDIALOGOPTIONS
{
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
}
[
object,
uuid(43826d1e-e718-42ee-bc55-a1e261c37bfe),
pointer_default(unique)
]
interface IShellItem : IUnknown
{
HRESULT BindToHandler(
[in, unique] void* pbc,
[in] REFGUID bhid,
[in] REFIID riid,
[out, iid_is(riid)] void** ppv);
HRESULT GetParent([out] IShellItem** ppsi);
HRESULT GetDisplayName(
[in] uint sigdnName,
[out, string, annotation("_Outptr_result_nullonfailure_")] WCHAR** ppszName);
HRESULT GetAttributes(
[in] ULONG sfgaoMask,
[out] ULONG* psfgaoAttribs);
HRESULT Compare(
[in] IShellItem* psi,
[in] ULONG hint,
[out] int* piOrder);
}
[
object,
uuid(B63EA76D-1F85-456F-A19C-48159EFA858B),
pointer_default(unique)
]
interface IShellItemArray : IUnknown
{
HRESULT BindToHandler([in, unique] void* pbc, [in] REFGUID bhid, [in] REFIID riid, [out, iid_is(riid)] void** ppvOut);
HRESULT GetPropertyStore([in] UINT flags, [in] REFIID riid, [out, iid_is(riid)] void** ppv);
HRESULT GetPropertyDescriptionList([in] void* keyType, [in] REFIID riid, [out, iid_is(riid)] void** ppv);
HRESULT GetAttributes([in] int AttribFlags, [in] UINT sfgaoMask, [out] UINT* psfgaoAttribs);
HRESULT GetCount([out] DWORD* pdwNumItems);
HRESULT GetItemAt([in] DWORD dwIndex, [out] IShellItem** ppsi);
HRESULT EnumItems([out] void** ppenumShellItems);
}
[
object,
uuid(B4DB1657-70D7-485E-8E3E-6FCB5A5C1802),
pointer_default(unique)
]
interface IModalWindow : IUnknown
{
[local]
HRESULT Show(
[in, unique] HWND hwndOwner);
}
[
object,
uuid(42F85136-DB7E-439C-85F1-E4075D135FC8),
pointer_default(unique)
]
interface IFileDialog : IModalWindow
{
HRESULT SetFileTypes(
[in] UINT cFileTypes,
[in, size_is(cFileTypes)] void* rgFilterSpec);
HRESULT SetFileTypeIndex([in] UINT iFileType);
HRESULT GetFileTypeIndex([out] UINT* piFileType);
HRESULT Advise(
[in] void* pfde,
[out] DWORD* pdwCookie);
HRESULT Unadvise([in] DWORD dwCookie);
HRESULT SetOptions([in] FILEOPENDIALOGOPTIONS fos);
HRESULT GetOptions([out] FILEOPENDIALOGOPTIONS* pfos);
HRESULT SetDefaultFolder([in] IShellItem* psi);
HRESULT SetFolder([in] IShellItem* psi);
HRESULT GetFolder([out] IShellItem** ppsi);
HRESULT GetCurrentSelection([out] IShellItem** ppsi);
HRESULT SetFileName([in, string] WCHAR* pszName);
HRESULT GetFileName([out, string] WCHAR** pszName);
HRESULT SetTitle([in, string] WCHAR* pszTitle);
HRESULT SetOkButtonLabel([in, string] WCHAR* pszText);
HRESULT SetFileNameLabel([in, string] WCHAR* pszLabel);
HRESULT GetResult([out] IShellItem** ppsi);
HRESULT AddPlace(
[in] IShellItem* psi,
[in] INT32 fdap);
HRESULT SetDefaultExtension([in, string] WCHAR* pszDefaultExtension);
HRESULT Close([in] HRESULT hr);
HRESULT SetClientGuid([in] REFGUID guid);
HRESULT ClearClientData();
HRESULT SetFilter([in] void* pFilter);
}
[
object,
uuid(D57C7288-D4AD-4768-BE02-9D969532D960),
pointer_default(unique)
]
interface IFileOpenDialog : IFileDialog
{
HRESULT GetResults(
[out] IShellItemArray** ppenum);
HRESULT GetSelectedItems(
[out] IShellItemArray** ppsai);
}

2
src/tools/MicroComGenerator/ParseException.cs

@ -7,7 +7,7 @@ namespace MicroComGenerator
public int Line { get; }
public int Position { get; }
public ParseException(string message, int line, int position) : base(message)
public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}")
{
Line = line;
Position = position;

Loading…
Cancel
Save