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 }