Announcement

Collapse
No announcement yet.

Capture TAB in RichEdit - Replace With Custom Action

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Capture TAB in RichEdit - Replace With Custom Action

    I'll put this in the Source Code forum, but wanted to see what comments on changes/issues might be appropriate before doing so.

    I wanted to custom handle the TAB key when it was pressed inside a RichEdit control. Subclassing will let me see the key (via WM_Char) but I had to use the WM_GetDlgCode message to send the DLGC_WantAllKeys value to the RichEdit window procedure to prevent TAB from moving focus to the next control.

    I looked through the forum messages and never quite found a complete example. It may have been there but all I found were general suggestions on using subclassing or intercepting the IsDialogMessage in an SDK solution.

    So I put together this working example. Comments are welcome.


    Code:
    #Compile Exe
    #Dim All
    #Include "RichEdit.inc"
    #Include "CommCtrl.inc"
    %ID_RichEdit = 500
    Global hDlg As Dword, hRichEdit As Dword, OldProc&
    Function PBMain() As Long
       Local style&, buf$
       buf$ =  "This is sample" + $CrLf + "text for the" + $CrLf + "edit control."
       style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
                 Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
       Dialog New Pixels, 0, "Test Code",300,300,200,155, %WS_OverlappedWindow To hDlg
       Control Add Button, hDlg, 100,"Paste", 30,10,140,20
       LoadLibrary("riched32.dll") : InitCommonControls
       Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,160,100, style&
       SendMessage hRichEdit, %EM_SETEVENTMASK, 0, %ENM_SELCHANGE Or %ENM_CHANGE Or %ENM_LINK Or %ENM_KeyEvents
       Control Handle hDlg, %ID_RichEdit To hRichEdit
       Dialog Show Modal hDlg Call DlgProc
    End Function
    
    CallBack Function DlgProc() As Long
       Local temp$
       Select Case CB.Msg
          Case %WM_InitDialog
             OldProc& = SetWindowLong(GetDlgItem(hDlg, %ID_RichEdit), %GWL_WndProc, CodePTR(NewProc))  'subclass
          Case %WM_Destroy
             SetWindowLong hRichEdit, %GWL_WNDPROC, OldProc&   'un-subclass
          Case %WM_Command
             If CB.Ctl = 100 AND CB.Ctlmsg = %BN_Clicked Then
                MsgBox "No action asssigned."
             End If
       End Select
    End Function
    
    Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
       Select Case Msg
          Case %WM_GETDLGCODE                 
               Function = %DLGC_WANTALLKEYS  'establish control by the RichEdit
               Exit Function
          Case %WM_Char                           'responds when the TAB key is pressed
               Select Case wParam
                  Case %VK_Tab
                     'detects TAB key - take custom action here
                     Static iTabCount& : Local temp$ : Incr iTabCount
                     Control Get Text hDlg, %ID_RichEdit TO temp$
                     Control Set Text hDlg, %ID_RichEdit, temp$ + Str$(iTabCount)
                     Dialog Set Text hDlg, "Tab pressed"   'just to show the TAB key was seen
                     Function = 0 : Exit Function    'do not further process the TAB key
               End Select
       End Select
       Function = CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
    End Function
    I tried the DLGC_WANTTAB value, but that seemed to stop all other keys from being recognized. I thought it might simply modify just the TAB behavior, but apparently not.

  • #2
    FWIW, <TAB> is a key which has a defined "Windows" action, so I'd make sure that is what the user wants.. i.e., I would not 'design' anything from scratch to use the <TAB> key for some application-specific purpose.

    I have a couple of examples in source code forum, all of which do...
    Code:
                 ELSEIF wParam = %VK_TAB THEN
                    LOCAL iKeyState AS LONG
                          iKeyState =  BITS&(GetKeyState (%VK_SHIFT))
                      ' STDOUT "keyState " & FORMAT$(iKeyState) & HEX$(iKeyState, 4) & " " & BIN$ (iKeyState, 32)
                          SetFocus     GetNextDlgTabItem (GetParent(GETPARENT(hWnd)), GetParent(hWnd), ISTRUE (iKeyState > &h7F))
                          FUNCTION = 0
                          EXIT FUNCTION
    .. which I suppose I could have just let Windows handle, because that's what Windows does with the <TAB> key.
    Michael Mattias
    Tal Systems Inc. (retired)
    Racine WI USA
    [email protected]
    http://www.talsystems.com

    Comment


    • #3
      Your code for handling the WM_GETDLGCODE message for a control you did not write is not correct.
      Both the current form
      Code:
        SELECT CASE Msg
          CASE %WM_GETDLGCODE
            FUNCTION = %DLGC_WANTALLKEYS  'establish control by the RichEdit
            EXIT FUNCTION
      and what I suspect you did before.
      Code:
        SELECT CASE Msg
          CASE %WM_GETDLGCODE
            FUNCTION = %DLGC_WANTTAB
            EXIT FUNCTION
      If this is a control you wrote from scratch, then you would return the codes the control processes.
      If this is control you did not write, you first query the control for the codes it processes and then
      make changes to that set. Failing to do so can change the behaviour of the control in unanticipated ways.

      There are slightly different ways to handle this message, but the very basic form would be as follows:
      Code:
        LOCAL lMsgResult    AS LONG
        
        SELECT CASE Msg
          CASE %WM_GETDLGCODE
            ' Get the dialog codes the control processes
            lMsgResult = CallWindowProc(OldProc&, hWnd, %WM_GETDLGCODE, 0, lParam)
            ' Tell the OS(IsDialogMessage) we want to process the tab key 
            lMsgResult = lMsgResult OR %DLGC_WANTTAB        
            ' Prevent the control from automatically selecting its
            ' entire contents when it gains the input focus
            lMsgResult = lMsgResult AND NOT %DLGC_HASSETSEL        
            FUNCTION = lMsgResult
            EXIT FUNCTION
      Last edited by Dominic Mitchell; 27 Oct 2009, 10:07 PM.
      Dominic Mitchell
      Phoenix Visual Designer
      http://www.phnxthunder.com

      Comment


      • #4
        Gary, Give Case %WM_KeyUp a try instead of Wm_Char. Works for me.

        ================================
        "It is a miracle that curiosity
        survives formal education."
        Albert Einstein (1879-1955)
        ================================
        It's a pretty day. I hope you enjoy it.

        Gösta

        JWAM: (Quit Smoking): http://www.SwedesDock.com/smoking
        LDN - A Miracle Drug: http://www.SwedesDock.com/LDN/

        Comment


        • #5
          Thanks Gosta,

          Is there an advantage to using the WM_KeyUp? I assume you have a reason for the suggestion but I'm not sure what it is ...

          Comment


          • #6
            Code:
            ' Get the dialog codes the control processes
                  lMsgResult = CallWindowProc(OldProc&, hWnd, %WM_GETDLGCODE, 0, lParam)
                  ' Tell the OS(IsDialogMessage) we want to process the tab key 
                  lMsgResult = lMsgResult OR %DLGC_WANTTAB
            Thank you for that.

            That's something I never found in any documentation, makes perfect sense, and it explains why I have often had to play with the DLCG_WANTxxxx flags depending on what kind of control I have been subclassing to get the same behavior.

            MCM
            Michael Mattias
            Tal Systems Inc. (retired)
            Racine WI USA
            [email protected]
            http://www.talsystems.com

            Comment


            • #7
              MCM:
              In this case, I'm writing an Intellisense demo and TAB is the Microsoft method of inserting the suggestion/selection, so I'm matching that. Users of Intellisense would expect it.


              Dominic:
              That is a welcome piece of information. I was seeing some unexpected results yesterday and your comments may clarify why.

              Thanks!

              Comment


              • #8
                Doesn't this code get run multiple times? And that's ok? There's no reason to have it run only one time (re: the assignment of %DLGC_ constants).

                Code:
                SELECT CASE Msg
                    CASE %WM_GETDLGCODE
                         .....

                Comment


                • #9
                  Hmm.... having trouble with Dominic's code.

                  I modified the code section to match Dominic's suggestion, but now my code doesn't recognize the TAB key at all - it's still handled by the dialog.

                  Did I not make the change correctly?

                  Code:
                     
                  Select Case Msg
                     Case %WM_GETDLGCODE
                        iResult& = CallWindowProc(OldProc&, hWnd, %WM_GetDlgCode, 0, lParam)
                        Function = iResult& Or %DLGC_WantTAb
                        Exit Function
                  ... edited after the fact ... left off the Exit Function
                  Last edited by Gary Beene; 28 Oct 2009, 02:12 PM.

                  Comment


                  • #10
                    To actually test the capture you should add another control like a button to check if it get's the focus on tab.
                    hellobasic

                    Comment


                    • #11
                      Yes, I'm using the example I posted above to start the thread - it has the extra button.

                      And with Dominic's approach (getting the current flag and using OR %DLGC_WantTAB) pressing the TAB key dances between the button and the edit control. Not what I was expecting, based on his comments.
                      Last edited by Gary Beene; 28 Oct 2009, 01:50 PM.

                      Comment


                      • #12
                        pressing the TAB key dances between the button and the edit control. Not what I was expecting, based on his comments.
                        What is your subclass proc doing with the TAB key?
                        Michael Mattias
                        Tal Systems Inc. (retired)
                        Racine WI USA
                        [email protected]
                        http://www.talsystems.com

                        Comment


                        • #13
                          I'm not sure I can be of help here as I've never done this sort of thing myself. However, the Win32 Programmer's Reference says %WM_GETDLGCODE can be used to allow you to handle special things. It also says that the default window procedure will always return 0 so if this code is in a control's sub classed window procedure:

                          Code:
                          Select Case Msg
                             Case %WM_GETDLGCODE
                                iResult& = CallWindowProc(OldProc&, hWnd, %WM_GetDlgCode, 0, lParam)
                                Function = iResult& Or %DLGC_WantTAb
                          It will always return 0 if OldProc& is the default window procedure for that particular control.

                          I'm thinking you just want FUNCTION = %DLGC_WANTTAB but this advice is from a quick read through of this thread and the Win32 help file.
                          Jeff Blakeney

                          Comment


                          • #14
                            Hi Jeff.

                            I've tried that. If all I use is the line:

                            Code:
                            Function = %DLGC_WantTAB
                            then the RE will not accept other keystrokes (letters, enter, ...)

                            If I use this:

                            Code:
                            Function = %DLGC_WantAllKeys
                            I seem to get the results I want - I can capture the TAB key and prevent moving focus. But I've been playing with it and think I've seen some other effects.

                            So what Dominic mentions seems to make sense - ORing the WantTAB to whatever the current flags are. But when I try it like this:

                            Code:
                            iResult& = CallWindowProc(OldProc&, hWnd, %WM_GetDlgCode, 0, lParam)
                            Function = iResult& Or %DLGC_WantTAb
                            The TAB key then moves focus - different than what Dominic's comments lead me to expect.

                            Obviously I don't know all I need to know on this topic! The explanatory facts are eluding me.

                            Here's the example again, with Dominic's code - the other options I"ve tried are there too, but commented out.

                            Code:
                            #Compile Exe
                            #Dim All
                            #Include "RichEdit.inc"
                            #Include "CommCtrl.inc"
                            %ID_RichEdit = 500
                            Global hDlg As Dword, hRichEdit As Dword, OldProc&
                            Function PBMain() As Long
                               Local style&, buf$
                               buf$ =  "This is sample" + $CrLf + "text for the" + $CrLf + "edit control."
                               style& = %WS_Child Or %WS_Visible Or %ES_MultiLine Or %WS_VScroll Or %ES_AutoHScroll _
                                         Or %WS_HScroll Or %ES_AutoVScroll Or %ES_WantReturn Or %ES_NoHideSel Or %WS_TabStop
                               Dialog New Pixels, 0, "Test Code",300,300,200,155, %WS_OverlappedWindow To hDlg
                               Control Add Button, hDlg, 100,"Paste", 30,10,140,20
                               LoadLibrary("riched32.dll") : InitCommonControls
                               Control Add "RichEdit", hDlg, %ID_RichEdit, buf$,20,40,160,100, style&
                               SendMessage hRichEdit, %EM_SETEVENTMASK, 0, %ENM_SELCHANGE Or %ENM_CHANGE Or %ENM_LINK Or %ENM_KeyEvents
                               Control Handle hDlg, %ID_RichEdit To hRichEdit
                               Dialog Show Modal hDlg Call DlgProc
                            End Function
                            
                            CallBack Function DlgProc() As Long
                               Local temp$
                               Select Case CB.Msg
                                  Case %WM_InitDialog
                                     OldProc& = SetWindowLong(GetDlgItem(hDlg, %ID_RichEdit), %GWL_WndProc, CodePTR(NewProc))  'subclass
                                  Case %WM_Destroy
                                     SetWindowLong hRichEdit, %GWL_WNDPROC, OldProc&   'un-subclass
                                  Case %WM_Command
                                     If CB.Ctl = 100 AND CB.Ctlmsg = %BN_Clicked Then
                                        MsgBox "No action asssigned."
                                     End If
                               End Select
                            End Function
                            
                            Function NewProc(ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
                               Local iResult&
                               Select Case Msg
                                  Case %WM_GETDLGCODE                 'establish control by the RichEdit
                                        iResult& = CallWindowProc(OldProc&, hWnd, %WM_GetDlgCode, 0, lParam)
                                        Function = iResult& Or %DLGC_WantTAb
                            '           Function = %DlgC_WantChars Or %DlgC_WantTab Or %DlgC_WantArrows  'ALLKEYS
                            '           Function = %DLGC_WANTTAB
                            '           Function = %DLGC_WANTALLKEYS
                                       Exit Function
                                  Case %WM_Char
                                       Select Case wParam
                                          Case %VK_Tab
                                             'detects TAB key - do whatever you want
                                             Static iTabCount& : Local temp$ : Incr iTabCount
                                             Control Get Text hDlg, %ID_RichEdit TO temp$
                                             Control Set Text hDlg, %ID_RichEdit, temp$ + Str$(iTabCount)
                                             Dialog Set Text hDlg, "Tab pressed"   'just to show the TAB key was seen
                                             Function = 0 : Exit Function    'do not further process the TAB key
                                       End Select
                               End Select
                               Function = CallWindowProc(OldProc&, hWnd, Msg, wParam, lParam)
                            End Function
                            Last edited by Gary Beene; 28 Oct 2009, 02:25 PM.

                            Comment


                            • #15
                              >>> iResult& = CallWindowProc(OldProc&, hWnd, %WM_GetDlgCode, 0, lParam)

                              Maybe you could try.....
                              Code:
                              iResult = SendMessage (hWnd, %WM_GETDLGCODE, %NULL, %NULL) 
                              lResult OR= [mysettings]
                              ==>
                              The WM_GETDLGCODE message is sent to the window procedure associated with a control....To override this default behavior, the control can respond to the WM_GETDLGCODE message to indicate the types of input it wants to process itself
                              ..... the window procedure for the predefined control classes return a code appropriate for each class.

                              The WM_GETDLGCODE message and the returned values are useful only with user-defined dialog box controls or standard controls modified by subclassing.
                              ????

                              MCM
                              Michael Mattias
                              Tal Systems Inc. (retired)
                              Racine WI USA
                              [email protected]
                              http://www.talsystems.com

                              Comment


                              • #16
                                I have found a way, try this in your windowproc (not the control subclass proc):

                                Code:
                                    Case %WM_NEXTDLGCTL
                                    
                                        If GetFocus() = GetDlgItem( CbHndl, %ID_FORM1_RICHEDIT1 ) Then
                                            keybd_event %VK_CONTROL, 0, 0, 0
                                            keybd_event %VK_TAB, 0, 0, 0
                                            keybd_event %VK_TAB, 0, %KEYEVENTF_KEYUP, 0
                                            keybd_event %VK_CONTROL, 0, %KEYEVENTF_KEYUP, 0
                                            Function = 1
                                        End If
                                hellobasic

                                Comment


                                • #17
                                  Edwin!

                                  Very nice. And actually, to simply negate the TAB key, all I have to do is these new few lines. No subclassing required.

                                  Code:
                                      Case %WM_NEXTDLGCTL
                                          If GetFocus = hRichEdit Then
                                              Function = 1
                                          End If
                                  Your first two lines show me how to add a TAB character (Control+TAB). That's useful, although in this particular case I want to do something else.

                                  But I'm not sure why the 2nd two lines of your code are necessary (the KeyUp lines). Can you explain?

                                  But the real magic is capturing %WM_NextDlgCtl and returning FUNCTION=1. That ends the TAB processing by the dialog.

                                  Thanks very much!

                                  Comment


                                  • #18
                                    The WM_CHAR message is synthesized by the OS(TranslateMessage) from the WM_KEYDOWN message, therefore,
                                    the control is seeing this series of messages:

                                    WM_KEYDOWN -> WM_CHAR

                                    As a result, this code does not stop the focus from being moved by IsDialogMessage.
                                    Code:
                                    CASE %WM_CHAR                           'responds when the TAB key is pressed
                                         SELECT CASE wParam
                                            CASE %VK_TAB
                                               'detects TAB key - take custom action here
                                               STATIC iTabCount& : LOCAL temp$ : INCR iTabCount
                                               CONTROL GET TEXT hDlg, %ID_RICHEDIT TO temp$
                                               CONTROL SET TEXT hDlg, %ID_RICHEDIT, temp$ + STR$(iTabCount)
                                               DIALOG SET TEXT hDlg, "Tab pressed"   'just to show the TAB key was seen
                                               FUNCTION = 0 : EXIT FUNCTION    'do not further process the TAB key
                                         END SELECT
                                    Use this code instead.

                                    Code:
                                         
                                    CASE %WM_KEYDOWN                           'responds when the TAB key is pressed
                                         SELECT CASE wParam
                                            CASE %VK_TAB
                                               'detects TAB key - take custom action here
                                               STATIC iTabCount& : LOCAL temp$ : INCR iTabCount
                                               CONTROL GET TEXT hDlg, %ID_RICHEDIT TO temp$
                                               CONTROL SET TEXT hDlg, %ID_RICHEDIT, temp$ + STR$(iTabCount)
                                               DIALOG SET TEXT hDlg, "Tab pressed"   'just to show the TAB key was seen
                                               FUNCTION = 0 : EXIT FUNCTION    'do not further process the TAB key
                                         END SELECT
                                    Dominic Mitchell
                                    Phoenix Visual Designer
                                    http://www.phnxthunder.com

                                    Comment


                                    • #19
                                      >But I'm not sure why the 2nd two lines of your code are necessary (the KeyUp lines). Can you explain?
                                      In most cases you must do it like this.
                                      It implies a 'full circle' of a button down and up.

                                      A bit similar to button click event which is derived from a button down + UP message.

                                      The keyboard works with down and up as well.
                                      (press<>release)
                                      hellobasic

                                      Comment


                                      • #20
                                        Dominic, is this placed in the dialogproc or RE subclass proc?
                                        hellobasic

                                        Comment

                                        Working...
                                        X