Skip to content

Commit

Permalink
Merge pull request #2322 from thoemmi/hotkey
Browse files Browse the repository at this point in the history
[RFC] Hotkey control
  • Loading branch information
punker76 committed Jan 16, 2016
2 parents b22cfe2 + cf2b93d commit 26b8abd
Show file tree
Hide file tree
Showing 13 changed files with 448 additions and 17 deletions.
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

0 comments on commit 26b8abd

Please sign in to comment.