mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-11 05:52:19 +02:00
Fix Keyboard Navigation and Xaml Island focus issues for KBM (dev/build-features) (#2429)
* Added XamlBridge code and implemented keyboard focus navigation for xaml islands * Change global pointer to static
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
#include "EditKeyboardWindow.h"
|
#include "EditKeyboardWindow.h"
|
||||||
#include "SingleKeyRemapControl.h"
|
#include "SingleKeyRemapControl.h"
|
||||||
#include "KeyDropDownControl.h"
|
#include "KeyDropDownControl.h"
|
||||||
|
#include "XamlBridge.h"
|
||||||
|
|
||||||
LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM);
|
LRESULT CALLBACK EditKeyboardWindowProc(HWND, UINT, WPARAM, LPARAM);
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ bool isEditKeyboardWindowRegistrationCompleted = false;
|
|||||||
// Holds the native window handle of EditKeyboard Window.
|
// Holds the native window handle of EditKeyboard Window.
|
||||||
HWND hwndEditKeyboardNativeWindow = nullptr;
|
HWND hwndEditKeyboardNativeWindow = nullptr;
|
||||||
std::mutex editKeyboardWindowMutex;
|
std::mutex editKeyboardWindowMutex;
|
||||||
|
// Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure
|
||||||
|
static XamlBridge* xamlBridgePtr = nullptr;
|
||||||
|
|
||||||
// Function to create the Edit Keyboard Window
|
// Function to create the Edit Keyboard Window
|
||||||
void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState)
|
void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState)
|
||||||
@@ -60,18 +63,15 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
|
|||||||
hwndEditKeyboardNativeWindow = _hWndEditKeyboardWindow;
|
hwndEditKeyboardNativeWindow = _hWndEditKeyboardWindow;
|
||||||
hwndLock.unlock();
|
hwndLock.unlock();
|
||||||
|
|
||||||
// This DesktopWindowXamlSource is the object that enables a non-UWP desktop application
|
// Create the xaml bridge object
|
||||||
// to host UWP controls in any UI element that is associated with a window handle (HWND).
|
XamlBridge xamlBridge(_hWndEditKeyboardWindow);
|
||||||
DesktopWindowXamlSource desktopSource;
|
// DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors
|
||||||
// Get handle to corewindow
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource;
|
||||||
auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
|
// Create the desktop window xaml source object and set its content
|
||||||
// Parent the DesktopWindowXamlSource object to current window
|
hWndXamlIslandEditKeyboardWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource);
|
||||||
check_hresult(interop->AttachToWindow(_hWndEditKeyboardWindow));
|
|
||||||
|
|
||||||
// Get the new child window's hwnd
|
// Set the pointer to the xaml bridge object
|
||||||
interop->get_WindowHandle(&hWndXamlIslandEditKeyboardWindow);
|
xamlBridgePtr = &xamlBridge;
|
||||||
// Update the xaml island window size becuase initially is 0,0
|
|
||||||
SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, 0, 0, 400, 400, SWP_SHOWWINDOW);
|
|
||||||
|
|
||||||
// Header for the window
|
// Header for the window
|
||||||
Windows::UI::Xaml::Controls::RelativePanel header;
|
Windows::UI::Xaml::Controls::RelativePanel header;
|
||||||
@@ -310,10 +310,9 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
|
|||||||
xamlContainer.SetAlignRightWithPanel(scrollViewer, true);
|
xamlContainer.SetAlignRightWithPanel(scrollViewer, true);
|
||||||
xamlContainer.Children().Append(header);
|
xamlContainer.Children().Append(header);
|
||||||
xamlContainer.Children().Append(scrollViewer);
|
xamlContainer.Children().Append(scrollViewer);
|
||||||
|
|
||||||
xamlContainer.UpdateLayout();
|
xamlContainer.UpdateLayout();
|
||||||
desktopSource.Content(xamlContainer);
|
|
||||||
|
|
||||||
|
desktopSource.Content(xamlContainer);
|
||||||
////End XAML Island section
|
////End XAML Island section
|
||||||
if (_hWndEditKeyboardWindow)
|
if (_hWndEditKeyboardWindow)
|
||||||
{
|
{
|
||||||
@@ -322,17 +321,16 @@ void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMan
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Message loop:
|
// Message loop:
|
||||||
MSG msg = {};
|
xamlBridge.MessageLoop();
|
||||||
while (GetMessage(&msg, NULL, 0, 0))
|
|
||||||
{
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
desktopSource.Close();
|
|
||||||
|
|
||||||
|
// Reset pointers to nullptr
|
||||||
|
xamlBridgePtr = nullptr;
|
||||||
hWndXamlIslandEditKeyboardWindow = nullptr;
|
hWndXamlIslandEditKeyboardWindow = nullptr;
|
||||||
hwndLock.lock();
|
hwndLock.lock();
|
||||||
hwndEditKeyboardNativeWindow = nullptr;
|
hwndEditKeyboardNativeWindow = nullptr;
|
||||||
|
|
||||||
|
// Cannot be done in WM_DESTROY because that causes crashes due to fatal app exit
|
||||||
|
xamlBridge.ClearXamlIslands();
|
||||||
}
|
}
|
||||||
|
|
||||||
LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
|
LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
|
||||||
@@ -346,10 +344,17 @@ LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wPar
|
|||||||
GetClientRect(hWnd, &rcClient);
|
GetClientRect(hWnd, &rcClient);
|
||||||
SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
|
SetWindowPos(hWndXamlIslandEditKeyboardWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
|
||||||
break;
|
break;
|
||||||
case WM_DESTROY:
|
|
||||||
PostQuitMessage(0);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
|
// If the Xaml Bridge object exists, then use it's message handler to handle keyboard focus operations
|
||||||
|
if (xamlBridgePtr != nullptr)
|
||||||
|
{
|
||||||
|
return xamlBridgePtr->MessageHandler(messageCode, wParam, lParam);
|
||||||
|
}
|
||||||
|
else if (messageCode == WM_DESTROY)
|
||||||
|
{
|
||||||
|
PostQuitMessage(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
return DefWindowProc(hWnd, messageCode, wParam, lParam);
|
return DefWindowProc(hWnd, messageCode, wParam, lParam);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -357,6 +362,7 @@ LRESULT CALLBACK EditKeyboardWindowProc(HWND hWnd, UINT messageCode, WPARAM wPar
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to check if there is already a window active if yes bring to foreground
|
||||||
bool CheckEditKeyboardWindowActive()
|
bool CheckEditKeyboardWindowActive()
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|||||||
@@ -4,5 +4,5 @@
|
|||||||
// Function to create the Edit Keyboard Window
|
// Function to create the Edit Keyboard Window
|
||||||
void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState);
|
void createEditKeyboardWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState);
|
||||||
|
|
||||||
// Function to check if there is already a window active if yes bring to foreground.
|
// Function to check if there is already a window active if yes bring to foreground
|
||||||
bool CheckEditKeyboardWindowActive();
|
bool CheckEditKeyboardWindowActive();
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "EditShortcutsWindow.h"
|
#include "EditShortcutsWindow.h"
|
||||||
#include "ShortcutControl.h"
|
#include "ShortcutControl.h"
|
||||||
#include "KeyDropDownControl.h"
|
#include "KeyDropDownControl.h"
|
||||||
|
#include "XamlBridge.h"
|
||||||
|
|
||||||
LRESULT CALLBACK EditShortcutsWindowProc(HWND, UINT, WPARAM, LPARAM);
|
LRESULT CALLBACK EditShortcutsWindowProc(HWND, UINT, WPARAM, LPARAM);
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ bool isEditShortcutsWindowRegistrationCompleted = false;
|
|||||||
// Holds the native window handle of EditShortcuts Window.
|
// Holds the native window handle of EditShortcuts Window.
|
||||||
HWND hwndEditShortcutsNativeWindow = nullptr;
|
HWND hwndEditShortcutsNativeWindow = nullptr;
|
||||||
std::mutex editShortcutsWindowMutex;
|
std::mutex editShortcutsWindowMutex;
|
||||||
|
// Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure
|
||||||
|
static XamlBridge* xamlBridgePtr = nullptr;
|
||||||
|
|
||||||
// Function to create the Edit Shortcuts Window
|
// Function to create the Edit Shortcuts Window
|
||||||
void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState)
|
void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState)
|
||||||
@@ -61,18 +64,15 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
|
|||||||
hwndEditShortcutsNativeWindow = _hWndEditShortcutsWindow;
|
hwndEditShortcutsNativeWindow = _hWndEditShortcutsWindow;
|
||||||
hwndLock.unlock();
|
hwndLock.unlock();
|
||||||
|
|
||||||
// This DesktopWindowXamlSource is the object that enables a non-UWP desktop application
|
// Create the xaml bridge object
|
||||||
// to host UWP controls in any UI element that is associated with a window handle (HWND).
|
XamlBridge xamlBridge(_hWndEditShortcutsWindow);
|
||||||
DesktopWindowXamlSource desktopSource;
|
// DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors
|
||||||
// Get handle to corewindow
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource;
|
||||||
auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
|
// Create the desktop window xaml source object and set its content
|
||||||
// Parent the DesktopWindowXamlSource object to current window
|
hWndXamlIslandEditShortcutsWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource);
|
||||||
check_hresult(interop->AttachToWindow(_hWndEditShortcutsWindow));
|
|
||||||
|
|
||||||
// Get the new child window's hwnd
|
// Set the pointer to the xaml bridge object
|
||||||
interop->get_WindowHandle(&hWndXamlIslandEditShortcutsWindow);
|
xamlBridgePtr = &xamlBridge;
|
||||||
// Update the xaml island window size becuase initially is 0,0
|
|
||||||
SetWindowPos(hWndXamlIslandEditShortcutsWindow, 0, 0, 0, 400, 400, SWP_SHOWWINDOW);
|
|
||||||
|
|
||||||
// Header for the window
|
// Header for the window
|
||||||
Windows::UI::Xaml::Controls::RelativePanel header;
|
Windows::UI::Xaml::Controls::RelativePanel header;
|
||||||
@@ -233,8 +233,8 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
|
|||||||
xamlContainer.SetAlignRightWithPanel(scrollViewer, true);
|
xamlContainer.SetAlignRightWithPanel(scrollViewer, true);
|
||||||
xamlContainer.Children().Append(header);
|
xamlContainer.Children().Append(header);
|
||||||
xamlContainer.Children().Append(scrollViewer);
|
xamlContainer.Children().Append(scrollViewer);
|
||||||
|
|
||||||
xamlContainer.UpdateLayout();
|
xamlContainer.UpdateLayout();
|
||||||
|
|
||||||
desktopSource.Content(xamlContainer);
|
desktopSource.Content(xamlContainer);
|
||||||
|
|
||||||
////End XAML Island section
|
////End XAML Island section
|
||||||
@@ -245,17 +245,16 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Message loop:
|
// Message loop:
|
||||||
MSG msg = {};
|
xamlBridge.MessageLoop();
|
||||||
while (GetMessage(&msg, NULL, 0, 0))
|
|
||||||
{
|
|
||||||
TranslateMessage(&msg);
|
|
||||||
DispatchMessage(&msg);
|
|
||||||
}
|
|
||||||
desktopSource.Close();
|
|
||||||
|
|
||||||
|
// Reset pointers to nullptr
|
||||||
|
xamlBridgePtr = nullptr;
|
||||||
hWndXamlIslandEditShortcutsWindow = nullptr;
|
hWndXamlIslandEditShortcutsWindow = nullptr;
|
||||||
hwndLock.lock();
|
hwndLock.lock();
|
||||||
hwndEditShortcutsNativeWindow = nullptr;
|
hwndEditShortcutsNativeWindow = nullptr;
|
||||||
|
|
||||||
|
// Cannot be done in WM_DESTROY because that causes crashes due to fatal app exit
|
||||||
|
xamlBridge.ClearXamlIslands();
|
||||||
}
|
}
|
||||||
|
|
||||||
LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
|
LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wParam, LPARAM lParam)
|
||||||
@@ -269,10 +268,17 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
|
|||||||
GetClientRect(hWnd, &rcClient);
|
GetClientRect(hWnd, &rcClient);
|
||||||
SetWindowPos(hWndXamlIslandEditShortcutsWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
|
SetWindowPos(hWndXamlIslandEditShortcutsWindow, 0, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom, SWP_SHOWWINDOW);
|
||||||
break;
|
break;
|
||||||
case WM_DESTROY:
|
|
||||||
PostQuitMessage(0);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
|
// If the Xaml Bridge object exists, then use it's message handler to handle keyboard focus operations
|
||||||
|
if (xamlBridgePtr != nullptr)
|
||||||
|
{
|
||||||
|
return xamlBridgePtr->MessageHandler(messageCode, wParam, lParam);
|
||||||
|
}
|
||||||
|
else if (messageCode == WM_DESTROY)
|
||||||
|
{
|
||||||
|
PostQuitMessage(0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
return DefWindowProc(hWnd, messageCode, wParam, lParam);
|
return DefWindowProc(hWnd, messageCode, wParam, lParam);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -280,6 +286,7 @@ LRESULT CALLBACK EditShortcutsWindowProc(HWND hWnd, UINT messageCode, WPARAM wPa
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to check if there is already a window active if yes bring to foreground
|
||||||
bool CheckEditShortcutsWindowActive()
|
bool CheckEditShortcutsWindowActive()
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
|
|||||||
@@ -6,5 +6,5 @@
|
|||||||
// Function to create the Edit Shortcuts Window
|
// Function to create the Edit Shortcuts Window
|
||||||
void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState);
|
void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardManagerState);
|
||||||
|
|
||||||
// Function to check if there is already a window active if yes bring to foreground.
|
// Function to check if there is already a window active if yes bring to foreground
|
||||||
bool CheckEditShortcutsWindowActive();
|
bool CheckEditShortcutsWindowActive();
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="ShortcutControl.cpp" />
|
<ClCompile Include="ShortcutControl.cpp" />
|
||||||
<ClCompile Include="SingleKeyRemapControl.cpp" />
|
<ClCompile Include="SingleKeyRemapControl.cpp" />
|
||||||
|
<ClCompile Include="XamlBridge.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="EditKeyboardWindow.h" />
|
<ClInclude Include="EditKeyboardWindow.h" />
|
||||||
@@ -115,6 +116,7 @@
|
|||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="ShortcutControl.h" />
|
<ClInclude Include="ShortcutControl.h" />
|
||||||
<ClInclude Include="SingleKeyRemapControl.h" />
|
<ClInclude Include="SingleKeyRemapControl.h" />
|
||||||
|
<ClInclude Include="XamlBridge.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<ClCompile Include="pch.cpp" />
|
<ClCompile Include="pch.cpp" />
|
||||||
<ClCompile Include="SingleKeyRemapControl.cpp" />
|
<ClCompile Include="SingleKeyRemapControl.cpp" />
|
||||||
<ClCompile Include="KeyDropDownControl.cpp" />
|
<ClCompile Include="KeyDropDownControl.cpp" />
|
||||||
|
<ClCompile Include="XamlBridge.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="EditKeyboardWindow.h" />
|
<ClInclude Include="EditKeyboardWindow.h" />
|
||||||
@@ -15,6 +16,7 @@
|
|||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="SingleKeyRemapControl.h" />
|
<ClInclude Include="SingleKeyRemapControl.h" />
|
||||||
<ClInclude Include="KeyDropDownControl.h" />
|
<ClInclude Include="KeyDropDownControl.h" />
|
||||||
|
<ClInclude Include="XamlBridge.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
|||||||
297
src/modules/keyboardmanager/ui/XamlBridge.cpp
Normal file
297
src/modules/keyboardmanager/ui/XamlBridge.cpp
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "XamlBridge.h"
|
||||||
|
|
||||||
|
bool XamlBridge::FilterMessage(const MSG* msg)
|
||||||
|
{
|
||||||
|
// When multiple child windows are present it is needed to pre dispatch messages to all
|
||||||
|
// DesktopWindowXamlSource instances so keyboard accelerators and
|
||||||
|
// keyboard focus work correctly.
|
||||||
|
BOOL xamlSourceProcessedMessage = FALSE;
|
||||||
|
{
|
||||||
|
for (auto xamlSource : m_xamlSources)
|
||||||
|
{
|
||||||
|
auto xamlSourceNative2 = xamlSource.as<IDesktopWindowXamlSourceNative2>();
|
||||||
|
const auto hr = xamlSourceNative2->PreTranslateMessage(msg, &xamlSourceProcessedMessage);
|
||||||
|
winrt::check_hresult(hr);
|
||||||
|
if (xamlSourceProcessedMessage)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !!xamlSourceProcessedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto static invalidReason = static_cast<winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason>(-1);
|
||||||
|
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason GetReasonFromKey(WPARAM key)
|
||||||
|
{
|
||||||
|
auto reason = invalidReason;
|
||||||
|
if (key == VK_TAB)
|
||||||
|
{
|
||||||
|
byte keyboardState[256] = {};
|
||||||
|
WINRT_VERIFY(::GetKeyboardState(keyboardState));
|
||||||
|
reason = (keyboardState[VK_SHIFT] & 0x80) ?
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last :
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First;
|
||||||
|
}
|
||||||
|
else if (key == VK_LEFT)
|
||||||
|
{
|
||||||
|
reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left;
|
||||||
|
}
|
||||||
|
else if (key == VK_RIGHT)
|
||||||
|
{
|
||||||
|
reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right;
|
||||||
|
}
|
||||||
|
else if (key == VK_UP)
|
||||||
|
{
|
||||||
|
reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up;
|
||||||
|
}
|
||||||
|
else if (key == VK_DOWN)
|
||||||
|
{
|
||||||
|
reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down;
|
||||||
|
}
|
||||||
|
return reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to return the next xaml island in focus
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource XamlBridge::GetNextFocusedIsland(MSG* msg)
|
||||||
|
{
|
||||||
|
if (msg->message == WM_KEYDOWN)
|
||||||
|
{
|
||||||
|
const auto key = msg->wParam;
|
||||||
|
auto reason = GetReasonFromKey(key);
|
||||||
|
if (reason != invalidReason)
|
||||||
|
{
|
||||||
|
const BOOL previous =
|
||||||
|
(reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First ||
|
||||||
|
reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down ||
|
||||||
|
reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ?
|
||||||
|
false :
|
||||||
|
true;
|
||||||
|
|
||||||
|
const auto currentFocusedWindow = ::GetFocus();
|
||||||
|
const auto nextElement = ::GetNextDlgTabItem(parentWindow, currentFocusedWindow, previous);
|
||||||
|
for (auto xamlSource : m_xamlSources)
|
||||||
|
{
|
||||||
|
const auto nativeIsland = xamlSource.as<IDesktopWindowXamlSourceNative>();
|
||||||
|
HWND islandWnd = nullptr;
|
||||||
|
winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd));
|
||||||
|
if (nextElement == islandWnd)
|
||||||
|
{
|
||||||
|
return xamlSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to return the xaml island currently in focus
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource XamlBridge::GetFocusedIsland()
|
||||||
|
{
|
||||||
|
for (auto xamlSource : m_xamlSources)
|
||||||
|
{
|
||||||
|
if (xamlSource.HasFocus())
|
||||||
|
{
|
||||||
|
return xamlSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle focus navigation
|
||||||
|
bool XamlBridge::NavigateFocus(MSG* msg)
|
||||||
|
{
|
||||||
|
if (const auto nextFocusedIsland = GetNextFocusedIsland(msg))
|
||||||
|
{
|
||||||
|
const auto previousFocusedWindow = ::GetFocus();
|
||||||
|
RECT rect = {};
|
||||||
|
WINRT_VERIFY(::GetWindowRect(previousFocusedWindow, &rect));
|
||||||
|
const auto nativeIsland = nextFocusedIsland.as<IDesktopWindowXamlSourceNative>();
|
||||||
|
HWND islandWnd = nullptr;
|
||||||
|
winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd));
|
||||||
|
POINT pt = { rect.left, rect.top };
|
||||||
|
SIZE size = { rect.right - rect.left, rect.bottom - rect.top };
|
||||||
|
::ScreenToClient(islandWnd, &pt);
|
||||||
|
const auto hintRect = winrt::Windows::Foundation::Rect({ static_cast<float>(pt.x), static_cast<float>(pt.y), static_cast<float>(size.cx), static_cast<float>(size.cy) });
|
||||||
|
const auto reason = GetReasonFromKey(msg->wParam);
|
||||||
|
const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(reason, hintRect);
|
||||||
|
lastFocusRequestId = request.CorrelationId();
|
||||||
|
const auto result = nextFocusedIsland.NavigateFocus(request);
|
||||||
|
return result.WasFocusMoved();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const bool islandIsFocused = GetFocusedIsland() != nullptr;
|
||||||
|
byte keyboardState[256] = {};
|
||||||
|
WINRT_VERIFY(::GetKeyboardState(keyboardState));
|
||||||
|
const bool isMenuModifier = keyboardState[VK_MENU] & 0x80;
|
||||||
|
if (islandIsFocused && !isMenuModifier)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool isDialogMessage = !!IsDialogMessage(parentWindow, msg);
|
||||||
|
return isDialogMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to run the message loop for the xaml island window
|
||||||
|
int XamlBridge::MessageLoop()
|
||||||
|
{
|
||||||
|
MSG msg = {};
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
while (GetMessage(&msg, nullptr, 0, 0))
|
||||||
|
{
|
||||||
|
const bool xamlSourceProcessedMessage = FilterMessage(&msg);
|
||||||
|
if (!xamlSourceProcessedMessage)
|
||||||
|
{
|
||||||
|
if (!NavigateFocus(&msg))
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)msg.wParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const WPARAM invalidKey = (WPARAM)-1;
|
||||||
|
|
||||||
|
WPARAM GetKeyFromReason(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason reason)
|
||||||
|
{
|
||||||
|
auto key = invalidKey;
|
||||||
|
if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last || reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First)
|
||||||
|
{
|
||||||
|
key = VK_TAB;
|
||||||
|
}
|
||||||
|
else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left)
|
||||||
|
{
|
||||||
|
key = VK_LEFT;
|
||||||
|
}
|
||||||
|
else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right)
|
||||||
|
{
|
||||||
|
key = VK_RIGHT;
|
||||||
|
}
|
||||||
|
else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up)
|
||||||
|
{
|
||||||
|
key = VK_UP;
|
||||||
|
}
|
||||||
|
else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down)
|
||||||
|
{
|
||||||
|
key = VK_DOWN;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event triggered when focus is requested
|
||||||
|
void XamlBridge::OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args)
|
||||||
|
{
|
||||||
|
if (args.Request().CorrelationId() != lastFocusRequestId)
|
||||||
|
{
|
||||||
|
const auto reason = args.Request().Reason();
|
||||||
|
const BOOL previous =
|
||||||
|
(reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First ||
|
||||||
|
reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down ||
|
||||||
|
reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ?
|
||||||
|
false :
|
||||||
|
true;
|
||||||
|
|
||||||
|
const auto nativeXamlSource = sender.as<IDesktopWindowXamlSourceNative>();
|
||||||
|
HWND senderHwnd = nullptr;
|
||||||
|
winrt::check_hresult(nativeXamlSource->get_WindowHandle(&senderHwnd));
|
||||||
|
|
||||||
|
MSG msg = {};
|
||||||
|
msg.hwnd = senderHwnd;
|
||||||
|
msg.message = WM_KEYDOWN;
|
||||||
|
msg.wParam = GetKeyFromReason(reason);
|
||||||
|
if (!NavigateFocus(&msg))
|
||||||
|
{
|
||||||
|
const auto nextElement = ::GetNextDlgTabItem(parentWindow, senderHwnd, previous);
|
||||||
|
::SetFocus(nextElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Restore);
|
||||||
|
lastFocusRequestId = request.CorrelationId();
|
||||||
|
sender.NavigateFocus(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to initialise the xaml source object
|
||||||
|
HWND XamlBridge::InitDesktopWindowsXamlSource(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource)
|
||||||
|
{
|
||||||
|
HRESULT hr = S_OK;
|
||||||
|
|
||||||
|
auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>();
|
||||||
|
// Parent the DesktopWindowXamlSource object to current window
|
||||||
|
hr = interop->AttachToWindow(parentWindow);
|
||||||
|
winrt::check_hresult(hr);
|
||||||
|
|
||||||
|
// Get the new child window's hwnd
|
||||||
|
HWND hWndXamlIsland = nullptr;
|
||||||
|
hr = interop->get_WindowHandle(&hWndXamlIsland);
|
||||||
|
winrt::check_hresult(hr);
|
||||||
|
|
||||||
|
m_takeFocusEventRevokers.push_back(desktopSource.TakeFocusRequested(winrt::auto_revoke, { this, &XamlBridge::OnTakeFocusRequested }));
|
||||||
|
m_xamlSources.push_back(desktopSource);
|
||||||
|
|
||||||
|
return hWndXamlIsland;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to close and delete all the xaml source objects
|
||||||
|
void XamlBridge::ClearXamlIslands()
|
||||||
|
{
|
||||||
|
for (auto& takeFocusRevoker : m_takeFocusEventRevokers)
|
||||||
|
{
|
||||||
|
takeFocusRevoker.revoke();
|
||||||
|
}
|
||||||
|
m_takeFocusEventRevokers.clear();
|
||||||
|
|
||||||
|
for (auto xamlSource : m_xamlSources)
|
||||||
|
{
|
||||||
|
xamlSource.Close();
|
||||||
|
}
|
||||||
|
m_xamlSources.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invoked when the window is destroyed
|
||||||
|
void XamlBridge::OnDestroy(HWND)
|
||||||
|
{
|
||||||
|
PostQuitMessage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invoked when the window is activated
|
||||||
|
void XamlBridge::OnActivate(HWND, UINT state, HWND hwndActDeact, BOOL fMinimized)
|
||||||
|
{
|
||||||
|
if (state == WA_INACTIVE)
|
||||||
|
{
|
||||||
|
m_hwndLastFocus = GetFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function invoked when the window is set to focus
|
||||||
|
void XamlBridge::OnSetFocus(HWND, HWND hwndOldFocus)
|
||||||
|
{
|
||||||
|
if (m_hwndLastFocus)
|
||||||
|
{
|
||||||
|
SetFocus(m_hwndLastFocus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message Handler function for Xaml Island windows
|
||||||
|
LRESULT XamlBridge::MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept
|
||||||
|
{
|
||||||
|
switch (message)
|
||||||
|
{
|
||||||
|
HANDLE_MSG(parentWindow, WM_DESTROY, OnDestroy);
|
||||||
|
HANDLE_MSG(parentWindow, WM_ACTIVATE, OnActivate);
|
||||||
|
HANDLE_MSG(parentWindow, WM_SETFOCUS, OnSetFocus);
|
||||||
|
}
|
||||||
|
return DefWindowProc(parentWindow, message, wParam, lParam);
|
||||||
|
}
|
||||||
72
src/modules/keyboardmanager/ui/XamlBridge.h
Normal file
72
src/modules/keyboardmanager/ui/XamlBridge.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <unknwn.h> // To enable support for non-WinRT interfaces, unknwn.h must be included before any C++/WinRT headers.
|
||||||
|
#include <winrt/Windows.System.h>
|
||||||
|
#include <winrt/Windows.Foundation.h>
|
||||||
|
#include <winrt/Windows.Foundation.Collections.h>
|
||||||
|
#include <winrt/Windows.UI.Xaml.Hosting.h>
|
||||||
|
#include <winrt/Windows.UI.Xaml.Markup.h>
|
||||||
|
#include <windows.ui.xaml.hosting.desktopwindowxamlsource.h>
|
||||||
|
#include <windowsx.h>
|
||||||
|
|
||||||
|
// This class is used for handling XAML Island operations
|
||||||
|
class XamlBridge
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Function to run the message loop for the xaml island window
|
||||||
|
int MessageLoop();
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
XamlBridge(HWND parent) :
|
||||||
|
parentWindow(parent), lastFocusRequestId(winrt::guid())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to initialise the xaml source object
|
||||||
|
HWND InitDesktopWindowsXamlSource(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource);
|
||||||
|
|
||||||
|
// Function to close and delete all the xaml source objects
|
||||||
|
void ClearXamlIslands();
|
||||||
|
|
||||||
|
// Message Handler function for Xaml Island windows
|
||||||
|
LRESULT MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Stores the last window handle in focus
|
||||||
|
HWND m_hwndLastFocus = nullptr;
|
||||||
|
|
||||||
|
// Stores the handle of the parent native window
|
||||||
|
HWND parentWindow = nullptr;
|
||||||
|
|
||||||
|
// Stores the GUID of the last focus request
|
||||||
|
winrt::guid lastFocusRequestId;
|
||||||
|
|
||||||
|
// Function to return the xaml island currently in focus
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetFocusedIsland();
|
||||||
|
|
||||||
|
// Function to pre process the message on the xaml source object
|
||||||
|
bool FilterMessage(const MSG* msg);
|
||||||
|
|
||||||
|
// Event triggered when focus is requested
|
||||||
|
void OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args);
|
||||||
|
|
||||||
|
// Function to return the next xaml island in focus
|
||||||
|
winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource GetNextFocusedIsland(MSG* msg);
|
||||||
|
|
||||||
|
// Function to handle focus navigation
|
||||||
|
bool NavigateFocus(MSG* msg);
|
||||||
|
|
||||||
|
// Stores the focus event objects
|
||||||
|
std::vector<winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource::TakeFocusRequested_revoker> m_takeFocusEventRevokers;
|
||||||
|
|
||||||
|
// Stores the xaml source objects
|
||||||
|
std::vector<winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource> m_xamlSources;
|
||||||
|
|
||||||
|
// Function invoked when the window is destroyed
|
||||||
|
void OnDestroy(HWND);
|
||||||
|
|
||||||
|
// Function invoked when the window is activated
|
||||||
|
void OnActivate(HWND, UINT state, HWND hwndActDeact, BOOL fMinimized);
|
||||||
|
|
||||||
|
// Function invoked when the window is set to focus
|
||||||
|
void OnSetFocus(HWND, HWND hwndOldFocus);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user