Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions specs/Storage.Pickers/FileOpenPicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ runtimeclass FileOpenPicker
FileOpenPicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

IMap<String, IVector<String>> FileTypeChoices{ get; };
IVector<string> FileTypeFilter{ get; };
Int32 DefaultFileTypeFilterIndex;

string SuggestedFolder;
String SuggestedStartFolder;
Expand Down Expand Up @@ -65,6 +68,14 @@ var openPicker = new FileOpenPicker(this.AppWindow.Id)
// If not specified, the system uses a default label of "Open" (suitably translated).
CommitButtonText = "Choose selected files",

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
Title = "Open File",

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
SettingsIdentifier = "MySettingsIdentifier",

// (Optional) group file types into labeled choices
// FileTypeChoices takes precedence over FileTypeFilter when both defined.
FileTypeChoices = {
Expand All @@ -75,6 +86,13 @@ var openPicker = new FileOpenPicker(this.AppWindow.Id)
// (Optional) specify file extension filters. If not specified, defaults to all files (*.*).
FileTypeFilter = { ".txt", ".pdf", ".doc", ".docx" },

// (Optional) specify the index of the file type filter to be selected by default.
// The index is 0-based.
// When not specified, its value is -1 and the filter follows API's behavior. That is:
// When FileTypeFilter is in effect, auto-select the last one (All Files).
// Otherwise, auto-select the first one.
DefaultFileTypeFilterIndex = 1, // auto select Pictures

// (Optional) specify the view mode of the picker dialog. If not specified, defaults to List.
ViewMode = PickerViewMode.List,
};
Expand Down Expand Up @@ -109,6 +127,14 @@ openPicker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
// If not specified, the system uses a default label of "Open" (suitably translated).
openPicker.CommitButtonText(L"Choose selected files");

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
openPicker.Title(L"Open File");

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
openPicker.SettingsIdentifier(L"MySettingsIdentifier");

