/ src / modules / keyboardmanager / KeyboardManagerEditorLibrary / SingleKeyRemapControl.cpp
SingleKeyRemapControl.cpp
  1  #include "pch.h"
  2  #include "SingleKeyRemapControl.h"
  3  
  4  #include "KeyboardManagerState.h"
  5  #include "KeyboardManagerEditorStrings.h"
  6  #include "ShortcutControl.h"
  7  #include "UIHelpers.h"
  8  #include "EditorHelpers.h"
  9  #include "EditorConstants.h"
 10  
 11  //Both static members are initialized to null
 12  HWND SingleKeyRemapControl::EditKeyboardWindowHandle = nullptr;
 13  KBMEditor::KeyboardManagerState* SingleKeyRemapControl::keyboardManagerState = nullptr;
 14  // Initialized as new vector
 15  RemapBuffer SingleKeyRemapControl::singleKeyRemapBuffer;
 16  
 17  SingleKeyRemapControl::SingleKeyRemapControl(StackPanel table, StackPanel row, const int colIndex)
 18  {
 19      typeKey = Button();
 20      typeKey.as<Button>().Width(EditorConstants::RemapTableDropDownWidth);
 21      typeKey.as<Button>().Content(winrt::box_value(GET_RESOURCE_STRING(IDS_TYPE_BUTTON)));
 22  
 23      singleKeyRemapControlLayout = StackPanel();
 24      singleKeyRemapControlLayout.as<StackPanel>().Spacing(EditorConstants::ShortcutTableDropDownSpacing);
 25  
 26      // Key column (From key)
 27      if (colIndex == 0)
 28      {
 29          singleKeyRemapControlLayout.as<StackPanel>().Children().Append(typeKey.as<Button>());
 30  
 31          keyDropDownControlObjects.emplace_back(std::make_unique<KeyDropDownControl>(false));
 32          singleKeyRemapControlLayout.as<StackPanel>().Children().Append(keyDropDownControlObjects[0]->GetComboBox());
 33          // Set selection handler for the drop down
 34          keyDropDownControlObjects[0]->SetSelectionHandler(table, row, colIndex, singleKeyRemapBuffer);
 35      }
 36  
 37      // Hybrid column (To Key/Shortcut/Text)
 38      else
 39      {
 40          StackPanel keyComboAndSelectStackPanel;
 41          keyComboAndSelectStackPanel.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
 42          keyComboAndSelectStackPanel.Spacing(EditorConstants::ShortcutTableDropDownSpacing);
 43  
 44          hybridDropDownVariableSizedWrapGrid = VariableSizedWrapGrid();
 45          auto grid = hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>();
 46          grid.Orientation(Windows::UI::Xaml::Controls::Orientation::Horizontal);
 47          auto gridMargin = Windows::UI::Xaml::Thickness();
 48          gridMargin.Bottom = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed textInput
 49          grid.Margin(gridMargin);
 50  
 51          KeyDropDownControl::AddDropDown(table, row, grid, colIndex, singleKeyRemapBuffer, keyDropDownControlObjects, nullptr, true, true);
 52  
 53          singleKeyRemapControlLayout.as<StackPanel>().Children().Append(grid);
 54  
 55          auto textInput = TextBox();
 56  
 57          auto textBoxMargin = Windows::UI::Xaml::Thickness();
 58          textBoxMargin.Top = -EditorConstants::ShortcutTableDropDownSpacing; // compensate for a collapsed grid
 59          textBoxMargin.Bottom = EditorConstants::ShortcutTableDropDownSpacing;
 60          textInput.Margin(textBoxMargin);
 61          textInput.AcceptsReturn(false);
 62          textInput.Visibility(Visibility::Collapsed);
 63          textInput.Width(EditorConstants::TableDropDownHeight);
 64          singleKeyRemapControlLayout.as<StackPanel>().Children().Append(textInput);
 65          textInput.HorizontalAlignment(HorizontalAlignment::Left);
 66          textInput.TextChanged([this, row, table](winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) mutable {
 67              auto textbox = sender.as<TextBox>();
 68              auto text = textbox.Text();
 69              uint32_t rowIndex = -1;
 70              if (!table.Children().IndexOf(row, rowIndex))
 71              {
 72                  return;
 73              }
 74  
 75              singleKeyRemapBuffer[rowIndex].mapping[1] = text.c_str();
 76          });
 77  
 78          auto typeCombo = ComboBox();
 79          typeCombo.Width(EditorConstants::RemapTableDropDownWidth);
 80          typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeKeyShortcut()));
 81          typeCombo.Items().Append(winrt::box_value(KeyboardManagerEditorStrings::MappingTypeText()));
 82          keyComboAndSelectStackPanel.Children().Append(typeCombo);
 83          keyComboAndSelectStackPanel.Children().Append(typeKey.as<Button>());
 84          singleKeyRemapControlLayout.as<StackPanel>().Children().InsertAt(0, keyComboAndSelectStackPanel);
 85  
 86          typeCombo.SelectedIndex(0);
 87          typeCombo.SelectionChanged([this, typeCombo, grid, textInput](winrt::Windows::Foundation::IInspectable const&, SelectionChangedEventArgs const&) {
 88              const bool textSelected = typeCombo.SelectedIndex() == 1;
 89  
 90              const auto keyInputVisibility = textSelected ? Visibility::Collapsed : Visibility::Visible;
 91  
 92              grid.Visibility(keyInputVisibility);
 93              typeKey.as<Button>().Visibility(keyInputVisibility);
 94  
 95              const auto textInputVisibility = textSelected ? Visibility::Visible : Visibility::Collapsed;
 96              textInput.Visibility(textInputVisibility);
 97          });
 98      }
 99  
