使用附加行为在 WPF 中创建屏幕键盘
使用 WPF 中的附加行为实现数字屏幕键盘。

引言
本文介绍了一种使用 WPF 中的附加属性和 UserControl
实现数字屏幕键盘的方法。它可以在 WPF 应用程序中像 Popup 控件一样使用。而且,您可以轻松地将此示例修改为适合您选择的任何语言的全尺寸屏幕键盘。
源代码在 Visual Studio 2008 SP1 中编译并针对 .NET 3.0 作为目标框架进行了测试,演示 EXE 可以在安装了 .NET 3.0 的 Windows XP 或 Windows Vista 中运行。
代码设计
以下是主要设计目标一览
- 通过在任何
FrameworkElement
上设置附加属性PopupKeyboard.IsEnabled="true"
来简单地启用屏幕键盘。 - 通过设置
Placement
、PlacementTarget
、PlacementRectangle
、HorizontalOffset
和VerticalOffset
属性来定位控件,就像定位Popup
控件一样。 - 通过双击键盘所附加的
FrameworkElement
,在Normal
和Hidden
状态之间切换键盘。 - 当鼠标光标离开
FrameworkElement
时,键盘将记住其最后的状态,以便以后使用。
Using the Code
要使用该控件,您需要在项目中包含 PopupKeyboard.xaml 和 PopupKeyboard.xaml.cs 文件,然后向窗口添加 xmlns
。
xmlns:local="clr-namespace:VirtualKeyboard"
设置完成后,以下一组附加属性将可用于该窗口中的任何 FrameworkElement
。
PopupKeyboard.Placement
PopupKeyboard.PlacementTarget
PopupKeyboard.PlacementRectangle
PopupKeyboard.HorizontalOffset
PopupKeyboard.VerticalOffset
PopupKeyboard.CustomPopupPlacementCallback
PopupKeyboard.State
PopupKeyboard.Height
PopupKeyboard.Width
PopupKeyboard.IsEnabled
您可以查阅 MSDN 文档 Popup Placement Behavior,了解如何设置附加属性 Placement
、PlacementTarget
、PlacementRectangle
、HorizontalOffset
、VerticalOffset
和 CustomPopupPlacementCallback
。附加属性 State
、Height
和 Width
分别设置初始键盘状态(Normal
或 Hidden
)、键盘高度和宽度。最后,附加属性 IsEnabled
设置一个值,指示键盘是否可用。
以下是如何设置这些附加属性的示例
<TextBox
x:Name="txtEmployeeID"
local:PopupKeyboard.Placement="Bottom"
local:PopupKeyboard.PlacementTarget="{Binding ElementName=txtEmployeeID}"
local:PopupKeyboard.HorizontalOffset="20"
local:PopupKeyboard.Height="220"
local:PopupKeyboard.Width="200"
local:PopupKeyboard.IsEnabled="true"/>
此外,您还可以查看演示源代码,了解有关如何使用数字屏幕键盘的更多信息。
工作原理
数字屏幕键盘主要由两个类组成:PopupKeyboardUserControl
和 PopupKeyboard
。内部类 PopupKeyboardUserControl
继承自 UserControl
,包含 XAML 和屏幕键盘的所有逻辑。类 PopupKeyboard
是一个 static
类,它引用 PopupKeyboardUserControl
对象,并定义了显示和定位屏幕键盘所需的所有附加属性。
接下来,我将简要描述这两个类的实际编码方式
1. 键盘事件模拟
为了模拟键盘事件,我使用 keybd_event
函数来合成击键,以下是 Windows API 函数的语法
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern void keybd_event
(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
在 PopupKeyboard.xaml 文件中定义的每个 Button
,其 Click
事件都链接到事件处理程序 cmdNumericButton_Click
,其中包含模拟键盘事件的逻辑。例如,当您单击 Button
"btn010300" 时,将执行以下代码行,它会模拟用户按下键盘按钮 "数字 1"。
...
// Number 1
case "btn010300":
keybd_event(VK_1, 0, 0, (UIntPtr)0);
keybd_event(VK_1, 0, KEYEVENTF_KEYUP, (UIntPtr)0);
// event already handled
e.Handled = true;
break;
...
通过修改 XAML 文件 PopupKeyboard.xaml 并更改事件处理程序 cmdNumericButton_Click
,您可以实现任何您想要的屏幕键盘类型,而其余源代码无需任何更改。
2. 键盘是一个 Popup 控件
类 PopupKeyboardUserControl
定义了一个名为 IsOpen
的属性,该属性控制键盘是否打开。当设置 IsOpen = true
时,将调用 HookupParentPopup()
函数,该函数实际上创建一个 Popup
控件并将键盘附加到它。您可以在下面查看相关源代码
/// <summary>
/// IsOpen
/// </summary>
public static readonly DependencyProperty IsOpenProperty =
Popup.IsOpenProperty.AddOwner(
typeof(PopupKeyboardUserControl),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(IsOpenChanged)));
public bool IsOpen
{
get { return (bool)GetValue(IsOpenProperty); }
set { SetValue(IsOpenProperty, value); }
}
/// <summary>
/// PropertyChangedCallback method for IsOpen Property
/// </summary>
/// <param name=""element""></param>
/// <param name=""e""></param>
private static void IsOpenChanged(DependencyObject element,
DependencyPropertyChangedEventArgs e)
{
PopupKeyboardUserControl ctrl = (PopupKeyboardUserControl)element;
if ((bool)e.NewValue)
{
if (ctrl._parentPopup == null)
{
ctrl.HookupParentPopup();
}
}
}
/// <summary>
/// Create the Popup and attach the CustomControl to it.
/// </summary>
private void HookupParentPopup()
{
_parentPopup = new Popup();
_parentPopup.AllowsTransparency = true;
_parentPopup.PopupAnimation = PopupAnimation.Scroll;
// Set Height and Width
this.Height = this.NormalHeight;
this.Width = this.NormalWidth;
Popup.CreateRootPopup(_parentPopup, this);
}
3. 定义附加属性
Static
类 PopupKeyboard
包含启用和定位数字屏幕键盘所需的所有附加属性。例如,我在此列出附加属性 IsEnabled
的定义
/// <summary>
/// IsEnabled
/// </summary>
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled",
typeof(bool),
typeof(PopupKeyboard),
new FrameworkPropertyMetadata(false,
new PropertyChangedCallback(PopupKeyboard.OnIsEnabledChanged)));
[AttachedPropertyBrowsableForType(typeof(FrameworkElement))]
public static bool GetIsEnabled(DependencyObject element)
{
if (element == null)
throw new ArgumentNullException("element");
return (bool)element.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject element, bool value)
{
if (element == null)
throw new ArgumentNullException("element");
element.SetValue(IsEnabledProperty, value);
}
/// <summary>
/// PropertyChangedCallback method for IsEnabled Attached Property
/// </summary>
/// <param name=""element""></param>
/// <param name=""e""></param>
private static void OnIsEnabledChanged
(DependencyObject element, DependencyPropertyChangedEventArgs e)
{
FrameworkElement frameworkElement = element as FrameworkElement;
// Attach & detach handlers for events GotKeyboardFocus,
// LostKeyboardFocus, MouseDown, and SizeChanged
if (frameworkElement != null)
{
if (((bool)e.NewValue == true) && ((bool)e.OldValue == false))
{
frameworkElement.AddHandler(FrameworkElement.GotKeyboardFocusEvent,
new KeyboardFocusChangedEventHandler
(frameworkElement_GotKeyboardFocus), true);
frameworkElement.AddHandler(FrameworkElement.LostKeyboardFocusEvent,
new KeyboardFocusChangedEventHandler
(frameworkElement_LostKeyboardFocus), true);
frameworkElement.AddHandler(FrameworkElement.MouseDownEvent,
new MouseButtonEventHandler(frameworkElement_MouseDown), true);
frameworkElement.AddHandler(FrameworkElement.SizeChangedEvent,
new SizeChangedEventHandler(frameworkElement_SizeChanged), true);
}
else if (((bool)e.NewValue == false) && ((bool)e.OldValue == true))
{
frameworkElement.RemoveHandler(FrameworkElement.GotKeyboardFocusEvent,
new KeyboardFocusChangedEventHandler
(frameworkElement_GotKeyboardFocus));
frameworkElement.RemoveHandler(FrameworkElement.LostKeyboardFocusEvent,
new KeyboardFocusChangedEventHandler
(frameworkElement_LostKeyboardFocus));
frameworkElement.RemoveHandler(FrameworkElement.MouseUpEvent,
new MouseButtonEventHandler(frameworkElement_MouseDown));
frameworkElement.RemoveHandler(FrameworkElement.SizeChangedEvent,
new SizeChangedEventHandler(frameworkElement_SizeChanged));
}
}
Window currentWindow = Window.GetWindow(element);
// Attach or detach handler for event LocationChanged
if (currentWindow != null)
{
if (((bool)e.NewValue == true) && ((bool)e.OldValue == false))
{
currentWindow.LocationChanged += currentWindow_LocationChanged;
}
else if (((bool)e.NewValue == false) && ((bool)e.OldValue == true))
{
currentWindow.LocationChanged -= currentWindow_LocationChanged;
}
}
}
当为控件设置附加属性 PopupKeyboard.IsEnabled="true"
时,将为该控件添加 GotKeyboardFocus
、LostKeyboardFocus
、MouseUp
和 SizeChanged
事件的事件处理程序,并为当前窗口添加 LocationChanged
事件的事件处理程序。这些事件处理程序包含通过将附加属性中的设置传递给 PopupKeyboardUserControl
对象中的相应属性设置来启用、隐藏和重新定位数字屏幕键盘的逻辑。它们基本上将附加属性与内部类 PopupKeyboardUserControl
联系起来。
4. 在 GotKeyboardFocus 和 LostKeyboardFocus 事件处理程序中传递值
当触发 GetKeyboardFocus
事件时,将创建 PopupKeyboardUserControl
的新实例,并将附加属性中的设置作为如下所示传递
/// <summary>
/// Event handler for GotKeyboardFocus
/// </summary>
/// <param name=""sender""></param>
/// <param name=""e""></param>
private static void frameworkElement_GotKeyboardFocus
(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
FrameworkElement frameworkElement = sender as FrameworkElement;
if (frameworkElement != null)
{
if (PopupKeyboard._popupKeyboardUserControl == null)
{
_popupKeyboardUserControl = new PopupKeyboardUserControl();
// Set all the necessary properties
_popupKeyboardUserControl.Placement =
PopupKeyboard.GetPlacement(frameworkElement);
_popupKeyboardUserControl.PlacementTarget =
PopupKeyboard.GetPlacementTarget(frameworkElement);
_popupKeyboardUserControl.PlacementRectangle =
PopupKeyboard.GetPlacementRectangle(frameworkElement);
_popupKeyboardUserControl.HorizontalOffset =
PopupKeyboard.GetHorizontalOffset(frameworkElement);
_popupKeyboardUserControl.VerticalOffset =
PopupKeyboard.GetVerticalOffset(frameworkElement);
_popupKeyboardUserControl.StaysOpen = true;
_popupKeyboardUserControl.CustomPopupPlacementCallback =
PopupKeyboard.GetCustomPopupPlacementCallback(frameworkElement);
_popupKeyboardUserControl.State = PopupKeyboard.GetState(frameworkElement);
_popupKeyboardUserControl.NormalHeight =
PopupKeyboard.GetHeight(frameworkElement);
_popupKeyboardUserControl.NormalWidth =
PopupKeyboard.GetWidth(frameworkElement);
if (PopupKeyboard.GetState(frameworkElement) == KeyboardState.Normal)
PopupKeyboard._popupKeyboardUserControl.IsOpen = true;
}
}
}
同样,当触发 LostKeyboardFocus
事件时,将保存属性 State
的最新设置(Normal
或 Hidden
),并通过设置 IsOpen = false
来关闭键盘
/// <summary>
/// Event handler for LostKeyboardFocus
/// </summary>
/// <param name=""sender""></param>
/// <param name=""e""></param>
private static void frameworkElement_LostKeyboardFocus
(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
FrameworkElement frameworkElement = sender as FrameworkElement;
if (frameworkElement != null)
{
if (PopupKeyboard._popupKeyboardUserControl != null)
{
// Retrieves the setting for the State property
PopupKeyboard.SetState(frameworkElement, _popupKeyboardUserControl.State);
PopupKeyboard._popupKeyboardUserControl.IsOpen = false;
PopupKeyboard._popupKeyboardUserControl = null;
}
}
}
反馈
就是这样。我希望您觉得本文内容丰富且具有指导意义,如果您喜欢,请投票。
历史
- 2008 年 12 月 20 日 - 首次发布