WPF 中的触摸屏键盘控件






4.88/5 (43投票s)
一个模拟真实键盘基本功能的触摸屏键盘。
引言
对于一个触摸屏项目,用户需要一个触摸屏键盘来在文本框、密码框等中输入信息。因此,需要在 WPF 中实现一个键盘布局。这里使用 StackPanel
和 Button
实现了一个自定义键盘布局。
触摸屏键盘是一个无样式的 WPF 窗口。为了使键盘窗口和应用程序窗口在调整大小、超出屏幕、最小化、最大化以及窗口激活和停用问题上同步,需要做大量工作。未提供 Ctrl、Alt、Function 和箭头键功能,但实现了包括 Enter、Backspace、Shift、TAB、CapsLock 在内的基本键功能。此触摸屏键盘的一些功能包括:
- 窗口状态同步:它与窗口最大化、最小化同步。
- 它在 x 轴上永不超出屏幕。
- 它始终与应用程序窗口大小保持同步。
- 它与窗口移动保持同步。
- 无钩子:它不使用任何形式的钩子(无互操作性)。
键盘布局
键盘布局是使用 StackPanel
和 Button
创建的。如果你看键盘布局,你会看到有五行。对于这五行,使用了五个带有水平方向的 StackPanel
。这些 StackPanel
中的每一个都包含其对应行的键(Button
)。一个带有垂直方向的父 StackPanel
包含这五个 StackPanel
。
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" >
//ALL the keys(Buttons) of the first row of the keyboard layout.
</StackPanel>
<StackPanel Orientation="Horizontal" >
//ALL the keys(Buttons) of the Second row of the keyboard layout.
</StackPanel>
<StackPanel Orientation="Horizontal" >
//ALL the keys(Buttons) of the third row of the keyboard layout.
</StackPanel>
<StackPanel Orientation="Horizontal" >
//ALL the keys(Buttons) of the fourth row of the keyboard layout.
</StackPanel>
<StackPanel Orientation="Horizontal" >
//ALL the keys(Buttons) of the fifth row of the keyboard layout.
</StackPanel>
</StackPanel>
这里,所有的键都是具有特定外观和感觉的 Button
。我要特别感谢 Mark Heath 的文章 “在 XAML 中创建自定义 WPF 按钮模板”。按钮样式取自那里,按钮的所有功劳都归他。所有按键都使用 WPF 命令处理。
Ben Constable 的 Remora 模式
根据 Ben Constable 的说法,Remora 模式允许您将一段逻辑附加到您拥有的任何现有元素上。此模式可以使用 WPF 中的附加依赖项属性实现,Ben Constable 的博客中对此进行了展示。您可以在此处查看。
在这里,一个附加依赖项属性附加到一个对象。当对象被初始化时,它会设置附加依赖项属性的值,这会导致调用附加依赖项属性更改事件。在事件处理程序中,您可以添加您想要的功能,这是对象的附加功能。
<PasswordBox k:TouchScreenKeyboard.TouchScreenKeyboard="true" x:Name="txtPassword" />
代码
public static readonly DependencyProperty TouchScreenKeyboardProperty =
DependencyProperty.RegisterAttached("TouchScreenKeyboard", typeof(bool),
typeof(TouchScreenKeyboard), new UIPropertyMetadata(default(bool),
TouchScreenKeyboardPropertyChanged));
在这里,从触摸屏自定义控件公开了一个 TouchScreenKeyboard
附加属性。要获取触摸屏键盘功能,您必须将 TouchScreenKeyboard
附加属性设置为文本框或密码框。当此文本框或密码框被初始化时,它会设置 TouchScreenKeyboard
附加属性的值,这反过来会调用 TouchScreenKeyboardPropertyChanged
事件。
static void TouchScreenKeyboardPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
FrameworkElement host = sender as FrameworkElement;
if (host != null)
{
host.GotFocus += new RoutedEventHandler(OnGotFocus);
host.LostFocus += new RoutedEventHandler(OnLostFocus);
}
}
在 TouchScreenKeyboardPropertyChanged
中,我们在聚焦和失去焦点事件中添加功能。这里,host
是您正在设置 TouchScreenKeyboard
附加属性的文本框或密码框。在 ONGOTFocus
事件处理程序中,您将显示触摸屏键盘以输入键,在 UNFocus
事件处理程序中,键盘将消失。
代码
它如何同时移动父窗口和键盘窗口?
当 WPF 窗口移动时,它会触发 LocationChanged
事件。在查找虚拟键盘窗口的父窗口的代码中,以下代码写在 host
的焦点事件中:
FrameworkElement ct = host;
while (true)
{
if (ct is Window)
{
((Window)ct).LocationChanged +=
new EventHandler(TouchScreenKeyboard_LocationChanged);
break;
}
ct = (FrameworkElement)ct.Parent;
}
获取父窗口后,订阅父窗口的 LocationChanged
事件。现在,每当父窗口移动时,它将调用 LocationChanged
事件处理程序方法,并相应地更新 TouchScreenWindow
的位置。
它如何使键盘窗口与父窗口最大化同步?
当父窗口最大化时,每个子控件都会触发 LayoutUpdate
事件。因此,在焦点事件中,以下代码订阅了作为 host
的文本框或密码框的 LayoutUpdate
事件:
host.LayoutUpdated += new EventHandler(tb_LayoutUpdated);
现在,每当父窗口最大化时,它将调用 LayoutUpdated
事件处理程序方法,并相应地更新 TouchScreenWindow
的位置。
它如何使键盘窗口与父窗口大小调整同步?
当父窗口大小调整时,每个子控件都会触发 LayoutUpdate
事件。因此,在焦点事件中,以下代码订阅了作为 host
的文本框或密码框的 LayoutUpdate
事件:
host.LayoutUpdated += new EventHandler(tb_LayoutUpdated);
现在,每当父窗口大小调整时,它将调用 LayoutUpdated
事件处理程序方法,并相应地更新 TouchScreenWindow
的位置。
它如何将宿主控件的边框设为红色,背景设为黄色?
当宿主控件获得焦点时,其边框变为红色。因此,在 host
的 focus
事件中,编写了以下代码:
_PreviousTextBoxBackgroundBrush = host.Background;
_PreviousTextBoxBorderBrush = host.BorderBrush;
_PreviousTextBoxBorderThickness = host.BorderThickness;
host.Background = Brushes.Yellow;
host.BorderBrush = Brushes.Red;
host.BorderThickness = new Thickness(4);
在此事件中更改边框颜色、背景和厚度。在进行更改之前,会保存边框的属性值,以便以后可以恢复其原始外观。在失去焦点事件中,它会恢复其原始外观。
它如何限制触摸屏键盘 WPF 窗口在 x 轴上超出屏幕?
SystemParameters.VirtualScreenWidth
返回虚拟屏幕的宽度(以像素为单位)。现在我们知道下边界是 0,上边界是 SystemParameters.VirtualScreenWidth
。所以,它所要做的就是经过一些逻辑处理。逻辑写在以下代码中:
if (WidthTouchKeyboard + Actualpoint.X > SystemParameters.VirtualScreenWidth)
{
double difference = WidthTouchKeyboard + Actualpoint.X -
SystemParameters.VirtualScreenWidth;
_InstanceObject.Left = Actualpoint.X - difference;
}
else if (!(Actualpoint.X > 1))
{
_InstanceObject.Left = 1;
}
else
_InstanceObject.Left = Actualpoint.X;
这里的代码所做的是检查触摸屏键盘的最左侧 x 轴值和最右侧 x 轴值是否超出屏幕。如果超出屏幕,则将其重置为适当的值。否则,它什么也不做。
如何将子窗口定位到宿主控件的精确位置?
当宿主控件获得焦点时,它会触发 focus
事件。在 focus
事件中,我们获取宿主控件的位置。然后,我们相应地设置触摸屏键盘窗口的位置。
触摸屏键盘窗口如何始终置于宿主窗口之上,并且不会与其他窗口产生问题?
将触摸屏键盘窗口设置为最顶层将不起作用。因为在这种情况下,触摸屏键盘将始终保持在机器上所有其他窗口的顶部。为了解决这个问题,我们借助宿主窗口的 Activatewindow
和 Deactivatewindow
事件。当宿主窗口被激活时,它会触发 Activated
事件,当宿主窗口被停用时,它会触发 Deactivated
事件。
FrameworkElement ct = host;
while (true)
{
if (ct is Window)
{
((Window)ct).Activated += new EventHandler(TouchScreenKeyboard_Activated);
((Window)ct).Deactivated += new EventHandler(TouchScreenKeyboard_Deactivated);
break;
}
ct = (FrameworkElement)ct.Parent;
}
它订阅了父窗口的 Activated
和 Deactivated
事件。在 Activated
事件中,触摸屏键盘被置于最顶层,在 Deactivated
事件中,它会重置。这是此功能的代码:
static void TouchScreenKeyboard_Deactivated(object sender, EventArgs e)
{
if (_InstanceObject != null)
{
_InstanceObject.Topmost = false;
}
}
static void TouchScreenKeyboard_Activated(object sender, EventArgs e)
{
if (_InstanceObject != null)
{
_InstanceObject.Topmost = true;
}
}
示例代码
这里附上了一个项目,展示了触摸屏键盘控件的实际应用。
结论
感谢您阅读此文。我希望这篇文章能对某些人有所帮助。如果大家有任何问题,我很乐意回答。
参考文献
历史
- 首次发布 – 2009 年 1 月 16 日。