100      typeKey.as<Button>().Click([&, table, colIndex, row](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
101          // Using the XamlRoot of the typeKey to get the root of the XAML host
102          if (colIndex == 0)
103          {
104              keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectSingleKeyRemapWindowActivated, EditKeyboardWindowHandle);
105              createDetectKeyWindow(sender, sender.as<Button>().XamlRoot(), *keyboardManagerState);
106          }
107          else
108          {
109              keyboardManagerState->SetUIState(KBMEditor::KeyboardManagerUIState::DetectShortcutWindowInEditKeyboardWindowActivated, EditKeyboardWindowHandle);
110              ShortcutControl::CreateDetectShortcutWindow(sender, sender.as<Button>().XamlRoot(), *keyboardManagerState, colIndex, table, keyDropDownControlObjects, row, nullptr, true, true, EditKeyboardWindowHandle, singleKeyRemapBuffer);
111          }
112      });
113  
114      try
115      {
116          // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
117          singleKeyRemapControlLayout.as<StackPanel>().UpdateLayout();
118      }
119      catch (...)
120      {
121      }
122  }
123  
124  // Function to set the accessible names for all the controls in a row
125  void SingleKeyRemapControl::UpdateAccessibleNames(StackPanel sourceColumn, StackPanel mappedToColumn, Button deleteButton, int rowIndex)
126  {
127      sourceColumn.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_EDITKEYBOARD_SOURCEHEADER)));
128      mappedToColumn.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_EDITKEYBOARD_TARGETHEADER)));
129      deleteButton.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_AUTOMATIONPROPERTIES_ROW) + std::to_wstring(rowIndex) + L", " + GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
130  }
131  
132  void SingleKeyRemapControl::TextToMapChangedHandler(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::TextChangedEventArgs const& e) // TODO: remove
133  {
134      auto textbox = sender.as<TextBox>();
135      auto text = textbox.Text();
136      (void)text;
137  }
138  
139  // Function to add a new row to the remap keys table. If the originalKey and newKey args are provided, then the displayed remap keys are set to those values.
140  void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::vector<std::vector<std::unique_ptr<SingleKeyRemapControl>>>& keyboardRemapControlObjects, const DWORD originalKey, const KeyShortcutTextUnion newKey)
141  {
142      // Create new SingleKeyRemapControl objects dynamically so that we does not get destructed
143      std::vector<std::unique_ptr<SingleKeyRemapControl>> newrow;
144      StackPanel row = StackPanel();
145      parent.Children().Append(row);
146      newrow.emplace_back(std::make_unique<SingleKeyRemapControl>(parent, row, 0));
147      newrow.emplace_back(std::make_unique<SingleKeyRemapControl>(parent, row, 1));
148      keyboardRemapControlObjects.push_back(std::move(newrow));
149  
150      row.Padding({ 10, 15, 10, 5 });
151      row.Margin({ 0, 0, 0, 2 });
152      row.Orientation(Orientation::Horizontal);
153      row.Background(Application::Current().Resources().Lookup(box_value(L"CardBackgroundFillColorDefaultBrush")).as<Media::Brush>());
154      row.BorderBrush(Application::Current().Resources().Lookup(box_value(L"CardStrokeColorDefaultBrush")).as<Media::Brush>());
155      row.BorderThickness({ 0, 1, 0, 1 });
156  
157      // SingleKeyRemapControl for the original key.
158      auto originalElement = keyboardRemapControlObjects.back()[0]->getSingleKeyRemapControl();
159      originalElement.Width(EditorConstants::RemapTableDropDownWidth + EditorConstants::RemapTableDropDownSpacing);
160      row.Children().Append(originalElement);
161  
162      // Arrow icon
163      SymbolIcon arrowIcon(Symbol::Forward);
164      arrowIcon.VerticalAlignment(VerticalAlignment::Center);
165      arrowIcon.HorizontalAlignment(HorizontalAlignment::Center);
166      auto arrowIconContainer = UIHelpers::GetWrapped(arrowIcon, EditorConstants::TableArrowColWidth).as<StackPanel>();
167      arrowIconContainer.Orientation(Orientation::Vertical);
168      arrowIconContainer.VerticalAlignment(VerticalAlignment::Center);
169      arrowIconContainer.Margin({ 0, 0, 0, 10 });
170      row.Children().Append(arrowIconContainer);
171  
172      // SingleKeyRemapControl for the new remap key
173      auto targetElement = keyboardRemapControlObjects.back()[1]->getSingleKeyRemapControl();
174      targetElement.Width(EditorConstants::RemapTargetColumnWidth);
175      row.Children().Append(targetElement);
176  
177      // Set the key text if the two keys are not null (i.e. default args)
178      if (originalKey != NULL && !(newKey.index() == 0 && std::get<DWORD>(newKey) == NULL) && !(newKey.index() == 1 && !EditorHelpers::IsValidShortcut(std::get<Shortcut>(newKey))) && !(newKey.index() == 2 && std::get<std::wstring>(newKey).empty()))
179      {
180          singleKeyRemapBuffer.push_back(RemapBufferRow{ RemapBufferItem{ originalKey, newKey }, L"" });
181          keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][0]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(originalKey));
182          if (newKey.index() == 0)
183          {
184              keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects[0]->SetSelectedValue(std::to_wstring(std::get<DWORD>(newKey)));
185          }
186          else if (newKey.index() == 1)
187          {
188              KeyDropDownControl::AddShortcutToControl(std::get<Shortcut>(newKey), parent, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->hybridDropDownVariableSizedWrapGrid.as<VariableSizedWrapGrid>(), *keyboardManagerState, 1, keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1]->keyDropDownControlObjects, singleKeyRemapBuffer, row, nullptr, true, true);
189          }
190          else if (newKey.index() == 2)
191          {
192              auto& singleKeyRemapControl = keyboardRemapControlObjects[keyboardRemapControlObjects.size() - 1][1];
193  
194              const auto& firstLineStackPanel = singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(0).as<StackPanel>();
195  
196              firstLineStackPanel.Children().GetAt(0).as<ComboBox>().SelectedIndex(1);
197  
198              singleKeyRemapControl->singleKeyRemapControlLayout.as<StackPanel>().Children().GetAt(2).as<TextBox>().Text(std::get<std::wstring>(newKey));
199          }
200      }
201      else
202      {
203          // Initialize both keys to NULL
204          singleKeyRemapBuffer.push_back(RemapBufferRow{ RemapBufferItem{ (DWORD)0, (DWORD)0 }, L"" });
205      }
206  
207      // Delete row button
208      Windows::UI::Xaml::Controls::Button deleteRemapKeys;
209      deleteRemapKeys.Content(SymbolIcon(Symbol::Delete));
210      deleteRemapKeys.Background(Media::SolidColorBrush(Colors::Transparent()));
211      deleteRemapKeys.HorizontalAlignment(HorizontalAlignment::Center);
212      deleteRemapKeys.Margin({ 0, 0, 0, 10 });
213      deleteRemapKeys.Click([&, parent, row, deleteRemapKeys](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) {
214          uint32_t rowIndex;
215          // Get index of delete button
216          UIElementCollection children = parent.Children();
217          bool indexFound = children.IndexOf(row, rowIndex);
218  
219          // IndexOf could fail if the row got deleted and the button handler was invoked twice. In this case it should return
220          if (!indexFound)
221          {
222              return;
223          }
224  
225          // Update accessible names and background for each row after the deleted row
226          for (uint32_t i = rowIndex + 1; i < children.Size(); i++)
227          {
228              StackPanel row = children.GetAt(i).as<StackPanel>();
229              StackPanel sourceCol = row.Children().GetAt(0).as<StackPanel>();
230              StackPanel targetCol = row.Children().GetAt(2).as<StackPanel>();
231              Button delButton = row.Children().GetAt(3).as<Button>();
232              UpdateAccessibleNames(sourceCol, targetCol, delButton, i);
233          }
234  
235          if (auto automationPeer{ Automation::Peers::FrameworkElementAutomationPeer::FromElement(deleteRemapKeys) })
236          {
237              automationPeer.RaiseNotificationEvent(
238                  Automation::Peers::AutomationNotificationKind::ActionCompleted,
239                  Automation::Peers::AutomationNotificationProcessing::ImportantMostRecent,
240                  GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_EVENT),
241                  L"KeyRemappingDeletedNotificationEvent" /* unique name for this notification category */);
242          }
243  
244          children.RemoveAt(rowIndex);
245          try
246          {
247              // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
248              parent.UpdateLayout();
249          }
250          catch (...)
251          {
252          }
253          singleKeyRemapBuffer.erase(singleKeyRemapBuffer.begin() + rowIndex);
254  
255          // delete the SingleKeyRemapControl objects so that they get destructed
256          keyboardRemapControlObjects.erase(keyboardRemapControlObjects.begin() + rowIndex);
257      });
258  
259      // To set the accessible name of the delete button
260      deleteRemapKeys.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
261  
262      // Add tooltip for delete button which would appear on hover
263      ToolTip deleteRemapKeysToolTip;
264      deleteRemapKeysToolTip.Content(box_value(GET_RESOURCE_STRING(IDS_DELETE_REMAPPING_BUTTON)));
265      ToolTipService::SetToolTip(deleteRemapKeys, deleteRemapKeysToolTip);
266      row.Children().Append(deleteRemapKeys);
267      try
268      {
269          // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
270          parent.UpdateLayout();
271      }
272      catch (...)
273      {
274      }
275  
276      // Set accessible names
277      UpdateAccessibleNames(keyboardRemapControlObjects.back()[0]->getSingleKeyRemapControl(), keyboardRemapControlObjects.back()[1]->getSingleKeyRemapControl(), deleteRemapKeys, static_cast<int>(keyboardRemapControlObjects.size()));
278  }
279  
280  // Function to return the stack panel element of the SingleKeyRemapControl. This is the externally visible UI element which can be used to add it to other layouts
281  StackPanel SingleKeyRemapControl::getSingleKeyRemapControl()
282  {
283      return singleKeyRemapControlLayout.as<StackPanel>();
284  }
285  
286  // Function to create the detect remap key UI window
287  void SingleKeyRemapControl::createDetectKeyWindow(winrt::Windows::Foundation::IInspectable const& sender, XamlRoot xamlRoot, KBMEditor::KeyboardManagerState& keyboardManagerState)
288  {
289      // ContentDialog for detecting remap key. This is the parent UI element.
290      ContentDialog detectRemapKeyBox;
291  
292      // ContentDialog requires manually setting the XamlRoot (https://learn.microsoft.com/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands)
293      detectRemapKeyBox.XamlRoot(xamlRoot);
294      detectRemapKeyBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPEKEY_TITLE)));
295  
296      // Get the linked text block for the "Type Key" button that was clicked
297      ComboBox linkedRemapDropDown = UIHelpers::GetSiblingElement(sender).as<ComboBox>();
298  
299      auto unregisterKeys = [&keyboardManagerState]() {
300          keyboardManagerState.ClearRegisteredKeyDelays();
301      };
302  
303      auto onPressEnter = [linkedRemapDropDown,
304                           detectRemapKeyBox,
305                           &keyboardManagerState,
306                           unregisterKeys] {
307          // Save the detected key in the linked text block
308          DWORD detectedKey = keyboardManagerState.GetDetectedSingleRemapKey();
309  
310          if (detectedKey != NULL)
311          {
312              std::vector<DWORD> keyCodeList = keyboardManagerState.keyboardMap.GetKeyCodeList();
313  
314              // Update the drop down list with the new language to ensure that the correct key is displayed
315              linkedRemapDropDown.ItemsSource(UIHelpers::ToBoxValue(keyboardManagerState.keyboardMap.GetKeyNameList()));
316              linkedRemapDropDown.SelectedValue(winrt::box_value(std::to_wstring(detectedKey)));
317          }
318  
319          // Hide the type key UI
320          detectRemapKeyBox.Hide();
321      };
322  
323      auto onReleaseEnter = [&keyboardManagerState,
324                             unregisterKeys] {
325          // Reset the keyboard manager UI state
326          keyboardManagerState.ResetUIState();
327          // Revert UI state back to Edit Keyboard window
328          keyboardManagerState.SetUIState(KBMEditor::KeyboardManagerUIState::EditKeyboardWindowActivated, EditKeyboardWindowHandle);
329          unregisterKeys();
330      };
331  
332      auto onAccept = [onPressEnter,
333                       onReleaseEnter] {
334          onPressEnter();
335          onReleaseEnter();
336      };
337  
338      // OK button
339      detectRemapKeyBox.DefaultButton(ContentDialogButton::Primary);
340      detectRemapKeyBox.PrimaryButtonText(GET_RESOURCE_STRING(IDS_OK_BUTTON));
341      detectRemapKeyBox.PrimaryButtonClick([onAccept](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) {
342          // Cancel default dialog events
343          args.Cancel(true);
344  
345          onAccept();
346      });
347  
348      // NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
349      keyboardManagerState.RegisterKeyDelay(
350          VK_RETURN,
351          std::bind(&KBMEditor::KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
352          [onPressEnter, detectRemapKeyBox](DWORD) {
353              detectRemapKeyBox.Dispatcher().RunAsync(
354                  Windows::UI::Core::CoreDispatcherPriority::Normal,
355                  [onPressEnter] {
356                      onPressEnter();
357                  });
358          },
359          [onReleaseEnter, detectRemapKeyBox](DWORD) {
360              detectRemapKeyBox.Dispatcher().RunAsync(
361                  Windows::UI::Core::CoreDispatcherPriority::Normal,
362                  [onReleaseEnter]() {
363                      onReleaseEnter();
364                  });
365          });
366  
367      auto onCancel = [&keyboardManagerState,
368                       detectRemapKeyBox,
369                       unregisterKeys] {
370          detectRemapKeyBox.Hide();
371  
372          // Reset the keyboard manager UI state
373          keyboardManagerState.ResetUIState();
374  
375          // Revert UI state back to Edit Keyboard window
376          keyboardManagerState.SetUIState(KBMEditor::KeyboardManagerUIState::EditKeyboardWindowActivated, EditKeyboardWindowHandle);
377          unregisterKeys();
378      };
379  
380      // Cancel button
381      detectRemapKeyBox.CloseButtonText(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON));
382      detectRemapKeyBox.CloseButtonClick([onCancel](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) {
383          // Cancel default dialog events
384          args.Cancel(true);
385  
386          onCancel();
387      });
388  
389      // NOTE: UnregisterKeys should never be called on the DelayThread, as it will re-enter the mutex. To avoid this it is run on the dispatcher thread
390      keyboardManagerState.RegisterKeyDelay(
391          VK_ESCAPE,
392          std::bind(&KBMEditor::KeyboardManagerState::SelectDetectedRemapKey, &keyboardManagerState, std::placeholders::_1),
393          [onCancel, detectRemapKeyBox](DWORD) {
394              detectRemapKeyBox.Dispatcher().RunAsync(
395                  Windows::UI::Core::CoreDispatcherPriority::Normal,
396                  [onCancel] {
397                      onCancel();
398                  });
399          },
400          nullptr);
401  
402      // StackPanel parent for the displayed text in the dialog
403      Windows::UI::Xaml::Controls::StackPanel stackPanel;
404      detectRemapKeyBox.Content(stackPanel);
405  
406      // Header textblock
407      TextBlock text;
408      text.Text(GET_RESOURCE_STRING(IDS_TYPEKEY_HEADER));
409      text.Margin({ 0, 0, 0, 10 });
410      stackPanel.Children().Append(text);
411  
412      // Target StackPanel to place the selected key
413      Windows::UI::Xaml::Controls::StackPanel keyStackPanel;
414      keyStackPanel.Orientation(Orientation::Horizontal);
415      stackPanel.Children().Append(keyStackPanel);
416  
417      TextBlock holdEscInfo;
418      holdEscInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDESC));
419      holdEscInfo.FontSize(12);
420      holdEscInfo.Margin({ 0, 20, 0, 0 });
421      stackPanel.Children().Append(holdEscInfo);
422  
423      TextBlock holdEnterInfo;
424      holdEnterInfo.Text(GET_RESOURCE_STRING(IDS_TYPE_HOLDENTER));
425      holdEnterInfo.FontSize(12);
426      holdEnterInfo.Margin({ 0, 0, 0, 0 });
427      stackPanel.Children().Append(holdEnterInfo);
428  
429      try
430      {
431          // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception.
432          stackPanel.UpdateLayout();
433      }
434      catch (...)
435      {
436      }
437  
438      // Configure the keyboardManagerState to store the UI information.
439      keyboardManagerState.ConfigureDetectSingleKeyRemapUI(keyStackPanel);
440  
441      // Show the dialog
442      detectRemapKeyBox.ShowAsync();
443  }