Announcement

Collapse
No announcement yet.

Key-press messages

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

  • Key-press messages

    I'm trying to capture key-press messages, but they aren't available directly in the callback--my optimistic CASE %WM_KEYDOWN was a tad weak --but that's really what I'm shooting for. Sooo, I tried subclassing each button and it seems to work okay, but I thought there might be a better way. Can I somehow sub/superclass the whole dialog at once? Or is there another way higher/faster/stronger? :coffee4:
    Code:
    #PBFORMS Created
    
    #COMPILE EXE
    #DIM ALL
    
    '--------------------------------------------------------------------------------
    '   ** Includes **
    '--------------------------------------------------------------------------------
    #PBFORMS Begin Includes
    #IF NOT %DEF(%WINAPI)
        #INCLUDE "WIN32API.INC"
    #ENDIF
    #PBFORMS End Includes
    '--------------------------------------------------------------------------------
    
    '--------------------------------------------------------------------------------
    '   ** Constants **
    '--------------------------------------------------------------------------------
    #PBFORMS Begin Constants
    %IDD_DIALOG1    = 101
    %IDC_BUTTON1    = 1001
    %IDC_BUTTON2    = 1002
    %IDC_BUTTON3    = 1003
    #PBFORMS End Constants
    '--------------------------------------------------------------------------------
    
    '--------------------------------------------------------------------------------
    '   ** Declarations **
    '--------------------------------------------------------------------------------
    DECLARE CALLBACK FUNCTION ShowDIALOG1Proc()
    DECLARE FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
    #PBFORMS Declarations
    '--------------------------------------------------------------------------------
    GLOBAL globHdlg AS LONG
    GLOBAL globKey AS LONG
    
    '--------------------------------------------------------------------------------
    FUNCTION PBMAIN()
        ShowDIALOG1 %HWND_DESKTOP
    END FUNCTION
    '--------------------------------------------------------------------------------
    
    '--------------------------------------------------------------------------------
    '   ** CallBacks **
    '--------------------------------------------------------------------------------
    CALLBACK FUNCTION ShowDIALOG1Proc()
    
        SELECT CASE CBMSG
            CASE %WM_KEYDOWN  '<< this is really all I'm trying to do
               'code code code
               
            CASE %WM_COMMAND
                SELECT CASE CBCTL
                    CASE %IDC_BUTTON1
                        IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                            MSGBOX "%IDC_BUTTON1=" + FORMAT$(%IDC_BUTTON1), _
                                %MB_SYSTEMMODAL
                        END IF
                    CASE %IDC_BUTTON2
                        IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                            MSGBOX "%IDC_BUTTON2=" + FORMAT$(%IDC_BUTTON2), _
                                %MB_SYSTEMMODAL
                        END IF
                    CASE %IDC_BUTTON3
                        IF CBCTLMSG = %BN_CLICKED OR CBCTLMSG = 1 THEN
                            MSGBOX "%IDC_BUTTON3=" + FORMAT$(%IDC_BUTTON3), _
                                %MB_SYSTEMMODAL
                        END IF
    
                END SELECT
        END SELECT
    
    END FUNCTION
    
    FUNCTION newKeyProc(BYVAL h AS LONG, BYVAL wMsg AS LONG, BYVAL wParam AS LONG, BYVAL lParam AS LONG) AS LONG
       SELECT CASE wMsg
          CASE %WM_KEYDOWN
             IF wParam = %VK_A THEN
                DIALOG POST globHdlg, %WM_COMMAND, MAKLNG(%IDC_BUTTON1, %BN_CLICKED), 0
             ELSEIF wParam = %VK_B THEN
                DIALOG POST globHdlg, %WM_COMMAND, MAKLNG(%IDC_BUTTON2, %BN_CLICKED), 0
             ELSEIF wParam = %VK_C THEN
                DIALOG POST globHdlg, %WM_COMMAND, MAKLNG(%IDC_BUTTON3, %BN_CLICKED), 0
             END IF
          CASE %WM_DESTROY
             SetWindowLong h, %GWL_WNDPROC, globKey
       END SELECT
       
       FUNCTION = callWindowProc(globKey, h, wMsg, wParam, lParam)
    END FUNCTION
    
    '--------------------------------------------------------------------------------
    
    '--------------------------------------------------------------------------------
    '   ** Dialogs **
    '--------------------------------------------------------------------------------
    FUNCTION ShowDIALOG1(BYVAL hParent AS DWORD) AS LONG
        LOCAL lRslt, hButton AS LONG
    #PBFORMS Begin Dialog %IDD_DIALOG1->->
        LOCAL hDlg AS DWORD
    
        DIALOG NEW hParent, "Dialog1", 258, 176, 122, 71, %WS_SYSMENU TO hDlg
        CONTROL ADD BUTTON, hDlg, %IDC_BUTTON1, "a", 15, 15, 30, 30
        CONTROL ADD BUTTON, hDlg, %IDC_BUTTON2, "b", 45, 15, 30, 30
        CONTROL ADD BUTTON, hDlg, %IDC_BUTTON3, "c", 75, 15, 30, 30
    
    #PBFORMS End Dialog
        globHdlg = hDlg
        CONTROL HANDLE hDlg, %IDC_BUTTON1 TO hButton
        globKey = SetWindowLong (hButton, %GWL_WNDPROC, CODEPTR(newKeyProc))
        CONTROL HANDLE hDlg, %IDC_BUTTON2 TO hButton
        globKey = SetWindowLong (hButton, %GWL_WNDPROC, CODEPTR(newKeyProc))
        CONTROL HANDLE hDlg, %IDC_BUTTON3 TO hButton
        globKey = SetWindowLong (hButton, %GWL_WNDPROC, CODEPTR(newKeyProc))
        
        DIALOG SHOW MODAL hDlg, CALL ShowDIALOG1Proc TO lRslt
    
        FUNCTION = lRslt
    END FUNCTION
    '--------------------------------------------------------------------------------

  • #2
    Keyboard messages are sent to an applications message que and not directly to the window procedure of the window/control with the focus.

    The message loop then forwards the message to the controls (TranslateMessage).

    To process the messages to controls can be done in a number of ways.

    (1) Subclass each control and then process the WM_KEYUP, WM_KEYDOWN and WM_CHAR messages

    (2) Trap the keyboard messages in the message loop before they are sent to the controls. This allows you to access all the keyboard messages to all the controls, without subclassing.

    (3) use a hook procedure to hook into all the windows keyboard messages.

    Normally with DDT, you let DDT handle the message loop, so with DDT you would have to use a custom message loop to preprocess messages in the message loop.

    With DDT, I think a good approach would be to write a universal subclassing routine. Create a subroutine which can subclass any control you like. That routine would subclass the control sending all its window procedure messages to a single universal subclass window procedure. Store the original window procedure address in a window property and use the API property functions to retrieve the appropriate window procedure address for each control. Define a couple user messages (%WM_USER + #) which can be used for forwarding the messages. In the WM_KEYUP, WM_KEYDOWN and WM_CHAR messages in the subclass window procedure, forward the messages (using your custom user messages) to the parent dialog. This would allow you to handle all your keyboard processing in the parent dialogs dialog procedure for all the controls on the dialog, rather than have a separate routine for each control.
    Last edited by Chris Boss; 16 Apr 2009, 05:58 PM.
    Chris Boss
    Computer Workshop
    Developer of "EZGUI"
    http://cwsof.com
    http://twitter.com/EZGUIProGuy

    Comment


    • #3
      (2) Trap the keyboard messages in the message loop before they are sent to the controls. This allows you to access all the keyboard messages to all the controls, without subclassing.
      Thanks Chris, this sounds like the best choice in my case, but I don't know about hooks, so that could be a contender too. Can I trap the keyboard messages and still have DDT style programming? I'll check around for custom message loop preprocessing in poffs etc. The universal routine, with atom fcns sounds excellent, tho I think may be beyond my grasp at this juncture.

      Comment


      • #4
        John,

        The most versatile way would be to use a global keyboard hook:
        Here's one example by Paul Dixon but there is more to be found on these forums.

        Kind regards
        Eddy

        Comment


        • #5
          I am not sure how much stuff DDT does in the message loop, so it is hard to say if there is a downside to using your own message loop, rather than the standard DDT Dialog DoEvents loop.

          The other problem is that you can't have your own custom message loop for Modal dialogs.

          I think the subclass method may be best for DDT.

          Here is an example of using subclassing to trap keyboard messages which are forwarded to the parent dialog:

          Code:
          ' ***************************************************************
          '   This code can be used Royalty Free and Freely Distributed !
          ' ***************************************************************
          #COMPILE EXE
          #REGISTER NONE
          #DIM ALL          '  This is helpful to prevent errors in coding
           
          #INCLUDE "win32api.inc"   ' Must come first before other include files !
          ' *************************************************************
          '                  Constants and Declares (#1)
          ' *************************************************************
          %FORM1_LABEL1             = 100
          %FORM1_BUTTON1            = 105
          %FORM1_BUTTON2            = 110
          %FORM1_TEXT1              = 115
          %FORM1_LISTBOX1           = 120
          ' --------------------------------------------------
          DECLARE SUB ShowDialog_Form1(BYVAL hParent&)
          DECLARE CALLBACK FUNCTION Form1_DLGPROC
          ' --------------------------------------------------
          DECLARE CALLBACK FUNCTION CBF_FORM1_BUTTON1()
          DECLARE CALLBACK FUNCTION CBF_FORM1_BUTTON2()
          DECLARE CALLBACK FUNCTION CBF_FORM1_TEXT1()
          DECLARE CALLBACK FUNCTION CBF_FORM1_LISTBOX1()
          '
          ' *************************************************************
          '               Application Globals Variables (#2)
          ' *************************************************************
          '
          GLOBAL hForm1&    ' Dialog handle
          %App_KeyMessage     =    %WM_USER+400
          TYPE AppKey
               KMsg AS LONG
               KhCtrl AS LONG
               KCtrlID AS LONG
               KwParam AS LONG
               KlParam AS LONG
          END TYPE
          '
          ' *************************************************************
          '                    Application Entrance
          ' *************************************************************
          '
          FUNCTION PBMAIN
              LOCAL Count&
              ShowDialog_Form1 0
              DO
                  DIALOG DOEVENTS TO Count&
              LOOP UNTIL Count&=0
              MakeKeySubClass 0,0  ' clears subclass atoms
          END FUNCTION
          '
          ' *************************************************************
          '                    Application Dialogs (#3)
          ' *************************************************************
          '
          SUB ShowDialog_Form1(BYVAL hParent&)
              LOCAL Style&, ExStyle&
              LOCAL N&, CT&        '  Variables used for Reading Data in Arrays for Listbox and Combobox
              '   hParent& = 0 if no parent Dialog
              Style& = %WS_POPUP OR %DS_MODALFRAME OR %WS_CAPTION OR %WS_MINIMIZEBOX OR %WS_SYSMENU OR %DS_CENTER
              ExStyle& = 0
              DIALOG NEW hParent&, "Your Dialog", 0, 0,  267,  177, Style&, ExStyle& TO hForm1&
              CONTROL ADD LABEL, hForm1&,  %FORM1_LABEL1,  "Label  1", 40, 153, 192, 12, _
                  %WS_CHILD OR %WS_VISIBLE OR %SS_CENTER OR %WS_BORDER
              CONTROL ADD "Button", hForm1&,  %FORM1_BUTTON1,  "Button  1", 29, 20, 53, 15, _
                  %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON OR %WS_TABSTOP CALL CBF_FORM1_BUTTON1
              MakeKeySubClass hForm1&,%FORM1_BUTTON1
              CONTROL ADD "Button", hForm1&,  %FORM1_BUTTON2,  "Button  2", 29, 49, 53, 15, _
                  %WS_CHILD OR %WS_VISIBLE OR %BS_PUSHBUTTON OR %WS_TABSTOP CALL CBF_FORM1_BUTTON2
              MakeKeySubClass hForm1&,%FORM1_BUTTON2
              CONTROL ADD TEXTBOX, hForm1&,  %FORM1_TEXT1,  "", 93, 20, 83, 47, _
                  %WS_CHILD OR %WS_VISIBLE OR %ES_MULTILINE OR %ES_WANTRETURN OR %ES_LEFT OR %WS_TABSTOP, _
                  %WS_EX_CLIENTEDGE CALL CBF_FORM1_TEXT1
              MakeKeySubClass hForm1&,%FORM1_TEXT1
              ' - - - - - - - - - - - - - - - - - - - - - - - - -
              DIM LISTBOX1_List(4) AS LOCAL STRING
              DATA "Item 1","Item 2","Item 3","Item 4","Item 5"
              FOR N&=0 TO 4
                  CT&=CT&+1
                  LISTBOX1_List(N&)=READ$(CT&)
              NEXT N&
              ' - - - - - - - - - - - - - - - - - - - - - - - - -
              CONTROL ADD LISTBOX, hForm1&,  %FORM1_LISTBOX1, LISTBOX1_List(), 187, 17, 53, 52, _
                  %WS_CHILD OR %WS_VISIBLE OR %LBS_NOTIFY OR %LBS_SORT OR %LBS_NOINTEGRALHEIGHT OR %WS_VSCROLL OR %WS_TABSTOP, _
                  %WS_EX_CLIENTEDGE CALL CBF_FORM1_LISTBOX1
              MakeKeySubClass hForm1&,%FORM1_LISTBOX1
              DIALOG SHOW MODELESS hForm1& , CALL Form1_DLGPROC
          END SUB
          '
          ' *************************************************************
          '                             Dialog Callback Procedure
          '                             for Form Form1
          '                             uses Global Handle - hForm1&
          ' *************************************************************
          '
          CALLBACK FUNCTION Form1_DLGPROC
              SELECT CASE CBMSG
                  CASE %App_KeyMessage
                       LOCAL K AS AppKey PTR, T$
                       K=CBWPARAM
                       SELECT CASE @K.KMsg
                            CASE %WM_KEYUP
                                 T$="WM_KEYUP"
                            CASE %WM_KEYDOWN
                                 T$="WM_KEYDOWN"
                            CASE %WM_CHAR
                                 T$="WM_CHAR"
                            CASE ELSE
                                 T$=""
                       END SELECT
                       IF T$<>"" THEN
                            T$=T$+" "+STR$(@K.KCtrlID)+"    "+STR$(@K.KwParam)+" , "+STR$(@K.KlParam)
                       END IF
                       CONTROL SET TEXT hForm1&,%FORM1_LABEL1, T$
                       EXIT FUNCTION
                  CASE ELSE
              END SELECT
          END FUNCTION
          '
          ' *************************************************************
          '   Application Callback Functions (or Subs) for Controls (#4)
          ' *************************************************************
          '
          CALLBACK FUNCTION CBF_FORM1_BUTTON1
              IF CBCTLMSG=%BN_CLICKED THEN
              END IF
          END FUNCTION
          ' ------------------------------------------------
          '
          CALLBACK FUNCTION CBF_FORM1_BUTTON2
              IF CBCTLMSG=%BN_CLICKED THEN
              END IF
          END FUNCTION
          ' ------------------------------------------------
          '
          CALLBACK FUNCTION CBF_FORM1_TEXT1
              IF CBCTLMSG=%EN_CHANGE THEN
              END IF
          END FUNCTION
          ' ------------------------------------------------
          '
          CALLBACK FUNCTION CBF_FORM1_LISTBOX1
              LOCAL CVal&
              ' Return Current Selection in CVal&
              CONTROL SEND CBHNDL , CBCTL, %LB_GETCURSEL, 0,0 TO CVal&
              IF CBCTLMSG=%LBN_SELCHANGE THEN
              END IF
          END FUNCTION
          '
          GLOBAL App_OriginalWinProcAtom&
          '
          SUB MakeKeySubClass(BYVAL hDlg&, BYVAL ID&)
               LOCAL T AS ASCIIZ*256, hCtrl&, WA&
               STATIC AFlag&
               IF AFlag&=0 THEN
                    AFlag&=1
                    T="origwinproc"
                    App_OriginalWinProcAtom&=GlobalAddAtom(T)
               END IF
               IF hDlg&=0 AND ID&=0 THEN
                    AFlag&=0
                    IF App_OriginalWinProcAtom&<>0 THEN GlobalDeleteAtom App_OriginalWinProcAtom&
               END IF
               hCtrl&=GetDlgitem(hDlg&, ID&)
               IF hCtrl&<>0 THEN
                    WA&=SetWindowLong(hCtrl&, %GWL_WNDPROC, BYVAL CODEPTR(KeySubClassProc))
                    SetProp hCtrl&, BYVAL MAKLNG(App_OriginalWinProcAtom&,0), WA&
               END IF
          END SUB
          '
          FUNCTION KeySubClassProc(BYVAL hWnd&, BYVAL Msg&, BYVAL wParam&, BYVAL lParam&) AS LONG
               LOCAL WA&
               WA&=GetProp(hWnd&, BYVAL MAKLNG(App_OriginalWinProcAtom&,0))
               SELECT CASE Msg&
                    CASE %WM_KEYUP, %WM_KEYDOWN, %WM_CHAR
                         LOCAL K AS AppKey
                         K.KMsg = Msg&
                         K.KhCtrl = hWnd&
                         K.KCtrlID = GetDlgCtrlID(hWnd&)
                         K.KwParam = wParam&
                         K.KlParam = lParam&
                         IF SendMessage(GetParent(hWnd&), %App_KeyMessage , VARPTR(K),0)<>0 THEN
                              FUNCTION=0
                              EXIT FUNCTION  ' prevent default processing
                         END IF
                    CASE %WM_DESTROY
                         SetWindowLong hWnd&, %GWL_WNDPROC, WA&
                         RemoveProp hWnd&, BYVAL MAKLNG(App_OriginalWinProcAtom&,0)
               END SELECT
               FUNCTION=CallWindowProc(WA&, hWnd&, Msg&, wParam&, lParam&)
          END FUNCTION
          Last edited by Chris Boss; 16 Apr 2009, 05:59 PM.
          Chris Boss
          Computer Workshop
          Developer of "EZGUI"
          http://cwsof.com
          http://twitter.com/EZGUIProGuy

          Comment


          • #6
            Thanks Eddy for the link, I'll look at that.

            Thanks Chris for the code, I'll check it out & compile it now.

            Comment


            • #7
              AS long as you are just getting started with this key-press thing...

              You probably want to look at doing whatever it is you are going to do on the WM_KEYUP notification rather than the WM_KEYDOWN.

              The reason being, if the user holds the key down, you can get multiple keydown notrifications when the keyboard repeat thing kicks in.. and you only ever get one "key up" notification.
              Michael Mattias
              Tal Systems (retired)
              Port Washington WI USA
              [email protected]com
              http://www.talsystems.com

              Comment


              • #8
                Thanks Chris for the informational tour de force. It demos much in a concise package. I think I'll be able to incorporate the code pretty quickly, and possibly even understand how it works in a couple da... we... months. I'm a thanking you for that, really.

                Thanks too Eddy for the hook link, which may come in handy at some point.

                And thanks MCM for the tip. I'll need handle those events.

                Comment


                • #9
                  AS long as you are just getting started with this key-press thing...

                  You probably want to look at doing whatever it is you are going to do on the WM_KEYUP notification rather than the WM_KEYDOWN.

                  The reason being, if the user holds the key down, you can get multiple keydown notrifications when the keyboard repeat thing kicks in.. and you only ever get one "key up" notification.
                  MCM I am on the exact OPPOSITE side of that note (Heh...Imagine that )

                  In my limited experience (limited as in not having researched just the WHYYYyyy??? but the idea that it happens), the %KEYUP can be missed. So I use %KEYDOWN (since it is what initiates the whole process to begin with)

                  That way the process is not missed at all (or at least in my experience) and if it is...then programmer error.
                  Engineer's Motto: If it aint broke take it apart and fix it

                  "If at 1st you don't succeed... call it version 1.0"

                  "Half of Programming is coding"....."The other 90% is DEBUGGING"

                  "Document my code????" .... "WHYYY??? do you think they call it CODE? "

                  Comment

                  Working...
                  X