用 C# 做一个键盘记录器

某天写代码时突发奇想想知道自己用那个键用的最多,于是在网上搜了一通键盘记录软件,没找到一个满意的,于是决定自己写一个出来
ps:第一次用 C# 写 Windows 程序,可能很多地方不够规范

先撸一个简单的界面出来,大概长这个样子
界面
列表表头这样设置,id宽度为0且不可调整(即隐藏该列),每列分别是:用于排序的id,按键中文名,按键名,次数
表头
添加加一个托盘图标和菜单
托盘菜单

再写一个数据管理的类,根据 Windows 键盘表的顺序把需要记录的键写到一个数组里去

using System;
using System.IO;
using System.Windows.Forms;

namespace KeyboardRecord {

	public class Data {

		public string[] nameZh = {
			"退格","Tab","回车","大写锁","Esc",
			"空格",
			"PageUp","PageDown","End","Home",
			"左","上","右","下",
			// 14
			"插入","删除",
			// 16
			"0","1","2","3","4","5","6","7","8","9",
			"右括号","感叹号","@","井号","$","百分号","^","&","星号","左括号",
			// 36
			"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
			// 62
			"Win","App",
			// 64
			"F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12",
			// 76
			"左Shift","右Shift","左Ctrl","右Ctrl","左Alt","右Alt",
			// 82
			"分号","等于号","逗号","横线(减号)","点","斜杠","`","左中括号","反斜杠","右中括号","单引号",
			// 93
			"冒号","加号","小于号","下划线","大于号","问号","~","左花括号","|","右花括号","双引号",
			// 104
			"复制","粘贴","撤销","保存","剪切"
		};

		public string[] name = {
			// 0
			"Back","Tab","Enter","Caps","Esc",
			// 5
			"Space",
			"PageUp","PageDown","End","Home",
			"Left","Up","Right","Down",
			// 14
			"Insert","Delete",
			// 16
			"0","1","2","3","4","5","6","7","8","9",
			")","!","@","#","$","%","^","&","*","(",
			// 36
			"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
			// 62
			"Win","App",
			// 64
			"F1","F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12",
			// 76
			"LShift","RShift","LCtrl","RCtrl","LAlt","RAlt",
			// 82
			";","=",",","-",".","/","`","[","\\","]","'",
			// 93
			":","+","<","_",">","?","~","{","|","}","\"",
			// 104
			"Ctrl+C","Ctrl+V","Ctrl+Z","Ctrl+S","Ctrl+X"
		};
		// 存放按键次数的数组
		public int[] times;

		public Data() {
			loadData();
		}


