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  }