Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotkey control #2322

Merged
merged 11 commits into from
Jan 16, 2016
265 changes: 265 additions & 0 deletions MahApps.Metro/Controls/HotKeyBox.cs
Original file line number Diff line number Diff line change
@@ -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<HotKey>
{
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();
}

}
}
5 changes: 5 additions & 0 deletions MahApps.Metro/MahApps.Metro.NET45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
<Compile Include="Controls\Helper\MouseWheelChange.cs" />
<Compile Include="Controls\Helper\MouseWheelState.cs" />
<Compile Include="Controls\Helper\SliderHelper.cs" />
<Compile Include="Controls\HotKeyBox.cs" />
<Compile Include="Controls\Helper\VisibilityHelper.cs" />
<Compile Include="Controls\MetroAnimatedSingleRowTabControl.cs" />
<Compile Include="Controls\MetroAnimatedTabControl.cs" />
Expand Down Expand Up @@ -591,6 +592,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Themes\HotKeyBox.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Themes\MetroAnimatedSingleRowTabControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
Expand Down
5 changes: 5 additions & 0 deletions MahApps.Metro/MahApps.Metro.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
<Compile Include="Controls\Helper\TextBoxHelper.cs" />
<Compile Include="Controls\Helper\ToggleButtonHelper.cs" />
<Compile Include="Controls\Helper\VisibilityHelper.cs" />
<Compile Include="Controls\HotKeyBox.cs" />
<Compile Include="Controls\LayoutInvalidationCatcher.cs" />
<Compile Include="Controls\MetroAnimatedSingleRowTabControl.cs" />
<Compile Include="Controls\MetroAnimatedTabControl.cs" />
Expand Down Expand Up @@ -538,6 +539,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Themes\HotKeyBox.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Themes\MetroAnimatedSingleRowTabControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down
12 changes: 12 additions & 0 deletions MahApps.Metro/Native/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
10 changes: 8 additions & 2 deletions MahApps.Metro/Native/UnsafeNativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace MahApps.Metro.Native

/// <devdoc>http://msdn.microsoft.com/en-us/library/ms182161.aspx</devdoc>
[SuppressUnmanagedCodeSecurity]
internal static class UnsafeNativeMethods
internal static class UnsafeNativeMethods
{
/// <devdoc>http://msdn.microsoft.com/en-us/library/windows/desktop/aa969518%28v=vs.85%29.aspx</devdoc>
[DllImport("dwmapi", PreserveSig = false, CallingConvention = CallingConvention.Winapi)]
Expand Down Expand Up @@ -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);
}
}
1 change: 1 addition & 0 deletions MahApps.Metro/Themes/Generic.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Themes/TransitioningContentControl.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Themes/WindowButtonCommands.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Themes/WindowCommands.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Themes/HotKeyBox.xaml" />

<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Themes/Dialogs/BaseMetroDialog.xaml" />
</ResourceDictionary.MergedDictionaries>
Expand Down
Loading