Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
18 changes: 18 additions & 0 deletions dev/Interop/StoragePickers/FileOpenPicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include "PickerCommon.h"
#include "PickFileResult.h"
#include "PickerLocalization.h"
#include <FrameworkUdk/Containment.h>

// Bug 63006043: [1.8 servicing] Fix focus not restored after Storage Pickers dialog closes
#define WINAPPSDK_CHANGEID_63006043 63006043, WinAppSDK_1_8_11

namespace winrt::Microsoft::Windows::Storage::Pickers::implementation
{
Expand Down Expand Up @@ -74,6 +78,13 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation

CaptureParameters(parameters);

std::optional<PickerCommon::DialogFocusRestorer> focusRestorer;
if (WinAppSdk::Containment::IsChangeEnabled<WINAPPSDK_CHANGEID_63006043>())
{
// Capture focus on the UI thread so it can be restored after the dialog closes (issue #6505).
focusRestorer.emplace();
}

auto cancellationToken = co_await winrt::get_cancellation_token();
cancellationToken.enable_propagation(true);
co_await winrt::resume_background();
Expand Down Expand Up @@ -127,6 +138,13 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation

CaptureParameters(parameters);

std::optional<PickerCommon::DialogFocusRestorer> focusRestorer;
if (WinAppSdk::Containment::IsChangeEnabled<WINAPPSDK_CHANGEID_63006043>())
{
// Capture focus on the UI thread so it can be restored after the dialog closes (issue #6505).
focusRestorer.emplace();
}

auto cancellationToken = co_await winrt::get_cancellation_token();
cancellationToken.enable_propagation(true);
co_await winrt::resume_background();
Expand Down
10 changes: 10 additions & 0 deletions dev/Interop/StoragePickers/FileSavePicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
// Bug 60257559: [1.8 servicing] Bugfix: FileSavePicker.PickSaveFileAsync() should not truncate file when the picked file exists.
#define WINAPPSDK_CHANGEID_60257559 60257559, WinAppSDK_1_8_4

// Bug 63006043: [1.8 servicing] Fix focus not restored after Storage Pickers dialog closes
#define WINAPPSDK_CHANGEID_63006043 63006043, WinAppSDK_1_8_11

namespace winrt::Microsoft::Windows::Storage::Pickers::implementation
{

Expand Down Expand Up @@ -104,6 +107,13 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation

CaptureParameters(parameters);

std::optional<PickerCommon::DialogFocusRestorer> focusRestorer;
if (WinAppSdk::Containment::IsChangeEnabled<WINAPPSDK_CHANGEID_63006043>())
{
// Capture focus on the UI thread so it can be restored after the dialog closes (issue #6505).
focusRestorer.emplace();
}

auto defaultFileExtension = m_defaultFileExtension;
auto suggestedFolder = m_suggestedFolder;
auto suggestedFileName = m_suggestedFileName;
Expand Down
15 changes: 13 additions & 2 deletions dev/Interop/StoragePickers/FolderPicker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
#include "TerminalVelocityFeatures-StoragePickers.h"
#include "PickerCommon.h"
#include "PickFolderResult.h"
#include "PickerLocalization.h"
#include "PickerLocalization.h"
#include <FrameworkUdk/Containment.h>

// Bug 63006043: [1.8 servicing] Fix focus not restored after Storage Pickers dialog closes
#define WINAPPSDK_CHANGEID_63006043 63006043, WinAppSDK_1_8_11

namespace winrt::Microsoft::Windows::Storage::Pickers::implementation
{
Expand Down Expand Up @@ -67,7 +71,14 @@ namespace winrt::Microsoft::Windows::Storage::Pickers::implementation
parameters.AllFilesText = PickerLocalization::GetStoragePickersLocalizationText(PickerCommon::AllFilesLocalizationKey);

CaptureParameters(parameters);


std::optional<PickerCommon::DialogFocusRestorer> focusRestorer;
if (WinAppSdk::Containment::IsChangeEnabled<WINAPPSDK_CHANGEID_63006043>())
{
// Capture focus on the UI thread so it can be restored after the dialog closes (issue #6505).
focusRestorer.emplace();
}

auto cancellationToken = co_await winrt::get_cancellation_token();
cancellationToken.enable_propagation(true);
co_await winrt::resume_background();
Expand Down
16 changes: 16 additions & 0 deletions dev/Interop/StoragePickers/ManualTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,21 @@ auto& file = co_await picker.PickSaveFileAsync();
**Example of unexpected behavior - without the fix:**
![alter-extension-with-choices-beforefix](./media/alter-extension-with-choices-beforefix.gif)

### Keyboard focus is restored after the dialog closes
Run from a WinUI 3 app with a button whose Click handler opens a picker. This applies to every pick
method: `FileOpenPicker.PickSingleFileAsync` / `PickMultipleFilesAsync`, `FileSavePicker.PickSaveFileAsync`,
and `FolderPicker.PickSingleFolderAsync`.

Test code (C++), in the button's Click handler:
```C++
winrt::Microsoft::Windows::Storage::Pickers::FileOpenPicker picker{ AppWindow().Id() };
co_await picker.PickSingleFileAsync();
```

1. Click the button. The file dialog opens.
2. Pick a file and confirm. The dialog closes.
3. Without touching the mouse, press Enter.

**Expected behavior - with the fix:** Enter re-invokes the button and the dialog reopens.

**Example of unexpected behavior - without the fix:** Enter does nothing.
22 changes: 22 additions & 0 deletions dev/Interop/StoragePickers/PickerCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,28 @@ namespace {
namespace PickerCommon {
using namespace winrt;


DialogFocusRestorer::DialogFocusRestorer()
{
// Capture on the UI thread: GetFocus returns the focused window of the calling
// thread's message queue, which is the WinUI content island hosting the focused element.
m_focusedWindow = ::GetFocus();
m_dispatcherQueue = winrt::Microsoft::UI::Dispatching::DispatcherQueue::GetForCurrentThread();
}

DialogFocusRestorer::~DialogFocusRestorer()
{
if (m_focusedWindow && m_dispatcherQueue)
{
HWND focusedWindow = m_focusedWindow;
// Marshal back to the UI thread so SetFocus runs on the thread that owns the window.
m_dispatcherQueue.TryEnqueue([focusedWindow]()
{
::SetFocus(focusedWindow);
});
}
}

bool IsHStringNullOrEmpty(winrt::hstring value)
{
return value.empty();
Expand Down
23 changes: 23 additions & 0 deletions dev/Interop/StoragePickers/PickerCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Security.Cryptography.Core.h>
#include <winrt/Microsoft.UI.Windowing.h>
#include <winrt/Microsoft.UI.Dispatching.h>
#include <optional>

namespace PickerCommon {
winrt::hstring GetPathFromShellItem(winrt::com_ptr<IShellItem> shellItem);
Expand All @@ -28,6 +30,27 @@ namespace PickerCommon {
void ValidateSuggestedFileName(winrt::hstring const& suggestedFileName);
void ValidateSuggestedFolder(winrt::hstring const& path);

// Restores keyboard focus to the WinUI window after a COM file dialog closes.
//
// These dialogs run on a background thread (winrt::resume_background), so the SetFocus they
// perform on close comes from a thread that does not own the window and is ignored, leaving the
// window unable to receive key presses until the user clicks it again (issue #6505).
//
// Construct this RAII helper on the UI thread before switching to the background thread: it
// captures the focused window at construction and, on destruction, marshals SetFocus back to
// the UI thread via its DispatcherQueue, restoring focus once the dialog has closed.
struct DialogFocusRestorer
{
DialogFocusRestorer();
~DialogFocusRestorer();
DialogFocusRestorer(DialogFocusRestorer const&) = delete;
DialogFocusRestorer& operator=(DialogFocusRestorer const&) = delete;

private:
HWND m_focusedWindow{ nullptr };
winrt::Microsoft::UI::Dispatching::DispatcherQueue m_dispatcherQueue{ nullptr };
};

struct PickerParameters {
HWND HWnd{};
winrt::hstring CommitButtonText;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ namespace Microsoft.Windows.ApplicationModel.WindowsAppRuntime

// 1.8.10
ThemeSettings_OffThreadDestructorFix = 62451730,

// 1.8.11
StoragePickers_RestoreFocusAfterDialogCloses = 63006043,
};

/// Represents a version of the Windows App Runtime.
Expand Down