Skip to content

Commit a85531d

Browse files
committed
Open dialog provider api
1. Opened native file dialog provider 2. Updated dependencies 3. Added auto add extension for winapi saving dialog 4. Now wildcard always added to filters
1 parent 2557236 commit a85531d

6 files changed

Lines changed: 85 additions & 64 deletions

File tree

INativeDialogProvider.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
namespace SharpFileDialog
22
{
3-
internal interface INativeDialogProvider
3+
public interface INativeDialogProvider
44
{
55
bool CurrentPlatformSupported { get; }
6+
67
int Priority { get; }
78

89
bool OpenDialog(NativeFileDialog.Filter[]? filters, string? defaultPath, out string? outPath);

NativeFileDialog.cs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ namespace SharpFileDialog
1313
{
1414
public static class NativeFileDialog
1515
{
16-
public static bool CurrentPlatformSupported => _provider is not null;
16+
public static bool CurrentPlatformSupported => Provider is not null;
1717

18-
static readonly INativeDialogProvider? _provider;
18+
public static INativeDialogProvider? Provider { get; set; }
1919

20-
static NativeFileDialog()
20+
static bool _providerSearched = false;
21+
22+
public static bool SetDefaultProvider()
2123
{
24+
_providerSearched = true;
25+
var previousProvider = Provider;
2226
Assembly currentAssembly = Assembly.GetExecutingAssembly();
2327
IEnumerable<Type> nativeProviders = currentAssembly.GetTypes().Where(type => type.GetInterface(nameof(INativeDialogProvider)) is not null);
2428

@@ -31,12 +35,14 @@ static NativeFileDialog()
3135
if (!provider.CurrentPlatformSupported)
3236
continue;
3337

34-
if (_provider is null || _provider.Priority < provider.Priority)
35-
_provider = provider;
38+
if (Provider is null || Provider.Priority < provider.Priority)
39+
Provider = provider;
3640
}
3741
}
3842
catch { }
3943
}
44+
45+
return Provider != previousProvider;
4046
}
4147

4248
#if USE_NOTNULLWHEN
@@ -83,12 +89,15 @@ public static bool PickFolder(string? defaultPath, out string? outPath)
8389
return provider.PickFolder(defaultPath, out outPath);
8490
}
8591

86-
private static INativeDialogProvider EnsureProviderAvailable()
92+
static INativeDialogProvider EnsureProviderAvailable()
8793
{
88-
if (_provider is null)
94+
if (!_providerSearched)
95+
SetDefaultProvider();
96+
97+
if (Provider is null)
8998
throw new PlatformNotSupportedException("Dialog provider for current platform is not available!");
9099

91-
return _provider;
100+
return Provider;
92101
}
93102

94103
public struct Filter

NativeProviders/GTKDialogProvider.cs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
namespace SharpFileDialog.NativeProviders
77
{
8-
internal class GTKDialogProvider : INativeDialogProvider
8+
internal sealed class GTKDialogProvider : INativeDialogProvider
99
{
1010
public bool CurrentPlatformSupported => InitCheck();
1111
public int Priority => 10;
@@ -17,8 +17,7 @@ public bool OpenDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
1717
var dialog = new FileChooserNative("Open", null, FileChooserAction.Open, "_Open", "_Cancel");
1818

1919
// Build the filter list
20-
if (filters is not null)
21-
AddFiltersToDialog(dialog, filters);
20+
AddFiltersToDialog(dialog, filters);
2221

2322
// Set the default path
2423
if (defaultPath is not null)
@@ -39,8 +38,7 @@ public bool OpenDialogMultiple(NativeFileDialog.Filter[]? filters, string? defau
3938
};
4039

4140
// Build the filter list
42-
if (filters is not null)
43-
AddFiltersToDialog(dialog, filters);
41+
AddFiltersToDialog(dialog, filters);
4442

4543
// Set the default path
4644
if (defaultPath is not null)
@@ -61,8 +59,7 @@ public bool SaveDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
6159
};
6260

6361
// Build the filter list
64-
if (filters is not null)
65-
AddFiltersToDialog(dialog, filters);
62+
AddFiltersToDialog(dialog, filters);
6663

6764
// Set the default path
6865
if (defaultPath is not null)
@@ -126,23 +123,21 @@ static void SetDefaultPath(IFileChooser widget, string path)
126123
widget.SetCurrentFolder(path);
127124
}
128125

