文章目录
前言
前阵子因为种种原因,想把键盘锁定起来,在网上搜寻工具甚久,未果,于是打算自己实现。
本文将介绍如何用C#窗体WinForm开发一个小工具,实现拦截和处理键盘事件、锁定键盘、解锁键盘的功能。
流程
- 1.创建窗体、按钮
- 2.创建键盘控制类
- 3.引入WindowsAPI、DLL
- 4.实现一键锁定/解锁键盘
- 5.异常退出的应对方法
- 6.组合键的锁定
实现
1.创建窗体、按钮
新建一个winform窗体,并且放置两个按钮
2. 建立WindowsAPI类
右键项目,新建一个cs类,我这里叫做KeyBroadClass。我们将在这类中引入WindowsAPI、DLL,方便调用。
3. 引入WindowsAPI、DLL
3.1添加键盘钩子
private const int WH_KEYBOARD_LL = 13; // 定义键盘低级钩子常量
private const int WM_KEYDOWN = 0x0100; // 定义键盘按下消息常量
3.2技术细节
钩子:钩子(Hook)是一种在Windows操作系统中用于拦截和处理事件的机制。它允许应用程序截获并处理其他应用程序或操作系统本身产生的消息、事件或指令。在钩子机制下,应用程序可以对系统事件进行监视、控制和处理,从而实现一些特定的功能或行为。常见的钩子类型包括键盘钩子、鼠标钩子、消息钩子等。
常见的低级钩子例如:
常量值是windows定义写死的,因此在代码中可以直接使用
- 键盘高级钩子(WH_KEYBOARD):2
- 鼠标高级钩子(WH_MOUSE):7
- 消息高级钩子(WH_MSGFILTER):-1
- CBT高级钩子(WH_CBT):5
- Shell高级钩子(WH_SHELL):10
- 线程高级钩子(WH_CALLWNDPROC):4
- 键盘低级钩子(WH_KEYBOARD_LL):13
- 鼠标低级钩子(WH_MOUSE_LL):14
低级钩子和高级钩子的区别:
低级钩子(Low-level Hook)和高级钩子(High-level Hook)是钩子的两种类型。
低级钩子是一种系统级别的钩子,它可以拦截并处理所有的系统事件,包括鼠标、键盘、消息等,也可以阻止事件的传递。低级钩子的优点在于它可以截获所有的事件,但缺点是它会影响系统性能,因此需要谨慎使用。
高级钩子是一种应用级别的钩子,它只能拦截并处理当前应用程序产生的事件,无法拦截系统级别的事件。高级钩子的优点在于它不会影响系统性能,缺点是它无法监控全局事件。
我们这里选择低级钩子,以便进行全局键盘的拦截和控制。
3.3引入dll方法
// 声明钩子回调函数委托
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
// 导入Windows API函数,用于设置键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
// 导入Windows API函数,用于卸载键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
// 导入Windows API函数,用于传递钩子事件到下一个钩子处理程序
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
其中关键方法是SetWindowsHookEx,我们即将用它锁定键盘。以下是方法参数的含义,其他更多请查阅资料。
SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
- idHook:指定要安装的钩子类型。
- lpfn:指定钩子回调函数的地址。
- hMod:指定要监视的线程。
- dwThreadId:指定要安装的钩子所属的进程ID。
依照方法,继续定义参数
private IntPtr hookId = IntPtr.Zero; // 钩子句柄
private LowLevelKeyboardProc keyboardProc;
// 构造函数
public KeyBroadClass()
{
keyboardProc = HookCallback; // 设置钩子回调函数
}
// 声明钩子回调函数委托
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
添加方法,去调用外部DLL方法
// 钩子回调函数,用于拦截和处理键盘事件
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
// 禁止键盘事件
return (IntPtr)1;
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
// 方法用于锁定键盘
public bool LockKeyboard()
{
// 安装键盘钩子
hookId = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardProc, IntPtr.Zero, 0);
return true;
}
// 方法用于解锁键盘
public bool UnlockKeyboard()
{
// 卸载键盘钩子
if (hookId != IntPtr.Zero)
{
UnhookWindowsHookEx(hookId);
hookId = IntPtr.Zero;
return true;
}
return false;
}
一些参数含义的解释,例:SetWindowsHookEx,使用了SetWindowsHookEx后会传回hookid,这是当前的钩子句柄,我们后面解锁或控制都得使用这个hookid。
- WH_KEYBOARD_LL:传入13,代表是键盘低级钩子
- keyboardProc:指定钩子回调函数的地址,即当钩子事件发生时要执行的函数。传入我们刚刚的写的委托。
- IntPtr.Zero:这里为0表示监视所有线程。
- 0:这里为0表示安装全局钩子。
此时,拦截键盘和锁定类方法就完整了
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace 你的命名空间
{
public class KeyBroadClass
{
private const int WH_KEYBOARD_LL = 13; // 定义键盘低级钩子常量
private const int WM_KEYDOWN = 0x0100; // 定义键盘按下消息常量
private IntPtr hookId = IntPtr.Zero; // 钩子句柄
private LowLevelKeyboardProc keyboardProc;
// 构造函数
public KeyBroadClass()
{
keyboardProc = HookCallback; // 设置钩子回调函数
}
// 声明钩子回调函数委托
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
// 导入Windows API函数,用于设置键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
// 导入Windows API函数,用于卸载键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
// 导入Windows API函数,用于传递钩子事件到下一个钩子处理程序
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
// 钩子回调函数,用于拦截和处理键盘事件
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
// 禁止键盘事件
return (IntPtr)1;
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
// 方法用于锁定键盘
public bool LockKeyboard()
{
// 安装键盘钩子
hookId = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardProc, IntPtr.Zero, 0);
return true;
}
// 方法用于解锁键盘
public bool UnlockKeyboard()
{
// 卸载键盘钩子
if (hookId != IntPtr.Zero)
{
UnhookWindowsHookEx(hookId);
hookId = IntPtr.Zero;
return true;
}
return false;
}
}
}
4.实现一键锁定/解锁键盘
返回窗体,双击按钮添加方法,并且添加键盘类的实例化,调用键盘类方法。
两个按钮我命名为button1和button2。先把button2的Enabled属性设置为false,当锁定键盘后再为true。并且锁定键盘后要把button1的Enabled设置为true,以防误操作。
根据逻辑,填写代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LockKeyBoard
{
public partial class Form1 : Form
{
//实例化刚刚的键盘类
KeyBroadClass KeyBroad = new KeyBroadClass();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//锁定键盘
if (KeyBroad.LockKeyboard())
{
MessageBox.Show("咋瓦鲁多!键盘已锁定。", "菊菊提示");
button1.Enabled = false;
button2.Enabled = true;
}
else
{
MessageBox.Show("键盘锁定失败。", "菊菊提示");
}
}
private void button2_Click(object sender, EventArgs e)
{
//解锁键盘
if (KeyBroad.UnlockKeyboard())
{
MessageBox.Show("时间开始流逝……键盘解锁。", "菊菊提示");
button1.Enabled = true;
button2.Enabled = false;
}
else
{
MessageBox.Show("键盘解锁失败。", "菊菊提示");
}
}
}
}
至此已经差不多完成了99%,键盘锁定和解锁已经实现。
5.异常情况的处理
虽然实现了功能,但是还不能结束,要考虑到程序异常退出的情况。如果在锁定键盘的情况下程序异常退出,键盘将会一直都是锁定状态。所以我们要做个应对措施,对窗体关闭和退出的方法进行重写,以便在何种情况退出和关闭时都可以结束键盘解锁。
在窗体代码处添加上:
/// <summary>
/// 重写退出方法
/// </summary>
/// <param name="e"></param>
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 在关闭窗体时解锁键盘以确保不会永久锁定
KeyBroadBlock.UnlockKeyboard();
base.OnFormClosing(e);
}
完整代码为:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace LockKeyBoard
{
public partial class Form1 : Form
{
//实例化刚刚的键盘类
KeyBroadClass KeyBroad = new KeyBroadClass();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//锁定键盘
if (KeyBroad.LockKeyboard())
{
MessageBox.Show("咋瓦鲁多!键盘已锁定。", "菊菊提示");
button1.Enabled = false;
button2.Enabled = true;
}
else
{
MessageBox.Show("键盘锁定失败。", "菊菊提示");
}
}
private void button2_Click(object sender, EventArgs e)
{
//解锁键盘
if (KeyBroad.UnlockKeyboard())
{
MessageBox.Show("时间开始流逝……键盘解锁。", "菊菊提示");
button1.Enabled = true;
button2.Enabled = false;
}
else
{
MessageBox.Show("键盘解锁失败。", "菊菊提示");
}
}
/// <summary>
/// 重写退出方法
/// </summary>
/// <param name="e"></param>
protected override void OnFormClosing(FormClosingEventArgs e)
{
// 在关闭窗体时解锁键盘以确保不会永久锁定
KeyBroadBlock.UnlockKeyboard();
base.OnFormClosing(e);
}
}
//键盘类,你可以写在这,也可以重新新建个cs类文件,建议重新新建cs类来存放,这样更直观好看。
public class KeyBroadClass
{
private const int WH_KEYBOARD_LL = 13; // 定义键盘低级钩子常量
private const int WM_KEYDOWN = 0x0100; // 定义键盘按下消息常量
private IntPtr hookId = IntPtr.Zero; // 钩子句柄
private LowLevelKeyboardProc keyboardProc;
// 构造函数
public KeyBroadClass()
{
keyboardProc = HookCallback; // 设置钩子回调函数
}
// 声明钩子回调函数委托
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
// 导入Windows API函数,用于设置键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
// 导入Windows API函数,用于卸载键盘钩子
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
// 导入Windows API函数,用于传递钩子事件到下一个钩子处理程序
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
// 钩子回调函数,用于拦截和处理键盘事件
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
// 禁止键盘事件
return (IntPtr)1;
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
// 方法用于锁定键盘
public bool LockKeyboard()
{
// 安装键盘钩子
hookId = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardProc, IntPtr.Zero, 0);
return true;
}
// 方法用于解锁键盘
public bool UnlockKeyboard()
{
// 卸载键盘钩子
if (hookId != IntPtr.Zero)
{
UnhookWindowsHookEx(hookId);
hookId = IntPtr.Zero;
return true;
}
return false;
}
}
}
键盘类需要引用类:
using System.Runtime.InteropServices;
组合键的锁定
2024.1.3 网友发现组合键不能锁定,本人试过确实这样的,查阅过资料后修正。低级键盘钩子通常只能拦截单个键,组合键是由操作系统更高级的级别的处理。但是我们仍然能在代码里进行尝试拦截。
修改我们的HookCallback
方法即可
// 钩子回调函数,用于拦截和处理键盘事件
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
// 禁止键盘事件
return (IntPtr)1;
}
if (nCode >= 0)
{
// 读取虚拟按键代码
int vkCode = Marshal.ReadInt32(lParam);
bool alt = (Control.ModifierKeys & Keys.Alt) != 0;
bool control = (Control.ModifierKeys & Keys.Control) != 0;
// 检查是否是组合键
if (alt && vkCode == (int)Keys.Tab) // 拦截 Alt + Tab
{
return (IntPtr)1;
}
if (alt && vkCode == (int)Keys.F4) // 拦截 Alt + F4
{
return (IntPtr)1;
}
if (control && alt && vkCode == (int)Keys.Delete) // 拦截 Ctrl + Alt + Del
{
return (IntPtr)1;
}
}
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
尝试过后没有什么问题,但是可能会失灵,尤其是拦截 Ctrl + Alt + Del 组合键在大多数情况下是不可能的,因为这是一个由操作系统级别特别处理的安全功能
小结
完结撒花。通过使用Windows API函数来安装钩子和控制键盘状态,我们可以实现一些有趣和实用的功能。本人抛砖引玉,如果功能和代码或函数理解错误的地方,还请大佬斧正。希望和大家一起交流学习,不懂的可以在评论区问我。