[求助软件] 计时器软件 - 鼠标点击别处(非计时器按钮)一次自动计时,再点击别处一次自动停止计时。

[求助软件] 计时器软件 - 鼠标点击别处(非计时器按钮)一次自动计时,再点击别处一次自动停止计时。

背景:

  • 需要测试某个软件1000个文件A的导入时间,点击导入后,开始计时。导入完成后,点击结束计时,计算总时间。
  • 再导入1000个文件B,计算总时间。

需求:

  • 计时器软件能自动响应鼠标点击后开始计时,即使不是直接点击计时器软件本身,也能响应全局鼠标点击bing开始计时。

测试过:

  • 普通计时软件只能点击自身按钮才能启停,如果我点击测试软件的导入按钮后,再移动鼠标去点击计时器软件,可能会造成计时不精确。

请问有无这样的软件?

找个录屏软件 录屏就行了
然后把录屏扔到剪映之类的剪辑软件里,看看你点击的那两帧是啥时间。进行一次计算就好了。

有的录屏软件本身就自带记录鼠标动作的功能。更方便。

手动点击计时肯定不会准。能CLI操作最好,就算必须是GUI操作,也可以写个脚本控制点击和计时。

AI时代, 我感觉这种软件挺好写的.

你可以尝试捕获"失去焦点"(LostFocus/UnFocus)事件,具体思路是

  • 让你的软件在软件失去焦点的时候开始计时
  • 然后,在第二次失去焦点的时候结束计时

使用的时候就是

  • 首先把要复制的东西启动,但是别点确定
  • 打开计时软件,或者说让焦点在软件上
  • 点击你要测试软件的导入按钮,这时候系统焦点会自动离开计时软件,或者说计时软件失去了焦点
  • 然后,在导入期间,点击计时软件,也就是让他得到焦点
  • 最后,等到计时结束,你随便点击什么都行,只要让计时器再次失去焦点,就算结束

以上的思路完美符合你的要求,也就是,“鼠标点击别处一次自动计时,再点击别处一次自动停止计时。”

不过呢,我觉得更好的办法是做系统检测,比方说

  • 每秒钟获取一次CPU占用(或者硬盘占用,或者网络占用什么的,取决于你的导入软件的内容)
  • 然后,设定一个阈值,比方说CPU占用超过10%意味着正在工作,等到CPU占用降低到5%以下意味着工作结束
  • 之后,就一切自动了,自动开始,自动停止。多好
  • 或者你觉得1秒钟一次不稳定,那就一秒钟10次或者50次嘛,反正也不是很麻烦

甚至不需要剪辑软件,找个可以显示毫秒的计时器(例如这个),置顶(如果没有可以参考这个),然后录屏
播放录屏视频然后通过帧步进逼近到开始和结束的两帧分别看计时器的时间显示

想了两个思路:1.实时抓取鼠标动作,在某个xy轴坐标区域范围内点击鼠标左键时响应,第一次开始计时,第二次结束计时。2.让脚本自己去点击测试软件的开始和结束按钮,截取开始和结束button的图片以及结束条件,每100毫秒进行一次屏幕识别,如果识别到结束条件则自动点击结束按钮

如果是自己开发的软件,直接打印日志应该会更方便。

如果是命令行软件,也可以写成脚本来计时。比如,执行操作前打印系统时间,然后开始操作,导入结束后再打印一次系统时间。

找AI写一个,保存为FloatingTimer.cs,编译后使用