129-
static void AddFiltersToDialog(IFileChooser widget, NativeFileDialog.Filter[] filters)
126+
static void AddFiltersToDialog(IFileChooser widget, NativeFileDialog.Filter[]? filters)
130127
{
131-
if (filters.Length == 0)
132-
return;
133-
134-
foreach (var filter in filters)
135-
{
136-
var fileFilter = new FileFilter
128+
if (filters is not null)
129+
foreach (var filter in filters)
137130
{
138-
Name = filter.Name
139-
};
131+
var fileFilter = new FileFilter
132+
{
133+
Name = filter.Name
134+
};
140135

141-
foreach (var extension in filter.Extensions)
142-
fileFilter.AddPattern("*." + extension);
136+
foreach (var extension in filter.Extensions)
137+
fileFilter.AddPattern("*." + extension);
143138

144-
widget.AddFilter(fileFilter);
145-
}
139+
widget.AddFilter(fileFilter);
140+
}
146141

147142
// always append a wildcard option to the end
148143
var wildcard = new FileFilter { Name = "Any File" };

NativeProviders/WinAPIDialogProvider.cs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Windows.Win32.UI.Shell;
66
using Windows.Win32.UI.Shell.Common;
77
using System.Runtime.InteropServices;
8+
using System.Linq;
89

910
#if NET6_0_OR_GREATER
1011
using System.Runtime.Versioning;
@@ -15,7 +16,7 @@ namespace SharpFileDialog.NativeProviders
1516
#if NET6_0_OR_GREATER
1617
[SupportedOSPlatform("windows6.0.6000")]
1718
#endif
18-
internal class WinAPIDialogProvider : INativeDialogProvider
19+
internal sealed class WinAPIDialogProvider : INativeDialogProvider
1920
{
2021
public bool CurrentPlatformSupported => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
2122
(Environment.OSVersion.Version.Major > 6 ||
@@ -36,9 +37,7 @@ public bool OpenDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
3637
goto end;
3738
}
3839

39-
if (filters is not null)
40-
AddFiltersToDialog(dialog, filters);
41-
40+
AddFiltersToDialog(dialog, filters);
4241
if (defaultPath is not null)
4342
SetDefaultPath(dialog, defaultPath);
4443

@@ -100,9 +99,7 @@ public bool OpenDialogMultiple(NativeFileDialog.Filter[]? filters, string? defau
10099
goto end;
101100
}
102101

103-
if (filters is not null)
104-
AddFiltersToDialog(dialog, filters);
105-
102+
AddFiltersToDialog(dialog, filters);
106103
if (defaultPath is not null)
107104
SetDefaultPath(dialog, defaultPath);
108105

@@ -237,9 +234,7 @@ public bool SaveDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
237234
goto end;
238235
}
239236

240-
if (filters is not null)
241-
AddFiltersToDialog(dialog, filters);
242-
237+
AddFiltersToDialog(dialog, filters);
243238
if (defaultPath is not null)
244239
SetDefaultPath(dialog, defaultPath);
245240

@@ -275,6 +270,13 @@ public bool SaveDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
275270
Marshal.FinalReleaseComObject(shellItem);
276271

277272
end:
273+
if (outPath is not null && filters is not null)
274+
{
275+
dialog.GetFileTypeIndex(out uint fileType);
276+
277+
outPath = AddExtension(outPath, unchecked((int)fileType) - 1, filters);
278+
}
279+
278280
if (dialog is not null)
279281
Marshal.FinalReleaseComObject(dialog);
280282

@@ -287,7 +289,7 @@ public bool SaveDialog(NativeFileDialog.Filter[]? filters, string? defaultPath,
287289
return outPath is not null;
288290
}
289291

290-
private static void SetDefaultPath(IFileDialog dialog, string defaultPath)
292+
static void SetDefaultPath(IFileDialog dialog, string defaultPath)
291293
{
292294
if (string.IsNullOrWhiteSpace(defaultPath))
293295
return;
@@ -307,42 +309,41 @@ private static void SetDefaultPath(IFileDialog dialog, string defaultPath)
307309
Marshal.FinalReleaseComObject(folder);
308310
}
309311