		public void loadData() {

			times = new int[name.Length];
			// 从本地的文件读取数据
			string path = Path.Combine((Application.StartupPath + @"\"),"data.csv");
			try {
				if(!File.Exists(path)) return;
				using StreamReader reader = new StreamReader(new FileStream(path,FileMode.Open,FileAccess.Read,FileShare.None));
				for(int i = 0;i<name.Length;i++) {
					// 一次读一行数据
					string line = reader.ReadLine();
					string[] sp = line.Split('\t');
					times[i] = int.Parse(sp[1]);
				}
			} catch(Exception e) {
				// 读取遇到问题就从上一次保存的文件恢复数据(我可不想辛辛苦苦记录的数据说没就没了)
				File.Delete(path);
				if(File.Exists(path + ".bak")) {
					File.Move(path + ".bak",path);
					// 重新载入数据
					loadData();
					MessageBox.Show("载入数据时出错,尝试从备份文件中恢复\n"+e.Message,"错误");
				} else {
					MessageBox.Show("载入数据时出错!\n"+e.Message,"错误");
				}
			}
		}
		// 保存记录的数据
		public void saveData() {
			string path = Path.Combine((Application.StartupPath + @"\"),"data.csv");
			try {
				// 把之前保存的数据备份
				if(File.Exists(path)) {
					if(File.Exists(path + ".bak")) File.Delete(path + ".bak");
					File.Copy(path,path + ".bak");
				}
				using StreamWriter writer = new StreamWriter(new FileStream(path,FileMode.Create,FileAccess.Write,FileShare.None));
				for(int i = 0;i<name.Length;i++) {
					// 一行的数据格式为:名称 + Tab + 按键次数
					writer.Write(name[i]);
					writer.Write('\t');
					writer.Write(times[i].ToString());
					writer.Write("\r\n");
				}
			} catch(Exception e) {
				if(File.Exists(path)) File.Delete(path);
				MessageBox.Show("保存数据时出错!\n"+e.Message,"错误");
			}
		}
	}
}

把界面交互做出来

using System;
using System.Collections;
using System.Windows.Forms;

namespace KeyboardRecord {

	public partial class Form1 : Form {
        // 储存按键次数的类
        private Data data;
        // 监听按键按下的钩子类
        private KeyboardHook keyboardHook;
        // 用于给列表元素排序的类
        private ListViewItemComparer comparer;

        public Form1() {

			InitializeComponent();
            // 给托盘菜单的选项添加点击事件
            show.Click += ShowApp;
            exit.Click += ExitApp;
        }

		private void MainLoad(object sender,EventArgs e) {
            // 初始化需要用到的类
            data = new Data();
            keyboardHook = new KeyboardHook(data);
            comparer = new ListViewItemComparer();

            keyList.ListViewItemSorter = comparer;

            initList();
        }

        // 初始化列表
        private void initList() {
            // 把记录的数据加到ListView里去
            for(int i = 0;i<data.name.Length;i++) {
                keyList.Items.Add(new ListViewItem(new string[] { i.ToString(), data.name[i],data.nameZh[i],data.times[i].ToString() }));
            }
        }

        // 双击托盘图标显示程序主界面
        private void IconDoubleClick(object sender,MouseEventArgs e) {
            ShowApp(null,null);
        }

        // 监听窗口最小化
        private void MainSizeChanged(object sender,EventArgs e) {
            if(WindowState == FormWindowState.Minimized) {
                // 最小化的时候在任务栏里隐藏
                this.ShowInTaskbar = false;
            }
        }

        // 监听右上角关闭按钮
        private void MainFormClosing(object sender,FormClosingEventArgs e) {
            // 让窗口最小化,而不是退出程序
            WindowState = FormWindowState.Minimized;
            // 取消点击事件,让系统不会关闭程序
            e.Cancel = true;
        }

        // 显示主界面
        private void ShowApp(object sender,EventArgs e) {
            if(WindowState == FormWindowState.Minimized) {
                // 还原窗口
                WindowState = FormWindowState.Normal;
                Activate();
                // 重新在任务栏里显示
                this.ShowInTaskbar = true;
            }
		}

        // 确认是否退出
        private void ExitApp(object sender,EventArgs e) {
            if(MessageBox.Show("退出程序?","退出",MessageBoxButtons.OKCancel,MessageBoxIcon.Question) == DialogResult.OK) {
                // 保存数据
                data.saveData();
                // 停止监听按键
                keyboardHook.Stop();
                notifyIcon.Visible = false;
                // 关闭所有的线程
                this.Dispose();
                this.Close();
            }
        }

        // 刷新列表
        private void UpdateList() {
            // 移除现有的所有Item
            keyList.Items.Clear();
            initList();
		}

        // 启动按钮
        private void status_Click(object sender,EventArgs e) {
			if(keyboardHook.IsStarted) {
                // 停止监听
                keyboardHook.Stop();
                status.Text = "开始";
                Invalidate();
			} else {
                // 启用钩子,监听按键操作
                keyboardHook.Start();
                status.Text = "停止";
                Invalidate();
            }
        }

        // 导出按钮
		private void explore_Click(object sender,EventArgs e) {
            data.saveData();
            MessageBox.Show("导出完成!","完成");
		}

        // 清除按钮
		private void clear_Click(object sender,EventArgs e) {
            if(MessageBox.Show("确定清除记录的次数?","清除",MessageBoxButtons.OKCancel,MessageBoxIcon.Question) == DialogResult.OK) {
                data.times = new int[data.name.Length];
                UpdateList();
            }
		}

        // List表头点击监听
		private void keyList_SelectedIndexChanged(object sender,ColumnClickEventArgs e) {
            // 设置点击的列
            comparer.setIndex(e.Column);
            // 执行排序
            keyList.Sort();
		}

	}

	public class ListViewItemComparer : IComparer {
        // 是否反向排序
        private bool flip = true;
        // 要排序的列
        private int index;

        public int Compare(object x,object y) {
            // 获取需要排序的文本
            string s1 = ((ListViewItem)x).SubItems[index].Text;
            string s2 = ((ListViewItem)y).SubItems[index].Text;
            // 正向排序/反向排序
            if(flip) return int.Parse(s1) > int.Parse(s2) ? 1 : -1;
            else return int.Parse(s1) > int.Parse(s2) ? -1 : 1;
        } 

        public void setIndex(int index) {
			// 当点击的是按键次数的列时按按键次数排序,否则就按id排序
			if(index==3) this.index = 3;
            else this.index = 0;
            // 反向排序
            flip = !flip;
		}
    }
}

然后就到了最重要的按键监听记录的类了,不清楚怎么监听按键,于是网上找了段代码,一通修改

using System;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Windows.Forms;

namespace KeyboardRecord {

    // 钩子类,负责监听键盘点击事件
    public abstract class GlobalHook {

        #region Windows API Code

        [StructLayout(LayoutKind.Sequential)]
        protected class KeyboardHookStruct {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public int dwExtraInfo;
        }

        [DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall,SetLastError = true)]
        protected static extern int SetWindowsHookEx(int idHook,HookProc lpfn,IntPtr hMod,int dwThreadId);

        [DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall,SetLastError = true)]
        protected static extern int UnhookWindowsHookEx(int idHook);

        [DllImport("user32.dll",CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        protected static extern int CallNextHookEx(int idHook,int nCode,int wParam, IntPtr lParam);

        [DllImport("user32")]
        protected static extern int ToAscii( int uVirtKey, int uScanCode, byte[] lpbKeyState,   byte[] lpwTransKey,  int fuState);

        [DllImport("user32")]
        protected static extern int GetKeyboardState(byte[] pbKeyState);

        [DllImport("user32.dll",CharSet = CharSet.Auto,CallingConvention = CallingConvention.StdCall)]
        protected static extern short GetKeyState(int vKey);

        protected delegate int HookProc(int nCode,int wParam,IntPtr lParam);

        protected const int WH_KEYBOARD_LL = 13;

        protected const int WM_KEYDOWN = 0x100;
        protected const int WM_KEYUP = 0x101;

        protected const int WM_SYSKEYDOWN = 0x104;
        protected const int WM_SYSKEYUP = 0x105;

        protected const byte VK_SHIFT = 0x10;

        protected const byte VK_LSHIFT = 0xA0;
        protected const byte VK_RSHIFT = 0xA1;

        protected const byte VK_LCONTROL = 0xA2;
        protected const byte VK_RCONTROL = 0x3;

        protected const byte VK_LALT = 0xA4;
        protected const byte VK_RALT = 0xA5;

        #endregion

        #region 私有变量
        protected int _hookType;
        protected int _handleToHook;
        protected bool _isStarted;
        protected HookProc _hookCallback;
        #endregion

        #region Properties
        public bool IsStarted {
            get {
                return _isStarted;
            }
        }
        #endregion

        #region Constructor
        public GlobalHook() {
            Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
        }
        #endregion

        #region Methods
        public void Start() {
            if(!_isStarted && _hookType != 0) {
                // Make sure we keep a reference to this delegate!
                // If not, GC randomly collects it, and a NullReference exception is thrown
                _hookCallback = new HookProc(HookCallbackProcedure);
                _handleToHook = SetWindowsHookEx( _hookType,  _hookCallback,Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]),0);
                // Were we able to sucessfully start hook?
                if(_handleToHook != 0) {
                    _isStarted = true;
                }
            }
        }

        public void Stop() {
            if(_isStarted) {
                UnhookWindowsHookEx(_handleToHook);
                _isStarted = false;
            }
        }

        protected virtual int HookCallbackProcedure(int nCode,Int32 wParam,IntPtr lParam) {
            // This method must be overriden by each extending hook
            return 0;
        }

        protected void Application_ApplicationExit(object sender,EventArgs e) {
            if(_isStarted) {
                Stop();
            }
        }
        #endregion
    }

    public class KeyboardHook : GlobalHook {
        // 储存按键次数的类
        public Data data;

        public KeyboardHook(Data data) {
            this.data = data;
            _hookType = WH_KEYBOARD_LL;
        }

        // 监听的回调方法
        protected override int HookCallbackProcedure(int nCode,int wParam,IntPtr lParam) {
            if(nCode > -1) {
                KeyboardHookStruct keyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam,typeof(KeyboardHookStruct));
                // 只需要监听按下事件
				if(wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
                    // 获取按键的代码
                    int code = keyboardHookStruct.vkCode;
                    // 根据按键代码让对应数据的数据+1
                    // 只要合理设计一下数组就能大幅减少if-else的数量
                    // 根据code算出数组储存的位置
					if(code<32) {
					    switch(code) {
                            case 8:data.times[0]++;break;// back
                            case 9: data.times[1]++; break;// tab
                            case 13: data.times[2]++; break;// enter
                            case 20: data.times[3]++; break;//caps
                            case 27: data.times[4]++; break;// esc
                        }
                    } else if(code<41){
                        // 方向键区域
                        data.times[code-27]++;
					}else if(code>47&&code<65) {
                        // 如果shift也是按下状态的话数据的就是数字上面的符号
						if(GetKeyState(VK_LSHIFT) + GetKeyState(VK_RSHIFT) != 0) data.times[code-22]++;
                        // 0-9
                        else data.times[code-32]++;
                    }else if(code>64&&code<91) {
						// a-z
                        // 如果此时ctrl也是按下状态
						if(GetKeyState(162)!=0) {
                            // ctrl 组合键
						    if(code==67) data.times[104]++;
                            else if(code==86) data.times[105]++;
                            else if(code==90) data.times[106]++;
                            else if(code==83) data.times[107]++;
                            else if(code==88) data.times[108]++;
						}else data.times[code-29]++;
					}else if(code>95&&code<106) {
                        // 小键盘0-9
                        data.times[code-80]++;
                    }else if(code<112) {
                        // 不常用的键和小键盘符号
                        switch(code) {
                            case 45: data.times[14]++;break;// ins
                            case 46: data.times[15]++; break;// del
                            case 91: data.times[62]++; break; // win
                            case 93: data.times[63]++; break;// apps
                            case 106: data.times[34]++; break;// 乘
                            case 107: data.times[94]++; break;// 加
                            case 109: data.times[85]++; break;// 减
                            case 110: data.times[86]++; break;// 小数点
                            case 111: data.times[87]++; break;// 除
                        }
                    }else if(code<124) {
                        // F1-F12
                        data.times[code-48]++;
					}else if(code>159&&code<166) {
                        //left right shift  left right ctrl
                        data.times[code-84]++;
                    }else if(code>185&&code<193) {
                        // ;=,-./`[\]'
                        if(GetKeyState(VK_LSHIFT) + GetKeyState(VK_RSHIFT) != 0) data.times[code-93]++;
                        else data.times[code-104]++;
					}else if(code>218&&code<223) {
                        if(GetKeyState(VK_LSHIFT) + GetKeyState(VK_RSHIFT) != 0) data.times[code-119]++;
                        else data.times[code-130]++;
                    }
                }
            }
            return CallNextHookEx(_handleToHook,nCode,wParam,lParam);
        }
    }
}

至此,一个键盘记录软件就差不多完成了

用了一段时间后发现我用的最多的键居然是退格

按键记录

最后附上项目github链接:https://github.com/2891954521/KeyboardRecord

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你想要一个C#编写的文档记录工具,可以帮助用户创建和管理文档记录。以下是一个简单的示例,展示了如何使用Windows窗体应用程序来实现这个功能: ```csharp using System; using System.Collections.Generic; using System.Windows.Forms; namespace DocumentRecordTool { public partial class MainForm : Form { private List<string> documents; public MainForm() { InitializeComponent(); documents = new List<string>(); } private void btnAddDocument_Click(object sender, EventArgs e) { string documentName = txtDocumentName.Text; if (!string.IsNullOrEmpty(documentName)) { documents.Add(documentName); lstDocuments.Items.Add(documentName); txtDocumentName.Clear(); } } private void btnRemoveDocument_Click(object sender, EventArgs e) { if (lstDocuments.SelectedIndex != -1) { string selectedDocument = lstDocuments.SelectedItem.ToString(); documents.Remove(selectedDocument); lstDocuments.Items.Remove(selectedDocument); } } } } ``` 在Visual Studio中,你可以创建一个新的Windows窗体应用程序项目,并将上述代码添加到`MainForm.cs`文件中。然后,你可以在窗体设计视图中添加两个按钮(`btnAddDocument`和`btnRemoveDocument`)、一个文本框(`txtDocumentName`)和一个列表框(`lstDocuments`)来与用户交互。 这个示例中的代码会将用户输入的文档名称添加到一个列表中,并将列表显示在列表框中。用户可以通过点击"Add"按钮来添加文档记录,并通过点击"Remove"按钮来删除选中的文档记录。 请注意,这只是一个基本示例,你可以根据自己的需求进行扩展和修改。例如,你可以添加更多的字段来记录文档的详细信息,或者使用数据库来存储和管理文档记录

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值