├── WpfKb.TestClient
├── Resources
│ ├── AbstractBlue.png
│ ├── Images.xaml
│ └── Styles.xaml
├── App.xaml.cs
├── MainWindow.xaml.cs
├── AssemblyInfo.cs
├── App.xaml
├── WpfKb.TestClient.csproj
└── MainWindow.xaml
├── README.md
├── WpfKb
├── LogicalKeys
│ ├── EmptyKey.cs
│ ├── ILogicalKey.cs
│ ├── CaseSensitiveKey.cs
│ ├── ShiftSensitiveKey.cs
│ ├── NumLockSensitiveKey.cs
│ ├── ModifierKeyBase.cs
│ ├── TogglingModifierKey.cs
│ ├── StringKey.cs
│ ├── VirtualKey.cs
│ ├── InstantaneousModifierKey.cs
│ ├── MultiCharacterKey.cs
│ ├── ChordKey.cs
│ └── LogicalKeyBase.cs
├── Input
│ ├── Native
│ │ ├── HARDWAREINPUT.cs
│ │ ├── MOUSEKEYBDHARDWAREINPUT.cs
│ │ ├── INPUT.cs
│ │ ├── KEYBDINPUT.cs
│ │ └── MOUSEINPUT.cs
│ ├── VirtualKeyCode.cs
│ └── InputSimulator.cs
├── Controls
│ ├── OnScreenKeyEventArgs.cs
│ ├── OnScreenKeypad.cs
│ ├── FloatingTouchScreenKeyboard.xaml
│ ├── OnScreenKeyboardSection.cs
│ ├── UniformOnScreenKeyboard.cs
│ ├── FloatingTouchScreenKeyboard.xaml.cs
│ ├── OnScreenKeyboard.cs
│ ├── OnScreenKey.cs
│ └── OnScreenWebKeyboard.cs
├── WpfKb.csproj
├── Styles
│ └── OnScreenKeyoard.xaml
└── Behaviors
│ └── AutoHideBehavior.cs
├── LICENSE
├── WpfKb.sln
└── .gitignore
/WpfKb.TestClient/Resources/AbstractBlue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/imasm/wpfkb/HEAD/WpfKb.TestClient/Resources/AbstractBlue.png
--------------------------------------------------------------------------------
/WpfKb.TestClient/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace WpfKb.TestClient
4 | {
5 | public partial class App : Application
6 | {
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WPF Touch Screen Keyboard
2 |
3 | Fork of https://wpfkb.codeplex.com
4 |
5 | ## Project Description
6 | WPF Touch Screen Keyboard is a re-usable control and toolkit for anyone developing a touch screen application in WPF.
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WpfKb.TestClient/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace WpfKb.TestClient
4 | {
5 | public partial class MainWindow : Window
6 | {
7 | public MainWindow()
8 | {
9 | InitializeComponent();
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/EmptyKey.cs:
--------------------------------------------------------------------------------
1 | namespace WpfKb.LogicalKeys
2 | {
3 | public class EmptyKey : LogicalKeyBase
4 | {
5 |
6 | public EmptyKey(string displayName)
7 | {
8 | DisplayName = displayName;
9 | }
10 |
11 | public EmptyKey()
12 | {
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/ILogicalKey.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 |
3 | namespace WpfKb.LogicalKeys
4 | {
5 | public interface ILogicalKey : INotifyPropertyChanged
6 | {
7 | string DisplayName { get; }
8 | void Press();
9 | event LogicalKeyPressedEventHandler LogicalKeyPressed;
10 | }
11 | }
--------------------------------------------------------------------------------
/WpfKb.TestClient/Resources/Images.xaml:
--------------------------------------------------------------------------------
1 |
3 | pack://application:,,,/WpfKb.TestClient;component/Resources/AbstractBlue.png
4 |
--------------------------------------------------------------------------------
/WpfKb/Input/Native/HARDWAREINPUT.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | // ReSharper disable InconsistentNaming
3 |
4 | namespace WpfKb.Input.Native
5 | {
6 | [StructLayout(LayoutKind.Sequential)]
7 | internal struct HARDWAREINPUT
8 | {
9 | internal int Msg;
10 | internal short ParamL;
11 | internal short ParamH;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/CaseSensitiveKey.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using WpfKb.Input;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class CaseSensitiveKey : MultiCharacterKey
7 | {
8 | public CaseSensitiveKey(VirtualKeyCode keyCode, IList keyDisplays)
9 | : base(keyCode, keyDisplays)
10 | {
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/ShiftSensitiveKey.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using WpfKb.Input;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class ShiftSensitiveKey : MultiCharacterKey
7 | {
8 | public ShiftSensitiveKey(VirtualKeyCode keyCode, IList keyDisplays)
9 | : base(keyCode, keyDisplays)
10 | {
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/NumLockSensitiveKey.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using WpfKb.Input;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class NumLockSensitiveKey : MultiCharacterKey
7 | {
8 | public NumLockSensitiveKey(VirtualKeyCode keyCode, IList keyDisplays)
9 | : base(keyCode, keyDisplays)
10 | {
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/WpfKb/Input/Native/MOUSEKEYBDHARDWAREINPUT.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 |
3 | // ReSharper disable InconsistentNaming
4 | namespace WpfKb.Input.Native
5 | {
6 | [StructLayout(LayoutKind.Explicit)]
7 | internal struct MOUSEKEYBDHARDWAREINPUT
8 | {
9 | [FieldOffset(0)]
10 | internal MOUSEINPUT Mouse;
11 |
12 | [FieldOffset(0)]
13 | internal KEYBDINPUT Keyboard;
14 |
15 | [FieldOffset(0)]
16 | internal HARDWAREINPUT Hardware;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/WpfKb/Input/Native/INPUT.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | // ReSharper disable InconsistentNaming
3 |
4 | namespace WpfKb.Input.Native
5 | {
6 | [StructLayout(LayoutKind.Sequential)]
7 | internal struct INPUT
8 | {
9 | internal InputType Type;
10 | internal MOUSEKEYBDHARDWAREINPUT Data;
11 | internal static int Size => Marshal.SizeOf(typeof(INPUT));
12 | }
13 |
14 | internal enum InputType : uint
15 | {
16 | MOUSE = 0,
17 | KEYBOARD = 1,
18 | HARDWARE = 2
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenKeyEventArgs.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace WpfKb.Controls
4 | {
5 | public class OnScreenKeyEventArgs : RoutedEventArgs
6 | {
7 | public OnScreenKey OnScreenKey { get; private set; }
8 |
9 | public OnScreenKeyEventArgs(RoutedEvent routedEvent, OnScreenKey onScreenKey)
10 | : base(routedEvent)
11 | {
12 | OnScreenKey = onScreenKey;
13 | }
14 | }
15 |
16 | public delegate void OnScreenKeyEventHandler(DependencyObject sender, OnScreenKeyEventArgs e);
17 | }
--------------------------------------------------------------------------------
/WpfKb.TestClient/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/WpfKb/Input/Native/KEYBDINPUT.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 |
4 | // ReSharper disable InconsistentNaming
5 | namespace WpfKb.Input.Native
6 | {
7 |
8 | [StructLayout(LayoutKind.Sequential)]
9 | internal struct KEYBDINPUT
10 | {
11 | internal VirtualKeyCode Vk; // ushort
12 | internal ushort Scan; //ScanCodeShort
13 | internal KEYEVENTF Flags;
14 | internal uint Time;
15 | internal UIntPtr ExtraInfo;
16 | }
17 |
18 | [Flags]
19 | internal enum KEYEVENTF : uint
20 | {
21 | EXTENDEDKEY = 0x0001,
22 | KEYUP = 0x0002,
23 | SCANCODE = 0x0008,
24 | UNICODE = 0x0004
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/ModifierKeyBase.cs:
--------------------------------------------------------------------------------
1 | using WpfKb.Input;
2 |
3 | namespace WpfKb.LogicalKeys
4 | {
5 | public abstract class ModifierKeyBase : VirtualKey
6 | {
7 | private bool _isInEffect;
8 |
9 | public bool IsInEffect
10 | {
11 | get { return _isInEffect; }
12 | set
13 | {
14 | if (value != _isInEffect)
15 | {
16 | _isInEffect = value;
17 | OnPropertyChanged("IsInEffect");
18 | }
19 | }
20 | }
21 |
22 | protected ModifierKeyBase(VirtualKeyCode keyCode) :
23 | base(keyCode)
24 | {
25 | }
26 |
27 | public abstract void SynchroniseKeyState();
28 | }
29 | }
--------------------------------------------------------------------------------
/WpfKb.TestClient/App.xaml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/WpfKb/Input/Native/MOUSEINPUT.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | // ReSharper disable InconsistentNaming
4 |
5 | namespace WpfKb.Input.Native
6 | {
7 | [StructLayout(LayoutKind.Sequential)]
8 | internal struct MOUSEINPUT
9 | {
10 | internal int X;
11 | internal int Y;
12 | internal int MouseData;
13 | internal MOUSEEVENTF Flags;
14 | internal uint Time;
15 | internal UIntPtr ExtraInfo;
16 | }
17 |
18 | [Flags]
19 | internal enum MOUSEEVENTF : uint
20 | {
21 | ABSOLUTE = 0x8000,
22 | HWHEEL = 0x01000,
23 | MOVE = 0x0001,
24 | MOVE_NOCOALESCE = 0x2000,
25 | LEFTDOWN = 0x0002,
26 | LEFTUP = 0x0004,
27 | RIGHTDOWN = 0x0008,
28 | RIGHTUP = 0x0010,
29 | MIDDLEDOWN = 0x0020,
30 | MIDDLEUP = 0x0040,
31 | VIRTUALDESK = 0x4000,
32 | WHEEL = 0x0800,
33 | XDOWN = 0x0080,
34 | XUP = 0x0100
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/TogglingModifierKey.cs:
--------------------------------------------------------------------------------
1 | using WpfKb.Input;
2 |
3 | namespace WpfKb.LogicalKeys
4 | {
5 | public class TogglingModifierKey : ModifierKeyBase
6 | {
7 | public TogglingModifierKey(string displayName, VirtualKeyCode keyCode) :
8 | base(keyCode)
9 | {
10 | DisplayName = displayName;
11 | }
12 |
13 | public override void Press()
14 | {
15 | // This is a bit tricky because we can only get the state of a toggling key after the input has been
16 | // read off the MessagePump. Ergo if we make that assumption that in the time it takes to run this method
17 | // we will be toggling the state of the key, set IsInEffect to the new state and then press the key.
18 | IsInEffect = !InputSimulator.IsTogglingKeyInEffect(KeyCode);
19 | base.Press();
20 | }
21 |
22 | public override void SynchroniseKeyState()
23 | {
24 | IsInEffect = InputSimulator.IsTogglingKeyInEffect(KeyCode);
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/StringKey.cs:
--------------------------------------------------------------------------------
1 |
2 | using WpfKb.Input;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class StringKey : LogicalKeyBase
7 | {
8 | private string _stringToSimulate;
9 |
10 | public virtual string StringToSimulate
11 | {
12 | get { return _stringToSimulate; }
13 | set
14 | {
15 | if (value != _stringToSimulate)
16 | {
17 | _stringToSimulate = value;
18 | OnPropertyChanged("StringToSimulate");
19 | }
20 | }
21 | }
22 |
23 | public StringKey(string displayName, string stringToSimulate)
24 | {
25 | DisplayName = displayName;
26 | _stringToSimulate = stringToSimulate;
27 | }
28 |
29 | public StringKey()
30 | {
31 | }
32 |
33 | public override void Press()
34 | {
35 | InputSimulator.SimulateTextEntry(_stringToSimulate);
36 | base.Press();
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/VirtualKey.cs:
--------------------------------------------------------------------------------
1 | using WpfKb.Input;
2 |
3 | namespace WpfKb.LogicalKeys
4 | {
5 | public class VirtualKey : LogicalKeyBase
6 | {
7 | private VirtualKeyCode _keyCode;
8 |
9 | public virtual VirtualKeyCode KeyCode
10 | {
11 | get { return _keyCode; }
12 | set
13 | {
14 | if (value != _keyCode)
15 | {
16 | _keyCode = value;
17 | OnPropertyChanged("KeyCode");
18 | }
19 | }
20 | }
21 |
22 | public VirtualKey(VirtualKeyCode keyCode, string displayName)
23 | {
24 | DisplayName = displayName;
25 | KeyCode = keyCode;
26 | }
27 |
28 | public VirtualKey(VirtualKeyCode keyCode)
29 | {
30 | KeyCode = keyCode;
31 | }
32 |
33 | public VirtualKey()
34 | {
35 | }
36 |
37 | public override void Press()
38 | {
39 | InputSimulator.SimulateKeyPress(_keyCode);
40 | base.Press();
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Ivan Masmitjà
4 | Copyright (c) 2009 - 2010 michaelnoonan
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/InstantaneousModifierKey.cs:
--------------------------------------------------------------------------------
1 | using WpfKb.Input;
2 |
3 | namespace WpfKb.LogicalKeys
4 | {
5 | public class InstantaneousModifierKey : ModifierKeyBase
6 | {
7 | public InstantaneousModifierKey(string displayName, VirtualKeyCode keyCode) :
8 | base(keyCode)
9 | {
10 | DisplayName = displayName;
11 | }
12 |
13 | public override void Press()
14 | {
15 | if (IsInEffect) InputSimulator.SimulateKeyUp(KeyCode);
16 | else InputSimulator.SimulateKeyDown(KeyCode);
17 |
18 | // We need to use IsKeyDownAsync here so we will know exactly what state the key will be in
19 | // once the active windows read the input from the MessagePump. IsKeyDown will only report
20 | // the correct value after the input has been read from the MessagePump and will not be correct
21 | // by the time we set IsInEffect.
22 | IsInEffect = InputSimulator.IsKeyDownAsync(KeyCode);
23 | OnKeyPressed();
24 | }
25 |
26 | public override void SynchroniseKeyState()
27 | {
28 | IsInEffect = InputSimulator.IsKeyDownAsync(KeyCode);
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/WpfKb/WpfKb.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0-windows
5 | true
6 | michaelnoonan, imasmitja
7 | WpfKb
8 | 2.0.0.0
9 | $(AssemblyVersion)
10 | $(AssemblyVersion)
11 | An on-screen or touch screen keyboard control and toolkit for WPF.
12 | https://github.com/imasm/wpfkb
13 | Copyright © 2017 Ivan Masmitjà; Copyright © 2009 - 2010 michaelnoonan
14 | IMasm.$(AssemblyName)
15 | README.md
16 | https://github.com/imasm/wpfkb
17 | WPF;keyboard
18 |
19 |
20 |
21 |
22 | True
23 | \
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/MultiCharacterKey.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using WpfKb.Input;
4 |
5 | namespace WpfKb.LogicalKeys
6 | {
7 | public class MultiCharacterKey : VirtualKey
8 | {
9 | private int _selectedIndex;
10 | public IList KeyDisplays { get; protected set; }
11 | public string SelectedKeyDisplay { get; protected set; }
12 |
13 | public int SelectedIndex
14 | {
15 | get { return _selectedIndex; }
16 | set
17 | {
18 | if (value != _selectedIndex)
19 | {
20 | _selectedIndex = value;
21 | SelectedKeyDisplay = KeyDisplays[value];
22 | DisplayName = SelectedKeyDisplay;
23 | OnPropertyChanged("SelectedIndex");
24 | OnPropertyChanged("SelectedKeyDisplay");
25 | }
26 | }
27 | }
28 |
29 | public MultiCharacterKey(VirtualKeyCode keyCode, IList keyDisplays) :
30 | base(keyCode)
31 | {
32 | if (keyDisplays == null) throw new ArgumentNullException("keyDisplays");
33 | if (keyDisplays.Count <= 0)
34 | throw new ArgumentException("Please provide a list of one or more keyDisplays", "keyDisplays");
35 | KeyDisplays = keyDisplays;
36 | DisplayName = keyDisplays[0];
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/ChordKey.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using WpfKb.Input;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class ChordKey : LogicalKeyBase
7 | {
8 | public IList ModifierKeys { get; private set; }
9 | public IList Keys { get; private set; }
10 |
11 | public ChordKey(string displayName, VirtualKeyCode modifierKey, VirtualKeyCode key)
12 | : this(displayName, new List { modifierKey }, new List { key })
13 | {
14 | }
15 |
16 | public ChordKey(string displayName, IList modifierKeys, VirtualKeyCode key)
17 | : this(displayName, modifierKeys, new List { key })
18 | {
19 | }
20 |
21 | public ChordKey(string displayName, VirtualKeyCode modifierKey, IList keys)
22 | : this(displayName, new List { modifierKey }, keys)
23 | {
24 | }
25 |
26 | public ChordKey(string displayName, IList modifierKeys, IList keys)
27 | {
28 | DisplayName = displayName;
29 | ModifierKeys = modifierKeys;
30 | Keys = keys;
31 | }
32 |
33 | public override void Press()
34 | {
35 | InputSimulator.SimulateModifiedKeyStroke(ModifierKeys, Keys);
36 | base.Press();
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/WpfKb.TestClient/WpfKb.TestClient.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | true
7 | WpfKb.TestClient
8 | 2.0.0.0
9 | $(AssemblyVersion)
10 | $(AssemblyVersion)
11 | WpfKb demo.
12 | https://github.com/imasm/wpfkb
13 | Copyright © 2017 Ivan Masmitjà; Copyright © 2009 - 2010 michaelnoonan
14 | IMasm.$(AssemblyName)
15 | https://github.com/imasm/wpfkb
16 | WPF;keyboard;demo
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Never
30 |
31 |
32 |
33 |
34 |
35 | XamlIntelliSenseFileGenerator
36 | Never
37 |
38 |
39 | XamlIntelliSenseFileGenerator
40 | Never
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/WpfKb.TestClient/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
25 |
26 |
27 |
28 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/WpfKb.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32929.385
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfKb", "WpfKb\WpfKb.csproj", "{1CFEE3EC-5983-4D6F-8549-D529312BDC0B}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfKb.TestClient", "WpfKb.TestClient\WpfKb.TestClient.csproj", "{3E6262D8-676E-41D0-A919-80087E4C230E}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {3E6262D8-676E-41D0-A919-80087E4C230E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {3E6262D8-676E-41D0-A919-80087E4C230E}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {3E6262D8-676E-41D0-A919-80087E4C230E}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {3E6262D8-676E-41D0-A919-80087E4C230E}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {1CFEE3EC-5983-4D6F-8549-D529312BDC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {1CFEE3EC-5983-4D6F-8549-D529312BDC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {1CFEE3EC-5983-4D6F-8549-D529312BDC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {1CFEE3EC-5983-4D6F-8549-D529312BDC0B}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {97C7737C-9245-47BF-8C1C-70197F61736F}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/WpfKb/LogicalKeys/LogicalKeyBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 |
4 | namespace WpfKb.LogicalKeys
5 | {
6 | public class LogicalKeyEventArgs : EventArgs
7 | {
8 | public ILogicalKey Key { get; private set; }
9 |
10 | public LogicalKeyEventArgs(ILogicalKey key)
11 | {
12 | Key = key;
13 | }
14 | }
15 |
16 | public delegate void LogicalKeyPressedEventHandler(object sender, LogicalKeyEventArgs e);
17 |
18 | public abstract class LogicalKeyBase : ILogicalKey
19 | {
20 | public event LogicalKeyPressedEventHandler LogicalKeyPressed;
21 | public event PropertyChangedEventHandler PropertyChanged;
22 |
23 | private string _displayName;
24 |
25 | public virtual string DisplayName
26 | {
27 | get { return _displayName; }
28 | set
29 | {
30 | if (value != _displayName)
31 | {
32 | _displayName = value;
33 | OnPropertyChanged("DisplayName");
34 | }
35 | }
36 | }
37 |
38 | public virtual void Press()
39 | {
40 | OnKeyPressed();
41 | }
42 |
43 | protected void OnKeyPressed()
44 | {
45 | if (LogicalKeyPressed != null) LogicalKeyPressed(this, new LogicalKeyEventArgs(this));
46 | }
47 |
48 | protected virtual void OnPropertyChanged(string propertyName)
49 | {
50 | var handler = PropertyChanged;
51 | if (handler != null)
52 | {
53 | var args = new PropertyChangedEventArgs(propertyName);
54 | handler(this, args);
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenKeypad.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.ObjectModel;
2 | using WpfKb.Input;
3 | using WpfKb.LogicalKeys;
4 |
5 |
6 | namespace WpfKb.Controls
7 | {
8 | public class OnScreenKeypad : UniformOnScreenKeyboard
9 | {
10 | public OnScreenKeypad()
11 | {
12 | Keys = new ObservableCollection
13 | {
14 | new OnScreenKey { GridRow = 0, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.VK_7, "7") },
15 | new OnScreenKey { GridRow = 0, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.VK_8, "8") },
16 | new OnScreenKey { GridRow = 0, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.VK_9, "9") },
17 | new OnScreenKey { GridRow = 1, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.VK_4, "4") },
18 | new OnScreenKey { GridRow = 1, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.VK_5, "5") },
19 | new OnScreenKey { GridRow = 1, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.VK_6, "6") },
20 | new OnScreenKey { GridRow = 2, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.VK_1, "1") },
21 | new OnScreenKey { GridRow = 2, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.VK_2, "2") },
22 | new OnScreenKey { GridRow = 2, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.VK_3, "3") },
23 | new OnScreenKey { GridRow = 3, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.DELETE, "Clear") },
24 | new OnScreenKey { GridRow = 3, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.VK_0, "0") },
25 | new OnScreenKey { GridRow = 3, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.BACK, "Del") },
26 | };
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
2 | [Bb]in/
3 | [Oo]bj/
4 |
5 | # mstest test results
6 | TestResults
7 |
8 | ## Ignore Visual Studio temporary files, build results, and
9 | ## files generated by popular Visual Studio add-ons.
10 |
11 | # User-specific files
12 | *.suo
13 | *.user
14 | *.sln.docstates
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Rr]elease/
19 | x64/
20 | *_i.c
21 | *_p.c
22 | *.ilk
23 | *.meta
24 | *.obj
25 | *.pch
26 | *.pdb
27 | *.pgc
28 | *.pgd
29 | *.rsp
30 | *.sbr
31 | *.tlb
32 | *.tli
33 | *.tlh
34 | *.tmp
35 | *.log
36 | *.bak
37 | *.vspscc
38 | *.vssscc
39 | .builds
40 |
41 | # Visual C++ cache files
42 | ipch/
43 | *.aps
44 | *.ncb
45 | *.opensdf
46 | *.sdf
47 |
48 | # Visual Studio profiler
49 | *.psess
50 | *.vsp
51 | *.vspx
52 |
53 | # Guidance Automation Toolkit
54 | *.gpState
55 |
56 | # ReSharper is a .NET coding add-in
57 | _ReSharper*
58 |
59 | # NCrunch
60 | *.ncrunch*
61 | .*crunch*.local.xml
62 |
63 | # Installshield output folder
64 | [Ee]xpress
65 |
66 | # DocProject is a documentation generator add-in
67 | DocProject/buildhelp/
68 | DocProject/Help/*.HxT
69 | DocProject/Help/*.HxC
70 | DocProject/Help/*.hhc
71 | DocProject/Help/*.hhk
72 | DocProject/Help/*.hhp
73 | DocProject/Help/Html2
74 | DocProject/Help/html
75 |
76 | # Click-Once directory
77 | publish
78 |
79 | # Publish Web Output
80 | *.Publish.xml
81 |
82 | # NuGet Packages Directory
83 | packages
84 |
85 | # Windows Azure Build Output
86 | csx
87 | *.build.csdef
88 |
89 | # Windows Store app package directory
90 | AppPackages/
91 |
92 | # Others
93 | [Bb]in
94 | [Oo]bj
95 | sql
96 | TestResults
97 | [Tt]est[Rr]esult*
98 | *.Cache
99 | ClientBin
100 | [Ss]tyle[Cc]op.*
101 | ~$*
102 | *.dbmdl
103 | Generated_Code #added for RIA/Silverlight projects
104 |
105 | # Backup & report files from converting an old project file to a newer
106 | # Visual Studio version. Backup files are not needed, because we have git ;-)
107 | _UpgradeReport_Files/
108 | Backup*/
109 | Visual Studio*/
110 | UpgradeLog*.XML
111 | tools/
112 | output/*
113 | /.vs
114 |
--------------------------------------------------------------------------------
/WpfKb/Input/VirtualKeyCode.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable InconsistentNaming
2 | namespace WpfKb.Input
3 | {
4 | public enum VirtualKeyCode : ushort
5 | {
6 | LBUTTON = 1,
7 | RBUTTON,
8 | CANCEL,
9 | MBUTTON,
10 | XBUTTON1,
11 | XBUTTON2,
12 | BACK = 8,
13 | TAB,
14 | CLEAR = 12,
15 | RETURN,
16 | SHIFT = 16,
17 | CONTROL,
18 | MENU,
19 | PAUSE,
20 | CAPITAL,
21 | KANA,
22 | HANGEUL = 21,
23 | HANGUL = 21,
24 | JUNJA = 23,
25 | FINAL,
26 | HANJA,
27 | KANJI = 25,
28 | ESCAPE = 27,
29 | CONVERT,
30 | NONCONVERT,
31 | ACCEPT,
32 | MODECHANGE,
33 | SPACE,
34 | PRIOR,
35 | NEXT,
36 | END,
37 | HOME,
38 | LEFT,
39 | UP,
40 | RIGHT,
41 | DOWN,
42 | SELECT,
43 | PRINT,
44 | EXECUTE,
45 | SNAPSHOT,
46 | INSERT,
47 | DELETE,
48 | HELP,
49 | VK_0,
50 | VK_1,
51 | VK_2,
52 | VK_3,
53 | VK_4,
54 | VK_5,
55 | VK_6,
56 | VK_7,
57 | VK_8,
58 | VK_9,
59 | VK_A = 65,
60 | VK_B,
61 | VK_C,
62 | VK_D,
63 | VK_E,
64 | VK_F,
65 | VK_G,
66 | VK_H,
67 | VK_I,
68 | VK_J,
69 | VK_K,
70 | VK_L,
71 | VK_M,
72 | VK_N,
73 | VK_O,
74 | VK_P,
75 | VK_Q,
76 | VK_R,
77 | VK_S,
78 | VK_T,
79 | VK_U,
80 | VK_V,
81 | VK_W,
82 | VK_X,
83 | VK_Y,
84 | VK_Z,
85 | LWIN,
86 | RWIN,
87 | APPS,
88 | SLEEP = 95,
89 | NUMPAD0,
90 | NUMPAD1,
91 | NUMPAD2,
92 | NUMPAD3,
93 | NUMPAD4,
94 | NUMPAD5,
95 | NUMPAD6,
96 | NUMPAD7,
97 | NUMPAD8,
98 | NUMPAD9,
99 | MULTIPLY,
100 | ADD,
101 | SEPARATOR,
102 | SUBTRACT,
103 | DECIMAL,
104 | DIVIDE,
105 | F1,
106 | F2,
107 | F3,
108 | F4,
109 | F5,
110 | F6,
111 | F7,
112 | F8,
113 | F9,
114 | F10,
115 | F11,
116 | F12,
117 | F13,
118 | F14,
119 | F15,
120 | F16,
121 | F17,
122 | F18,
123 | F19,
124 | F20,
125 | F21,
126 | F22,
127 | F23,
128 | F24,
129 | NUMLOCK = 144,
130 | SCROLL,
131 | LSHIFT = 160,
132 | RSHIFT,
133 | LCONTROL,
134 | RCONTROL,
135 | LMENU,
136 | RMENU,
137 | BROWSER_BACK,
138 | BROWSER_FORWARD,
139 | BROWSER_REFRESH,
140 | BROWSER_STOP,
141 | BROWSER_SEARCH,
142 | BROWSER_FAVORITES,
143 | BROWSER_HOME,
144 | VOLUME_MUTE,
145 | VOLUME_DOWN,
146 | VOLUME_UP,
147 | MEDIA_NEXT_TRACK,
148 | MEDIA_PREV_TRACK,
149 | MEDIA_STOP,
150 | MEDIA_PLAY_PAUSE,
151 | LAUNCH_MAIL,
152 | LAUNCH_MEDIA_SELECT,
153 | LAUNCH_APP1,
154 | LAUNCH_APP2,
155 | OEM_1 = 186,
156 | OEM_PLUS,
157 | OEM_COMMA,
158 | OEM_MINUS,
159 | OEM_PERIOD,
160 | OEM_2,
161 | OEM_3,
162 | OEM_4 = 219,
163 | OEM_5,
164 | OEM_6,
165 | OEM_7,
166 | OEM_8,
167 | OEM_102 = 226,
168 | PROCESSKEY = 229,
169 | PACKET = 231,
170 | ATTN = 246,
171 | CRSEL,
172 | EXSEL,
173 | EREOF,
174 | PLAY,
175 | ZOOM,
176 | NONAME,
177 | PA1,
178 | OEM_CLEAR
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/WpfKb/Controls/FloatingTouchScreenKeyboard.xaml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
42 |
43 |
45 |
46 |
48 |
49 |
50 |
51 |
52 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/WpfKb/Styles/OnScreenKeyoard.xaml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
83 |
84 |
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenKeyboardSection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Collections.ObjectModel;
7 |
8 | namespace WpfKb.Controls
9 | {
10 | public class OnScreenKeyboardSection : Grid
11 | {
12 | private ObservableCollection _keys;
13 | private List _buttonRows;
14 |
15 | public ObservableCollection Keys
16 | {
17 | get { return _keys; }
18 | set
19 | {
20 | if (value == _keys) return;
21 |
22 | Reset();
23 | _keys = value;
24 | LayoutKeys();
25 | }
26 | }
27 |
28 | public OnScreenKeyboardSection()
29 | {
30 | Margin = new Thickness(5);
31 | _buttonRows = new List();
32 | _keys = new ObservableCollection();
33 | _keys.CollectionChanged += Keys_CollectionChanged;
34 | }
35 |
36 | private void Keys_CollectionChanged(object sender,
37 | System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
38 | {
39 | switch (e.Action)
40 | {
41 | case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
42 | case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
43 | LayoutKeys();
44 | break;
45 | case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
46 | throw new NotSupportedException("You cannot currently replace keys.");
47 | case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
48 | Reset();
49 | break;
50 | default:
51 | break;
52 | }
53 | }
54 |
55 | private void LayoutKeys()
56 | {
57 | if (_keys == null || _keys.Count == 0) return;
58 |
59 | ResizeGrid(_keys);
60 | for (var buttonRowIndex = 0; buttonRowIndex < _buttonRows.Count; buttonRowIndex++)
61 | {
62 | var grid = _buttonRows[buttonRowIndex];
63 | grid.Children.Clear();
64 | foreach (var key in _keys.Where(x => x.GridRow == buttonRowIndex))
65 | {
66 | grid.Children.Add(key);
67 | }
68 | }
69 | }
70 |
71 | private void ResizeGrid(IEnumerable keys)
72 | {
73 | if (keys == null) throw new ArgumentNullException("keys");
74 |
75 | // Make sure there's enough rows
76 | var rowCount = keys.Max(x => x.GridRow) + 1;
77 | for (var extraRowIndex = RowDefinitions.Count; extraRowIndex < rowCount; extraRowIndex++)
78 | {
79 | // Button Row
80 | RowDefinitions.Add(new RowDefinition());
81 |
82 | // Add a grid for the buttons
83 | var g = new Grid();
84 | _buttonRows.Add(g);
85 | Children.Add(g);
86 | g.SetValue(RowProperty, extraRowIndex);
87 | }
88 |
89 | // Make sure each row has enough columns
90 | for (var buttonRowIndex = 0; buttonRowIndex < rowCount; buttonRowIndex++)
91 | {
92 | var grid = _buttonRows[buttonRowIndex];
93 | var colCount = keys.Where(x => x.GridRow == buttonRowIndex).Max(x => x.GridColumn) + 1;
94 | for (var colsToAdd = colCount - grid.ColumnDefinitions.Count; colsToAdd > 0; colsToAdd--)
95 | {
96 | // Add the extra Column
97 | grid.ColumnDefinitions.Add(new ColumnDefinition());
98 | }
99 | for (var colsToRemove = grid.ColumnDefinitions.Count - colCount; colsToRemove > 0; colsToRemove--)
100 | {
101 | // Remove the extra Column
102 | grid.ColumnDefinitions.RemoveAt(0);
103 | }
104 |
105 | // Set the width of each column according to the key's GridWidth definition
106 | keys.Where(x => x.GridRow == buttonRowIndex && x.GridWidth.Value != 1).ToList()
107 | .ForEach(x => grid.ColumnDefinitions[x.GridColumn].Width = x.GridWidth);
108 |
109 | }
110 | }
111 |
112 | private void Reset()
113 | {
114 | _keys = null;
115 | Children.Clear();
116 | RowDefinitions.Clear();
117 | ColumnDefinitions.Clear();
118 | }
119 | }
120 | }
--------------------------------------------------------------------------------
/WpfKb.TestClient/Resources/Styles.xaml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
17 |
18 |
92 |
93 |
--------------------------------------------------------------------------------
/WpfKb/Controls/UniformOnScreenKeyboard.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Collections.ObjectModel;
3 | using System.Collections.Specialized;
4 | using System.Windows.Controls;
5 | using System.Windows;
6 |
7 | namespace WpfKb.Controls
8 | {
9 | public class UniformOnScreenKeyboard : Grid
10 | {
11 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(UniformOnScreenKeyboard), new UIPropertyMetadata(true, OnAreAnimationsEnabledPropertyChanged));
12 |
13 | private ObservableCollection _keys;
14 |
15 | public bool AreAnimationsEnabled
16 | {
17 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
18 | set { SetValue(AreAnimationsEnabledProperty, value); }
19 | }
20 |
21 | public ObservableCollection Keys
22 | {
23 | get { return _keys; }
24 | set
25 | {
26 | if (value == _keys) return;
27 |
28 | Reset();
29 | _keys = value;
30 | if (_keys != null)
31 | foreach (var key in _keys)
32 | {
33 | Children.Add(key);
34 | }
35 | ResizeGrid();
36 | }
37 | }
38 |
39 | public UniformOnScreenKeyboard()
40 | {
41 | _keys = new ObservableCollection();
42 | _keys.CollectionChanged += Keys_CollectionChanged;
43 | }
44 |
45 | private static void OnAreAnimationsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
46 | {
47 | var keyboard = (UniformOnScreenKeyboard)d;
48 | keyboard.Keys.ToList().ForEach(x => x.AreAnimationsEnabled = (bool)e.NewValue);
49 | }
50 |
51 | private void Keys_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
52 | {
53 | switch (e.Action)
54 | {
55 | case NotifyCollectionChangedAction.Add:
56 | // Add the keys to the Grid's Children collection and resize the grid
57 | foreach (var key in e.NewItems.OfType())
58 | {
59 | key.AreAnimationsEnabled = AreAnimationsEnabled;
60 | Children.Add(key);
61 | }
62 | ResizeGrid();
63 | break;
64 | case NotifyCollectionChangedAction.Remove:
65 | // Remove the keys from the Grid's Children collection and resize the grid
66 | foreach (var key in e.OldItems.OfType())
67 | {
68 | Children.Remove(key);
69 | }
70 | ResizeGrid();
71 | break;
72 | case NotifyCollectionChangedAction.Replace:
73 | // Throw everything away and start again
74 | Children.Clear();
75 | foreach (var key in _keys)
76 | {
77 | Children.Add(key);
78 | }
79 | ResizeGrid();
80 | break;
81 | case NotifyCollectionChangedAction.Reset:
82 | Reset();
83 | break;
84 | default:
85 | break;
86 | }
87 | }
88 |
89 |
90 | private void ResizeGrid()
91 | {
92 | if (_keys == null)
93 | {
94 | Reset();
95 | return;
96 | }
97 |
98 | // Make sure there's the right number of rows
99 | var rowCount = _keys.Max(x => x.GridRow) + 1;
100 | for (var rowsToAdd = rowCount - RowDefinitions.Count; rowsToAdd > 0; rowsToAdd--)
101 | {
102 | // Add the extra Row
103 | RowDefinitions.Add(new RowDefinition());
104 | }
105 | for (var rowsToRemove = RowDefinitions.Count - rowCount; rowsToRemove > 0; rowsToRemove--)
106 | {
107 | // Remove the extra Row
108 | RowDefinitions.RemoveAt(0);
109 | }
110 |
111 | // Make sure there's the right number of cols
112 | var colCount = _keys.Max(x => x.GridColumn) + 1;
113 | for (var colsToAdd = colCount - ColumnDefinitions.Count; colsToAdd > 0; colsToAdd--)
114 | {
115 | // Add the extra Column
116 | ColumnDefinitions.Add(new ColumnDefinition());
117 | }
118 | for (var colsToRemove = ColumnDefinitions.Count - colCount; colsToRemove > 0; colsToRemove--)
119 | {
120 | // Remove the extra Column
121 | ColumnDefinitions.RemoveAt(0);
122 | }
123 | }
124 |
125 | private void Reset()
126 | {
127 | _keys = null;
128 | Children.Clear();
129 | RowDefinitions.Clear();
130 | ColumnDefinitions.Clear();
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/WpfKb/Input/InputSimulator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Runtime.InteropServices;
5 | using System.Text;
6 | using WpfKb.Input.Native;
7 |
8 | namespace WpfKb.Input
9 | {
10 | public static class InputSimulator
11 | {
12 | [DllImport("user32.dll", SetLastError = true)]
13 | private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure);
14 |
15 | [DllImport("user32.dll", SetLastError = true)]
16 | private static extern short GetAsyncKeyState(ushort virtualKeyCode);
17 |
18 | [DllImport("user32.dll", SetLastError = true)]
19 | private static extern short GetKeyState(ushort virtualKeyCode);
20 |
21 | [DllImport("user32.dll")]
22 | private static extern IntPtr GetMessageExtraInfo();
23 |
24 | public static bool IsKeyDownAsync(VirtualKeyCode keyCode)
25 | {
26 | short result = GetAsyncKeyState((ushort)keyCode);
27 | return result < 0;
28 | }
29 |
30 | public static bool IsKeyDown(VirtualKeyCode keyCode)
31 | {
32 | short result = GetKeyState((ushort)keyCode);
33 | return result < 0;
34 | }
35 |
36 | public static bool IsTogglingKeyInEffect(VirtualKeyCode keyCode)
37 | {
38 | short result = GetKeyState((ushort)keyCode);
39 | return (result & 1) == 1;
40 | }
41 |
42 | public static void SimulateKeyDown(VirtualKeyCode keyCode)
43 | {
44 | INPUT down = default(INPUT);
45 | down.Type = InputType.KEYBOARD;
46 | down.Data.Keyboard = default(KEYBDINPUT);
47 | down.Data.Keyboard.Vk = keyCode;
48 | down.Data.Keyboard.Scan = 0;
49 | down.Data.Keyboard.Flags = 0u;
50 | down.Data.Keyboard.Time = 0u;
51 | down.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
52 | uint numberOfSuccessfulSimulatedInputs = SendInput(1u, new INPUT[]
53 | {
54 | down
55 | }, Marshal.SizeOf(typeof(INPUT)));
56 | if (numberOfSuccessfulSimulatedInputs == 0u)
57 | {
58 | throw new Exception(string.Format("The key down simulation for {0} was not successful.", keyCode));
59 | }
60 | }
61 |
62 | public static void SimulateKeyUp(VirtualKeyCode keyCode)
63 | {
64 | INPUT up = default(INPUT);
65 | up.Type = InputType.KEYBOARD;
66 | up.Data.Keyboard = default(KEYBDINPUT);
67 | up.Data.Keyboard.Vk = keyCode;
68 | up.Data.Keyboard.Scan = 0;
69 | up.Data.Keyboard.Flags = KEYEVENTF.KEYUP;
70 | up.Data.Keyboard.Time = 0u;
71 | up.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
72 | uint numberOfSuccessfulSimulatedInputs = SendInput(1u, new INPUT[]
73 | {
74 | up
75 | }, Marshal.SizeOf(typeof(INPUT)));
76 | if (numberOfSuccessfulSimulatedInputs == 0u)
77 | {
78 | throw new Exception(string.Format("The key up simulation for {0} was not successful.", keyCode));
79 | }
80 | }
81 |
82 | public static void SimulateKeyPress(VirtualKeyCode keyCode)
83 | {
84 | INPUT down = default(INPUT);
85 | down.Type = InputType.KEYBOARD;
86 | down.Data.Keyboard = default(KEYBDINPUT);
87 | down.Data.Keyboard.Vk = keyCode;
88 | down.Data.Keyboard.Scan = 0;
89 | down.Data.Keyboard.Flags = 0u;
90 | down.Data.Keyboard.Time = 0u;
91 | down.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
92 |
93 | INPUT up = default(INPUT);
94 | up.Type = InputType.KEYBOARD;
95 | up.Data.Keyboard = default(KEYBDINPUT);
96 | up.Data.Keyboard.Vk = keyCode;
97 | up.Data.Keyboard.Scan = 0;
98 | up.Data.Keyboard.Flags = KEYEVENTF.KEYUP;
99 | up.Data.Keyboard.Time = 0u;
100 | up.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
101 | uint numberOfSuccessfulSimulatedInputs = SendInput(2u, new INPUT[]
102 | {
103 | down,
104 | up
105 | }, Marshal.SizeOf(typeof(INPUT)));
106 | if (numberOfSuccessfulSimulatedInputs == 0u)
107 | {
108 | throw new Exception(string.Format("The key press simulation for {0} was not successful.", keyCode));
109 | }
110 | }
111 |
112 | public static void SimulateTextEntry(string text)
113 | {
114 | if ((uint) text.Length > int.MaxValue)
115 | {
116 | throw new ArgumentException(
117 | string.Format("The text parameter is too long. It must be less than {0} characters.", 2147483647u), "text");
118 | }
119 |
120 | byte[] chars = Encoding.ASCII.GetBytes(text);
121 | int len = chars.Length;
122 | INPUT[] inputList = new INPUT[len * 2];
123 | for (int x = 0; x < len; x++)
124 | {
125 | ushort scanCode = (ushort)chars[x];
126 | INPUT down = default(INPUT);
127 | down.Type = InputType.KEYBOARD;
128 | down.Data.Keyboard = default(KEYBDINPUT);
129 | down.Data.Keyboard.Vk = 0;
130 | down.Data.Keyboard.Scan = scanCode;
131 | down.Data.Keyboard.Flags = KEYEVENTF.UNICODE;
132 | down.Data.Keyboard.Time = 0u;
133 | down.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
134 |
135 | INPUT up = default(INPUT);
136 | up.Type = InputType.KEYBOARD;
137 | up.Data.Keyboard = default(KEYBDINPUT);
138 | up.Data.Keyboard.Vk = 0;
139 | up.Data.Keyboard.Scan = scanCode;
140 | up.Data.Keyboard.Flags = KEYEVENTF.UNICODE | KEYEVENTF.KEYUP;
141 | up.Data.Keyboard.Time = 0u;
142 | up.Data.Keyboard.ExtraInfo = UIntPtr.Zero;
143 | if ((scanCode & 65280) == 57344)
144 | {
145 | down.Data.Keyboard.Flags = (down.Data.Keyboard.Flags | KEYEVENTF.EXTENDEDKEY);
146 | up.Data.Keyboard.Flags = (up.Data.Keyboard.Flags | KEYEVENTF.EXTENDEDKEY);
147 | }
148 | inputList[2 * x] = down;
149 | inputList[2 * x + 1] = up;
150 | }
151 | uint numberOfSuccessfulSimulatedInputs = InputSimulator.SendInput((uint)(len * 2), inputList,
152 | Marshal.SizeOf(typeof(INPUT)));
153 | }
154 |
155 | public static void SimulateModifiedKeyStroke(VirtualKeyCode modifierKeyCode, VirtualKeyCode keyCode)
156 | {
157 | SimulateKeyDown(modifierKeyCode);
158 | SimulateKeyPress(keyCode);
159 | SimulateKeyUp(modifierKeyCode);
160 | }
161 |
162 | public static void SimulateModifiedKeyStroke(IEnumerable modifierKeyCodes,
163 | VirtualKeyCode keyCode)
164 | {
165 | List modifierKeyCodesList = modifierKeyCodes?.ToList();
166 | modifierKeyCodesList?.ForEach(SimulateKeyDown);
167 | SimulateKeyPress(keyCode);
168 | modifierKeyCodesList?.Reverse().ToList().ForEach(SimulateKeyUp);
169 | }
170 |
171 |
172 | public static void SimulateModifiedKeyStroke(VirtualKeyCode modifierKey, IEnumerable keyCodes)
173 | {
174 | SimulateKeyDown(modifierKey);
175 | keyCodes?.ToList().ForEach(SimulateKeyPress);
176 | SimulateKeyUp(modifierKey);
177 | }
178 |
179 | public static void SimulateModifiedKeyStroke(IEnumerable modifierKeyCodes,
180 | IEnumerable keyCodes)
181 | {
182 | List modifierKeyCodesList = modifierKeyCodes?.ToList();
183 |
184 | modifierKeyCodesList?.ForEach(SimulateKeyDown);
185 |
186 | keyCodes?.ToList().ForEach(SimulateKeyPress);
187 |
188 | modifierKeyCodesList?.Reverse().ToList().ForEach(SimulateKeyUp);
189 |
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/WpfKb/Behaviors/AutoHideBehavior.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows.Input;
4 | using Microsoft.Xaml.Behaviors;
5 | using System.Windows;
6 | using System.Windows.Media.Animation;
7 | using System.Windows.Threading;
8 |
9 | namespace WpfKb.Behaviors
10 | {
11 | public class AutoHideBehavior : Behavior
12 | {
13 | public enum ClickAction
14 | {
15 | None,
16 | Show,
17 | AcceleratedHide,
18 | }
19 |
20 | public static readonly DependencyProperty ActionWhenClickedProperty = DependencyProperty.Register("ActionWhenClicked", typeof(ClickAction), typeof(AutoHideBehavior), new UIPropertyMetadata(ClickAction.Show));
21 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(AutoHideBehavior), new UIPropertyMetadata(true));
22 | public static readonly DependencyProperty HideDelayProperty = DependencyProperty.Register("HideDelay", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(5d));
23 | public static readonly DependencyProperty HideDurationProperty = DependencyProperty.Register("HideDuration", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(0.5d));
24 | public static readonly DependencyProperty IsAllowedToHideProperty = DependencyProperty.Register("IsAllowedToHide", typeof(bool), typeof(AutoHideBehavior), new UIPropertyMetadata(true, OnIsAllowedToHidePropertyChanged));
25 | public static readonly DependencyProperty IsAllowedToShowProperty = DependencyProperty.Register("IsAllowedToShow", typeof(bool), typeof(AutoHideBehavior), new UIPropertyMetadata(true));
26 | public static readonly DependencyProperty IsShownProperty = DependencyProperty.Register("IsShown", typeof(bool), typeof(AutoHideBehavior), new UIPropertyMetadata(true, OnIsShownPropertyChanged));
27 | public static readonly DependencyProperty MaxOpacityProperty = DependencyProperty.Register("MaxOpacity", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(1d));
28 | public static readonly DependencyProperty MinOpacityProperty = DependencyProperty.Register("MinOpacity", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(0d));
29 | public static readonly DependencyProperty ShowDurationProperty = DependencyProperty.Register("ShowDuration", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(0d));
30 | public static readonly DependencyProperty TimerIntervalProperty = DependencyProperty.Register("TimerInterval", typeof(double), typeof(AutoHideBehavior), new UIPropertyMetadata(0.3d));
31 |
32 | private DispatcherTimer _timer;
33 | private DateTime _lastActivityTime;
34 |
35 | public ClickAction ActionWhenClicked
36 | {
37 | get { return (ClickAction)GetValue(ActionWhenClickedProperty); }
38 | set { SetValue(ActionWhenClickedProperty, value); }
39 | }
40 |
41 | public bool AreAnimationsEnabled
42 | {
43 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
44 | set { SetValue(AreAnimationsEnabledProperty, value); }
45 | }
46 |
47 | public double HideDelay
48 | {
49 | get { return (double)GetValue(HideDelayProperty); }
50 | set { SetValue(HideDelayProperty, value); }
51 | }
52 |
53 | public double HideDuration
54 | {
55 | get { return (double)GetValue(HideDurationProperty); }
56 | set { SetValue(HideDurationProperty, value); }
57 | }
58 |
59 | public bool IsAllowedToHide
60 | {
61 | get { return (bool)GetValue(IsAllowedToHideProperty); }
62 | set { SetValue(IsAllowedToHideProperty, value); }
63 | }
64 |
65 | public bool IsAllowedToShow
66 | {
67 | get { return (bool)GetValue(IsAllowedToShowProperty); }
68 | set { SetValue(IsAllowedToShowProperty, value); }
69 | }
70 |
71 | public bool IsShown
72 | {
73 | get { return (bool)GetValue(IsShownProperty); }
74 | set { SetValue(IsShownProperty, value); }
75 | }
76 |
77 | public double MaxOpacity
78 | {
79 | get { return (double)GetValue(MaxOpacityProperty); }
80 | set { SetValue(MaxOpacityProperty, value); }
81 | }
82 |
83 | public double MinOpacity
84 | {
85 | get { return (double)GetValue(MinOpacityProperty); }
86 | set { SetValue(MinOpacityProperty, value); }
87 | }
88 |
89 | public double ShowDuration
90 | {
91 | get { return (double)GetValue(ShowDurationProperty); }
92 | set { SetValue(ShowDurationProperty, value); }
93 | }
94 |
95 | public double TimerInterval
96 | {
97 | get { return (double)GetValue(TimerIntervalProperty); }
98 | set { SetValue(TimerIntervalProperty, value); }
99 | }
100 |
101 | protected override void OnAttached()
102 | {
103 | base.OnAttached();
104 | AssociatedObject.PreviewMouseDown += HandlePreviewMouseDown;
105 | Show();
106 | PrepareToHide();
107 | }
108 |
109 | protected override void OnDetaching()
110 | {
111 | base.OnDetaching();
112 | AssociatedObject.PreviewMouseDown -= HandlePreviewMouseDown;
113 | }
114 |
115 | private static void OnIsAllowedToHidePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
116 | {
117 | var behavior = (AutoHideBehavior)d;
118 | behavior.PingActivity();
119 | }
120 |
121 | private static void OnIsShownPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
122 | {
123 | var behavior = (AutoHideBehavior) d;
124 | if ((bool)e.NewValue) behavior.Show();
125 | else behavior.Hide();
126 | }
127 |
128 | private void HandlePreviewMouseDown(object sender, MouseButtonEventArgs e)
129 | {
130 | _lastActivityTime = DateTime.Now;
131 | switch (ActionWhenClicked)
132 | {
133 | case ClickAction.Show:
134 | Show();
135 | break;
136 |
137 | case ClickAction.AcceleratedHide:
138 | HideFast();
139 | break;
140 | }
141 | }
142 |
143 | private void PrepareToHide()
144 | {
145 | PingActivity();
146 | if (_timer == null)
147 | {
148 | _timer = new DispatcherTimer(TimeSpan.FromSeconds(TimerInterval), DispatcherPriority.Background,
149 | Tick, Dispatcher);
150 | }
151 | }
152 |
153 | private void Tick(object sender, EventArgs e)
154 | {
155 | AssociatedObject.IsHitTestVisible = AssociatedObject.Opacity > 0;
156 | if (DateTime.Now - _lastActivityTime > TimeSpan.FromSeconds(HideDelay))
157 | {
158 | if (AssociatedObject.Opacity >= MaxOpacity && IsAllowedToHide) Hide();
159 | else _lastActivityTime = DateTime.Now;
160 | }
161 | }
162 |
163 | public void PingActivity()
164 | {
165 | _lastActivityTime = DateTime.Now;
166 | }
167 |
168 | public void Show()
169 | {
170 | if (AssociatedObject == null) return;
171 | PingActivity();
172 | PrepareToHide();
173 | if (IsAllowedToShow)
174 | {
175 | var duration = AreAnimationsEnabled
176 | ? new Duration(TimeSpan.FromSeconds(ShowDuration))
177 | : new Duration(TimeSpan.Zero);
178 |
179 | IsShown = true;
180 | AssociatedObject.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(MaxOpacity, duration));
181 | }
182 | }
183 |
184 | public void Hide()
185 | {
186 | if (AssociatedObject == null) return;
187 |
188 | var duration = AreAnimationsEnabled
189 | ? new Duration(TimeSpan.FromSeconds(HideDuration))
190 | : new Duration(TimeSpan.Zero);
191 |
192 | IsShown = false;
193 | AssociatedObject.BeginAnimation(UIElement.OpacityProperty, new DoubleAnimation(MinOpacity, duration));
194 | }
195 |
196 | public void HideFast()
197 | {
198 | if (AssociatedObject == null) return;
199 | IsShown = false;
200 | AssociatedObject.BeginAnimation(UIElement.OpacityProperty, null);
201 | AssociatedObject.BeginAnimation(UIElement.OpacityProperty,
202 | new DoubleAnimation(MinOpacity, new Duration(TimeSpan.Zero)));
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/WpfKb/Controls/FloatingTouchScreenKeyboard.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Input;
5 |
6 | namespace WpfKb.Controls
7 | {
8 | public partial class FloatingTouchScreenKeyboard
9 | {
10 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(true));
11 | public static readonly DependencyProperty IsAllowedToFadeProperty = DependencyProperty.Register("IsAllowedToFade", typeof(bool), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(true));
12 | public static readonly DependencyProperty IsDraggingProperty = DependencyProperty.Register("IsDragging", typeof(bool), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(false));
13 | public static readonly DependencyProperty IsDragHelperAllowedToHideProperty = DependencyProperty.Register("IsDragHelperAllowedToHide", typeof(bool), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(false));
14 | public static readonly DependencyProperty IsKeyboardShownProperty = DependencyProperty.Register("IsKeyboardShown", typeof(bool), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(true));
15 | public static readonly DependencyProperty MaximumKeyboardOpacityProperty = DependencyProperty.Register("MaximumKeyboardOpacity", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(0.9d));
16 | public static readonly DependencyProperty MinimumKeyboardOpacityProperty = DependencyProperty.Register("MinimumKeyboardOpacity", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(0.2d));
17 | public static readonly DependencyProperty KeyboardHideDelayProperty = DependencyProperty.Register("KeyboardHideDelay", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(5d));
18 | public static readonly DependencyProperty KeyboardHideAnimationDurationProperty = DependencyProperty.Register("KeyboardHideAnimationDuration", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(0.5d));
19 | public static readonly DependencyProperty KeyboardShowAnimationDurationProperty = DependencyProperty.Register("KeyboardShowAnimationDuration", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(0.5d));
20 | public static readonly DependencyProperty DeadZoneProperty = DependencyProperty.Register("DeadZone", typeof(double), typeof(FloatingTouchScreenKeyboard), new UIPropertyMetadata(5d));
21 |
22 | private Point _mouseDownPosition;
23 | private Point _mouseDownOffset;
24 | private bool _isAllowedToFadeValueBeforeDrag;
25 |
26 | public FloatingTouchScreenKeyboard()
27 | {
28 | InitializeComponent();
29 | }
30 |
31 | ///
32 | /// Gets or sets a value indicating whether animations are enabled.
33 | ///
34 | ///
35 | /// true if animations are enabled; otherwise, false.
36 | ///
37 | public bool AreAnimationsEnabled
38 | {
39 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
40 | set { SetValue(AreAnimationsEnabledProperty, value); }
41 | }
42 |
43 | ///
44 | /// Gets or sets a value that indicates whether the Keyboard is allowed to fade. This is a dependency property.
45 | ///
46 | public bool IsAllowedToFade
47 | {
48 | get { return (bool)GetValue(IsAllowedToFadeProperty); }
49 | set { SetValue(IsAllowedToFadeProperty, value); }
50 | }
51 |
52 | ///
53 | /// Gets a value that indicates if the keyboard is currently being dragged. This is a dependency property.
54 | ///
55 | public bool IsDragging
56 | {
57 | get { return (bool)GetValue(IsDraggingProperty); }
58 | private set { SetValue(IsDraggingProperty, value); }
59 | }
60 |
61 | ///
62 | /// Gets or sets a value that indicates if the drag helper text is allowed to hide. This is a dependency property.
63 | ///
64 | public bool IsDragHelperAllowedToHide
65 | {
66 | get { return (bool)GetValue(IsDragHelperAllowedToHideProperty); }
67 | set { SetValue(IsDragHelperAllowedToHideProperty, value); }
68 | }
69 |
70 | ///
71 | /// Gets a value that indicates that the keyboard is shown (not faded). This is a dependency property.
72 | ///
73 | public bool IsKeyboardShown
74 | {
75 | get { return (bool)GetValue(IsKeyboardShownProperty); }
76 | private set { SetValue(IsKeyboardShownProperty, value); }
77 | }
78 |
79 | ///
80 | /// Gets or sets the maximum opacity for a fully displayed keyboard. This is a dependency property.
81 | ///
82 | public double MaximumKeyboardOpacity
83 | {
84 | get { return (double)GetValue(MaximumKeyboardOpacityProperty); }
85 | set { SetValue(MaximumKeyboardOpacityProperty, value); }
86 | }
87 |
88 | ///
89 | /// Gets or sets the opacity to use for a partially hidden keyboard. This is a dependency property.
90 | ///
91 | public double MinimumKeyboardOpacity
92 | {
93 | get { return (double)GetValue(MinimumKeyboardOpacityProperty); }
94 | set { SetValue(MinimumKeyboardOpacityProperty, value); }
95 | }
96 |
97 | ///
98 | /// Gets or sets the number of seconds to wait after the last keyboard activity before hiding the keyboard. This is a dependency property.
99 | ///
100 | public double KeyboardHideDelay
101 | {
102 | get { return (double)GetValue(KeyboardHideDelayProperty); }
103 | set { SetValue(KeyboardHideDelayProperty, value); }
104 | }
105 |
106 | ///
107 | /// Gets or sets the duration in seconds for the keyboard hide animation. This is a dependency property.
108 | ///
109 | public double KeyboardHideAnimationDuration
110 | {
111 | get { return (double)GetValue(KeyboardHideAnimationDurationProperty); }
112 | set { SetValue(KeyboardHideAnimationDurationProperty, value); }
113 | }
114 |
115 | ///
116 | /// Gets or sets the duration in seconds for the keyboard show animation. This is a dependency property.
117 | ///
118 | public double KeyboardShowAnimationDuration
119 | {
120 | get { return (double)GetValue(KeyboardShowAnimationDurationProperty); }
121 | set { SetValue(KeyboardShowAnimationDurationProperty, value); }
122 | }
123 |
124 | ///
125 | /// Gets or sets the maximum opacity for a fully displayed keyboard. This is a dependency property.
126 | ///
127 | public double DeadZone
128 | {
129 | get { return (double)GetValue(DeadZoneProperty); }
130 | set { SetValue(DeadZoneProperty, value); }
131 | }
132 |
133 | protected override void OnOpened(EventArgs e)
134 | {
135 | IsKeyboardShown = true;
136 | base.OnOpened(e);
137 | }
138 |
139 | protected override void OnClosed(EventArgs e)
140 | {
141 | IsKeyboardShown = false;
142 | base.OnClosed(e);
143 | }
144 |
145 | private void DragHandle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
146 | {
147 | _mouseDownPosition = e.GetPosition(PlacementTarget);
148 | _mouseDownOffset = new Point(HorizontalOffset, VerticalOffset);
149 | }
150 |
151 | private void DragHandle_PreviewMouseMove(object sender, MouseEventArgs e)
152 | {
153 | if (e.LeftButton == MouseButtonState.Pressed)
154 | {
155 | var delta = e.GetPosition(PlacementTarget) - _mouseDownPosition;
156 | if (!IsDragging && delta.Length > DeadZone)
157 | {
158 | IsDragging = true;
159 | IsDragHelperAllowedToHide = true;
160 | _isAllowedToFadeValueBeforeDrag = IsAllowedToFade;
161 | IsAllowedToFade = false;
162 | DragHandle.CaptureMouse();
163 | }
164 |
165 | if (IsDragging)
166 | {
167 | HorizontalOffset = _mouseDownOffset.X + delta.X;
168 | VerticalOffset = _mouseDownOffset.Y + delta.Y;
169 | }
170 | }
171 | }
172 |
173 | private void DragHandle_PreviewMouseUp(object sender, MouseButtonEventArgs e)
174 | {
175 | DragHandle.ReleaseMouseCapture();
176 | IsDragging = false;
177 | IsAllowedToFade = _isAllowedToFadeValueBeforeDrag;
178 | }
179 |
180 | }
181 | }
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenKeyboard.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 | using System.Collections.ObjectModel;
8 | using WpfKb.Input;
9 | using WpfKb.LogicalKeys;
10 |
11 | namespace WpfKb.Controls
12 | {
13 | public class OnScreenKeyboard : Grid
14 | {
15 |
16 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(OnScreenKeyboard), new UIPropertyMetadata(true, OnAreAnimationsEnabledPropertyChanged));
17 |
18 | private ObservableCollection _sections;
19 | private List _modifierKeys;
20 | private List _allLogicalKeys;
21 | private List _allOnScreenKeys;
22 |
23 | public bool AreAnimationsEnabled
24 | {
25 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
26 | set { SetValue(AreAnimationsEnabledProperty, value); }
27 | }
28 |
29 | private static void OnAreAnimationsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
30 | {
31 | var keyboard = (OnScreenKeyboard)d;
32 | keyboard._allOnScreenKeys.ToList().ForEach(x => x.AreAnimationsEnabled = (bool)e.NewValue);
33 | }
34 |
35 |
36 | public override void BeginInit()
37 | {
38 | SetValue(FocusManager.IsFocusScopeProperty, true);
39 | _modifierKeys = new List();
40 | _allLogicalKeys = new List();
41 | _allOnScreenKeys = new List();
42 |
43 | _sections = new ObservableCollection();
44 |
45 | var mainSection = new OnScreenKeyboardSection();
46 | var mainKeys = new ObservableCollection
47 | {
48 | new OnScreenKey { GridRow = 0, GridColumn = 0, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_3, new List { "`", "~" })},
49 | new OnScreenKey { GridRow = 0, GridColumn = 1, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_1, new List { "1", "!" })},
50 | new OnScreenKey { GridRow = 0, GridColumn = 2, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_2, new List { "2", "@" })},
51 | new OnScreenKey { GridRow = 0, GridColumn = 3, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_3, new List { "3", "#" })},
52 | new OnScreenKey { GridRow = 0, GridColumn = 4, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_4, new List { "4", "$" })},
53 | new OnScreenKey { GridRow = 0, GridColumn = 5, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_5, new List { "5", "%" })},
54 | new OnScreenKey { GridRow = 0, GridColumn = 6, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_6, new List { "6", "^" })},
55 | new OnScreenKey { GridRow = 0, GridColumn = 7, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_7, new List { "7", "&" })},
56 | new OnScreenKey { GridRow = 0, GridColumn = 8, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_8, new List { "8", "*" })},
57 | new OnScreenKey { GridRow = 0, GridColumn = 9, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_9, new List { "9", "(" })},
58 | new OnScreenKey { GridRow = 0, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_0, new List { "0", ")" })},
59 | new OnScreenKey { GridRow = 0, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_MINUS, new List { "-", "_" })},
60 | new OnScreenKey { GridRow = 0, GridColumn = 12, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_PLUS, new List { "=", "+" })},
61 | new OnScreenKey { GridRow = 0, GridColumn = 13, Key = new VirtualKey(VirtualKeyCode.BACK, "Bksp"), GridWidth = new GridLength(2, GridUnitType.Star)},
62 |
63 | new OnScreenKey { GridRow = 1, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.TAB, "Tab"), GridWidth = new GridLength(1.5, GridUnitType.Star)},
64 | new OnScreenKey { GridRow = 1, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Q, new List { "q", "Q" })},
65 | new OnScreenKey { GridRow = 1, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_W, new List { "w", "W" })},
66 | new OnScreenKey { GridRow = 1, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_E, new List { "e", "E" })},
67 | new OnScreenKey { GridRow = 1, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_R, new List { "r", "R" })},
68 | new OnScreenKey { GridRow = 1, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_T, new List { "t", "T" })},
69 | new OnScreenKey { GridRow = 1, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Y, new List { "y", "Y" })},
70 | new OnScreenKey { GridRow = 1, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_U, new List { "u", "U" })},
71 | new OnScreenKey { GridRow = 1, GridColumn = 8, Key = new CaseSensitiveKey(VirtualKeyCode.VK_I, new List { "i", "I" })},
72 | new OnScreenKey { GridRow = 1, GridColumn = 9, Key = new CaseSensitiveKey(VirtualKeyCode.VK_O, new List { "o", "O" })},
73 | new OnScreenKey { GridRow = 1, GridColumn = 10, Key = new CaseSensitiveKey(VirtualKeyCode.VK_P, new List { "p", "P" })},
74 | new OnScreenKey { GridRow = 1, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_4, new List { "[", "{" })},
75 | new OnScreenKey { GridRow = 1, GridColumn = 12, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_6, new List { "]", "}" })},
76 | new OnScreenKey { GridRow = 1, GridColumn = 13, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_5, new List { "\\", "|" }), GridWidth = new GridLength(1.3, GridUnitType.Star)},
77 |
78 | new OnScreenKey { GridRow = 2, GridColumn = 0, Key = new TogglingModifierKey("Caps", VirtualKeyCode.CAPITAL), GridWidth = new GridLength(1.7, GridUnitType.Star)},
79 | new OnScreenKey { GridRow = 2, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_A, new List { "a", "A" })},
80 | new OnScreenKey { GridRow = 2, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_S, new List { "s", "S" })},
81 | new OnScreenKey { GridRow = 2, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_D, new List { "d", "D" })},
82 | new OnScreenKey { GridRow = 2, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_F, new List { "f", "F" })},
83 | new OnScreenKey { GridRow = 2, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_G, new List { "g", "G" })},
84 | new OnScreenKey { GridRow = 2, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_H, new List { "h", "H" })},
85 | new OnScreenKey { GridRow = 2, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_J, new List { "j", "J" })},
86 | new OnScreenKey { GridRow = 2, GridColumn = 8, Key = new CaseSensitiveKey(VirtualKeyCode.VK_K, new List { "k", "K" })},
87 | new OnScreenKey { GridRow = 2, GridColumn = 9, Key = new CaseSensitiveKey(VirtualKeyCode.VK_L, new List { "l", "L" })},
88 | new OnScreenKey { GridRow = 2, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_1, new List { ";", ":" })},
89 | new OnScreenKey { GridRow = 2, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_7, new List { "\"", "\"" })},
90 | new OnScreenKey { GridRow = 2, GridColumn = 12, Key = new VirtualKey(VirtualKeyCode.RETURN, "Enter"), GridWidth = new GridLength(1.8, GridUnitType.Star)},
91 |
92 | new OnScreenKey { GridRow = 3, GridColumn = 0, Key = new InstantaneousModifierKey("Shift", VirtualKeyCode.SHIFT), GridWidth = new GridLength(2.4, GridUnitType.Star)},
93 | new OnScreenKey { GridRow = 3, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Z, new List { "z", "Z" })},
94 | new OnScreenKey { GridRow = 3, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_X, new List { "x", "X" })},
95 | new OnScreenKey { GridRow = 3, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_C, new List { "c", "C" })},
96 | new OnScreenKey { GridRow = 3, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_V, new List { "v", "V" })},
97 | new OnScreenKey { GridRow = 3, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_B, new List { "b", "B" })},
98 | new OnScreenKey { GridRow = 3, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_N, new List { "n", "N" })},
99 | new OnScreenKey { GridRow = 3, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_M, new List { "m", "M" })},
100 | new OnScreenKey { GridRow = 3, GridColumn = 8, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_COMMA, new List { ",", "<" })},
101 | new OnScreenKey { GridRow = 3, GridColumn = 9, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_PERIOD, new List { ".", ">" })},
102 | new OnScreenKey { GridRow = 3, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_2, new List { "/", "?" })},
103 | new OnScreenKey { GridRow = 3, GridColumn = 11, Key = new InstantaneousModifierKey("Shift", VirtualKeyCode.SHIFT), GridWidth = new GridLength(2.4, GridUnitType.Star)},
104 |
105 | new OnScreenKey { GridRow = 4, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.SPACE, " "), GridWidth = new GridLength(5, GridUnitType.Star)},
106 | };
107 |
108 | mainSection.Keys = mainKeys;
109 | mainSection.SetValue(ColumnProperty, 0);
110 | _sections.Add(mainSection);
111 | ColumnDefinitions.Add(new ColumnDefinition {Width = new GridLength(3, GridUnitType.Star)});
112 | Children.Add(mainSection);
113 |
114 | _allLogicalKeys.AddRange(mainKeys.Select(x => x.Key));
115 | _allOnScreenKeys.AddRange(mainSection.Keys);
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | _modifierKeys.AddRange(_allLogicalKeys.OfType());
124 | _allOnScreenKeys.ForEach(x => x.OnScreenKeyPress += OnScreenKeyPress);
125 |
126 | SynchroniseModifierKeyState();
127 |
128 | base.BeginInit();
129 | }
130 |
131 | void OnScreenKeyPress(DependencyObject sender, OnScreenKeyEventArgs e)
132 | {
133 | if (e.OnScreenKey.Key is ModifierKeyBase)
134 | {
135 | var modifierKey = (ModifierKeyBase)e.OnScreenKey.Key;
136 | if (modifierKey.KeyCode == VirtualKeyCode.SHIFT)
137 | {
138 | HandleShiftKeyPressed(modifierKey);
139 | }
140 | else if (modifierKey.KeyCode == VirtualKeyCode.CAPITAL)
141 | {
142 | HandleCapsLockKeyPressed(modifierKey);
143 | }
144 | else if (modifierKey.KeyCode == VirtualKeyCode.NUMLOCK)
145 | {
146 | HandleNumLockKeyPressed(modifierKey);
147 | }
148 | }
149 | else
150 | {
151 | ResetInstantaneousModifierKeys();
152 | }
153 | _modifierKeys.OfType().ToList().ForEach(x => x.SynchroniseKeyState());
154 | }
155 |
156 | private void SynchroniseModifierKeyState()
157 | {
158 | _modifierKeys.ToList().ForEach(x => x.SynchroniseKeyState());
159 | }
160 |
161 | private void ResetInstantaneousModifierKeys()
162 | {
163 | _modifierKeys.OfType().ToList().ForEach(x => { if (x.IsInEffect) x.Press(); });
164 | }
165 |
166 | void HandleShiftKeyPressed(ModifierKeyBase shiftKey)
167 | {
168 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex =
169 | InputSimulator.IsTogglingKeyInEffect(VirtualKeyCode.CAPITAL) ^ shiftKey.IsInEffect ? 1 : 0);
170 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex = shiftKey.IsInEffect ? 1 : 0);
171 | }
172 |
173 | void HandleCapsLockKeyPressed(ModifierKeyBase capsLockKey)
174 | {
175 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex =
176 | capsLockKey.IsInEffect ^ InputSimulator.IsKeyDownAsync(VirtualKeyCode.SHIFT) ? 1 : 0);
177 | }
178 |
179 | void HandleNumLockKeyPressed(ModifierKeyBase numLockKey)
180 | {
181 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex = numLockKey.IsInEffect? 1 : 0);
182 | }
183 | }
184 | }
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenKey.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows;
3 | using System.Windows.Controls;
4 | using System.Windows.Data;
5 | using System.Windows.Input;
6 | using System.Windows.Media;
7 | using System.Windows.Media.Animation;
8 | using WpfKb.LogicalKeys;
9 |
10 | namespace WpfKb.Controls
11 | {
12 | [TemplatePart(Name = ElementSurface, Type = typeof(Border))]
13 | [TemplatePart(Name = ElementMouseDownSurface, Type = typeof(Border))]
14 | [TemplatePart(Name = ElementKeyText, Type = typeof(TextBlock))]
15 | public class OnScreenKey : Control
16 | {
17 | private const string ElementSurface = "PART_Surface";
18 | private const string ElementMouseDownSurface = "PART_MouseDownSurface";
19 | private const string ElementKeyText = "PART_KeyText";
20 |
21 | public static readonly DependencyProperty KeyProperty = DependencyProperty.Register("Key", typeof(ILogicalKey), typeof(OnScreenKey), new UIPropertyMetadata(null, OnKeyChanged));
22 |
23 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(OnScreenKey), new UIPropertyMetadata(true));
24 | public static readonly DependencyProperty IsMouseOverAnimationEnabledProperty = DependencyProperty.Register("IsMouseOverAnimationEnabled", typeof(bool), typeof(OnScreenKey), new UIPropertyMetadata(false));
25 | public static readonly DependencyProperty IsOnScreenKeyDownProperty = DependencyProperty.Register("IsOnScreenKeyDown", typeof(bool), typeof(OnScreenKey), new UIPropertyMetadata(false));
26 | public static readonly DependencyProperty GridWidthProperty = DependencyProperty.Register("GridWidth", typeof(GridLength), typeof(OnScreenKey), new UIPropertyMetadata(new GridLength(1, GridUnitType.Star)));
27 |
28 | public static readonly DependencyProperty TextBrushProperty = DependencyProperty.Register("TextBrush", typeof(Brush), typeof(OnScreenKey), new PropertyMetadata(default(Brush)));
29 | public static readonly DependencyProperty OutsideBorderBrushProperty = DependencyProperty.Register("OutsideBorderBrush", typeof(Brush), typeof(OnScreenKey), new PropertyMetadata(default(Brush)));
30 | public static readonly DependencyProperty OutsideBorderThicknessProperty = DependencyProperty.Register("OutsideBorderThickness", typeof(Thickness), typeof(OnScreenKey), new PropertyMetadata(default(Thickness)));
31 |
32 | public static readonly DependencyProperty MouseOverBrushProperty = DependencyProperty.Register("MouseOverBrush", typeof(Brush), typeof(OnScreenKey), new PropertyMetadata(default(Brush)));
33 | public static readonly DependencyProperty MouseOverBorderBrushProperty = DependencyProperty.Register("MouseOverBorderBrush", typeof(Brush), typeof(OnScreenKey), new PropertyMetadata(default(Brush)));
34 |
35 | public static readonly RoutedEvent PreviewOnScreenKeyDownEvent = EventManager.RegisterRoutedEvent("PreviewOnScreenKeyDown", RoutingStrategy.Direct, typeof(OnScreenKeyEventHandler), typeof(OnScreenKey));
36 | public static readonly RoutedEvent PreviewOnScreenKeyUpEvent = EventManager.RegisterRoutedEvent("PreviewOnScreenKeyUp", RoutingStrategy.Direct, typeof(OnScreenKeyEventHandler), typeof(OnScreenKey));
37 | public static readonly RoutedEvent OnScreenKeyDownEvent = EventManager.RegisterRoutedEvent("OnScreenKeyDown", RoutingStrategy.Direct, typeof(OnScreenKeyEventHandler), typeof(OnScreenKey));
38 | public static readonly RoutedEvent OnScreenKeyUpEvent = EventManager.RegisterRoutedEvent("OnScreenKeyUp", RoutingStrategy.Direct, typeof(OnScreenKeyEventHandler), typeof(OnScreenKey));
39 | public static readonly RoutedEvent OnScreenKeyPressEvent = EventManager.RegisterRoutedEvent("OnScreenKeyPress", RoutingStrategy.Direct, typeof(OnScreenKeyEventHandler), typeof(OnScreenKey));
40 |
41 |
42 | private Border _keySurface;
43 | private Border _mouseDownSurface;
44 | private TextBlock _keyText;
45 | private Brush _keySurfaceBorderBrush;
46 | private Brush _keySurfaceBackground;
47 | private Brush _keyTextBrush;
48 |
49 |
50 | private readonly GradientBrush _keySurfaceMouseOverBrush = new LinearGradientBrush(
51 |
52 | new GradientStopCollection
53 |
54 | {
55 |
56 | new GradientStop(Color.FromRgb(0x78, 0x78, 0x78), 0),
57 |
58 | new GradientStop(Color.FromRgb(0x78, 0x78, 0x78), 0.6),
59 |
60 | new GradientStop(Color.FromRgb(0x50, 0x50, 0x50), 1)
61 |
62 | }, 90);
63 |
64 | public ILogicalKey Key
65 | {
66 | get { return (ILogicalKey)GetValue(KeyProperty); }
67 | set { SetValue(KeyProperty, value); }
68 | }
69 |
70 | #region NewTemplateBindings
71 | public Brush TextBrush
72 | {
73 | get { return (Brush)GetValue(TextBrushProperty); }
74 | set { SetValue(TextBrushProperty, value); }
75 | }
76 |
77 | public Brush OutsideBorderBrush
78 | {
79 | get { return (Brush)GetValue(OutsideBorderBrushProperty); }
80 | set { SetValue(OutsideBorderBrushProperty, value); }
81 | }
82 |
83 | public Thickness OutsideBorderThickness
84 | {
85 | get { return (Thickness)GetValue(OutsideBorderThicknessProperty); }
86 | set { SetValue(OutsideBorderThicknessProperty, value); }
87 | }
88 |
89 | public Brush MouseOverBrush
90 | {
91 | get { return (Brush)GetValue(MouseOverBrushProperty); }
92 | set { SetValue(MouseOverBrushProperty, value); }
93 | }
94 |
95 | public Brush MouseOverBorderBrush
96 | {
97 | get { return (Brush)GetValue(MouseOverBorderBrushProperty); }
98 | set { SetValue(MouseOverBorderBrushProperty, value); }
99 | }
100 | #endregion
101 |
102 |
103 |
104 | public bool AreAnimationsEnabled
105 | {
106 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
107 | set { SetValue(AreAnimationsEnabledProperty, value); }
108 | }
109 |
110 | public bool IsMouseOverAnimationEnabled
111 | {
112 | get { return (bool)GetValue(IsMouseOverAnimationEnabledProperty); }
113 | set { SetValue(IsMouseOverAnimationEnabledProperty, value); }
114 | }
115 |
116 | public bool IsOnScreenKeyDown
117 | {
118 | get { return (bool)GetValue(IsOnScreenKeyDownProperty); }
119 | private set { SetValue(IsOnScreenKeyDownProperty, value); }
120 | }
121 |
122 | public int GridRow
123 | {
124 | get { return (int)GetValue(Grid.RowProperty); }
125 | set { SetValue(Grid.RowProperty, value); }
126 | }
127 |
128 | public int GridColumn
129 | {
130 | get { return (int)GetValue(Grid.ColumnProperty); }
131 | set { SetValue(Grid.ColumnProperty, value); }
132 | }
133 |
134 | public GridLength GridWidth
135 | {
136 | get { return (GridLength)GetValue(GridWidthProperty); }
137 | set { SetValue(GridWidthProperty, value); }
138 | }
139 |
140 |
141 | public override void OnApplyTemplate()
142 | {
143 | base.OnApplyTemplate();
144 |
145 | _keySurface = Template.FindName(ElementSurface, this) as Border;
146 | _mouseDownSurface = Template.FindName(ElementMouseDownSurface, this) as Border;
147 | _keyText = Template.FindName(ElementKeyText, this) as TextBlock;
148 |
149 | _keySurfaceBorderBrush = _keySurface?.BorderBrush;
150 | _keySurfaceBackground = _keySurface?.Background;
151 | _keyTextBrush = _keyText?.Foreground;
152 | _keyText?.SetBinding(TextBlock.TextProperty, new Binding("DisplayName") { Source = this.Key });
153 | }
154 |
155 |
156 | protected static void OnKeyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
157 | {
158 | ((OnScreenKey)sender).SetupControl((ILogicalKey)e.NewValue);
159 | }
160 |
161 | public event OnScreenKeyEventHandler PreviewOnScreenKeyDown
162 | {
163 | add { AddHandler(PreviewOnScreenKeyDownEvent, value); }
164 | remove { RemoveHandler(PreviewOnScreenKeyDownEvent, value); }
165 | }
166 |
167 | protected OnScreenKeyEventArgs RaisePreviewOnScreenKeyDownEvent()
168 | {
169 | var args = new OnScreenKeyEventArgs(PreviewOnScreenKeyDownEvent, this);
170 | RaiseEvent(args);
171 | return args;
172 | }
173 |
174 | public event OnScreenKeyEventHandler PreviewOnScreenKeyUp
175 | {
176 | add { AddHandler(PreviewOnScreenKeyUpEvent, value); }
177 | remove { RemoveHandler(PreviewOnScreenKeyUpEvent, value); }
178 | }
179 |
180 | protected OnScreenKeyEventArgs RaisePreviewOnScreenKeyUpEvent()
181 | {
182 | var args = new OnScreenKeyEventArgs(PreviewOnScreenKeyUpEvent, this);
183 | RaiseEvent(args);
184 | return args;
185 | }
186 |
187 | public event OnScreenKeyEventHandler OnScreenKeyDown
188 | {
189 | add { AddHandler(OnScreenKeyDownEvent, value); }
190 | remove { RemoveHandler(OnScreenKeyDownEvent, value); }
191 | }
192 |
193 | protected OnScreenKeyEventArgs RaiseOnScreenKeyDownEvent()
194 | {
195 | var args = new OnScreenKeyEventArgs(OnScreenKeyDownEvent, this);
196 | RaiseEvent(args);
197 | return args;
198 | }
199 |
200 | public event OnScreenKeyEventHandler OnScreenKeyUp
201 | {
202 | add { AddHandler(OnScreenKeyUpEvent, value); }
203 | remove { RemoveHandler(OnScreenKeyUpEvent, value); }
204 | }
205 |
206 | protected OnScreenKeyEventArgs RaiseOnScreenKeyUpEvent()
207 | {
208 | var args = new OnScreenKeyEventArgs(OnScreenKeyUpEvent, this);
209 | RaiseEvent(args);
210 | return args;
211 | }
212 |
213 | public event OnScreenKeyEventHandler OnScreenKeyPress
214 | {
215 | add { AddHandler(OnScreenKeyPressEvent, value); }
216 | remove { RemoveHandler(OnScreenKeyPressEvent, value); }
217 | }
218 |
219 | protected OnScreenKeyEventArgs RaiseOnScreenKeyPressEvent()
220 | {
221 | var args = new OnScreenKeyEventArgs(OnScreenKeyPressEvent, this);
222 | RaiseEvent(args);
223 | return args;
224 | }
225 |
226 | private void SetupControl(ILogicalKey key)
227 | {
228 | _keyText?.SetBinding(TextBlock.TextProperty, new Binding("DisplayName") {Source = key});
229 | key.PropertyChanged += Key_PropertyChanged;
230 | key.LogicalKeyPressed += Key_VirtualKeyPressed;
231 | }
232 |
233 | void Key_VirtualKeyPressed(object sender, LogicalKeyEventArgs e)
234 | {
235 | RaiseOnScreenKeyPressEvent();
236 | }
237 |
238 | void Key_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
239 | {
240 | if (Key is ModifierKeyBase && e.PropertyName == "IsInEffect")
241 | {
242 | var key = ((ModifierKeyBase)Key);
243 | if (key.IsInEffect)
244 | {
245 | AnimateMouseDown();
246 | }
247 | else
248 | {
249 | AnimateMouseUp();
250 | }
251 | }
252 | }
253 |
254 | protected override void OnMouseDown(MouseButtonEventArgs e)
255 | {
256 | HandleKeyDown();
257 | base.OnMouseDown(e);
258 | }
259 |
260 | protected void HandleKeyDown()
261 | {
262 | var args = RaisePreviewOnScreenKeyDownEvent();
263 | if (args.Handled == false)
264 | {
265 | IsOnScreenKeyDown = true;
266 | AnimateMouseDown();
267 | Key.Press();
268 | }
269 | RaiseOnScreenKeyDownEvent();
270 | }
271 |
272 | protected override void OnMouseUp(MouseButtonEventArgs e)
273 | {
274 | HandleKeyUp();
275 | base.OnMouseUp(e);
276 | }
277 |
278 | private void HandleKeyUp()
279 | {
280 | var args = RaisePreviewOnScreenKeyUpEvent();
281 | if (args.Handled == false)
282 | {
283 | IsOnScreenKeyDown = false;
284 | AnimateMouseUp();
285 | }
286 | RaiseOnScreenKeyUpEvent();
287 | }
288 |
289 | private void AnimateMouseDown()
290 | {
291 | _mouseDownSurface.BeginAnimation(OpacityProperty, new DoubleAnimation(1, new Duration(TimeSpan.Zero)));
292 | _keyText.Foreground = OutsideBorderBrush;
293 | }
294 |
295 | private void AnimateMouseUp()
296 | {
297 | if ((Key is TogglingModifierKey || Key is InstantaneousModifierKey) && ((ModifierKeyBase)Key).IsInEffect) return;
298 | _keySurface.BorderBrush = _keySurfaceBorderBrush;
299 | _keyText.Foreground = _keyTextBrush;
300 | if (!AreAnimationsEnabled || Key is TogglingModifierKey || Key is InstantaneousModifierKey)
301 | {
302 | _mouseDownSurface.BeginAnimation(OpacityProperty, new DoubleAnimation(0, new Duration(TimeSpan.Zero)));
303 | }
304 | else
305 | {
306 | _mouseDownSurface.BeginAnimation(OpacityProperty, new DoubleAnimation(0, Duration.Automatic));
307 | }
308 | }
309 |
310 | protected override void OnMouseEnter(MouseEventArgs e)
311 | {
312 | if (IsMouseOverAnimationEnabled)
313 | {
314 | if (MouseOverBrush != null)
315 | _keySurface.Background = MouseOverBrush;
316 |
317 | if (MouseOverBorderBrush != null)
318 | _keySurface.BorderBrush = MouseOverBorderBrush;
319 | }
320 | base.OnMouseEnter(e);
321 | }
322 |
323 | protected override void OnMouseLeave(MouseEventArgs e)
324 | {
325 | if (IsMouseOverAnimationEnabled)
326 | {
327 | if (Key is TogglingModifierKey && ((ModifierKeyBase)Key).IsInEffect) return;
328 |
329 | _keySurface.Background = _keySurfaceBackground;
330 | _keySurface.BorderBrush = _keySurfaceBorderBrush;
331 | }
332 | if (IsOnScreenKeyDown)
333 | {
334 | HandleKeyUp();
335 | }
336 | base.OnMouseLeave(e);
337 | }
338 | }
339 | }
--------------------------------------------------------------------------------
/WpfKb/Controls/OnScreenWebKeyboard.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Controls;
6 | using System.Windows.Input;
7 | using WpfKb.Input;
8 | using WpfKb.LogicalKeys;
9 |
10 |
11 | namespace WpfKb.Controls
12 | {
13 | public class OnScreenWebKeyboard : Grid
14 | {
15 | public static readonly DependencyProperty AreAnimationsEnabledProperty = DependencyProperty.Register("AreAnimationsEnabled", typeof(bool), typeof(OnScreenWebKeyboard), new UIPropertyMetadata(true, OnAreAnimationsEnabledPropertyChanged));
16 |
17 | private ObservableCollection _sections;
18 | private List _modifierKeys;
19 | private List _allLogicalKeys;
20 | private List _allOnScreenKeys;
21 |
22 | public bool AreAnimationsEnabled
23 | {
24 | get { return (bool)GetValue(AreAnimationsEnabledProperty); }
25 | set { SetValue(AreAnimationsEnabledProperty, value); }
26 | }
27 |
28 | private static void OnAreAnimationsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
29 | {
30 | var keyboard = (OnScreenWebKeyboard)d;
31 | keyboard._allOnScreenKeys.ToList().ForEach(x => x.AreAnimationsEnabled = (bool)e.NewValue);
32 | }
33 |
34 | public override void BeginInit()
35 | {
36 | SetValue(FocusManager.IsFocusScopeProperty, true);
37 | _modifierKeys = new List();
38 | _allLogicalKeys = new List();
39 | _allOnScreenKeys = new List();
40 |
41 | _sections = new ObservableCollection();
42 |
43 | var mainSection = new OnScreenKeyboardSection();
44 | var mainKeys = new ObservableCollection
45 | {
46 | new OnScreenKey { GridRow = 0, GridColumn = 0, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_3, new List { "`", "~" })},
47 | new OnScreenKey { GridRow = 0, GridColumn = 1, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_1, new List { "1", "!" })},
48 | new OnScreenKey { GridRow = 0, GridColumn = 2, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_2, new List { "2", "@" })},
49 | new OnScreenKey { GridRow = 0, GridColumn = 3, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_3, new List { "3", "#" })},
50 | new OnScreenKey { GridRow = 0, GridColumn = 4, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_4, new List { "4", "$" })},
51 | new OnScreenKey { GridRow = 0, GridColumn = 5, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_5, new List { "5", "%" })},
52 | new OnScreenKey { GridRow = 0, GridColumn = 6, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_6, new List { "6", "^" })},
53 | new OnScreenKey { GridRow = 0, GridColumn = 7, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_7, new List { "7", "&" })},
54 | new OnScreenKey { GridRow = 0, GridColumn = 8, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_8, new List { "8", "*" })},
55 | new OnScreenKey { GridRow = 0, GridColumn = 9, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_9, new List { "9", "(" })},
56 | new OnScreenKey { GridRow = 0, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.VK_0, new List { "0", ")" })},
57 | new OnScreenKey { GridRow = 0, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_MINUS, new List { "-", "_" })},
58 | new OnScreenKey { GridRow = 0, GridColumn = 12, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_PLUS, new List { "=", "+" })},
59 | new OnScreenKey { GridRow = 0, GridColumn = 13, Key = new VirtualKey(VirtualKeyCode.BACK, "Bksp"), GridWidth = new GridLength(2, GridUnitType.Star)},
60 |
61 | new OnScreenKey { GridRow = 1, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.TAB, "Tab"), GridWidth = new GridLength(1.5, GridUnitType.Star)},
62 | new OnScreenKey { GridRow = 1, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Q, new List { "q", "Q" })},
63 | new OnScreenKey { GridRow = 1, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_W, new List { "w", "W" })},
64 | new OnScreenKey { GridRow = 1, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_E, new List { "e", "E" })},
65 | new OnScreenKey { GridRow = 1, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_R, new List { "r", "R" })},
66 | new OnScreenKey { GridRow = 1, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_T, new List { "t", "T" })},
67 | new OnScreenKey { GridRow = 1, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Y, new List { "y", "Y" })},
68 | new OnScreenKey { GridRow = 1, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_U, new List { "u", "U" })},
69 | new OnScreenKey { GridRow = 1, GridColumn = 8, Key = new CaseSensitiveKey(VirtualKeyCode.VK_I, new List { "i", "I" })},
70 | new OnScreenKey { GridRow = 1, GridColumn = 9, Key = new CaseSensitiveKey(VirtualKeyCode.VK_O, new List { "o", "O" })},
71 | new OnScreenKey { GridRow = 1, GridColumn = 10, Key = new CaseSensitiveKey(VirtualKeyCode.VK_P, new List { "p", "P" })},
72 | new OnScreenKey { GridRow = 1, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_4, new List { "[", "{" })},
73 | new OnScreenKey { GridRow = 1, GridColumn = 12, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_6, new List { "]", "}" })},
74 | new OnScreenKey { GridRow = 1, GridColumn = 13, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_5, new List { "\\", "|" }), GridWidth = new GridLength(1.3, GridUnitType.Star)},
75 |
76 | new OnScreenKey { GridRow = 2, GridColumn = 0, Key = new TogglingModifierKey("Caps", VirtualKeyCode.CAPITAL), GridWidth = new GridLength(1.7, GridUnitType.Star)},
77 | new OnScreenKey { GridRow = 2, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_A, new List { "a", "A" })},
78 | new OnScreenKey { GridRow = 2, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_S, new List { "s", "S" })},
79 | new OnScreenKey { GridRow = 2, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_D, new List { "d", "D" })},
80 | new OnScreenKey { GridRow = 2, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_F, new List { "f", "F" })},
81 | new OnScreenKey { GridRow = 2, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_G, new List { "g", "G" })},
82 | new OnScreenKey { GridRow = 2, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_H, new List { "h", "H" })},
83 | new OnScreenKey { GridRow = 2, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_J, new List { "j", "J" })},
84 | new OnScreenKey { GridRow = 2, GridColumn = 8, Key = new CaseSensitiveKey(VirtualKeyCode.VK_K, new List { "k", "K" })},
85 | new OnScreenKey { GridRow = 2, GridColumn = 9, Key = new CaseSensitiveKey(VirtualKeyCode.VK_L, new List { "l", "L" })},
86 | new OnScreenKey { GridRow = 2, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_1, new List { ";", ":" })},
87 | new OnScreenKey { GridRow = 2, GridColumn = 11, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_7, new List { "\"", "\"" })},
88 | new OnScreenKey { GridRow = 2, GridColumn = 12, Key = new VirtualKey(VirtualKeyCode.RETURN, "Enter"), GridWidth = new GridLength(1.8, GridUnitType.Star)},
89 |
90 | new OnScreenKey { GridRow = 3, GridColumn = 0, Key = new InstantaneousModifierKey("Shift", VirtualKeyCode.SHIFT), GridWidth = new GridLength(2.4, GridUnitType.Star)},
91 | new OnScreenKey { GridRow = 3, GridColumn = 1, Key = new CaseSensitiveKey(VirtualKeyCode.VK_Z, new List { "z", "Z" })},
92 | new OnScreenKey { GridRow = 3, GridColumn = 2, Key = new CaseSensitiveKey(VirtualKeyCode.VK_X, new List { "x", "X" })},
93 | new OnScreenKey { GridRow = 3, GridColumn = 3, Key = new CaseSensitiveKey(VirtualKeyCode.VK_C, new List { "c", "C" })},
94 | new OnScreenKey { GridRow = 3, GridColumn = 4, Key = new CaseSensitiveKey(VirtualKeyCode.VK_V, new List { "v", "V" })},
95 | new OnScreenKey { GridRow = 3, GridColumn = 5, Key = new CaseSensitiveKey(VirtualKeyCode.VK_B, new List { "b", "B" })},
96 | new OnScreenKey { GridRow = 3, GridColumn = 6, Key = new CaseSensitiveKey(VirtualKeyCode.VK_N, new List { "n", "N" })},
97 | new OnScreenKey { GridRow = 3, GridColumn = 7, Key = new CaseSensitiveKey(VirtualKeyCode.VK_M, new List { "m", "M" })},
98 | new OnScreenKey { GridRow = 3, GridColumn = 8, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_COMMA, new List { ",", "<" })},
99 | new OnScreenKey { GridRow = 3, GridColumn = 9, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_PERIOD, new List { ".", ">" })},
100 | new OnScreenKey { GridRow = 3, GridColumn = 10, Key = new ShiftSensitiveKey(VirtualKeyCode.OEM_2, new List { "/", "?" })},
101 | new OnScreenKey { GridRow = 3, GridColumn = 11, Key = new InstantaneousModifierKey("Shift", VirtualKeyCode.SHIFT), GridWidth = new GridLength(2.4, GridUnitType.Star)},
102 |
103 | new OnScreenKey { GridRow = 4, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.SPACE, " "), GridWidth = new GridLength(5, GridUnitType.Star)},
104 | new OnScreenKey { GridRow = 4, GridColumn = 1, Key = new StringKey("http://", "http://")},
105 | new OnScreenKey { GridRow = 4, GridColumn = 2, Key = new StringKey("https://", "https://")},
106 | };
107 |
108 | mainSection.Keys = mainKeys;
109 | mainSection.SetValue(ColumnProperty, 0);
110 | _sections.Add(mainSection);
111 | ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) });
112 | Children.Add(mainSection);
113 |
114 | _allLogicalKeys.AddRange(mainKeys.Select(x => x.Key));
115 | _allOnScreenKeys.AddRange(mainSection.Keys);
116 |
117 |
118 | var specialSection = new OnScreenKeyboardSection();
119 | var specialKeys = new ObservableCollection
120 | {
121 | new OnScreenKey { GridRow = 0, GridColumn = 0, Key = new ChordKey("Select All", VirtualKeyCode.CONTROL, VirtualKeyCode.VK_A), GridWidth = new GridLength(2, GridUnitType.Star)},
122 | new OnScreenKey { GridRow = 0, GridColumn = 1, Key = new ChordKey("Undo", VirtualKeyCode.CONTROL, VirtualKeyCode.VK_Z) },
123 | new OnScreenKey { GridRow = 1, GridColumn = 0, Key = new ChordKey("Copy", VirtualKeyCode.CONTROL, VirtualKeyCode.VK_C) },
124 | new OnScreenKey { GridRow = 1, GridColumn = 1, Key = new ChordKey("Cut", VirtualKeyCode.CONTROL, VirtualKeyCode.VK_X) },
125 | new OnScreenKey { GridRow = 1, GridColumn = 2, Key = new ChordKey("Paste", VirtualKeyCode.CONTROL, VirtualKeyCode.VK_V) },
126 | new OnScreenKey { GridRow = 2, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.DELETE, "Del") },
127 | new OnScreenKey { GridRow = 2, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.HOME, "Home") },
128 | new OnScreenKey { GridRow = 2, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.END, "End") },
129 | new OnScreenKey { GridRow = 3, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.PRIOR, "PgUp") },
130 | new OnScreenKey { GridRow = 3, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.UP, "Up") },
131 | new OnScreenKey { GridRow = 3, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.NEXT, "PgDn") },
132 | new OnScreenKey { GridRow = 4, GridColumn = 0, Key = new VirtualKey(VirtualKeyCode.LEFT, "Left") },
133 | new OnScreenKey { GridRow = 4, GridColumn = 1, Key = new VirtualKey(VirtualKeyCode.DOWN, "Down") },
134 | new OnScreenKey { GridRow = 4, GridColumn = 2, Key = new VirtualKey(VirtualKeyCode.RIGHT, "Right") },
135 | };
136 |
137 | specialSection.Keys = specialKeys;
138 | specialSection.SetValue(ColumnProperty, 1);
139 | _sections.Add(specialSection);
140 | ColumnDefinitions.Add(new ColumnDefinition());
141 | Children.Add(specialSection);
142 |
143 | _allLogicalKeys.AddRange(specialKeys.Select(x => x.Key));
144 | _allOnScreenKeys.AddRange(specialSection.Keys);
145 |
146 |
147 |
148 | _modifierKeys.AddRange(_allLogicalKeys.OfType());
149 | _allOnScreenKeys.ForEach(x => x.OnScreenKeyPress += OnScreenKeyPress);
150 |
151 | SynchroniseModifierKeyState();
152 |
153 | base.BeginInit();
154 | }
155 |
156 | void OnScreenKeyPress(DependencyObject sender, OnScreenKeyEventArgs e)
157 | {
158 | if (e.OnScreenKey.Key is ModifierKeyBase)
159 | {
160 | var modifierKey = (ModifierKeyBase)e.OnScreenKey.Key;
161 | if (modifierKey.KeyCode == VirtualKeyCode.SHIFT)
162 | {
163 | HandleShiftKeyPressed(modifierKey);
164 | }
165 | else if (modifierKey.KeyCode == VirtualKeyCode.CAPITAL)
166 | {
167 | HandleCapsLockKeyPressed(modifierKey);
168 | }
169 | else if (modifierKey.KeyCode == VirtualKeyCode.NUMLOCK)
170 | {
171 | HandleNumLockKeyPressed(modifierKey);
172 | }
173 | }
174 | else
175 | {
176 | ResetInstantaneousModifierKeys();
177 | }
178 | _modifierKeys.OfType().ToList().ForEach(x => x.SynchroniseKeyState());
179 | }
180 |
181 | private void SynchroniseModifierKeyState()
182 | {
183 | _modifierKeys.ToList().ForEach(x => x.SynchroniseKeyState());
184 | }
185 |
186 | private void ResetInstantaneousModifierKeys()
187 | {
188 | _modifierKeys.OfType().ToList().ForEach(x => { if (x.IsInEffect) x.Press(); });
189 | }
190 |
191 | void HandleShiftKeyPressed(ModifierKeyBase shiftKey)
192 | {
193 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex =
194 | InputSimulator.IsTogglingKeyInEffect(VirtualKeyCode.CAPITAL) ^ shiftKey.IsInEffect ? 1 : 0);
195 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex = shiftKey.IsInEffect ? 1 : 0);
196 | }
197 |
198 | void HandleCapsLockKeyPressed(ModifierKeyBase capsLockKey)
199 | {
200 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex =
201 | capsLockKey.IsInEffect ^ InputSimulator.IsKeyDownAsync(VirtualKeyCode.SHIFT) ? 1 : 0);
202 | }
203 |
204 | void HandleNumLockKeyPressed(ModifierKeyBase numLockKey)
205 | {
206 | _allLogicalKeys.OfType().ToList().ForEach(x => x.SelectedIndex = numLockKey.IsInEffect ? 1 : 0);
207 | }
208 | }
209 | }
--------------------------------------------------------------------------------