310-
private static void AddFiltersToDialog(IFileDialog dialog, NativeFileDialog.Filter[] filters)
312+
static void AddFiltersToDialog(IFileDialog dialog, NativeFileDialog.Filter[]? filters)
311313
{
312-
if (filters.Length == 0) return;
313-
314314
const string WILDCARD_NAME = "Any File";
315315
const string WILDCARD = "*.*";
316316

317317
// filter.Length plus 1 because we hardcode the *.* wildcard after the while loop
318-
COMDLG_FILTERSPEC[] specList = new COMDLG_FILTERSPEC[filters.Length + 1];
318+
COMDLG_FILTERSPEC[] specList = new COMDLG_FILTERSPEC[(filters?.Length ?? 0) + 1];
319319

320-
for (int i = 0; i < filters.Length; i++)
321-
{
322-
string filter = "*." + string.Join(";*.", filters[i].Extensions);
323-
324-
unsafe
320+
if (filters is not null)
321+
for (int i = 0; i < filters.Length; i++)
325322
{
326-
fixed (char* pFilter = filter)
327-
specList[i].pszSpec = new PCWSTR(pFilter);
328-
fixed (char* pName = filters[i].Name)
329-
specList[i].pszName = new PCWSTR(pName);
323+
string filter = "*." + string.Join(";*.", filters[i].Extensions);
324+
325+
unsafe
326+
{
327+
fixed (char* pFilter = filter)
328+
specList[i].pszSpec = new PCWSTR(pFilter);
329+
fixed (char* pName = filters[i].Name)
330+
specList[i].pszName = new PCWSTR(pName);
331+
}
330332
}
331-
}
332333

333334
// Add wildcard
334335
unsafe
335336
{
336337
fixed (char* pWildcard = WILDCARD)
337-
specList[filters.Length].pszSpec = new PCWSTR(pWildcard);
338+
specList[specList.Length - 1].pszSpec = new PCWSTR(pWildcard);
338339
fixed (char* pWildcardName = WILDCARD_NAME)
339-
specList[filters.Length].pszName = new PCWSTR(pWildcardName);
340+
specList[specList.Length - 1].pszName = new PCWSTR(pWildcardName);
340341
}
341342

342343
dialog.SetFileTypes(specList);
343344
}
344345

345-
private static HRESULT InitializeCOM()
346+
static HRESULT InitializeCOM()
346347
{
347348
const int RPC_E_CHANGED_MODE = -2147417850;
348349

@@ -356,11 +357,27 @@ private static HRESULT InitializeCOM()
356357
}
357358

358359
/// <inheritdoc cref="PInvoke.SHCreateItemFromParsingName(string, IBindCtx, in Guid, out object)"/>
359-
private static HRESULT SHCreateItemFromParsingName<T>(string pszPath, IBindCtx? pbc, out T item) where T : class
360+
static HRESULT SHCreateItemFromParsingName<T>(string pszPath, IBindCtx? pbc, out T item) where T : class
360361
{
361362
HRESULT result = PInvoke.SHCreateItemFromParsingName(pszPath, pbc, typeof(T).GUID, out object ppv);
362363
item = (T)ppv;
363364
return result;
364365
}
366+
367+
static string AddExtension(string path, int index, params NativeFileDialog.Filter[] filters)
368+
{
369+
if (index >= filters.Length)
370+
return path;
371+
372+
foreach (var filter in filters)
373+
foreach (var extension in filter.Extensions)
374+
if (path.EndsWith(extension.Trim()))
375+
return path;
376+
377+
if (!filters.Any() || !filters.First().Extensions.Any() || index == -1)
378+
return path;
379+
380+
return path + '.' + filters[index].Extensions.First().Trim();
381+
}
365382
}
366383
}

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ class Example
2323

2424
## Providers
2525
Currently supported dialog providers:
26-
- WinAPI
27-
- GTK+
26+
- WinAPI (Auto add extension on file save supported)
27+
- GTK
2828

2929
## Adding your own provider
30-
There is no way to add your own provider without recompiling library because class `NativeFileDialog` searches for providers inside library, but not outside
31-
But if you really need to add it, just create new class in project that inherits interface `INativeDialogProvider` and add implementations
30+
You can use custom provider by setting property 'NativeFileDialog.Provider' or bundled providers with method 'NativeFileDialog.SetDefaultProvider()'.
31+
To create custom provider create new class, inherit interface `INativeDialogProvider` and add implementations.

SharpFileDialog.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
<Nullable>enable</Nullable>
77
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
88
<Authors>TheDearbear</Authors>
9-
<PackageProjectUrl></PackageProjectUrl>
109
<PackageReadmeFile>README.md</PackageReadmeFile>
1110
<RepositoryUrl>https://github.com/TheDearbear/SharpFileDialog</RepositoryUrl>
1211
<PackageTags>dialog;csharp;files</PackageTags>
@@ -16,7 +15,7 @@
1615
</PropertyGroup>
1716

1817
<ItemGroup>
19-
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.18-beta">
18+
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.46-beta">
2019
<PrivateAssets>all</PrivateAssets>
2120
</PackageReference>
2221
<PackageReference Include="GtkSharp" Version="3.24.24.95" />

0 commit comments

Comments
 (0)