RichTextBox events firing order

Hi,

I came acroos what looks like a small but vexing problem:

I have a richtextbox (rtb) that does some basic syntax highlighting. The highlighting is handled through the 'Textchanged' event and contains some richtextbox.select() commands in order to change the color of the selected keywords. So far so good.

Unfortunately, this seems to have an adverse effect on the standard (I didn't code anything) undo (ctrl+z), redo (ctrl+y) commands but not paste (ctrl+c). Without the texychanged code, they work fine (ie if I type abcd and then ctrl+z the abcd disappears, then ctrl+y and it reappears), with the code present ctrl+z does not suppress the typed text but moves the cursor back at the beginning and ctrl+y doesn't appear to do anything.

I though this happened because the undo/redo fired AFTER the textchanged event and therefore attempted to undo things that happened to the rtb text during the syntaxt highlighting process, so I tried to intercept the undo/redo in the 'keydown' event, which, If I believe my debugger (VS2005 standard edition using C#), fires BEFORE the textchanged event, so I did something like that :

private void rtb_KeyDown(object sender, KeyEventArgs e)

{

//check if ctrl+z

if ((e.Modifiers & Keys.Control) == Keys.Control && (e.KeyCode == Keys.V || e.KeyCode == Keys.Z || e.KeyCode == Keys.Y))

{keyWordCheck = true;

switch (e.KeyCode)

{

case Keys.Z:

MessageBox.Show(rtb.UndoActionName, "Undo Action");

rtbQuery.Undo();

e.Handled = true;

break;

case Keys.Y:

MessageBox.Show(rtb.RedoActionName, "Redo Action");

rtb.Redo();

e.Handled = true;

break;

}

}

...

so that I could see the last action performed before it is undone/redone.

This is where I got completely puzzled : Although 'keydown' is supposed to execute BEFORE textchanged, it seems affected by the content of the textchanged member function. With the textchanged code commented out, my typing 'abcd' followed by ctrl+z would show me 'Typing' as the last action, and wipe the 'abcd' out, with the textchanged code present, it would show me 'Unknown' as the last action and move the pointer back to the beginning of the text but the 'abcd' would stay. I tried the same using 'previewKeyDown' with the same result.

Could somebody please clarify the sequence in which the ctrl+z/ctrl+y execute as opposed to the keydown and textchanged events and/or how to circumvent this problem.

Thank you,

MM



Answer this question

RichTextBox events firing order

  • Jason Callas

    Jay,

    Thank you, that was very clear! Now it all makes sense... almost ;-)

    Just another point : where do I get the right values for the constants (KEYDOWN...), and how do I test for CTRL+Z for example, is the a bitwise and as explained for the keydown handler or something else. Could you elaborate on the difference between KEYDOWN and SYSKEYDOWN or point me to the right documentation I did search C# help to no avail.

    Thanks,

    MM


  • ku19832001

    I do not know the diff. between KEYDOWN and SYSKEYDOWN, perhaps experts in this forum may provide us the details.

    Key values are available at http://blogs.msdn.com/michkap/archive/2006/03/23/558658.aspx

    Here is another place good reference : http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c46c.aspx#q656q

    I gathered the details long back from Microsoft/msdn and kept it in my test program for all the values I know. This is Visual C++ 2003, and you may need to translate it in C#

    protected: bool ProcessCmdKey(Message* msg, Keys keyData)
    {
    const int WM_KEYDOWN = 0x100;
    const int WM_SYSKEYDOWN = 0x104;
    if ((msg->Msg == WM_KEYDOWN) || (msg->Msg == WM_SYSKEYDOWN))
    {
    switch(keyData)
    {
    case Keys::F1 :
    this->Parent->Text = S"Keys::F1 key captured";
    break;
    case Keys::F2 :
    this->Parent->Text = S"Keys::F2 key captured";
    break;
    case Keys::Shift | Keys::F2 :
    this->Parent->Text = S"Keys::Shift | Keys::F2 key captured";
    break;
    case Keys::F3 :
    this->Parent->Text = S"Keys::F3 key captured";
    break;
    case Keys::Shift | Keys::F3 :
    this->Parent->Text = S"Keys::Shift | Keys::F3 key captured";
    break;
    case Keys::F4 :
    this->Parent->Text = S"Keys::F4 key captured";
    break;
    case Keys::F5 :
    this->Parent->Text = S"Keys::F5 key captured";
    break;
    case Keys::F6 :
    this->Parent->Text = S"Keys::F6 key captured";
    break;
    case Keys::F7 :
    this->Parent->Text = S"Keys::F7 key captured";
    break;
    case Keys::F8 :
    this->Parent->Text = S"Keys::F8 key captured";
    break;
    case Keys::F9 :
    this->Parent->Text = S"Keys::F9 key captured";
    break;
    case Keys::Control | Keys::F9 :
    this->Parent->Text = S"Keys::Control | Keys::F9 key captured";
    break;
    case Keys::Shift | Keys::F9 :
    this->Parent->Text = S"Keys::Shift | Keys::F9 key captured";
    break;
    case Keys::F10 :
    this->Parent->Text = S"Keys::F10 key captured";
    break;
    case Keys::F11 :
    this->Parent->Text = S"Keys::F11 key captured";
    break;
    case Keys::F12 :
    this->Parent->Text = S"Keys::F12 key captured";
    break;
    case Keys::Control | Keys::F12 :
    this->Parent->Text = S"Keys::Control | Keys::F12 key captured";
    break;
    case Keys::Control | Keys::A :
    this->Parent->Text = S"Keys::Control | Keys::A key captured";
    break;
    case Keys::Control | Keys::Alt | Keys::B :
    this->Parent->Text = S"Keys::Control | Keys::Alt | Keys::B key captured";
    break;
    case Keys::Control | Keys::C :
    this->Parent->Text = S"Keys::Control | Keys::C key captured";
    break;
    case Keys::Control | Keys::D :
    this->Parent->Text = S"Keys::Control | Keys::D key captured";
    break;
    case Keys::Control | Keys::Alt | Keys::D :
    this->Parent->Text = S"Keys::Control | Keys::Alt | Keys::D key captured";
    break;
    case Keys::Control | Keys::E :
    this->Parent->Text = S"Keys::Control | Keys::E key captured";
    break;
    case Keys::Control | Keys::Alt | Keys::E :
    this->Parent->Text = S"Keys::Control | Keys::Alt | Keys::E key captured";
    break;
    case Keys::Control | Keys::F :
    this->Parent->Text = S"Keys::Control | Keys::F key captured";
    break;
    case Keys::Control | Keys::G :
    this->Parent->Text = S"Keys::Control | Keys::G key captured";
    break;
    case Keys::Control | Keys::M :
    this->Parent->Text = S"Keys::Control | Keys::M key captured";
    break;
    case Keys::Control | Keys::N :
    this->Parent->Text = S"Keys::Control | Keys::N key captured";
    break;
    case Keys::Control | Keys::O :
    this->Parent->Text = S"Keys::Control | Keys::O key captured";
    break;
    case Keys::Control | Keys::P :
    this->Parent->Text = S"Keys::Control | Keys::P key captured";
    break;
    case Keys::Control | Keys::R :
    this->Parent->Text = S"Keys::Control | Keys::R key captured";
    break;
    case Keys::Shift | Keys::Control | Keys::R :
    this->Parent->Text = S"Keys::Shift | Keys::Control | Keys::R key captured";
    break;
    case Keys::Control | Keys::S :
    this->Parent->Text = S"Keys::Control | Keys::S key captured";
    break;
    case Keys::Shift | Keys::Control | Keys::S :
    this->Parent->Text = S"Keys::Shift | Keys::Control | Keys::S key captured";
    break;
    case Keys::Control | Keys::Alt | Keys::S :
    this->Parent->Text = S"Keys::Control | Keys::Alt | Keys::S key captured";
    break;
    case Keys::Control | Keys::T :
    this->Parent->Text = S"Keys::Control | Keys::T key captured";
    break;
    case Keys::Control | Keys::U :
    this->Parent->Text = S"Keys::Control | Keys::U key captured";
    break;
    case Keys::Control | Keys::L :
    this->Parent->Text = S"Keys::Control | Keys::L key captured";
    break;
    case Keys::Control | Keys::V :
    this->Parent->Text = S"Keys::Control | Keys::V key captured";
    break;
    case Keys::Control | Keys::Alt | Keys::W :
    this->Parent->Text = S"Keys::Control | Keys::Alt | Keys::W key captured";
    break;
    case Keys::Control | Keys::X :
    this->Parent->Text = S"Keys::Control | Keys::X key captured";
    break;
    case Keys::Control | Keys::Z :
    this->Parent->Text = S"Keys::Control | Keys::Z key captured";
    break;
    case Keys::Shift | Keys::Control | Keys::Z :
    this->Parent->Text = S"Keys::Shift | Keys::Control | Keys::Z key captured";
    break;
    case Keys::Alt | Keys::Up :
    this->Parent->Text = S"Keys::Alt | Keys::Up key captured";
    break;
    case Keys::Alt | Keys::Down :
    this->Parent->Text = S"Keys::Alt | Keys::Down key captured";
    break;
    case Keys::Control | Keys::Home :
    this->Parent->Text = S"Keys::Control | Keys::Home key captured";
    break;
    case Keys::Control | Keys::End :
    this->Parent->Text = S"Keys::Control | Keys::End key captured";
    break;
    case Keys::Control | Keys::Tab :
    this->Parent->Text = S"Keys::Control | Keys::Tab key captured";
    break;
    case Keys::Control | Keys::Enter :
    this->Parent->Text = S"Keys::Control | Keys::Enter key captured";
    break;
    case Keys::Back :
    this->Parent->Text = S"Keys::Back key captured";
    break;
    case Keys::Insert :
    this->Parent->Text = S"Keys::Insert key captured";
    break;
    case Keys::Delete :
    this->Parent->Text = S"Keys::Delete key captured";
    break;
    case Keys::NumPad0 :
    this->Parent->Text = S"Keys::NumPad0 key captured";
    break;
    case Keys::Home :
    this->Parent->Text = S"Keys::Home key captured";
    break;
    case Keys::End :
    this->Parent->Text = S"Keys::End key captured";
    break;
    case Keys::Right :
    this->Parent->Text = S"Keys::Right key captured";
    break;
    case Keys::Left :
    this->Parent->Text = S"Keys::Left key captured";
    break;
    case Keys::Up :
    this->Parent->Text = S"Keys::Up key captured";
    break;
    case Keys::Down :
    this->Parent->Text = S"Keys::Down key captured";
    break;
    case Keys::Space :
    this->Parent->Text = S"Keys::Space key captured";
    break;
    case Keys::Tab :
    this->Parent->Text = S"Keys::Tab key captured";
    break;
    case Keys::Enter :
    this->Parent->Text = S"Keys::Enter key captured";
    break;
    case Keys::OemSemicolon :
    this->Parent->Text = S"Keys::OemSemicolon key captured";
    break;
    case Keys::OemPeriod :
    this->Parent->Text = S"Keys::OemPeriod key captured";
    break;
    case Keys::OemOpenBrackets :
    this->Parent->Text = S"Keys::OemOpenBrackets key captured";
    break;
    default:
    this->Parent->Text = S"";
    break;
    }
    }
    return (__super::ProcessCmdKey(msg,keyData));
    }



  • Ori'

    I understand the frustration. You can not handle the keystroke handlers by keydown or keypress. You need to have your class extending from richtextbox, and in that use ProcessCmdKey to handle it.

    This sample is from Visual c++ 2003 version.

    protected: bool ProcessCmdKey(Message* msg, Keys keyData)
    {
    const int WM_KEYDOWN = 0x100;
    const int WM_SYSKEYDOWN = 0x104;
    if ((msg->Msg == WM_KEYDOWN) || (msg->Msg == WM_SYSKEYDOWN))
    {
    switch(keyData)
    {
    case Keys::F1 :
    this->Parent->Text = S"Keys::F1 key captured";
    break;
    default:
    this->Parent->Text = S"";
    break;
    }
    }
    return (__super::ProcessCmdKey(msg,keyData));
    }

    Good Luck,



  • viru

    Jay,

    Thank you, but after further investigation and tries it seems I'm still at the same point.

    I did manage to test for Ctrl+Z etc.. using the technique you described (implementing a RichTextbox subclass and overriding the ProcessCmdKey) but the App still exhibits the same behaviour :

    In a nutshell it seems the'Undo()' command is still affected by code contained in the textchanged event whatever I do.

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)

    {

    bool ret = false;

    const int WM_KEYDOWN = 0x100;

    const int WM_SYSKEYDOWN = 0x104;

    KeysConverter kc = new KeysConverter();

    if ((msg.Msg == WM_KEYDOWN) || (msg.Msg == WM_SYSKEYDOWN))

    {

    String sKey = kc.ConvertToString(keyData);

    if (sKey.Equals("Ctrl+Z"))

    {

    keyWordCheck = false;

    string s = this.UndoActionName;

    // for the exact same keyboard input, s shows 'Unknown' if the Texthanged event contains some code affecting the selection or 'typing' if it does not.

    this.Undo();

    keyWordCheck = true;

    ret = true;

    }...

    The piece of code above has a different effect depending on the code within the 'TextChanged' event. If 'Textchanged' contains stuff that affects the text content (syntax highlighting based on calling richtextbox.select() etc..).

    If I type 'abcd' and hit Ctrl-Z with the syntaxhighlight code present, the UndoActionName shown when I first get in the ProcessCmdKey function is 'Unknown' and if the code is commented out, then the UndoActionName is 'Typing'. So somehow, ProcessCmdKey 'knows' what has happened or will happen in the text before the pointer reaches TextChanged.

    I'm puzzled, baffled, flabbergasted

    Thanks anyway,

    MM


  • RichTextBox events firing order