// (Optional) group file types into labeled choices
// FileTypeChoices takes precedence over FileTypeFilter when both defined.
auto choices = openPicker.FileTypeChoices();
Expand All @@ -118,6 +144,13 @@ choices.Insert(L"Pictures", winrt::single_threaded_vector<winrt::hstring>({ L".p
// (Optional) specify file extension filters. If not specified, defaults to all files (*.*).
openPicker.FileTypeFilter().ReplaceAll({ L".txt", L".pdf", L".doc", L".docx" });

// (Optional) specify the index of the file type filter to be selected by default.
// The index is 0-based.
// When not specified, its value is -1 and the filter follows API's behavior. That is:
// When FileTypeFilter is in effect, auto-select the last one (All Files).
// Otherwise, auto-select the first one.
openPicker.DefaultFileTypeFilterIndex(1); // auto select Pictures

// (Optional) specify the view mode of the picker dialog. If not specified, defaults to List.
openPicker.ViewMode(PickerViewMode::List);
```
Expand Down
63 changes: 59 additions & 4 deletions specs/Storage.Pickers/FileSavePicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,17 @@ runtimeclass FileSavePicker
FileSavePicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

string DefaultFileExtension;
string SuggestedFileName;

IMap<string, IVector<string>> FileTypeChoices{ get; };
Int32 DefaultFileTypeFilterIndex;

Boolean ShowOverwritePrompt;
Boolean CreateNewFileIfNotExists;

string SuggestedFolder;
String SuggestedStartFolder;
Expand Down Expand Up @@ -64,14 +71,38 @@ var savePicker = new FileSavePicker(this.AppWindow.Id)
// If not specified, the system uses a default label of "Save" (suitably translated).
CommitButtonText = "Save Document",

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
Title = "Save File",

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
SettingsIdentifier = "MySettingsIdentifier",

// (Optional) categorized extension types. If not specified, "All Files (*.*)" is allowed.
// Note that when "All Files (*.*)" is allowed, end users can save a file without an extension.
FileTypeChoices = {
{ "Documents", new List<string> { ".txt", ".doc", ".docx" } }
{ "Text", new List<string> {".txt"} },
{ "Documents", new List<string> { ".doc", ".docx" } }
},

// (Optional) specify the index of the file type filter to be selected by default.
// The index is 0-based.
// When not specified, its value is -1.
DefaultFileTypeFilterIndex = 1, // this will auto-select Documents

// (Optional) Show a warning prompt of file overwrite when user tries to pick an existing file.
// set to true by default.
ShowOverwritePrompt = true,

// (Optional) create an empty file when the picked file does not yet exist.
// set to true by default.
CreateNewFileIfNotExists = true,

// (Optional) specify the default file extension (will be appended to SuggestedFileName).
// If not specified, no extension will be appended.
// Note: the default extension applies when the active filter is "All Files (*)"
// or includes multiple extensions, and the default extension is one of them.
// If not applied, no extension will be appended.
DefaultFileExtension = ".txt",
};
```
Expand Down Expand Up @@ -108,12 +139,36 @@ savePicker.SuggestedFileName(L"NewDocument");
// If not specified, the system uses a default label of "Save" (suitably translated).
savePicker.CommitButtonText(L"Save Document");

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
savePicker.Title(L"Save File");

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
savePicker.SettingsIdentifier(L"MySettingsIdentifier");

// (Optional) categorized extension types. If not specified, "All Files (*.*)" is allowed.
// Note that when "All Files (*.*)" is allowed, end users can save a file without an extension.
savePicker.FileTypeChoices().Insert(L"Text", winrt::single_threaded_vector<winrt::hstring>({ L".txt" }));
savePicker.FileTypeChoices().Insert(L"Texts", winrt::single_threaded_vector<winrt::hstring>({ L".txt" }));
savePicker.FileTypeChoices().Insert(L"Documents", winrt::single_threaded_vector<winrt::hstring>({ L".doc", L".docx" }));

// (Optional) specify the index of the file type filter to be selected by default.
// The index is 0-based.
// When not specified, its value is -1.
savePicker.DefaultFileTypeFilterIndex(1); // this will auto-select Documents

// (Optional) Show a warning prompt of file overwrite when user tries to pick an existing file.
// set to true by default.
savePicker.ShowOverwritePrompt(true);

// (Optional) create an empty file when the picked file does not yet exist.
// set to true by default.
savePicker.CreateNewFileIfNotExists(true);

// (Optional) specify the default file extension (will be appended to SuggestedFileName).
// If not specified, no extension will be appended.
// Note: the default extension applies when the selected filter is "All Files (*)"
// or includes multiple extensions, and the default extension is one of them.
// If not applied, no extension will be appended.
savePicker.DefaultFileExtension(L".txt");
```

Expand Down
71 changes: 71 additions & 0 deletions specs/Storage.Pickers/FolderPicker.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ runtimeclass FolderPicker
FolderPicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

string SuggestedFolder;
String SuggestedStartFolder;
Expand All @@ -27,6 +29,7 @@ runtimeclass FolderPicker
PickerViewMode ViewMode;

Windows.Foundation.IAsyncOperation<PickFolderResult> PickSingleFolderAsync();
Windows.Foundation.IAsyncOperation<IVectorView<PickFolderResult>> PickMultipleFoldersAsync();
}
```

Expand Down Expand Up @@ -66,6 +69,14 @@ var folderPicker = new FolderPicker(this.AppWindow.Id)
// If not specified, the system uses a default label of "Open" (suitably translated).
CommitButtonText = "Select Folder",

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
Title = "Select Folder",

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
SettingsIdentifier = "MySettingsIdentifier",

// (Optional) specify the view mode of the picker dialog. If not specified, default to List.
ViewMode = PickerViewMode.List,
};
Expand Down Expand Up @@ -100,6 +111,14 @@ folderPicker.SuggestedStartLocation(PickerLocationId::DocumentsLibrary);
// If not specified, the system uses a default label of "Open" (suitably translated).
folderPicker.CommitButtonText(L"Select Folder");

// (Optional) specify the title of the picker.
// If not specified, the system uses a default title.
folderPicker.Title(L"Select Folder");

// (Optional) specify the settings identifier of the picker.
// It allows the picker to remember its state (e.g. size, location, etc) across sessions.
folderPicker.SettingsIdentifier(L"MySettingsIdentifier");

// (Optional) specify the view mode of the picker dialog. If not specified, default to List.
folderPicker.ViewMode(PickerViewMode::List);
```
Expand Down Expand Up @@ -148,6 +167,58 @@ else
}
```

## FolderPicker.PickMultipleFoldersAsync

Displays a UI element that allows the user to choose multiple folders.

Returns a collection of lightweight objects that have the path of the picked folders.

Returns an empty list (`Count` = 0) if the folder dialog was cancelled or closed without a selection.

### Examples

C#

```C#
using Microsoft.Windows.Storage.Pickers;

var folderPicker = new FolderPicker(this.AppWindow.Id);

var results = await folderPicker.PickMultipleFoldersAsync();
if (results.Count > 0)
{
var pickedFolderPaths = results.Select(f => f.Path);
foreach (var path in pickedFolderPaths)
{
// Do something with the folder path
}
}
else
{
// error handling.
}
```

C++
```C++
#include <winrt/Microsoft.Windows.Storage.Pickers.h>
using namespace winrt::Microsoft::Windows::Storage::Pickers;

FolderPicker folderPicker(AppWindow().Id());
auto results{ co_await folderPicker.PickMultipleFoldersAsync() };
if (results.Size() > 0)
{
for (auto const& result : results)
{
auto path{ result.Path() };
}
}
else
{
// error handling.
}
```

# See Also

[PickFolderResult](./PickFolderResult.md)
Expand Down
58 changes: 57 additions & 1 deletion specs/Storage.Pickers/Microsoft.Windows.Storage.Pickers.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,28 @@ to `SuggestedStartLocation`, then to the system default.
catagorized filter types. When both `FileTypeChoices` and `FileTypeFilter` are provided,
`FileTypeChoices` is used and `FileTypeFilter` is ignored.

1. Adding `DefaultFileTypeFilterIndex` for `FileOpenPicker` and `FileSavePicker`. This allows
setting the default selected file type filter index. Note this index is 0-based. When it is
-1 (the default value), the selected filter might be override by the API's default behavior.

1. The property `SettingsIdentifier` for all 3 pickers will be available in the new Storage.Pickers
APIs from WindowsAppSDK2.0. `SettingsIdentifier` allows the picker to remember its state (e.g. size,
location, etc) across sessions. When two different apps use the same string for SettingsIdentifier
property, they will have their respective independent states (Read more in Note 2).

1. Adding `ShowOverwritePrompt` for `FileSavePicker`. This Boolean properties default to `true` and
control whether the picker warns about overwriting when user picked an existing file via
FileSavePicker.

1. Adding `CreateNewFileIfNotExists` for `FileSavePicker`. This Boolean properties default to `true`
and control whether to auto-create the picked file when it doesn't exist.

1. Adding `Title` for all 3 pickers. `Title` allows setting the title of the picker dialog.

1. Adding `PickMultipleFoldersAsync` for `FolderPicker`. This allows selecting multiple folders in
the folder picker dialog.


# Conceptual pages

# API
Expand Down Expand Up @@ -119,9 +141,12 @@ namespace Microsoft.Windows.Storage.Pickers
FileOpenPicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

IMap<string, IVector<string>> FileTypeChoices{ get; };
IVector<string> FileTypeFilter{ get; };
int DefaultFileTypeFilterIndex;

string SuggestedFolder;
string SuggestedStartFolder;
Expand All @@ -138,10 +163,17 @@ namespace Microsoft.Windows.Storage.Pickers
FileSavePicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

string DefaultFileExtension;
string SuggestedFileName;

IMap<string, IVector<string>> FileTypeChoices{ get; };
int DefaultFileTypeFilterIndex;

bool ShowOverwritePrompt;
bool CreateNewFileIfNotExists;

string SuggestedFolder;
string SuggestedStartFolder;
Expand All @@ -155,6 +187,8 @@ namespace Microsoft.Windows.Storage.Pickers
FolderPicker(Microsoft.UI.WindowId windowId);

string CommitButtonText;
string Title;
string SettingsIdentifier;

string SuggestedFolder;
string SuggestedStartFolder;
Expand All @@ -163,11 +197,14 @@ namespace Microsoft.Windows.Storage.Pickers
PickerViewMode ViewMode;

Windows.Foundation.IAsyncOperation<PickFolderResult> PickSingleFolderAsync();
Windows.Foundation.IAsyncOperation<IVectorView<PickFolderResult>> PickMultipleFoldersAsync();
}
}
```

Note: **Understanding SuggestedStartFolder/SuggestedStartLocation vs SuggestedFolder:**
# Notes

Note 1: **Understanding SuggestedStartFolder/SuggestedStartLocation vs SuggestedFolder:**

These two kinds of properties have fundamentally different behaviors in terms of when and how they
affect the picker:
Expand All @@ -187,3 +224,22 @@ affect the picker:
navigation history.

`SuggestedStartFolder` takes precedence over `SuggestedStartLocation` when both specified.

Note 2: **The implementation of SettingsIdentifier**

When an app sets `SettingsIdentifier`, the picker persists its window placement and navigation
history through the underlying [`IFileDialog::SetClientGuid`](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setclientguid)
API. The implementation derives that `ClientGuid` as follows:

- First it tries to obtain the packaged app's Identity via [`GetCurrentApplicationUserModelId`](https://learn.microsoft.com/en-us/windows/win32/api/appmodel/nf-appmodel-getcurrentapplicationusermodelid).
- If the process has no package identity found (typical for unpackaged apps, win32 apps, etc.),
it falls back to the full path to the running executable retrieved from `wil::GetModuleFileNameW`.
- The chosen identifier is concatenated with the caller-provided `SettingsIdentifier` value using a
`"<app identifier>|<value of SettingsIdentifier>"` format. That string is then hashed with MD5
and coerced into an GUID.
- The calculated GUID will be passed to set the `ClientGuid`.

This means the feature works for both packaged and unpackaged apps. Packaged apps remain distinct by
their package identity, while unpackaged apps are differentiated by the absolute path of their
executable. As long as an app uses a stable combination of package identity (or executable path) and
`SettingsIdentifier`, the picker will reopen with the same saved settings across sessions.