//编译命令: C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe /target:winexe /out:FloatingTimer.exe /reference:System.Windows.Forms.dll /reference:System.Drawing.dll /reference:System.dll FloatingTimer.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace FloatingTimerApp
{
    public static class Program
    {
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new TimerForm());
        }
    }

    public enum EventMode
    {
        None,
        LeftMouseDown,
        LeftMouseUp,
        LeftMouseClick,
        LeftMouseLongPress,
        RightMouseDown,
        RightMouseUp,
        RightMouseClick,
        RightMouseLongPress,
        KeyDown,
        KeyUp,
        KeyClick,
        KeyLongPress
    }

    public class AppSettings
    {
        public EventMode StartEventMode = EventMode.LeftMouseUp;
        public Keys StartKey = Keys.None;

        public EventMode StopEventMode = EventMode.LeftMouseDown;
        public Keys StopKey = Keys.None;

        public EventMode LapEventMode = EventMode.None;
        public Keys LapKey = Keys.None;

        public double Opacity = 0.8D;
    }

    public class TimerForm : Form
    {
        private Label lblTime;
        private Label lblLap;
        private ListBox lstLaps;
        private Stopwatch stopwatch;
        private Timer uiTimer;

        private AppSettings settings = new AppSettings();
        
        private Form bgForm;
        private bool isSyncingPositions = false;
        private DateTime initTime = DateTime.Now;

        private Stopwatch globalTime = Stopwatch.StartNew();
        private long leftMouseDownTime = 0;
        private long rightMouseDownTime = 0;
        private Dictionary<Keys, long> keyPressTimes = new Dictionary<Keys, long>();
        
        private delegate IntPtr LowLevelProc(int nCode, IntPtr wParam, IntPtr lParam);
        private LowLevelProc _mouseProc;
        private LowLevelProc _keyboardProc;
        private IntPtr _mouseHookID = IntPtr.Zero;
        private IntPtr _keyboardHookID = IntPtr.Zero;

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private const int WH_KEYBOARD_LL = 13;
        private const int WH_MOUSE_LL = 14;

        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private const int WM_SYSKEYDOWN = 0x0104;
        private const int WM_SYSKEYUP = 0x0105;

        private const int WM_LBUTTONDOWN = 0x0201;
        private const int WM_LBUTTONUP = 0x0202;
        private const int WM_RBUTTONDOWN = 0x0204;
        private const int WM_RBUTTONUP = 0x0205;

        [StructLayout(LayoutKind.Sequential)]
        private struct KBDLLHOOKSTRUCT
        {
            public int vkCode;
            public int scanCode;
            public int flags;
            public int time;
            public IntPtr dwExtraInfo;
        }

        public TimerForm()
        {
            InitializeComponent();
            SetupHooks();
        }

        private void InitializeComponent()
        {
            this.stopwatch = new Stopwatch();
            this.uiTimer = new Timer();
            this.uiTimer.Interval = 10; // High refresh rate for millisecond display
            this.uiTimer.Tick += new EventHandler(this.UiTimer_Tick);

            this.lblTime = new Label();
            this.lblTime.AutoSize = true;
            this.lblTime.Font = new Font("Consolas", 24F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(0)));
            this.lblTime.ForeColor = Color.Cyan;
            this.lblTime.Location = new Point(10, 5);
            this.lblTime.Name = "lblTime";
            this.lblTime.Size = new Size(160, 37);
            this.lblTime.Text = "00:00.000";

            this.lblLap = new Label();
            this.lblLap.AutoSize = true;
            this.lblLap.Font = new Font("Consolas", 11F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
            this.lblLap.ForeColor = Color.Yellow;
            this.lblLap.Location = new Point(14, 45);
            this.lblLap.Name = "lblLap";
            this.lblLap.Size = new Size(0, 17);
            this.lblLap.Text = "右键点击进行设置";

            this.lstLaps = new ListBox();
            this.lstLaps.Location = new Point(10, 70);
            this.lstLaps.Size = new Size(180, 100);
            this.lstLaps.BackColor = Color.Black;
            this.lstLaps.ForeColor = Color.Yellow;
            this.lstLaps.BorderStyle = BorderStyle.None;
            this.lstLaps.Font = new Font("Consolas", 11F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
            this.lstLaps.Visible = false;

            this.BackColor = Color.Black;
            this.ClientSize = new Size(200, 75);
            this.Controls.Add(this.lstLaps);
            this.Controls.Add(this.lblLap);
            this.Controls.Add(this.lblTime);
            this.FormBorderStyle = FormBorderStyle.None;
            this.Name = "TimerForm";
            this.TopMost = true;
            this.StartPosition = FormStartPosition.CenterScreen;
            this.FormClosing += new FormClosingEventHandler(this.TimerForm_FormClosing);
            
            // For dragging from background
            this.MouseDown += new MouseEventHandler(this.TimerForm_MouseDown);
            this.lblLap.MouseDown += new MouseEventHandler(this.TimerForm_MouseDown);

            ContextMenu cm = new ContextMenu();
            cm.MenuItems.Add("设置...", new EventHandler(OpenSettings));
            cm.MenuItems.Add("重置计时器", new EventHandler(MenuResetTimer));
            cm.MenuItems.Add("-");
            cm.MenuItems.Add("退出", new EventHandler(ExitApp));
            this.ContextMenu = cm;
            
            // Allow resetting from label too
            this.lblTime.ContextMenu = cm;
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            this.BackColor = Color.Black;
            this.TransparencyKey = Color.Black;
            this.Opacity = 1.0;

            bgForm = new Form();
            bgForm.FormBorderStyle = FormBorderStyle.None;
            bgForm.BackColor = Color.Black;
            bgForm.Opacity = settings.Opacity;
            bgForm.ShowInTaskbar = false;
            bgForm.TopMost = true;
            bgForm.StartPosition = FormStartPosition.Manual;
            bgForm.Bounds = this.Bounds;
            bgForm.ContextMenu = this.ContextMenu;
            
            bgForm.MouseDown += new MouseEventHandler((sender, args) => {
                if (args.Button == MouseButtons.Left) {
                    ReleaseCapture();
                    SendMessage(bgForm.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
                }
            });

            bgForm.LocationChanged += new EventHandler((sender, args) => {
                if (!isSyncingPositions) {
                    isSyncingPositions = true;
                    this.Location = bgForm.Location;
                    isSyncingPositions = false;
                }
            });

            this.LocationChanged += new EventHandler((sender, args) => {
                if (!isSyncingPositions) {
                    isSyncingPositions = true;
                    if (bgForm != null) bgForm.Location = this.Location;
                    isSyncingPositions = false;
                }
            });

            this.SizeChanged += new EventHandler((sender, args) => {
                if (bgForm != null) bgForm.Size = this.Size;
            });

            this.Owner = bgForm;
            bgForm.Show();
        }

        protected override void OnFormClosed(FormClosedEventArgs e)
        {
            if (bgForm != null) bgForm.Close();
            base.OnFormClosed(e);
        }

        private void MenuResetTimer(object sender, EventArgs e)
        {
            ResetTimer();
        }

        private void OpenSettings(object sender, EventArgs e)
        {
            if (this.ContextMenu != null)
            {
               // Just to ensure normal workflow
            }
            using (SettingsForm sf = new SettingsForm(this.settings))
            {
                if (sf.ShowDialog(this) == DialogResult.OK)
                {
                    if (bgForm != null) bgForm.Opacity = this.settings.Opacity;
                }
            }
        }

        private void ExitApp(object sender, EventArgs e)
        {
            Application.Exit();
        }

        public const int WM_NCLBUTTONDOWN = 0xA1;
        public const int HT_CAPTION = 0x2;

        [DllImportAttribute("user32.dll")]
        public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
        [DllImportAttribute("user32.dll")]
        public static extern bool ReleaseCapture();

        private void TimerForm_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                ReleaseCapture();
                SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
            }
        }

        private void UiTimer_Tick(object sender, EventArgs e)
        {
            TimeSpan ts = stopwatch.Elapsed;
            lblTime.Text = string.Format("{0:00}:{1:00}.{2:000}", ts.Minutes, ts.Seconds, ts.Milliseconds);
        }

        private void SetupHooks()
        {
            _mouseProc = HookMouseCallback;
            _keyboardProc = HookKeyboardCallback;
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                IntPtr hMod = GetModuleHandle(curModule.ModuleName);
                _mouseHookID = SetWindowsHookEx(WH_MOUSE_LL, _mouseProc, hMod, 0);
                _keyboardHookID = SetWindowsHookEx(WH_KEYBOARD_LL, _keyboardProc, hMod, 0);
            }
        }

        private void TimerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_mouseHookID != IntPtr.Zero)
                UnhookWindowsHookEx(_mouseHookID);
            if (_keyboardHookID != IntPtr.Zero)
                UnhookWindowsHookEx(_keyboardHookID);
        }

        // --- Hook Callbacks ---
        
        // We use boolean flags to avoid double triggering events within the same hook call
        private bool isProcessingEvent = false;

        private IntPtr HookMouseCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && !isProcessingEvent)
            {
                isProcessingEvent = true;
                int msg = wParam.ToInt32();
                
                try
                {
                    if (msg == WM_LBUTTONDOWN)
                    {
                        leftMouseDownTime = globalTime.ElapsedMilliseconds;
                        HandleGlobalEvent(EventMode.LeftMouseDown, Keys.None);
                    }
                    else if (msg == WM_LBUTTONUP)
                    {
                        HandleGlobalEvent(EventMode.LeftMouseUp, Keys.None);
                        if (leftMouseDownTime > 0)
                        {
                            long duration = globalTime.ElapsedMilliseconds - leftMouseDownTime;
                            if (duration < 500) HandleGlobalEvent(EventMode.LeftMouseClick, Keys.None);
                            else HandleGlobalEvent(EventMode.LeftMouseLongPress, Keys.None);
                            leftMouseDownTime = 0;
                        }
                    }
                    else if (msg == WM_RBUTTONDOWN)
                    {
                        rightMouseDownTime = globalTime.ElapsedMilliseconds;
                        HandleGlobalEvent(EventMode.RightMouseDown, Keys.None);
                    }
                    else if (msg == WM_RBUTTONUP)
                    {
                        HandleGlobalEvent(EventMode.RightMouseUp, Keys.None);
                        if (rightMouseDownTime > 0)
                        {
                            long duration = globalTime.ElapsedMilliseconds - rightMouseDownTime;
                            if (duration < 500) HandleGlobalEvent(EventMode.RightMouseClick, Keys.None);
                            else HandleGlobalEvent(EventMode.RightMouseLongPress, Keys.None);
                            rightMouseDownTime = 0;
                        }
                    }
                }
                catch { }
                
                isProcessingEvent = false;
            }
            return CallNextHookEx(_mouseHookID, nCode, wParam, lParam);
        }

        private IntPtr HookKeyboardCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && !isProcessingEvent)
            {
                isProcessingEvent = true;
                int msg = wParam.ToInt32();
                KBDLLHOOKSTRUCT kbdStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
                Keys key = (Keys)kbdStruct.vkCode;
                
                try 
                {
                    if (msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN)
                    {
                        if (!keyPressTimes.ContainsKey(key))
                        {
                            keyPressTimes.Add(key, globalTime.ElapsedMilliseconds);
                            HandleGlobalEvent(EventMode.KeyDown, key);
                        }
                    }
                    else if (msg == WM_KEYUP || msg == WM_SYSKEYUP)
                    {
                        HandleGlobalEvent(EventMode.KeyUp, key);
                        if (keyPressTimes.ContainsKey(key))
                        {
                            long duration = globalTime.ElapsedMilliseconds - keyPressTimes[key];
                            keyPressTimes.Remove(key);
                            if (duration < 500) HandleGlobalEvent(EventMode.KeyClick, key);
                            else HandleGlobalEvent(EventMode.KeyLongPress, key);
                        }
                    }
                }
                catch { }

                isProcessingEvent = false;
            }
            return CallNextHookEx(_keyboardHookID, nCode, wParam, lParam);
        }

        // --- Logic ---
        private long lapStartTimeMs = 0;
        private int lapCount = 0;

        private void HandleGlobalEvent(EventMode mode, Keys key)
        {
            // Must invoke to UI thread to manipulate labels safely
            if (this.InvokeRequired)
            {
                this.BeginInvoke(new Action<EventMode, Keys>(ProcessEventParams), new object[] { mode, key });
            }
            else
            {
                ProcessEventParams(mode, key);
            }
        }

        private void ProcessEventParams(EventMode mode, Keys key)
        {
            if ((DateTime.Now - initTime).TotalMilliseconds < 500) return;

            bool matchStart = IsMatch(mode, key, settings.StartEventMode, settings.StartKey);
            bool matchStop = IsMatch(mode, key, settings.StopEventMode, settings.StopKey);
            bool matchLap = IsMatch(mode, key, settings.LapEventMode, settings.LapKey) && settings.LapEventMode != EventMode.None;

            if (matchLap && stopwatch.IsRunning)
            {
                TriggerLap();
            }
            else if (matchStart && matchStop)
            {
                if (stopwatch.IsRunning)
                {
                    StopTimer();
                }
                else
                {
                    StartTimer();
                }
            }
            else if (matchStop)
            {
                StopTimer();
            }
            else if (matchStart)
            {
                StartTimer();
            }
        }

        private bool IsMatch(EventMode actualMode, Keys actualKey, EventMode targetMode, Keys targetKey)
        {
            if (actualMode != targetMode) return false;
            if (targetMode == EventMode.KeyDown || targetMode == EventMode.KeyUp || 
                targetMode == EventMode.KeyClick || targetMode == EventMode.KeyLongPress)
            {
                if (actualKey != targetKey && targetKey != Keys.None) return false;
            }
            return true;
        }

        private void StartTimer()
        {
            if (!stopwatch.IsRunning)
            {
                if (settings.LapEventMode == EventMode.None && stopwatch.ElapsedTicks > 0) return; // 单次使用时在此锁定。圈记模式允许暂停后继续使用

                stopwatch.Start();
                if (lapStartTimeMs == 0) // Resume edge case setup
                {
                    lapStartTimeMs = stopwatch.ElapsedMilliseconds;
                }
                uiTimer.Start();
                lblLap.Text = "计时中...";
            }
        }

        private void TriggerLap()
        {
            lapCount++;
            long current = stopwatch.ElapsedMilliseconds;
            long lapDiff = current - lapStartTimeMs;
            lapStartTimeMs = current;
            TimeSpan diff = TimeSpan.FromMilliseconds(lapDiff);
            string lapStr = string.Format("第 {0} 圈: {1:00}:{2:00}.{3:000}", lapCount, diff.Minutes, diff.Seconds, diff.Milliseconds);
            lblLap.Text = lapStr;

            if (!lstLaps.Visible)
            {
                lstLaps.Visible = true;
                this.Height = 180;
            }
            lstLaps.Items.Add(lapStr);
            lstLaps.TopIndex = lstLaps.Items.Count - 1;
        }

        private void StopTimer()
        {
            if (stopwatch.IsRunning)
            {
                stopwatch.Stop();
                uiTimer.Stop();
                UiTimer_Tick(null, null); // update UI one last time
                if (settings.LapEventMode == EventMode.None || lapCount == 0)
                {
                    lblLap.Text = "已停止";
                }
            }
        }

        private void ResetTimer()
        {
            stopwatch.Reset();
            lapStartTimeMs = 0;
            lapCount = 0;
            lblLap.Text = "就绪";
            lblTime.Text = "00:00.000";
            lstLaps.Items.Clear();
            lstLaps.Visible = false;
            this.Height = 75;
            uiTimer.Stop();
        }
    }

    public class SettingsForm : Form
    {
        private class ModeItem
        {
            public string Text { get; set; }
            public EventMode Value { get; set; }
            public override string ToString() { return Text; }
        }

        private AppSettings settings;
        private ComboBox cbStartMode;
        private ComboBox cbStopMode;
        private ComboBox cbLapMode;
        private TextBox txtStartKey;
        private TextBox txtStopKey;
        private TextBox txtLapKey;
        private TrackBar tbOpacity;
        private Button btnSave;
        private Label lblHint;

        private Keys startKeyBuffer;
        private Keys stopKeyBuffer;
        private Keys lapKeyBuffer;

        public SettingsForm(AppSettings currentSettings)
        {
            this.settings = currentSettings;
            this.startKeyBuffer = currentSettings.StartKey;
            this.stopKeyBuffer = currentSettings.StopKey;
            this.lapKeyBuffer = currentSettings.LapKey;
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            this.Text = "计时器设置";
            this.Size = new Size(320, 360);
            this.FormBorderStyle = FormBorderStyle.FixedDialog;
            this.MaximizeBox = false;
            this.StartPosition = FormStartPosition.CenterParent;

            Label lblStart = new Label();
            lblStart.Text = "开始事件:";
            lblStart.Location = new Point(10, 15);
            lblStart.AutoSize = true;

            cbStartMode = new ComboBox();
            cbStartMode.Location = new Point(100, 10);
            cbStartMode.Width = 180;
            cbStartMode.DropDownStyle = ComboBoxStyle.DropDownList;
            PopulateModes(cbStartMode, settings.StartEventMode);
            cbStartMode.SelectedIndexChanged += new EventHandler(this.ConfigUI);

            txtStartKey = new TextBox();
            txtStartKey.Location = new Point(100, 40);
            txtStartKey.Width = 180;
            txtStartKey.ReadOnly = true;
            txtStartKey.Text = startKeyBuffer.ToString();
            txtStartKey.KeyDown += new KeyEventHandler(this.TxtStartKey_KeyDown);

            Label lblStop = new Label();
            lblStop.Text = "结束事件:";
            lblStop.Location = new Point(10, 75);
            lblStop.AutoSize = true;

            cbStopMode = new ComboBox();
            cbStopMode.Location = new Point(100, 70);
            cbStopMode.Width = 180;
            cbStopMode.DropDownStyle = ComboBoxStyle.DropDownList;
            PopulateModes(cbStopMode, settings.StopEventMode);
            cbStopMode.SelectedIndexChanged += new EventHandler(this.ConfigUI);

            txtStopKey = new TextBox();
            txtStopKey.Location = new Point(100, 100);
            txtStopKey.Width = 180;
            txtStopKey.ReadOnly = true;
            txtStopKey.Text = stopKeyBuffer.ToString();
            txtStopKey.KeyDown += new KeyEventHandler(this.TxtStopKey_KeyDown);

            Label lblLap = new Label();
            lblLap.Text = "记圈事件:";
            lblLap.Location = new Point(10, 135);
            lblLap.AutoSize = true;

            cbLapMode = new ComboBox();
            cbLapMode.Location = new Point(100, 130);
            cbLapMode.Width = 180;
            cbLapMode.DropDownStyle = ComboBoxStyle.DropDownList;
            PopulateModes(cbLapMode, settings.LapEventMode);
            cbLapMode.SelectedIndexChanged += new EventHandler(this.ConfigUI);

            txtLapKey = new TextBox();
            txtLapKey.Location = new Point(100, 160);
            txtLapKey.Width = 180;
            txtLapKey.ReadOnly = true;
            txtLapKey.Text = lapKeyBuffer.ToString();
            txtLapKey.KeyDown += new KeyEventHandler(this.TxtLapKey_KeyDown);

            Label lblOpacity = new Label();
            lblOpacity.Text = "不透明度:";
            lblOpacity.Location = new Point(10, 195);
            lblOpacity.AutoSize = true;

            tbOpacity = new TrackBar();
            tbOpacity.Location = new Point(100, 190);
            tbOpacity.Width = 180;
            tbOpacity.Minimum = 10;
            tbOpacity.Maximum = 100;
            tbOpacity.TickFrequency = 10;
            tbOpacity.Value = (int)(settings.Opacity * 100);

            lblHint = new Label();
            lblHint.Text = "提示: 选中输入框并按下键盘任意键即可绑定";
            lblHint.Location = new Point(10, 245);
            lblHint.AutoSize = true;
            lblHint.ForeColor = Color.Gray;

            btnSave = new Button();
            btnSave.Text = "保存设置";
            btnSave.Location = new Point(100, 270);
            btnSave.Width = 120;
            btnSave.Click += new EventHandler(this.BtnSave_Click);

            this.Controls.Add(lblStart);
            this.Controls.Add(cbStartMode);
            this.Controls.Add(txtStartKey);
            this.Controls.Add(lblStop);
            this.Controls.Add(cbStopMode);
            this.Controls.Add(txtStopKey);
            this.Controls.Add(lblLap);
            this.Controls.Add(cbLapMode);
            this.Controls.Add(txtLapKey);
            this.Controls.Add(lblOpacity);
            this.Controls.Add(tbOpacity);
            this.Controls.Add(lblHint);
            this.Controls.Add(btnSave);

            ConfigUI(null, null);
        }

        private void PopulateModes(ComboBox cb, EventMode current)
        {
            Array values = Enum.GetValues(typeof(EventMode));
            foreach (object val in values)
            {
                EventMode mode = (EventMode)val;
                string text = mode.ToString();
                switch (mode)
                {
                    case EventMode.None: text = "无选项 (停用)"; break;
                    case EventMode.LeftMouseDown: text = "鼠标左键按下"; break;
                    case EventMode.LeftMouseUp: text = "鼠标左键弹起"; break;
                    case EventMode.LeftMouseClick: text = "鼠标左键单击"; break;
                    case EventMode.LeftMouseLongPress: text = "鼠标左键长按"; break;
                    case EventMode.RightMouseDown: text = "鼠标右键按下"; break;
                    case EventMode.RightMouseUp: text = "鼠标右键弹起"; break;
                    case EventMode.RightMouseClick: text = "鼠标右键单击"; break;
                    case EventMode.RightMouseLongPress: text = "鼠标右键长按"; break;
                    case EventMode.KeyDown: text = "键盘按键按下"; break;
                    case EventMode.KeyUp: text = "键盘按键弹起"; break;
                    case EventMode.KeyClick: text = "键盘按键单击"; break;
                    case EventMode.KeyLongPress: text = "键盘按键长按"; break;
                }
                ModeItem item = new ModeItem { Text = text, Value = mode };
                cb.Items.Add(item);
                if (mode == current)
                {
                    cb.SelectedItem = item;
                }
            }
        }

        private void ConfigUI(object sender, EventArgs e)
        {
            EventMode startMode = ((ModeItem)cbStartMode.SelectedItem).Value;
            if (startMode == EventMode.KeyDown || startMode == EventMode.KeyUp || 
                startMode == EventMode.KeyClick || startMode == EventMode.KeyLongPress)
            {
                txtStartKey.Enabled = true;
                txtStartKey.BackColor = Color.White;
            }
            else
            {
                txtStartKey.Enabled = false;
                txtStartKey.BackColor = Color.LightGray;
            }

            EventMode stopMode = ((ModeItem)cbStopMode.SelectedItem).Value;
            if (stopMode == EventMode.KeyDown || stopMode == EventMode.KeyUp || 
                stopMode == EventMode.KeyClick || stopMode == EventMode.KeyLongPress)
            {
                txtStopKey.Enabled = true;
                txtStopKey.BackColor = Color.White;
            }
            else
            {
                txtStopKey.Enabled = false;
                txtStopKey.BackColor = Color.LightGray;
            }

            EventMode lapMode = ((ModeItem)cbLapMode.SelectedItem).Value;
            if (lapMode == EventMode.KeyDown || lapMode == EventMode.KeyUp || 
                lapMode == EventMode.KeyClick || lapMode == EventMode.KeyLongPress)
            {
                txtLapKey.Enabled = true;
                txtLapKey.BackColor = Color.White;
            }
            else
            {
                txtLapKey.Enabled = false;
                txtLapKey.BackColor = Color.LightGray;
            }
        }

        private void TxtStartKey_KeyDown(object sender, KeyEventArgs e)
        {
            startKeyBuffer = e.KeyCode;
            txtStartKey.Text = startKeyBuffer.ToString();
            e.Handled = true;
            e.SuppressKeyPress = true;
        }

        private void TxtStopKey_KeyDown(object sender, KeyEventArgs e)
        {
            stopKeyBuffer = e.KeyCode;
            txtStopKey.Text = stopKeyBuffer.ToString();
            e.Handled = true;
            e.SuppressKeyPress = true;
        }

        private void TxtLapKey_KeyDown(object sender, KeyEventArgs e)
        {
            lapKeyBuffer = e.KeyCode;
            txtLapKey.Text = lapKeyBuffer.ToString();
            e.Handled = true;
            e.SuppressKeyPress = true;
        }

        private void BtnSave_Click(object sender, EventArgs e)
        {
            settings.StartEventMode = ((ModeItem)cbStartMode.SelectedItem).Value;
            settings.StartKey = startKeyBuffer;
            settings.StopEventMode = ((ModeItem)cbStopMode.SelectedItem).Value;
            settings.StopKey = stopKeyBuffer;
            settings.LapEventMode = ((ModeItem)cbLapMode.SelectedItem).Value;
            settings.LapKey = lapKeyBuffer;
            settings.Opacity = tbOpacity.Value / 100.0;

            this.DialogResult = DialogResult.OK;
            this.Close();
        }
    }
}

非常感谢代码支持,而且这个计时器的设计冗余性很好,界面简洁,设置里功能丰富。
现在是要点击计时器本身界面才能启停计时,我需要点击非计时器界面,即桌面上任何位置,及时计时器处在后台,也能计时和停止,能请帮忙再修改一下吗?
image

设计上就是不需要点击计时器的,如果在你要用的程序里操作鼠标或键盘的时候,计时器不知道有按键事件,说明那个程序是运行在管理员权限的,你只需要用管理员权限启动计时器就可以了

可以了,右键用管理员启动就可以了。太完美了,设置里能设置各种按键类型,界面也很精简,所有功能需求都实现了。伟大的创造者,感谢。