问题
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