65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中的触摸屏键盘控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (43投票s)

2009年1月15日

CPOL

6分钟阅读

viewsIcon

347615

downloadIcon

16938

一个模拟真实键盘基本功能的触摸屏键盘。

登录表单中的触摸屏键盘

引言

对于一个触摸屏项目,用户需要一个触摸屏键盘来在文本框、密码框等中输入信息。因此,需要在 WPF 中实现一个键盘布局。这里使用 StackPanelButton 实现了一个自定义键盘布局。

触摸屏键盘是一个无样式的 WPF 窗口。为了使键盘窗口和应用程序窗口在调整大小、超出屏幕、最小化、最大化以及窗口激活和停用问题上同步,需要做大量工作。未提供 Ctrl、Alt、Function 和箭头键功能,但实现了包括 Enter、Backspace、Shift、TAB、CapsLock 在内的基本键功能。此触摸屏键盘的一些功能包括:

  • 窗口状态同步:它与窗口最大化、最小化同步。
  • 它在 x 轴上永不超出屏幕。
  • 它始终与应用程序窗口大小保持同步。
  • 它与窗口移动保持同步。
  • 无钩子:它不使用任何形式的钩子(无互操作性)。

键盘布局

键盘布局是使用 StackPanelButton 创建的。如果你看键盘布局,你会看到有五行。对于这五行,使用了五个带有水平方向的 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 的位置。

它如何将宿主控件的边框设为红色,背景设为黄色?

当宿主控件获得焦点时,其边框变为红色。因此,在 hostfocus 事件中,编写了以下代码:

_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 事件中,我们获取宿主控件的位置。然后,我们相应地设置触摸屏键盘窗口的位置。

触摸屏键盘窗口如何始终置于宿主窗口之上,并且不会与其他窗口产生问题?

将触摸屏键盘窗口设置为最顶层将不起作用。因为在这种情况下,触摸屏键盘将始终保持在机器上所有其他窗口的顶部。为了解决这个问题,我们借助宿主窗口的 ActivatewindowDeactivatewindow 事件。当宿主窗口被激活时,它会触发 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;
}

它订阅了父窗口的 ActivatedDeactivated 事件。在 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 日。
© . All rights reserved.