如何让 Windows 10 平板电脑中的 "touchscreen keyboard" 停止在我们的 WPF 应用程序中打开两次?
How can I get the "touchscreen keyboard" in a Windows 10 tablet to stop opening twice in our WPF app?
请注意,我花了很多时间搜索包括 SO 在内的在线帖子,但到目前为止都没有成功。
问题是触摸屏键盘开始自动打开,因为 Windows 10 Touch Keyboard and Handwriting Panel Service
每当有人点击文本框时,而在 Windows 8.1 之前,键盘会打开仅由于来自我们的结构资产管理 (SAM) 应用程序的 C# API 调用。因此,在 Windows 10 中,只要有人单击文本框,虚拟键盘就会打开两次——一次是因为 SAM C# API 调用,一次是由于 Touch Keyboard and Handwriting Panel Service
.
请注意,我们已尝试禁用 Touch Keyboard and Handwriting Panel Service
,但这会导致触摸屏键盘根本不显示。
通常,让 OS 使用 Touch Keyboard and Handwriting Panel Service
打开此触摸屏键盘会很好,但问题是我们有时需要显示触摸屏键盘,而其他时候只显示数字键盘,因此仅依赖 Windows 服务不是一种选择。
以下是 类 在 Windows 8.1 中成功控制键盘的方法:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Input;
namespace Cpr.Apps.Sam.Controls
{
/// <summary>
/// Shows or hides the touch keyboard on tablets.
/// </summary>
public class TouchKeyboard
{
/// <summary>
/// The touch keyboard app's file path.
/// </summary>
private static string _touchKeyboardAppFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), @"Microsoft Shared\ink\TabTip.exe");
/// <summary>
/// Set to true if the app is currently running on a touch device and false otherwise.
/// </summary>
private static bool _isTouchScreen = Tablet.TabletDevices.Cast<TabletDevice>().Any(tabletDevice => tabletDevice.Type == TabletDeviceType.Touch);
/// <summary>
/// The keyboard visible flag.
/// </summary>
/// <remarks>
/// This flag only keeps track of the keyboard's visibility if it was set using this class.
/// </remarks>
private static bool _isKeyboardVisible;
/// <summary>
/// The delay after which the keyboard will be hidden, in seconds.
/// </summary>
/// <remarks>
/// The keyboard is not hidden immediately when the associated input field loses the keyboard focus, so that it will
/// not flicker if another input field with this behavior obtains the keyboard focus immediately afterwards.
/// </remarks>
private const double KEYBOARD_HIDE_DELAY = 0.25;
/// <summary>
/// The number of milliseconds per second. Used for time conversions.
/// </summary>
private const long MILLISECONDS_PER_SECOND = 1000;
/// <summary>
/// True if the current device has a touch screen and false otherwise.
/// </summary>
public static bool IsTouchScreen
{
get { return _isTouchScreen; }
}
/// <summary>
/// Shows the touch keyboard if the app is running on a touch device.
/// </summary>
/// <remarks>
/// This method does nothing if the app is not currently running on a touch device.
/// </remarks>
public static void Show()
{
// check if the app is running on a touch device
if (_isTouchScreen && _touchKeyboardAppFilePath != null)
{
try
{
// launch the touch keyboard app
Process.Start(_touchKeyboardAppFilePath);
// set the keyboard visible flag
_isKeyboardVisible = true;
}
catch (Exception)
{
// do nothing
}
}
}
/// <summary>
/// Hides the touch keyboard if the app is running on a touch device.
/// </summary>
/// <remarks>
/// This method does nothing if the app is not currently running on a touch device.
/// </remarks>
public static void Hide()
{
// check if the app is running on a touch device
if (_isTouchScreen)
{
// reset the keyboard visible flag
_isKeyboardVisible = false;
// hide the keyboard after a delay so that if another input field with this behavior obtains the focus immediately,
// the keyboard will not flicker
Timer timer = null;
timer = new Timer((obj) =>
{
// check if the keyboard should still be hidden
if (!_isKeyboardVisible)
{
// check if the keyboard is visible
var touchKeyboardWindowHandle = FindWindow("IPTip_Main_Window", null);
if (touchKeyboardWindowHandle != _nullPointer)
{
// hide the keyboard
SendMessage(touchKeyboardWindowHandle, WM_SYSCOMMAND, SC_CLOSE, _nullPointer);
}
}
// release the timer
timer.Dispose();
}, null, (long)(KEYBOARD_HIDE_DELAY * MILLISECONDS_PER_SECOND), Timeout.Infinite);
}
}
// Win32 null pointer parameter
private static IntPtr _nullPointer = new IntPtr(0);
// Win32 command from the Window menu
private const uint WM_SYSCOMMAND = 0x0112;
// Win32 command to close a window
private static IntPtr SC_CLOSE = new IntPtr(0xF060);
// Win32 API to get a window reference
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(string sClassName, string sAppName);
// Win32 API to send a message to a window
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
}
}
并且:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;
namespace Cpr.Apps.Sam.Styles.Behaviors
{
/// <summary>
/// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
/// </summary>
public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
{
protected override void OnAttached()
{
base.OnAttached();
// add the event handlers
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
}
/// <summary>
/// Called when the associated control receives the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// show the touch keyboard
TouchKeyboard.Show();
var textBox = sender as TextBox;
if (textBox != null)
textBox.SelectionStart = Math.Max(0, textBox.Text.Length); //Move the caret to the end of the text in the text box.
}
/// <summary>
/// Called when the associated control loses the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// hide the touch keyboard
TouchKeyboard.Hide();
}
}
}
基本上,我的问题是,如何让 Windows 10 中的触摸屏键盘与 Windows 8.1 中的一样?我可以在 OS 设置上更改一些配置值,还是需要更改注册表中的某些内容? Windows 8.1 和 Windows 10 中的 Touch Panel and Handwriting Service
有什么区别? TIA。
更新:
请注意,我已经探索过使用 "On Screen Keyboard",我认为它是基于 COM 而不是较新的 "Touchscreen Keyboard" 但这没有帮助,因为最终因为这个 COM 键盘需要管理员权限才能关闭或最小化它。这就是我尝试使用 "On Screen Keyboard":
using System;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;
namespace Cpr.Apps.Sam.Styles.Behaviors
{
/// <summary>
/// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
/// </summary>
public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
{
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
private const int SW_HIDE = 0;
private const int SW_RESTORE = 9;
private int hWnd;
private readonly string _USB = "USB";
private readonly string _keyboard = @"osk.exe";
private Process _keyboardProcess = null;
private ProcessStartInfo _startInfo = null;
protected override void OnAttached()
{
base.OnAttached();
// add the event handlers
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
}
private bool GetKeyboardPresent()
{
bool flag = false;
foreach (ManagementBaseObject managementBaseObject in new ManagementObjectSearcher("Select * from Win32_Keyboard").Get())
{
foreach (PropertyData property in managementBaseObject.Properties)
{
if (Convert.ToString(property.Value).Contains(this._USB))
{
flag = true;
break;
}
}
}
return flag;
}
/// <summary>
/// Called when the associated control receives the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// show the touch keyboard
// TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice. Use on screen keyboard instead.
//TouchKeyboard.Show();
if (!this.GetKeyboardPresent())
{
//_keyboardProcess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
Process[] pocesses = Process.GetProcessesByName(_keyboard);
if (pocesses.Any())
{
foreach (var proc in pocesses)
{
hWnd = (int) proc.MainWindowHandle;
ShowWindow(hWnd, SW_RESTORE);
}
}
else
{
_startInfo = new ProcessStartInfo(_keyboard);
_keyboardProcess = new Process
{
EnableRaisingEvents = true,
StartInfo = _startInfo
};
_keyboardProcess.Exited += new EventHandler(ProcessExited);
//Don't need this because it is for parent process: AppDomain.CurrentDomain.ProcessExit += (a, b) => _keyboardProcess.Kill();
_keyboardProcess.Start();
}
}
var textBox = sender as TextBox;
if (textBox != null)
textBox.SelectionStart = Math.Max(0, textBox.Text.Length); //Move the caret to the end of the text in the text box.
}
/// <summary>
/// Called when the associated control loses the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// hide the touch keyboard
// TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice. Use on screen keyboard instead.
//TouchKeyboard.Hide();
if (!GetKeyboardPresent() && _keyboardProcess != null)
{
//Keyboard doesn't minimize if I call Kill() or SW_HIDE, and instead this simply causes the textbox to lose focus so commented out this code
//Process[] pocesses = Process.GetProcessesByName("osk");
//for (int i = 0; i < pocesses.Count(); i++)
//{
// var proc = pocesses[i];
// proc.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
//hWnd = (int)proc.MainWindowHandle;
//ShowWindow(hWnd, SW_HIDE);
//}
//Task.Delay(500);
}
}
private void ProcessExited(object sender, System.EventArgs e)
{
Debug.WriteLine("Exited _keyboardProcess");
_keyboardProcess = null;
}
}
}
更新 2:
看来我可能需要将我的应用程序从 WPF 移植到 WinRT 才能在 Windows 10 上运行:请参阅 https://docs.microsoft.com/en-us/windows/uwp/porting/
Move from WPF and Silverlight to WinRT
在
Related Topics
最终使用 C# 内置的自定义 WPF 软件键盘,而不是 Windows“触摸键盘和手写面板服务”触摸屏键盘和屏幕键盘。
请注意,我花了很多时间搜索包括 SO 在内的在线帖子,但到目前为止都没有成功。
问题是触摸屏键盘开始自动打开,因为 Windows 10 Touch Keyboard and Handwriting Panel Service
每当有人点击文本框时,而在 Windows 8.1 之前,键盘会打开仅由于来自我们的结构资产管理 (SAM) 应用程序的 C# API 调用。因此,在 Windows 10 中,只要有人单击文本框,虚拟键盘就会打开两次——一次是因为 SAM C# API 调用,一次是由于 Touch Keyboard and Handwriting Panel Service
.
请注意,我们已尝试禁用 Touch Keyboard and Handwriting Panel Service
,但这会导致触摸屏键盘根本不显示。
通常,让 OS 使用 Touch Keyboard and Handwriting Panel Service
打开此触摸屏键盘会很好,但问题是我们有时需要显示触摸屏键盘,而其他时候只显示数字键盘,因此仅依赖 Windows 服务不是一种选择。
以下是 类 在 Windows 8.1 中成功控制键盘的方法:
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Input;
namespace Cpr.Apps.Sam.Controls
{
/// <summary>
/// Shows or hides the touch keyboard on tablets.
/// </summary>
public class TouchKeyboard
{
/// <summary>
/// The touch keyboard app's file path.
/// </summary>
private static string _touchKeyboardAppFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), @"Microsoft Shared\ink\TabTip.exe");
/// <summary>
/// Set to true if the app is currently running on a touch device and false otherwise.
/// </summary>
private static bool _isTouchScreen = Tablet.TabletDevices.Cast<TabletDevice>().Any(tabletDevice => tabletDevice.Type == TabletDeviceType.Touch);
/// <summary>
/// The keyboard visible flag.
/// </summary>
/// <remarks>
/// This flag only keeps track of the keyboard's visibility if it was set using this class.
/// </remarks>
private static bool _isKeyboardVisible;
/// <summary>
/// The delay after which the keyboard will be hidden, in seconds.
/// </summary>
/// <remarks>
/// The keyboard is not hidden immediately when the associated input field loses the keyboard focus, so that it will
/// not flicker if another input field with this behavior obtains the keyboard focus immediately afterwards.
/// </remarks>
private const double KEYBOARD_HIDE_DELAY = 0.25;
/// <summary>
/// The number of milliseconds per second. Used for time conversions.
/// </summary>
private const long MILLISECONDS_PER_SECOND = 1000;
/// <summary>
/// True if the current device has a touch screen and false otherwise.
/// </summary>
public static bool IsTouchScreen
{
get { return _isTouchScreen; }
}
/// <summary>
/// Shows the touch keyboard if the app is running on a touch device.
/// </summary>
/// <remarks>
/// This method does nothing if the app is not currently running on a touch device.
/// </remarks>
public static void Show()
{
// check if the app is running on a touch device
if (_isTouchScreen && _touchKeyboardAppFilePath != null)
{
try
{
// launch the touch keyboard app
Process.Start(_touchKeyboardAppFilePath);
// set the keyboard visible flag
_isKeyboardVisible = true;
}
catch (Exception)
{
// do nothing
}
}
}
/// <summary>
/// Hides the touch keyboard if the app is running on a touch device.
/// </summary>
/// <remarks>
/// This method does nothing if the app is not currently running on a touch device.
/// </remarks>
public static void Hide()
{
// check if the app is running on a touch device
if (_isTouchScreen)
{
// reset the keyboard visible flag
_isKeyboardVisible = false;
// hide the keyboard after a delay so that if another input field with this behavior obtains the focus immediately,
// the keyboard will not flicker
Timer timer = null;
timer = new Timer((obj) =>
{
// check if the keyboard should still be hidden
if (!_isKeyboardVisible)
{
// check if the keyboard is visible
var touchKeyboardWindowHandle = FindWindow("IPTip_Main_Window", null);
if (touchKeyboardWindowHandle != _nullPointer)
{
// hide the keyboard
SendMessage(touchKeyboardWindowHandle, WM_SYSCOMMAND, SC_CLOSE, _nullPointer);
}
}
// release the timer
timer.Dispose();
}, null, (long)(KEYBOARD_HIDE_DELAY * MILLISECONDS_PER_SECOND), Timeout.Infinite);
}
}
// Win32 null pointer parameter
private static IntPtr _nullPointer = new IntPtr(0);
// Win32 command from the Window menu
private const uint WM_SYSCOMMAND = 0x0112;
// Win32 command to close a window
private static IntPtr SC_CLOSE = new IntPtr(0xF060);
// Win32 API to get a window reference
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindow(string sClassName, string sAppName);
// Win32 API to send a message to a window
[DllImport("user32.dll", EntryPoint = "SendMessage", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
}
}
并且:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;
namespace Cpr.Apps.Sam.Styles.Behaviors
{
/// <summary>
/// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
/// </summary>
public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
{
protected override void OnAttached()
{
base.OnAttached();
// add the event handlers
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
}
/// <summary>
/// Called when the associated control receives the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// show the touch keyboard
TouchKeyboard.Show();
var textBox = sender as TextBox;
if (textBox != null)
textBox.SelectionStart = Math.Max(0, textBox.Text.Length); //Move the caret to the end of the text in the text box.
}
/// <summary>
/// Called when the associated control loses the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// hide the touch keyboard
TouchKeyboard.Hide();
}
}
}
基本上,我的问题是,如何让 Windows 10 中的触摸屏键盘与 Windows 8.1 中的一样?我可以在 OS 设置上更改一些配置值,还是需要更改注册表中的某些内容? Windows 8.1 和 Windows 10 中的 Touch Panel and Handwriting Service
有什么区别? TIA。
更新:
请注意,我已经探索过使用 "On Screen Keyboard",我认为它是基于 COM 而不是较新的 "Touchscreen Keyboard" 但这没有帮助,因为最终因为这个 COM 键盘需要管理员权限才能关闭或最小化它。这就是我尝试使用 "On Screen Keyboard":
using System;
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using Cpr.Apps.Sam.Controls;
namespace Cpr.Apps.Sam.Styles.Behaviors
{
/// <summary>
/// Behavior that shows the touch keyboard (on tablets only) when the associated control gets the keyboard focus.
/// </summary>
public class ControlShowTouchKeyboardOnFocusBehavior : Behavior<Control>
{
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
private const int SW_HIDE = 0;
private const int SW_RESTORE = 9;
private int hWnd;
private readonly string _USB = "USB";
private readonly string _keyboard = @"osk.exe";
private Process _keyboardProcess = null;
private ProcessStartInfo _startInfo = null;
protected override void OnAttached()
{
base.OnAttached();
// add the event handlers
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "GotKeyboardFocus", OnGotKeyboardFocus);
WeakEventManager<Control, KeyboardFocusChangedEventArgs>.AddHandler(AssociatedObject, "LostKeyboardFocus", OnLostKeyboardFocus);
}
private bool GetKeyboardPresent()
{
bool flag = false;
foreach (ManagementBaseObject managementBaseObject in new ManagementObjectSearcher("Select * from Win32_Keyboard").Get())
{
foreach (PropertyData property in managementBaseObject.Properties)
{
if (Convert.ToString(property.Value).Contains(this._USB))
{
flag = true;
break;
}
}
}
return flag;
}
/// <summary>
/// Called when the associated control receives the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// show the touch keyboard
// TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice. Use on screen keyboard instead.
//TouchKeyboard.Show();
if (!this.GetKeyboardPresent())
{
//_keyboardProcess.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
Process[] pocesses = Process.GetProcessesByName(_keyboard);
if (pocesses.Any())
{
foreach (var proc in pocesses)
{
hWnd = (int) proc.MainWindowHandle;
ShowWindow(hWnd, SW_RESTORE);
}
}
else
{
_startInfo = new ProcessStartInfo(_keyboard);
_keyboardProcess = new Process
{
EnableRaisingEvents = true,
StartInfo = _startInfo
};
_keyboardProcess.Exited += new EventHandler(ProcessExited);
//Don't need this because it is for parent process: AppDomain.CurrentDomain.ProcessExit += (a, b) => _keyboardProcess.Kill();
_keyboardProcess.Start();
}
}
var textBox = sender as TextBox;
if (textBox != null)
textBox.SelectionStart = Math.Max(0, textBox.Text.Length); //Move the caret to the end of the text in the text box.
}
/// <summary>
/// Called when the associated control loses the keyboard focus.
/// </summary>
/// <param name="sender">The object triggering this event.</param>
/// <param name="e">The event parameters.</param>
private void OnLostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
// hide the touch keyboard
// TouchKeyboard call here not needed in Windows 10 because of “Touch Keyboard and Handwriting Panel Service” causes virtual keyboard to show up twice. Use on screen keyboard instead.
//TouchKeyboard.Hide();
if (!GetKeyboardPresent() && _keyboardProcess != null)
{
//Keyboard doesn't minimize if I call Kill() or SW_HIDE, and instead this simply causes the textbox to lose focus so commented out this code
//Process[] pocesses = Process.GetProcessesByName("osk");
//for (int i = 0; i < pocesses.Count(); i++)
//{
// var proc = pocesses[i];
// proc.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
//hWnd = (int)proc.MainWindowHandle;
//ShowWindow(hWnd, SW_HIDE);
//}
//Task.Delay(500);
}
}
private void ProcessExited(object sender, System.EventArgs e)
{
Debug.WriteLine("Exited _keyboardProcess");
_keyboardProcess = null;
}
}
}
更新 2:
看来我可能需要将我的应用程序从 WPF 移植到 WinRT 才能在 Windows 10 上运行:请参阅 https://docs.microsoft.com/en-us/windows/uwp/porting/
Move from WPF and Silverlight to WinRT
在
Related Topics
最终使用 C# 内置的自定义 WPF 软件键盘,而不是 Windows“触摸键盘和手写面板服务”触摸屏键盘和屏幕键盘。