diff --git a/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml
index 147c7d782a..6d3547dc33 100644
--- a/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml
+++ b/src/common/Common.UI.Controls/Controls/KeyVisual/KeyVisual.xaml
@@ -1,11 +1,11 @@
+ xmlns:commoncontrols="using:Microsoft.PowerToys.Common.UI.Controls">
-
+
-
-
-
-
-
-
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyCharPresenter.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyCharPresenter.xaml.cs
deleted file mode 100644
index 0457e715ee..0000000000
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyCharPresenter.xaml.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices.WindowsRuntime;
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Microsoft.UI.Xaml.Data;
-using Microsoft.UI.Xaml.Documents;
-using Microsoft.UI.Xaml.Input;
-using Microsoft.UI.Xaml.Media;
-
-namespace KeyboardManagerEditorUI.Controls;
-
-public sealed partial class KeyCharPresenter : Control
-{
- public KeyCharPresenter()
- {
- DefaultStyleKey = typeof(KeyCharPresenter);
- }
-
- public object Content
- {
- get => (object)GetValue(ContentProperty);
- set => SetValue(ContentProperty, value);
- }
-
- public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyCharPresenter), new PropertyMetadata(default(string)));
-}
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml
deleted file mode 100644
index e0f04391c7..0000000000
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml.cs
deleted file mode 100644
index a0ed4b0306..0000000000
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/KeyVisual/KeyVisual.xaml.cs
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using Microsoft.UI.Xaml;
-using Microsoft.UI.Xaml.Controls;
-using Windows.System;
-
-namespace KeyboardManagerEditorUI.Controls
-{
- [TemplatePart(Name = KeyPresenter, Type = typeof(KeyCharPresenter))]
- [TemplateVisualState(Name = NormalState, GroupName = "CommonStates")]
- [TemplateVisualState(Name = DisabledState, GroupName = "CommonStates")]
- [TemplateVisualState(Name = InvalidState, GroupName = "CommonStates")]
- [TemplateVisualState(Name = WarningState, GroupName = "CommonStates")]
- public sealed partial class KeyVisual : Control
- {
- private const string KeyPresenter = "KeyPresenter";
- private const string NormalState = "Normal";
- private const string DisabledState = "Disabled";
- private const string InvalidState = "Invalid";
- private const string WarningState = "Warning";
- private KeyCharPresenter _keyPresenter;
-
- public object Content
- {
- get => (object)GetValue(ContentProperty);
- set => SetValue(ContentProperty, value);
- }
-
- public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(KeyVisual), new PropertyMetadata(default(string), OnContentChanged));
-
- public State State
- {
- get => (State)GetValue(StateProperty);
- set => SetValue(StateProperty, value);
- }
-
- public static readonly DependencyProperty StateProperty = DependencyProperty.Register(nameof(State), typeof(State), typeof(KeyVisual), new PropertyMetadata(State.Normal, OnStateChanged));
-
- public bool RenderKeyAsGlyph
- {
- get => (bool)GetValue(RenderKeyAsGlyphProperty);
- set => SetValue(RenderKeyAsGlyphProperty, value);
- }
-
- public static readonly DependencyProperty RenderKeyAsGlyphProperty = DependencyProperty.Register(nameof(RenderKeyAsGlyph), typeof(bool), typeof(KeyVisual), new PropertyMetadata(false, OnContentChanged));
-
-#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
- public KeyVisual()
-#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
- {
- this.DefaultStyleKey = typeof(KeyVisual);
- }
-
- protected override void OnApplyTemplate()
- {
- IsEnabledChanged -= KeyVisual_IsEnabledChanged;
- _keyPresenter = (KeyCharPresenter)this.GetTemplateChild(KeyPresenter);
- Update();
- SetVisualStates();
- IsEnabledChanged += KeyVisual_IsEnabledChanged;
- base.OnApplyTemplate();
- }
-
- private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((KeyVisual)d).SetVisualStates();
- }
-
- private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((KeyVisual)d).SetVisualStates();
- }
-
- private void SetVisualStates()
- {
- if (this != null)
- {
- if (State == State.Error)
- {
- VisualStateManager.GoToState(this, InvalidState, true);
- }
- else if (State == State.Warning)
- {
- VisualStateManager.GoToState(this, WarningState, true);
- }
- else if (!IsEnabled)
- {
- VisualStateManager.GoToState(this, DisabledState, true);
- }
- else
- {
- VisualStateManager.GoToState(this, NormalState, true);
- }
- }
- }
-
- private void Update()
- {
- if (Content == null)
- {
- return;
- }
-
- if (Content is string key)
- {
- switch (key)
- {
- case "Copilot":
- _keyPresenter.Style = (Style)Application.Current.Resources["CopilotKeyCharPresenterStyle"];
- break;
-
- case "Office":
- _keyPresenter.Style = (Style)Application.Current.Resources["OfficeKeyCharPresenterStyle"];
- break;
-
- default:
- _keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
- break;
- }
-
- return;
- }
-
- if (Content is int keyCode)
- {
- VirtualKey virtualKey = (VirtualKey)keyCode;
- switch (virtualKey)
- {
- case VirtualKey.Enter:
- SetGlyphOrText("\uE751", virtualKey);
- break;
-
- case VirtualKey.Back:
- SetGlyphOrText("\uE750", virtualKey);
- break;
-
- case VirtualKey.Shift:
- case (VirtualKey)160: // Left Shift
- case (VirtualKey)161: // Right Shift
- SetGlyphOrText("\uE752", virtualKey);
- break;
-
- case VirtualKey.Up:
- _keyPresenter.Content = "\uE0E4";
- break;
-
- case VirtualKey.Down:
- _keyPresenter.Content = "\uE0E5";
- break;
-
- case VirtualKey.Left:
- _keyPresenter.Content = "\uE0E2";
- break;
-
- case VirtualKey.Right:
- _keyPresenter.Content = "\uE0E3";
- break;
-
- case VirtualKey.LeftWindows:
- case VirtualKey.RightWindows:
- _keyPresenter.Style = (Style)Application.Current.Resources["WindowsKeyCharPresenterStyle"];
- break;
- }
- }
- }
-
- private void SetGlyphOrText(string glyph, VirtualKey key)
- {
- if (RenderKeyAsGlyph)
- {
- _keyPresenter.Content = glyph;
- _keyPresenter.Style = (Style)Application.Current.Resources["GlyphKeyCharPresenterStyle"];
- }
- else
- {
- _keyPresenter.Content = key.ToString();
- _keyPresenter.Style = (Style)Application.Current.Resources["DefaultKeyCharPresenterStyle"];
- }
- }
-
- private void KeyVisual_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- SetVisualStates();
- }
- }
-
- public enum State
- {
- Normal,
- Error,
- Warning,
- }
-}
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml
index e3842618ce..35c3974a55 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml
@@ -1,13 +1,14 @@
-
+
@@ -95,7 +96,7 @@
-
-
@@ -302,27 +304,37 @@
FontSize="13"
GotFocus="UrlPathInput_GotFocus"
Header="URL to open"
- PlaceholderText="https://example.com" />
+ PlaceholderText="https://example.com"
+ TextChanged="UrlPathInput_TextChanged" />
-
+
+
+
+
+
+ PlaceholderText="C:\Program Files\..."
+ TextChanged="ProgramPathInput_TextChanged" />
-
+ Content="{ui:FontIcon Glyph=,
+ FontSize=16}"
+ Style="{StaticResource SubtleButtonStyle}" />
+
-
+
+
+
+
+
-
+ Content="{ui:FontIcon Glyph=,
+ FontSize=16}"
+ Style="{StaticResource SubtleButtonStyle}" />
+
- Normal
- Elevated
- Different user
+
+
+
- Show window
- Start another
- Do nothing
- Close
- End task
+
+
+
+
+
- Normal
- Hidden
- Minimized
- Maximized
+
+
+
+
@@ -401,7 +421,7 @@
Grid.Row="1"
Grid.ColumnSpan="3"
Margin="0,16,0,0"
- IsClosable="True"
+ IsClosable="False"
IsOpen="False"
Severity="Warning" />
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml.cs
index f299d37a04..2e4a9a471e 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml.cs
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Controls/UnifiedMappingControl.xaml.cs
@@ -38,6 +38,20 @@ namespace KeyboardManagerEditorUI.Controls
private KeyInputMode _currentInputMode = KeyInputMode.OriginalKeys;
+ // Dirty tracking: marks fields that have had content then were cleared
+ private bool _textContentDirty;
+ private bool _urlPathDirty;
+ private bool _programPathDirty;
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Raised whenever the validation state of the control changes (inputs filled/cleared).
+ ///
+ public event EventHandler? ValidationStateChanged;
+
#endregion
#region Enums
@@ -132,6 +146,9 @@ namespace KeyboardManagerEditorUI.Controls
TriggerKeys.ItemsSource = _triggerKeys;
ActionKeys.ItemsSource = _actionKeys;
+ _triggerKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
+ _actionKeys.CollectionChanged += (_, _) => RaiseValidationStateChanged();
+
this.Unloaded += UnifiedMappingControl_Unloaded;
}
@@ -226,6 +243,9 @@ namespace KeyboardManagerEditorUI.Controls
}
}
}
+
+ HideValidationMessage();
+ RaiseValidationStateChanged();
}
private void ActionKeyToggleBtn_Checked(object sender, RoutedEventArgs e)
@@ -309,18 +329,36 @@ namespace KeyboardManagerEditorUI.Controls
UncheckAllToggleButtons();
}
+ private void TextContentBox_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ _textContentDirty = true;
+ RaiseValidationStateChanged();
+ }
+
private void UrlPathInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
+ private void UrlPathInput_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ _urlPathDirty = true;
+ RaiseValidationStateChanged();
+ }
+
private void ProgramPathInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
UncheckAllToggleButtons();
}
+ private void ProgramPathInput_TextChanged(object sender, TextChangedEventArgs e)
+ {
+ _programPathDirty = true;
+ RaiseValidationStateChanged();
+ }
+
private void ProgramArgsInput_GotFocus(object sender, RoutedEventArgs e)
{
CleanupKeyboardHook();
@@ -351,6 +389,7 @@ namespace KeyboardManagerEditorUI.Controls
if (file != null)
{
ProgramPathInput.Text = file.Path;
+ RaiseValidationStateChanged();
}
}
@@ -506,6 +545,31 @@ namespace KeyboardManagerEditorUI.Controls
#endregion
+ #region Public API - Validation
+
+ ///
+ /// Returns true when all required fields for the current action type are filled.
+ ///
+ public bool IsInputComplete()
+ {
+ // Trigger keys are always required
+ if (_triggerKeys.Count == 0)
+ {
+ return false;
+ }
+
+ return CurrentActionType switch
+ {
+ ActionType.KeyOrShortcut => _actionKeys.Count > 0,
+ ActionType.Text => !string.IsNullOrWhiteSpace(TextContentBox?.Text),
+ ActionType.OpenUrl => !string.IsNullOrWhiteSpace(UrlPathInput?.Text),
+ ActionType.OpenApp => !string.IsNullOrWhiteSpace(ProgramPathInput?.Text),
+ _ => false,
+ };
+ }
+
+ #endregion
+
#region Public API - Setters
///
@@ -686,6 +750,52 @@ namespace KeyboardManagerEditorUI.Controls
KeyboardHookHelper.Instance.CleanupHook();
}
+ private void RaiseValidationStateChanged()
+ {
+ UpdateInlineValidation();
+ ValidationStateChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Shows or hides the inline validation InfoBar based on the current state.
+ /// Only shows errors for output fields that have been interacted with (had content then cleared).
+ ///
+ private void UpdateInlineValidation()
+ {
+ // Only validate the active action type's output field
+ switch (CurrentActionType)
+ {
+ case ActionType.Text:
+ if (TextContentBox != null && _textContentDirty && string.IsNullOrWhiteSpace(TextContentBox.Text))
+ {
+ ShowValidationErrorFromType(ValidationErrorType.EmptyTargetText);
+ return;
+ }
+
+ break;
+
+ case ActionType.OpenUrl:
+ if (UrlPathInput != null && _urlPathDirty && string.IsNullOrWhiteSpace(UrlPathInput.Text))
+ {
+ ShowValidationErrorFromType(ValidationErrorType.EmptyUrl);
+ return;
+ }
+
+ break;
+
+ case ActionType.OpenApp:
+ if (ProgramPathInput != null && _programPathDirty && string.IsNullOrWhiteSpace(ProgramPathInput.Text))
+ {
+ ShowValidationErrorFromType(ValidationErrorType.EmptyProgramPath);
+ return;
+ }
+
+ break;
+ }
+
+ HideValidationMessage();
+ }
+
///
/// Resets all inputs to their default state.
///
@@ -698,6 +808,11 @@ namespace KeyboardManagerEditorUI.Controls
_currentInputMode = KeyInputMode.OriginalKeys;
+ // Reset dirty tracking
+ _textContentDirty = false;
+ _urlPathDirty = false;
+ _programPathDirty = false;
+
// Hide any validation messages
HideValidationMessage();
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs
index c1adae929e..7177f497af 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationErrorType.cs
@@ -21,6 +21,8 @@ namespace KeyboardManagerEditorUI.Helpers
DuplicateMapping,
SelfMapping,
EmptyTargetText,
+ EmptyUrl,
+ EmptyProgramPath,
OneKeyMapping,
}
}
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs
index f6e1bcfe10..450c030449 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Helpers/ValidationHelper.cs
@@ -25,6 +25,8 @@ namespace KeyboardManagerEditorUI.Helpers
{ ValidationErrorType.DuplicateMapping, ("Duplicate Remapping", "This key or shortcut is already remapped.") },
{ ValidationErrorType.SelfMapping, ("Invalid Remapping", "A key or shortcut cannot be remapped to itself. Please choose a different target.") },
{ ValidationErrorType.EmptyTargetText, ("Missing Target Text", "Please enter the text to be inserted when the shortcut is pressed.") },
+ { ValidationErrorType.EmptyUrl, ("Missing URL", "Please enter the URL to open when the shortcut is pressed.") },
+ { ValidationErrorType.EmptyProgramPath, ("Missing Program Path", "Please enter the program path to launch when the shortcut is pressed.") },
{ ValidationErrorType.OneKeyMapping, ("Invalid Remapping", "A single key cannot be remapped to a Program or URL shortcut. Please choose a combination of keys.") },
};
@@ -163,6 +165,40 @@ namespace KeyboardManagerEditorUI.Helpers
return error;
}
+ public static ValidationErrorType ValidateUrlMapping(
+ List originalKeys,
+ string url,
+ bool isAppSpecific,
+ string appName,
+ KeyboardMappingService mappingService,
+ bool isEditMode = false,
+ Remapping? editingRemapping = null)
+ {
+ if (string.IsNullOrWhiteSpace(url))
+ {
+ return ValidationErrorType.EmptyUrl;
+ }
+
+ return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
+ }
+
+ public static ValidationErrorType ValidateAppMapping(
+ List originalKeys,
+ string programPath,
+ bool isAppSpecific,
+ string appName,
+ KeyboardMappingService mappingService,
+ bool isEditMode = false,
+ Remapping? editingRemapping = null)
+ {
+ if (string.IsNullOrWhiteSpace(programPath))
+ {
+ return ValidationErrorType.EmptyProgramPath;
+ }
+
+ return ValidateProgramOrUrlMapping(originalKeys, isAppSpecific, appName, mappingService, isEditMode, editingRemapping);
+ }
+
public static bool IsDuplicateMapping(List keys, bool isEditMode, KeyboardMappingService mappingService)
{
int upperLimit = isEditMode ? 1 : 0;
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorUI.csproj b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorUI.csproj
index 408df7c629..5a09a82766 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorUI.csproj
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorUI.csproj
@@ -64,6 +64,7 @@
+
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/App.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/App.xaml
index 1e1537351b..796c0d86ca 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/App.xaml
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/App.xaml
@@ -8,19 +8,15 @@
-
-
-
+
+
+
-
-
960
-
M12.001 2C17.5238 2 22.001 6.47715 22.001 12C22.001 17.5228 17.5238 22 12.001 22C6.47813 22 2.00098 17.5228 2.00098 12C2.00098 6.47715 6.47813 2 12.001 2ZM12.7813 7.46897L12.6972 7.39635C12.4362 7.2027 12.078 7.20031 11.8146 7.38918L11.7206 7.46897L11.648 7.55308C11.4544 7.81407 11.452 8.17229 11.6409 8.43568L11.7206 8.52963L14.4403 11.2493H7.75027L7.6485 11.2561C7.31571 11.3013 7.05227 11.5647 7.00712 11.8975L7.00027 11.9993L7.00712 12.1011C7.05227 12.4339 7.31571 12.6973 7.6485 12.7424L7.75027 12.7493H14.4403L11.72 15.4697L11.6474 15.5538C11.4295 15.8474 11.4536 16.264 11.7198 16.5303C11.9861 16.7967 12.4027 16.8209 12.6964 16.6032L12.7805 16.5306L16.782 12.5306L16.8547 12.4464C17.0484 12.1854 17.0508 11.8272 16.8619 11.5638L16.7821 11.4698L12.7813 7.46897L12.6972 7.39635L12.7813 7.46897Z
-
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml
index d4e02233bb..a8c8c6455c 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml
@@ -35,7 +35,7 @@
-
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml.cs
index 4d82edfc6b..9e9ccb6cf2 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml.cs
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/KeyboardManagerEditorXAML/MainWindow.xaml.cs
@@ -33,9 +33,6 @@ namespace KeyboardManagerEditorUI
SetTitleBar();
this.Activated += MainWindow_Activated;
this.Closed += MainWindow_Closed;
-
- // Set the default page
- // RootView.SelectedItem = RootView.MenuItems[0];
}
private void SetTitleBar()
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml
deleted file mode 100644
index 984454e639..0000000000
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml
+++ /dev/null
@@ -1,488 +0,0 @@
-
-
-
-
- 800
- 800
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml
new file mode 100644
index 0000000000..bb3c06dbcd
--- /dev/null
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml
@@ -0,0 +1,525 @@
+
+
+
+
+ 800
+ 800
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml.cs b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml.cs
similarity index 91%
rename from src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml.cs
rename to src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml.cs
index 53b1b3d73d..ae79ab5626 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/All.xaml.cs
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorUI/Pages/MainPage.xaml.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
@@ -24,7 +25,7 @@ namespace KeyboardManagerEditorUI.Pages
/// A consolidated page that displays all mappings from Remappings, Text, Programs, and URLs pages.
///
#pragma warning disable SA1124 // Do not use regions
- public sealed partial class All : Page, IDisposable
+ public sealed partial class MainPage : Page, IDisposable, INotifyPropertyChanged
{
private KeyboardMappingService? _mappingService;
private bool _disposed;
@@ -33,6 +34,26 @@ namespace KeyboardManagerEditorUI.Pages
private bool _isEditMode;
private EditingItem? _editingItem;
+ private string _mappingState = "Empty";
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// Gets the current mapping state for the SwitchPresenter: "HasMappings" or "Empty".
+ ///
+ public string MappingState
+ {
+ get => _mappingState;
+ private set
+ {
+ if (_mappingState != value)
+ {
+ _mappingState = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MappingState)));
+ }
+ }
+ }
+
public ObservableCollection RemappingList { get; } = new ObservableCollection();
public ObservableCollection TextMappings { get; } = new ObservableCollection();
@@ -68,7 +89,7 @@ namespace KeyboardManagerEditorUI.Pages
public bool IsAllApps { get; set; } = true;
}
- public All()
+ public MainPage()
{
this.InitializeComponent();
@@ -79,7 +100,7 @@ namespace KeyboardManagerEditorUI.Pages
}
catch (Exception ex)
{
- Logger.LogError("Failed to initialize KeyboardMappingService in All page: " + ex.Message);
+ Logger.LogError("Failed to initialize KeyboardMappingService in MainPage page: " + ex.Message);
}
this.Unloaded += All_Unloaded;
@@ -215,11 +236,16 @@ namespace KeyboardManagerEditorUI.Pages
// Hook up the primary button click handler
RemappingDialog.PrimaryButtonClick += RemappingDialog_PrimaryButtonClick;
+ // Hook up real-time validation
+ UnifiedMappingControl.ValidationStateChanged += UnifiedMappingControl_ValidationStateChanged;
+ RemappingDialog.IsPrimaryButtonEnabled = UnifiedMappingControl.IsInputComplete();
+
// Show the dialog
await RemappingDialog.ShowAsync();
- // Unhook the handler
+ // Unhook the handlers
RemappingDialog.PrimaryButtonClick -= RemappingDialog_PrimaryButtonClick;
+ UnifiedMappingControl.ValidationStateChanged -= UnifiedMappingControl_ValidationStateChanged;
// Reset edit mode
_isEditMode = false;
@@ -229,6 +255,36 @@ namespace KeyboardManagerEditorUI.Pages
KeyboardHookHelper.Instance.CleanupHook();
}
+ private void UnifiedMappingControl_ValidationStateChanged(object? sender, EventArgs e)
+ {
+ if (!UnifiedMappingControl.IsInputComplete())
+ {
+ RemappingDialog.IsPrimaryButtonEnabled = false;
+ return;
+ }
+
+ // Run full validation (self-mapping, illegal shortcuts, etc.) when inputs are complete
+ if (_mappingService != null)
+ {
+ var actionType = UnifiedMappingControl.CurrentActionType;
+ List triggerKeys = UnifiedMappingControl.GetTriggerKeys();
+
+ if (triggerKeys != null && triggerKeys.Count > 0)
+ {
+ ValidationErrorType error = ValidateMapping(actionType, triggerKeys);
+ if (error != ValidationErrorType.NoError)
+ {
+ UnifiedMappingControl.ShowValidationErrorFromType(error);
+ RemappingDialog.IsPrimaryButtonEnabled = false;
+ return;
+ }
+ }
+ }
+
+ UnifiedMappingControl.HideValidationMessage();
+ RemappingDialog.IsPrimaryButtonEnabled = true;
+ }
+
#endregion
#region Save Logic
@@ -348,9 +404,20 @@ namespace KeyboardManagerEditorUI.Pages
_isEditMode);
case UnifiedMappingControl.ActionType.OpenUrl:
- case UnifiedMappingControl.ActionType.OpenApp:
- return ValidationHelper.ValidateProgramOrUrlMapping(
+ string urlContent = UnifiedMappingControl.GetUrl();
+ return ValidationHelper.ValidateUrlMapping(
triggerKeys,
+ urlContent,
+ isAppSpecific,
+ appName,
+ _mappingService!,
+ _isEditMode);
+
+ case UnifiedMappingControl.ActionType.OpenApp:
+ string programPath = UnifiedMappingControl.GetProgramPath();
+ return ValidationHelper.ValidateAppMapping(
+ triggerKeys,
+ programPath,
isAppSpecific,
appName,
_mappingService!,
@@ -620,6 +687,7 @@ namespace KeyboardManagerEditorUI.Pages
Logger.LogWarning($"Failed to delete remapping: {string.Join("+", remapping.Shortcut)}");
}
+ UpdateHasAnyMappings();
break;
default:
@@ -748,6 +816,16 @@ namespace KeyboardManagerEditorUI.Pages
LoadTextMappings();
LoadProgramShortcuts();
LoadUrlShortcuts();
+ UpdateHasAnyMappings();
+ }
+
+ private void UpdateHasAnyMappings()
+ {
+ bool hasAny = RemappingList.Count > 0
+ || TextMappings.Count > 0
+ || ProgramShortcuts.Count > 0
+ || UrlShortcuts.Count > 0;
+ MappingState = hasAny ? "HasMappings" : "Empty";
}
private void LoadRemappings()