首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 全域鉤子攔截滑鼠事件:限制特定 TextBox 僅允許右鍵複製

     編輯:關於C#

需求背景

表單中有多個 TextBox,需針對 特定 Panel 內的 TextBox 僅保留右鍵「複製」功能,封鎖貼上、剪下、全選等行為,且不影響其他輸入框。

技術選型:低層鉤子

  • WH_MOUSE_LL (14):全域低層滑鼠鉤子,可在應用程式外部也能攔截,但需注意效能與穩定性。
  • 替代方案:TextBox.ContextMenuStrip 客製化、ShortcutsEnabled=false + 自訂命令綁定、WPF InputBindings。本文示範鉤子做法以展示進階技術。

完整鉤子類別 (簡化版)

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

public class GlobalMouseHook : IDisposable
{
    private const int WH_MOUSE_LL = 14;
    private const int WM_RBUTTONDOWN = 0x204;
    private const int WM_RBUTTONUP = 0x205;

    private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
    private readonly LowLevelMouseProc _proc;
    private readonly IntPtr _hookId = IntPtr.Zero;

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hInstance, uint threadId);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

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

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    public event Action RightClickDetected;

    public GlobalMouseHook()
    {
        _proc = HookCallback;
        using (var curProcess = Process.GetCurrentProcess())
        using (var curModule = curProcess.MainModule)
        {
            _hookId = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(curModule.ModuleName), 0);
        }
        if (_hookId == IntPtr.Zero) throw new System.ComponentModel.Win32Exception();
    }

    private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && (wParam == (IntPtr)WM_RBUTTONDOWN || wParam == (IntPtr)WM_RBUTTONUP))
        {
            var info = Marshal.PtrToStructure(lParam);
            var args = new MouseEventArgs(MouseButtons.Right, 1, info.pt.x, info.pt.y, 0);
            RightClickDetected?.Invoke(args);
        }
        return CallNextHookEx(_hookId, nCode, wParam, lParam);
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT { public int x, y; }
    [StructLayout(LayoutKind.Sequential)]
    private struct MSLLHOOKSTRUCT { public POINT pt; public int mouseData, flags, time, dwExtraInfo; }

    public void Dispose()
    {
        if (_hookId != IntPtr.Zero) UnhookWindowsHookEx(_hookId);
        GC.SuppressFinalize(this);
    }
}

表單端整合:動態切換 ContextMenuStrip


public partial class MainForm : Form
{
    private readonly GlobalMouseHook _hook;
    private readonly ContextMenuStrip _copyOnlyMenu;

    public MainForm()
    {
        InitializeComponent();

        // 僅複製的選單
        _copyOnlyMenu = new ContextMenuStrip();
        _copyOnlyMenu.Items.Add("複製", null, (s, e) => 
        {
            if (ActiveControl is TextBox tb) Clipboard.SetText(tb.SelectedText);
        });

        _hook = new GlobalMouseHook();
        _hook.RightClickDetected += OnGlobalRightClick;
    }

    private void OnGlobalRightClick(MouseEventArgs e)
    {
        // 僅當焦點在 panelSpecial 內的 TextBox 時套用
        if (ActiveControl is TextBox tb && tb.Parent == panelSpecial)
        {
            // 在 UI 執行緒切換選單
            BeginInvoke((Action)(() => tb.ContextMenuStrip = _copyOnlyMenu));
        }
        else
        {
            BeginInvoke((Action)(() => 
            {
                if (ActiveControl is TextBox other) other.ContextMenuStrip = null; // 還原預設
            }));
        }
    }

    protected override void OnFormClosed(FormClosedEventArgs e)
    {
        _hook.Dispose();
        base.OnFormClosed(e);
    }
}

關鍵風險與緩解

風險說明緩解
鉤子阻塞回調執行過久會導致系統滑鼠卡頓回調僅做最小判斷,UI 操作用 BeginInvoke 非同步處理
多視窗衝突同一進程開啟多個視窗共用鉤子,邏輯複雜單視窗應用;多視窗改用 WH_MOUSE 執行緒鉤子或各視窗獨立管理
權限問題低完整性程序 (如瀏覽器) 無法鉤住高完整性視窗不適用跨完整性層級場景
除錯困難中斷點命中時鉤子卡住,滑鼠失效使用 Trace/Log 替代中斷點;或暫時停用鉤子除錯

更簡單的替代方案 (推薦)


// 僅需限制特定 TextBox 功能,無需全域鉤子
textBoxSpecial.ShortcutsEnabled = false; // 關閉 Ctrl+C/V/X/A
textBoxSpecial.ContextMenuStrip = new ContextMenuStrip();
textBoxSpecial.ContextMenuStrip.Items.Add("複製", null, (s, e) => 
    Clipboard.SetText(textBoxSpecial.SelectedText));

總結

全域滑鼠鉤子 (WH_MOUSE_LL) 能實現跨控制項、跨視窗的統一攔截邏輯,但複雜度高、風險大。單純「限制特定 TextBox 選單」建議使用 ContextMenuStrip + ShortcutsEnabled 純 Managed 方案,穩定且易維護。僅在需全系統級攔截 (如螢幕錄製、宏錄製、無障礙工具) 時才採用鉤子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved