XamlBridge.cpp
1 #include "pch.h" 2 #include "XamlBridge.h" 3 #include <windowsx.h> 4 #include <string> 5 6 bool XamlBridge::FilterMessage(const MSG* msg) 7 { 8 // When multiple child windows are present it is needed to pre dispatch messages to all 9 // DesktopWindowXamlSource instances so keyboard accelerators and 10 // keyboard focus work correctly. 11 BOOL xamlSourceProcessedMessage = FALSE; 12 { 13 for (auto xamlSource : m_xamlSources) 14 { 15 auto xamlSourceNative2 = xamlSource.as<IDesktopWindowXamlSourceNative2>(); 16 const auto hr = xamlSourceNative2->PreTranslateMessage(msg, &xamlSourceProcessedMessage); 17 winrt::check_hresult(hr); 18 if (xamlSourceProcessedMessage) 19 { 20 break; 21 } 22 } 23 } 24 25 return !!xamlSourceProcessedMessage; 26 } 27 28 const auto static invalidReason = static_cast<winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason>(-1); 29 30 winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason GetReasonFromKey(WPARAM key) 31 { 32 auto reason = invalidReason; 33 if (key == VK_TAB) 34 { 35 byte keyboardState[256] = {}; 36 WINRT_VERIFY(::GetKeyboardState(keyboardState)); 37 reason = (keyboardState[VK_SHIFT] & 0x80) ? 38 winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last : 39 winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First; 40 } 41 else if (key == VK_LEFT) 42 { 43 reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left; 44 } 45 else if (key == VK_RIGHT) 46 { 47 reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right; 48 } 49 else if (key == VK_UP) 50 { 51 reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up; 52 } 53 else if (key == VK_DOWN) 54 { 55 reason = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down; 56 } 57 return reason; 58 } 59 60 // Function to return the next xaml island in focus 61 winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource XamlBridge::GetNextFocusedIsland(MSG* msg) 62 { 63 if (msg->message == WM_KEYDOWN) 64 { 65 const auto key = msg->wParam; 66 auto reason = GetReasonFromKey(key); 67 if (reason != invalidReason) 68 { 69 const BOOL previous = 70 (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || 71 reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || 72 reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? 73 false : 74 true; 75 76 const auto currentFocusedWindow = ::GetFocus(); 77 const auto nextElement = ::GetNextDlgTabItem(parentWindow, currentFocusedWindow, previous); 78 for (auto xamlSource : m_xamlSources) 79 { 80 const auto nativeIsland = xamlSource.as<IDesktopWindowXamlSourceNative>(); 81 HWND islandWnd = nullptr; 82 winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd)); 83 if (nextElement == islandWnd) 84 { 85 return xamlSource; 86 } 87 } 88 } 89 } 90 91 return nullptr; 92 } 93 94 // Function to return the xaml island currently in focus 95 winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource XamlBridge::GetFocusedIsland() 96 { 97 for (auto xamlSource : m_xamlSources) 98 { 99 if (xamlSource.HasFocus()) 100 { 101 return xamlSource; 102 } 103 } 104 return nullptr; 105 } 106 107 // Function to handle focus navigation 108 bool XamlBridge::NavigateFocus(MSG* msg) 109 { 110 if (const auto nextFocusedIsland = GetNextFocusedIsland(msg)) 111 { 112 const auto previousFocusedWindow = ::GetFocus(); 113 RECT rect = {}; 114 WINRT_VERIFY(::GetWindowRect(previousFocusedWindow, &rect)); 115 const auto nativeIsland = nextFocusedIsland.as<IDesktopWindowXamlSourceNative>(); 116 HWND islandWnd = nullptr; 117 winrt::check_hresult(nativeIsland->get_WindowHandle(&islandWnd)); 118 POINT pt = { rect.left, rect.top }; 119 SIZE size = { rect.right - rect.left, rect.bottom - rect.top }; 120 ::ScreenToClient(islandWnd, &pt); 121 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) }); 122 const auto reason = GetReasonFromKey(msg->wParam); 123 const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(reason, hintRect); 124 lastFocusRequestId = request.CorrelationId(); 125 const auto result = nextFocusedIsland.NavigateFocus(request); 126 return result.WasFocusMoved(); 127 } 128 else 129 { 130 const bool islandIsFocused = GetFocusedIsland() != nullptr; 131 byte keyboardState[256] = {}; 132 WINRT_VERIFY(::GetKeyboardState(keyboardState)); 133 const bool isMenuModifier = keyboardState[VK_MENU] & 0x80; 134 if (islandIsFocused && !isMenuModifier) 135 { 136 return false; 137 } 138 const bool isDialogMessage = !!IsDialogMessage(parentWindow, msg); 139 return isDialogMessage; 140 } 141 } 142 143 // Function to run the message loop for the xaml island window 144 WPARAM XamlBridge::MessageLoop() 145 { 146 MSG msg = {}; 147 HRESULT hr = S_OK; 148 Logger::trace("XamlBridge::MessageLoop()"); 149 while (GetMessage(&msg, nullptr, 0, 0)) 150 { 151 const bool xamlSourceProcessedMessage = FilterMessage(&msg); 152 if (!xamlSourceProcessedMessage) 153 { 154 if (!NavigateFocus(&msg)) 155 { 156 TranslateMessage(&msg); 157 DispatchMessage(&msg); 158 } 159 } 160 } 161 162 Logger::trace("XamlBridge::MessageLoop() stopped"); 163 return msg.wParam; 164 } 165 166 static const WPARAM invalidKey = 0xFFFFFFFFFFFFFFFF; 167 168 constexpr WPARAM GetKeyFromReason(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason reason) 169 { 170 auto key = invalidKey; 171 if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Last || reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First) 172 { 173 key = VK_TAB; 174 } 175 else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Left) 176 { 177 key = VK_LEFT; 178 } 179 else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) 180 { 181 key = VK_RIGHT; 182 } 183 else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Up) 184 { 185 key = VK_UP; 186 } 187 else if (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down) 188 { 189 key = VK_DOWN; 190 } 191 return key; 192 } 193 194 // Event triggered when focus is requested 195 void XamlBridge::OnTakeFocusRequested(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource const& sender, winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSourceTakeFocusRequestedEventArgs const& args) 196 { 197 Logger::trace("XamlBridge::OnTakeFocusRequested()"); 198 if (args.Request().CorrelationId() != lastFocusRequestId) 199 { 200 const auto reason = args.Request().Reason(); 201 const BOOL previous = 202 (reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::First || 203 reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Down || 204 reason == winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Right) ? 205 false : 206 true; 207 208 const auto nativeXamlSource = sender.as<IDesktopWindowXamlSourceNative>(); 209 HWND senderHwnd = nullptr; 210 winrt::check_hresult(nativeXamlSource->get_WindowHandle(&senderHwnd)); 211 212 MSG msg = {}; 213 msg.hwnd = senderHwnd; 214 msg.message = WM_KEYDOWN; 215 msg.wParam = GetKeyFromReason(reason); 216 if (!NavigateFocus(&msg)) 217 { 218 const auto nextElement = ::GetNextDlgTabItem(parentWindow, senderHwnd, previous); 219 ::SetFocus(nextElement); 220 } 221 } 222 else 223 { 224 const auto request = winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationRequest(winrt::Windows::UI::Xaml::Hosting::XamlSourceFocusNavigationReason::Restore); 225 lastFocusRequestId = request.CorrelationId(); 226 sender.NavigateFocus(request); 227 } 228 } 229 230 // Function to initialise the xaml source object 231 HWND XamlBridge::InitDesktopWindowsXamlSource(winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource) 232 { 233 Logger::trace("XamlBridge::InitDesktopWindowsXamlSource()"); 234 HRESULT hr = S_OK; 235 winrt::init_apartment(apartment_type::single_threaded); 236 winxamlmanager = WindowsXamlManager::InitializeForCurrentThread(); 237 238 auto interop = desktopSource.as<IDesktopWindowXamlSourceNative>(); 239 // Parent the DesktopWindowXamlSource object to current window 240 hr = interop->AttachToWindow(parentWindow); 241 winrt::check_hresult(hr); 242 243 // Get the new child window's hwnd 244 HWND hWndXamlIsland = nullptr; 245 hr = interop->get_WindowHandle(&hWndXamlIsland); 246 winrt::check_hresult(hr); 247 248 m_takeFocusEventRevokers.push_back(desktopSource.TakeFocusRequested(winrt::auto_revoke, { this, &XamlBridge::OnTakeFocusRequested })); 249 m_xamlSources.push_back(desktopSource); 250 251 return hWndXamlIsland; 252 } 253 254 // Function to close and delete all the xaml source objects 255 void XamlBridge::ClearXamlIslands() 256 { 257 Logger::trace("XamlBridge::ClearXamlIslands() {} focus event revokers", m_takeFocusEventRevokers.size()); 258 for (auto& takeFocusRevoker : m_takeFocusEventRevokers) 259 { 260 takeFocusRevoker.revoke(); 261 } 262 m_takeFocusEventRevokers.clear(); 263 264 for (auto xamlSource : m_xamlSources) 265 { 266 xamlSource.Close(); 267 } 268 m_xamlSources.clear(); 269 270 winxamlmanager.Close(); 271 } 272 273 // Function invoked when the window is destroyed 274 void XamlBridge::OnDestroy(HWND) 275 { 276 Logger::trace("XamlBridge::OnDestroy()"); 277 PostQuitMessage(0); 278 } 279 280 // Function invoked when the window is activated 281 void XamlBridge::OnActivate(HWND, UINT state, HWND hwndActDeact, BOOL fMinimized) 282 { 283 if (state == WA_INACTIVE) 284 { 285 Logger::trace("XamlBridge::OnActivate()"); 286 m_hwndLastFocus = GetFocus(); 287 } 288 } 289 290 // Function invoked when the window is set to focus 291 void XamlBridge::OnSetFocus(HWND, HWND hwndOldFocus) 292 { 293 if (m_hwndLastFocus) 294 { 295 Logger::trace("XamlBridge::OnSetFocus()"); 296 SetFocus(m_hwndLastFocus); 297 } 298 } 299 300 301 std::wstring getMessageString(const UINT message) 302 { 303 switch (message) 304 { 305 case WM_NCDESTROY: 306 return L"WM_NCDESTROY"; 307 case WM_ACTIVATE: 308 return L"WM_ACTIVATE"; 309 case WM_SETFOCUS: 310 return L"WM_SETFOCUS"; 311 default: 312 return L""; 313 } 314 } 315 316 // Message Handler function for Xaml Island windows 317 LRESULT XamlBridge::MessageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept 318 { 319 auto msg = getMessageString(message); 320 if (msg != L"") 321 { 322 Logger::trace(L"XamlBridge::MessageHandler() message: {}", msg); 323 } 324 325 switch (message) 326 { 327 HANDLE_MSG(parentWindow, WM_NCDESTROY, OnDestroy); 328 HANDLE_MSG(parentWindow, WM_ACTIVATE, OnActivate); 329 HANDLE_MSG(parentWindow, WM_SETFOCUS, OnSetFocus); 330 } 331 332 return DefWindowProc(parentWindow, message, wParam, lParam); 333 }