创作背景:因项目需求,需要创建一个可以运行在触摸屏上的模拟键盘,开机自启动,启动后显示的是圆形悬浮框,当有输入需求时(改为鼠标左键长按300ms以上),显示键盘,或者双击圆形悬浮框显示键盘。我们知道想要完成此功能,大致需要如下知识储备:1、使用SendKeys
类来模拟键盘输入。;2、使用全局钩子来监控某些触发事件(例如何时需要显示键盘等)。在开放过程中,也有一些小tips,在后文涉及到的地方会有详尽描述,文章末尾也有相关源码链接。
相关功能:1、首先要在窗体代码前添加相关设置,使操作键盘时,窗体不获取焦点(一旦获取焦点,需要输入的进程就无法获取输入光标了)。以下代码放置在窗体的类中即可。
/// <summary>
/// 此段代码不可删除
/// </summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= (int)0x08000000L;
return cp;
}
}
2、利用SendKeys类来模拟键盘输入。示例代码如下:
/// <summary>
/// 普通按键
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_SendText_Click(object sender, EventArgs e)
{
Button button = (Button)sender;
string strBtnTxt = button.Text;
strBtnTxt = StringConvert.GetFinalStr(strBtnTxt);
if (strBtnTxt.Length == 1)
{
if (char.IsLetter(strBtnTxt[0]))
{
SendKeys.Send(strBtnTxt.ToLower());
}
else
{
SendKeys.Send(strBtnTxt);
}
}
else if (strBtnTxt.Length > 1)
{
SendKeys.Send("{" + strBtnTxt + "}");
}
}
3、使用全局钩子监控鼠标状态。本意是想监控所有进程,看是否存在需要输入的编辑框获取到焦点,但是经过多次尝试之后并没有成功,这里也希望各位大佬指正并给出建议。那我退而求其次,利用鼠标左键长按300毫秒以上的状态来判断是否唤醒键盘。钩子的部分代码如下:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WFTouch
{
public class MouseHook
{
private const int WM_MOUSEMOVE = 0x200;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_RBUTTONDOWN = 0x204;
private const int WM_MBUTTONDOWN = 0x207;
private const int WM_LBUTTONUP = 0x202;
private const int WM_RBUTTONUP = 0x205;
private const int WM_MBUTTONUP = 0x208;
private const int WM_LBUTTONDBLCLK = 0x203;
private const int WM_RBUTTONDBLCLK = 0x206;
private const int WM_MBUTTONDBLCLK = 0x209;
static int hMouseHook = 0;
public const int WH_MOUSE_LL = 14;//low level mouse event
public const int WH_MOUSE = 7;//normal level mouse event
static HookProc MouseHookProcedure;
[StructLayout(LayoutKind.Sequential)]
public class POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseHookStruct
{
public POINT pt;
public int hWnd;
public int wHitTestCode;
public int dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int GetLastError();
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();//获取在系统中的线程ID
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam);
public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);
public MouseHook()
{
}
~MouseHook()
{
Stop();
}
public static void Start()
{
if (hMouseHook == 0)
{
MouseHookProcedure = new HookProc(MouseHookProc);
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProcedure, GetModuleHandle("user32"), 0);//第一个参数是WH_MOUSE_LL,表示捕获所有线程的鼠标消息,同时最后一个参数必须是0
if (hMouseHook == 0)
{
int errorCode = GetLastError();
}
}
}
public static void Stop()
{
bool retMouse = true;
if (hMouseHook != 0)
{
retMouse = UnhookWindowsHookEx(hMouseHook);
hMouseHook = 0;
}
}
private static int MouseHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
StaticClass.strMDC = wParam.ToString();
//只处理鼠标左键按下的情况
if (wParam == WM_LBUTTONDOWN && nCode >= 0)
{
MouseButtons button = MouseButtons.None;
int clickCount = 0;
switch (wParam)
{
case WM_LBUTTONDOWN:
button = MouseButtons.Left;
clickCount = 1;
break;
case WM_LBUTTONUP:
button = MouseButtons.Left;
clickCount = 1;
break;
case WM_LBUTTONDBLCLK:
button = MouseButtons.Left;
clickCount = 2;
break;
case WM_RBUTTONDOWN:
button = MouseButtons.Right;
clickCount = 1;
break;
case WM_RBUTTONUP:
button = MouseButtons.Right;
clickCount = 1;
break;
case WM_RBUTTONDBLCLK:
button = MouseButtons.Right;
clickCount = 2;
break;
}
StaticClass.strMDC = clickCount.ToString();
MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));
MouseEventArgs e = new MouseEventArgs(button, clickCount, MyMouseHookStruct.pt.x, MyMouseHookStruct.pt.y, 0);
}
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
}
}
4、钩子的调用。在主窗口类中进行调用,这里我是使用的Timer控件,可根据需要使用线程也可以。在timer的执行方法中,调用ttttt()方法即可,方法名称是测试的时候随意定义的,确实有点随意,大家担待😀。其中还有其他功能的部分操作,涉及到窗口显示的大小等。
Stopwatch stopwatchSign = new Stopwatch();
public void ttttt()
{
if (stopwatchSign != null && stopwatchSign.ElapsedMilliseconds >= 300)
{
this.WindowState = FormWindowState.Normal;
this.Width = StaticClass.width;
this.Height = StaticClass.height;
stopwatchSign.Reset();
}
if (StaticClass.strMDC.Equals("1") && !stopwatchSign.IsRunning && !this.Visible)
{
stopwatchSign.Restart();
}
if (!StaticClass.strMDC.Equals("1"))
{
stopwatchSign.Stop();
}
}
5、圆形悬浮框。话不多说,关键代码如下:
private void TopForm_Load1(object sender, EventArgs e)
{
string strPath = System.AppDomain.CurrentDomain.BaseDirectory + "keyboard.png";
backgroundImage = new Bitmap(strPath);
// 设置窗口区域为圆角矩形
Region = new Region(new Rectangle(0, 0, Width, Height));
GraphicsPath path = new GraphicsPath();
path.AddEllipse(0, 0, Width, Height);
Region = new Region(path);
}
protected override void OnResize(EventArgs e)
{
//base.OnResize(e);
base.OnResize(e);
// 调整背景图片以填满窗口
this.Invalidate(); // 触发重绘
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// 不执行任何操作,避免默认的矩形绘制
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// 在窗口上绘制背景图片
e.Graphics.DrawImage(backgroundImage, 0, 0, Width, Height);
}
以上是此项目的一些重要功能点,其余的均是相互调用和细节处理,相对简单。完整的代码在我的主页中可以找到,如果大家么有积分下载,又特别想要源码,可以在文章末尾留言。源码没有进行优化处理,一些测试代码尚有残留,仅供参考。
代码链接:点击跳转至源码地址