diff --git a/MahApps.Metro/Controls/HotKeyBox.cs b/MahApps.Metro/Controls/HotKeyBox.cs new file mode 100644 index 0000000000..641c2df9e0 --- /dev/null +++ b/MahApps.Metro/Controls/HotKeyBox.cs @@ -0,0 +1,265 @@ +using System; +using System.Text; +using System.Windows; +using System.Windows.Input; +using System.Windows.Interop; +using System.Windows.Controls; +using MahApps.Metro.Native; + +namespace MahApps.Metro.Controls { + [TemplatePart(Name = PART_TextBox, Type = typeof(TextBox))] + public class HotKeyBox : Control + { + private const string PART_TextBox = "PART_TextBox"; + + public static readonly DependencyProperty HotKeyProperty = DependencyProperty.Register( + "HotKey", typeof(HotKey), typeof(HotKeyBox), + new FrameworkPropertyMetadata(default(HotKey), OnHotKeyChanged) { BindsTwoWayByDefault = true }); + + public HotKey HotKey + { + get { return (HotKey) GetValue(HotKeyProperty); } + set { SetValue(HotKeyProperty, value); } + } + + private static void OnHotKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var ctrl = (HotKeyBox)d; + ctrl.UpdateText(); + } + + public static readonly DependencyProperty AreModifierKeysRequiredProperty = DependencyProperty.Register( + "AreModifierKeysRequired", typeof(bool), typeof(HotKeyBox), new PropertyMetadata(default(bool))); + + public bool AreModifierKeysRequired + { + get { return (bool) GetValue(AreModifierKeysRequiredProperty); } + set { SetValue(AreModifierKeysRequiredProperty, value); } + } + + public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register( + "Watermark", typeof(string), typeof(HotKeyBox), new PropertyMetadata(default(string))); + + public string Watermark + { + get { return (string) GetValue(WatermarkProperty); } + set { SetValue(WatermarkProperty, value); } + } + + private static readonly DependencyPropertyKey TextPropertyKey = DependencyProperty.RegisterReadOnly( + "Text", typeof(string), typeof(HotKeyBox), new PropertyMetadata(default(string))); + + public static readonly DependencyProperty TextProperty = TextPropertyKey.DependencyProperty; + + public string Text + { + get { return (string) GetValue(TextProperty); } + private set { SetValue(TextPropertyKey, value); } + } + + private TextBox _textBox; + + static HotKeyBox() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(HotKeyBox), new FrameworkPropertyMetadata(typeof(HotKeyBox))); + } + + public override void OnApplyTemplate() + { + if (_textBox != null) + { + _textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown2; + _textBox.GotFocus -= TextBoxOnGotFocus; + _textBox.LostFocus -= TextBoxOnLostFocus; + _textBox.TextChanged -= TextBoxOnTextChanged; + } + + base.OnApplyTemplate(); + + _textBox = Template.FindName(PART_TextBox, this) as TextBox; + if (_textBox != null) + { + _textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown2; + _textBox.GotFocus += TextBoxOnGotFocus; + _textBox.LostFocus += TextBoxOnLostFocus; + _textBox.TextChanged += TextBoxOnTextChanged; + UpdateText(); + } + } + + private void TextBoxOnTextChanged(object sender, TextChangedEventArgs args) + { + _textBox.SelectionStart = _textBox.Text.Length; + } + + private void TextBoxOnGotFocus(object sender, RoutedEventArgs routedEventArgs) + { + ComponentDispatcher.ThreadPreprocessMessage += ComponentDispatcherOnThreadPreprocessMessage; + } + + private void ComponentDispatcherOnThreadPreprocessMessage(ref MSG msg, ref bool handled) + { + if (msg.message == Constants.WM_HOTKEY) + { + // swallow all hotkeys, so our control can catch the key strokes + handled = true; + } + } + + private void TextBoxOnLostFocus(object sender, RoutedEventArgs routedEventArgs) + { + ComponentDispatcher.ThreadPreprocessMessage -= ComponentDispatcherOnThreadPreprocessMessage; + } + + private void TextBoxOnPreviewKeyDown2(object sender, KeyEventArgs e) + { + var key = e.Key == Key.System ? e.SystemKey : e.Key; + switch (key) + { + case Key.Tab: + case Key.LeftShift: + case Key.RightShift: + case Key.LeftCtrl: + case Key.RightCtrl: + case Key.LeftAlt: + case Key.RightAlt: + case Key.RWin: + case Key.LWin: + return; + } + + e.Handled = true; + + var currentModifierKeys = GetCurrentModifierKeys(); + if (currentModifierKeys == ModifierKeys.None && key == Key.Back) + { + HotKey = null; + } + else if (currentModifierKeys != ModifierKeys.None || !AreModifierKeysRequired) + { + HotKey = new HotKey(key, currentModifierKeys); + } + + UpdateText(); + } + + private static ModifierKeys GetCurrentModifierKeys() + { + var modifier = ModifierKeys.None; + if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) + { + modifier |= ModifierKeys.Control; + } + if (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) + { + modifier |= ModifierKeys.Alt; + } + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + { + modifier |= ModifierKeys.Shift; + } + if (Keyboard.IsKeyDown(Key.LWin) || Keyboard.IsKeyDown(Key.RWin)) + { + modifier |= ModifierKeys.Windows; + } + return modifier; + } + + private void UpdateText() + { + var hotkey = HotKey; + Text = hotkey == null || hotkey.Key == Key.None ? string.Empty : hotkey.ToString(); + } + } + + public class HotKey : IEquatable + { + private readonly Key _key; + private readonly ModifierKeys _modifierKeys; + + public HotKey(Key key, ModifierKeys modifierKeys) + { + _key = key; + _modifierKeys = modifierKeys; + } + + public Key Key + { + get { return _key; } + } + + public ModifierKeys ModifierKeys + { + get { return _modifierKeys; } + } + + public override bool Equals(object obj) + { + return obj is HotKey && Equals((HotKey) obj); + } + + public override int GetHashCode() + { + unchecked + { + return ((int) _key*397) ^ (int) _modifierKeys; + } + } + + public bool Equals(HotKey other) + { + return _key == other._key && _modifierKeys == other._modifierKeys; + } + + public override string ToString() + { + var sb = new StringBuilder(); + if ((_modifierKeys & ModifierKeys.Alt) == ModifierKeys.Alt) + { + sb.Append(GetLocalizedKeyStringUnsafe(Constants.VK_MENU)); + sb.Append("+"); + } + if ((_modifierKeys & ModifierKeys.Control) == ModifierKeys.Control) + { + sb.Append(GetLocalizedKeyStringUnsafe(Constants.VK_CONTROL)); + sb.Append("+"); + } + if ((_modifierKeys & ModifierKeys.Shift) == ModifierKeys.Shift) + { + sb.Append(GetLocalizedKeyStringUnsafe(Constants.VK_SHIFT)); + sb.Append("+"); + } + if ((_modifierKeys & ModifierKeys.Windows) == ModifierKeys.Windows) + { + sb.Append("WINDOWS+"); + } + sb.Append(GetLocalizedKeyStringUnsafe(KeyInterop.VirtualKeyFromKey(_key)).ToUpper()); + return sb.ToString(); + } + + private static string GetLocalizedKeyStringUnsafe(int key) + { + // strip any modifier keys + long keyCode = key & 0xffff; + + var sb = new StringBuilder(256); + + long scanCode = UnsafeNativeMethods.MapVirtualKey((uint)keyCode, Constants.MAPVK_VK_TO_VSC); + + // shift the scancode to the high word + scanCode = (scanCode << 16); + if (keyCode == 45 || + keyCode == 46 || + keyCode == 144 || + (33 <= keyCode && keyCode <= 40)) + { + // add the extended key flag + scanCode |= 0x1000000; + } + + UnsafeNativeMethods.GetKeyNameText((int)scanCode, sb, 256); + return sb.ToString(); + } + + } +} \ No newline at end of file diff --git a/MahApps.Metro/MahApps.Metro.NET45.csproj b/MahApps.Metro/MahApps.Metro.NET45.csproj index 2b4829ddad..1113b854d3 100644 --- a/MahApps.Metro/MahApps.Metro.NET45.csproj +++ b/MahApps.Metro/MahApps.Metro.NET45.csproj @@ -169,6 +169,7 @@ + @@ -591,6 +592,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + MSBuild:Compile Designer diff --git a/MahApps.Metro/MahApps.Metro.csproj b/MahApps.Metro/MahApps.Metro.csproj index 617233c8f8..89dab6f745 100644 --- a/MahApps.Metro/MahApps.Metro.csproj +++ b/MahApps.Metro/MahApps.Metro.csproj @@ -135,6 +135,7 @@ + @@ -538,6 +539,10 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + Designer MSBuild:Compile diff --git a/MahApps.Metro/Native/Constants.cs b/MahApps.Metro/Native/Constants.cs index 68d00e4fe8..258911fa7c 100644 --- a/MahApps.Metro/Native/Constants.cs +++ b/MahApps.Metro/Native/Constants.cs @@ -167,5 +167,17 @@ public enum RedrawWindowFlags : uint public const int WM_MOVE = 0x0003; public const uint TOPMOST_FLAGS = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING; + + public const int WM_HOTKEY = 0x0312; + public const int VK_SHIFT = 0x10; + public const int VK_CONTROL = 0x11; + public const int VK_MENU = 0x12; + + /* used by UnsafeNativeMethods.MapVirtualKey */ + public const uint MAPVK_VK_TO_VSC = 0x00; + public const uint MAPVK_VSC_TO_VK = 0x01; + public const uint MAPVK_VK_TO_CHAR = 0x02; + public const uint MAPVK_VSC_TO_VK_EX = 0x03; + public const uint MAPVK_VK_TO_VSC_EX = 0x04; } } diff --git a/MahApps.Metro/Native/UnsafeNativeMethods.cs b/MahApps.Metro/Native/UnsafeNativeMethods.cs index 24970f4f92..6447004658 100644 --- a/MahApps.Metro/Native/UnsafeNativeMethods.cs +++ b/MahApps.Metro/Native/UnsafeNativeMethods.cs @@ -12,7 +12,7 @@ namespace MahApps.Metro.Native /// http://msdn.microsoft.com/en-us/library/ms182161.aspx [SuppressUnmanagedCodeSecurity] - internal static class UnsafeNativeMethods + internal static class UnsafeNativeMethods { /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa969518%28v=vs.85%29.aspx [DllImport("dwmapi", PreserveSig = false, CallingConvention = CallingConvention.Winapi)] @@ -227,6 +227,12 @@ internal static Point GetPoint(IntPtr ptr) [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] [DllImport("user32.dll", SetLastError = true)] - internal static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, Constants.RedrawWindowFlags flags); + internal static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, Constants.RedrawWindowFlags flags); + + [DllImport("user32.dll")] + internal static extern int MapVirtualKey(uint uCode, uint uMapType); + + [DllImport("user32.dll", CharSet = CharSet.Auto)] + internal static extern int GetKeyNameText(int lParam, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder str, int size); } } diff --git a/MahApps.Metro/Themes/Generic.xaml b/MahApps.Metro/Themes/Generic.xaml index 3021cd2b7e..ae8a638ac9 100644 --- a/MahApps.Metro/Themes/Generic.xaml +++ b/MahApps.Metro/Themes/Generic.xaml @@ -30,6 +30,7 @@ + diff --git a/MahApps.Metro/Themes/HotKeyBox.xaml b/MahApps.Metro/Themes/HotKeyBox.xaml new file mode 100644 index 0000000000..3f1d947526 --- /dev/null +++ b/MahApps.Metro/Themes/HotKeyBox.xaml @@ -0,0 +1,48 @@ + + + \ No newline at end of file diff --git a/samples/MetroDemo/ExampleViews/TextExamples.xaml b/samples/MetroDemo/ExampleViews/TextExamples.xaml index 0501350227..7e299808ba 100644 --- a/samples/MetroDemo/ExampleViews/TextExamples.xaml +++ b/samples/MetroDemo/ExampleViews/TextExamples.xaml @@ -29,8 +29,12 @@ + + + + - - - + + + -