How do I override Ctrl+Shift+0 (zero) for WinForms RichTextBox?

一曲冷凌霜 提交于 2021-02-10 12:13:45

问题


For my subclassed RichTextBox class, I can capture, suppress the default behaviour of and re-purpose Ctrl+Shift+#, as long as # is between 1 and 9. For Ctrl+Shift+0, I can't. I have experimented with ProcessCmdKey in the form class and onKeyDown and PreProcessMessage in the control class.

Here is sample code meant for the control class that should suppress Ctrl+Shift+0 but does not:

public override bool PreProcessMessage(ref Message msg)
{
    bool cancel = false;
    int vKeyCode = (int)msg.WParam;

    if(msg.Msg == WinApi.WM_KEYDOWN)
    {
        bool ctrlDown = (WinApi.GetKeyState(Keys.ControlKey) & (1 << 16)) == (1 << 16);
        bool altDown = (WinApi.GetKeyState(Keys.Alt) & (1 << 16)) == (1 << 16);
        bool shiftDown = (WinApi.GetKeyState(Keys.ShiftKey) & (1 << 16)) == (1 << 16);

        if(ctrlDown && shiftDown && vKeyCode == (int)Keys.D0)
        {
            Debug.WriteLine("Cancel!");
            cancel = true;
        }
    }

    return cancel ? true : base.PreProcessMessage(ref msg);
}

However, changing Keys.D0 to Keys.D1 shows that the sample otherwise works.

If it's a clue, the default behaviour of the RichTextBox, in response to Ctrl+Shift+0, is to change the font. I went hunting for documentation that mentions this as a built-in shortcut but I didn't find anything (maybe I'm not using the correct search terms).

How should I detect Ctrl+Shift+0 so that I can suppress the default behaviour and write my own?


回答1:


After some tries, I've found out how to actually suppress the Ctrl + Shift + D0. However the new problem was even tougher, the Ctrl + Shift + D0 was suppressed OK but the beep sound was generated when releasing the Ctrl and Shift, that's so annoying because you said you wanted to override these keys combination not discard it. So the beep sound should not be generated.

After searching much with a hope there was some style to apply to RichTextBox to prevent the beep sound or some message to discard resulting in suppressing the beep sound but there are not any such things. I were almost disappointed and intended to let your question unanswered forever. I didn't want to add the answer which just partially solved your problem. However it was luckily that I tried sending some key instead of the discarded 0 key to consume the keys combination and make the keys combination valid so no beep sound would be generated. Here is the entire code for you, note that we have to use some global low-level keyboard hook here, as I said the Application-level message filter also couldn't help:

    [DllImport("user32")]
    private static extern IntPtr SetWindowsHookEx(int hookType, KeyboardLowLevelProc proc, IntPtr moduleHandle, int threadID);
    [DllImport("user32")]
    private static extern int UnhookWindowsHookEx(IntPtr hHook);
    [DllImport("user32")]
    private static extern IntPtr CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
    [DllImport("kernel32")]
    private static extern IntPtr GetModuleHandle(string moduleName);

    public struct KBDLLHOOKSTRUCT
    {
        public Keys key;
        public int scanCode;
        public int flags;
        public int time;
        public IntPtr extra;
    }
    public delegate IntPtr KeyboardLowLevelProc(int hCode, IntPtr wParam, IntPtr lParam);
    public IntPtr KeyboardLowLevelCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0) {
            KBDLLHOOKSTRUCT kbd = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
            if (kbd.key == Keys.D0 && blockD0) {                
                if(ModifierKeys == (Keys.Control | Keys.Shift)) {
                    SendKeys.Send("{ESC}");
                    //Add custom code as the response to Ctrl + Shift + D0 here
                    //....
                }
                return new IntPtr(1);//Discard the default behavior
            }
        }
        return CallNextHookEx(hHook, nCode, wParam, lParam);
    }
    bool blockD0;
    KeyboardLowLevelProc proc; //this should be declared in the form scope
    IntPtr hHook;
    //your Form1 constructor
    public Form1(){
       InitializeComponent();
       //Get current module Handle
       IntPtr currentModuleHandle = GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName);
       //Set the keyboard hook
       hHook = SetWindowsHookEx(13, proc, currentModuleHandle, 0);//WH_KEYBOARD_LL = 13
       //register these Key events for your richTextBox1 
       richTextBox1.KeyDown += (s, e) => {
            if(e.KeyCode != Keys.D0) blockD0 = true;
       };
       richTextBox1.KeyUp += (s, e) => {
            if (ModifierKeys == Keys.None) blockD0 = false;
       };
       //Unhook keyboard when form is closed
       FormClosed += (s,e) => {
          if (hHook != IntPtr.Zero) {
              UnhookWindowsHookEx(hHook);
              hHook = IntPtr.Zero;
          }
       }
    }

A little about the explanation: I do not understand exactly why we have to use Global Low-level Keyboard hook here, I guess that when the keys combination Ctrl + Shift + D0 is pressed, there may be some key message cloned and dispatched to another thread, that's why all the manual interceptions in the current thread can't intercept or override the Ctrl + Shift + D0, however the global low-level keyboard hook can handle the key messages in all threads of the current module and it can intercept any key messages.

I mentioned about the beep sound problem, if you want to experience it, just remove the SendKeys.Send("{ESC}");, in fact you can try some other keys like 1, 2, ... they also make the keys combination Ctrl + Shift + ... valid and help avoid any beep sound.

UPDATE

The solution above works OK, it's the best solution with the point that the Ctrl + Shift + D0 should be discarded cleanly and totally. However it's a little long (as you can see). I've found that when you press the Ctrl + Shift + D0, the message WM_INPUTLANGCHANGEREQUEST is sent, this message causes the behavior which you don't want. So we can try another solution, with PreProcessMessage you can still catch the combination Ctrl + Shift + D0 but you just can't discard it (because it's dispatched to another thread), that means you can add your own code there, instead of discarding the Ctrl + Shift + D0, we can discard the effect/behavior it causes instead by discarding the message WM_INPUTLANGCHANGEREQUEST. We have the following code:

//Create a custom RichTextBox class
public class CustomRichTextBox : RichTextBox {
   protected override void WndProc(ref Message m){
     if(m.Msg == 0x50) return; //WM_INPUTLANGCHANGEREQUEST = 0x50
     base.WndProc(ref m);
   }
   public override bool PreProcessMessage(ref Message msg) {            
        if (msg.Msg == 0x100)//WM_KEYDOWN = 0x100
        {
           Keys keyData = (Keys)msg.WParam | ModifierKeys;
           if(keyData == (Keys.Control | Keys.Shift | Keys.D0)){
             //your own code goes here...
           }
        }
        return base.PreProcessMessage(ref msg);
   }
}

You can see that the second approach is much shorter, however as I said, it doesn't actually suppress the Ctrl + Shift + D0, it just suppresses the default behavior caused by the message WM_INPUTLANGCHANGEREQUEST. I guess it's enough to solve your problem.



来源:https://stackoverflow.com/questions/19556221/how-do-i-override-ctrlshift0-zero-for-winforms-richtextbox

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!