Edit Menus and the RichTextBox Control

From HashVB
Jump to: navigation, search

The What

RichTextBox controls present an interesting problem to VB developers in the form of edit menus. If you are a VB developer, creating an application in which you need an edit menu with functions such as cut, copy, paste, undo and redo, then you will clearly want the user to have the ability to use their respective standard keyboard accelerators. When you're using the RichTextBox (or even the TextBox), what you find when you try to do this is that it performs all of these functions twice when you press the accelerator key (but not when you choose the item from your menu). We will look at the reason why this happens, and the solution to the problem.

The Why

The reason this happens is because the identifiers for the accelerators of standard edit menu functions are different to the standard windows ones, and thus two events are registered for one accelerator keyset. What this means is that the code perform the function is executed twice, in two different places. In practical terms, what this means for your application is that you will paste text twice, try to cut and copy text twice (which will double the time it takes, and that's a problem if there's a lot of text), and undo and redo twice (which results in effectively nothing happening for the user, since VB doesn't do multiple-level undo in RichTextBoxes).

The How

The solution to the problem presented is a relatively simple one (or at least it is for anyone familiar with subclassing). The idea is to wait for the correct KeyDown message to arrive and simply consume it. That is to say that we never let the message actually get to the RichTextBox control, so it can't process it and perform the associated accelerator key action. Note that this sample uses code from another code sample ([1]).

   Implements ISubclass
   Private Sub Form_Load()
       SubclassWnd RichTextBox1.hwnd, Me
   End Sub
   Private Sub Form_Unload(Cancel As Integer)
       UnSubclassWnd RichTextBox1.hwnd
   End Sub
   Private Function ISubclass_WndProc( _
                                           ByVal hwnd As Long, _
                                           ByVal uMsg As Long, _
                                           ByVal wParam As Long, _
                                           ByVal lParam As Long, _
                                           bHandled As Boolean, _
                                           RetVal As Boolean _
                                       ) As Long
       Select Case uMsg
           Case WM_KEYDOWN
               '-- We have a KeyDown message, see if we need to consume it ...
               If GetKeyState(vbKeyControl) And &HF0000000 Then '-- First check if the ctrl key is down
                   Select Case wParam
                       '-- See if it's one of our standard edit menu messages
                       Case vbKeyV, vbKeyC, vbKeyX, vbKeyY, vbKeyV
                           '-- Consume the message
                           bHandled = True
                   End Select
               End If
       End Select
   End Function

Simple as that.