Announcement

Collapse
No announcement yet.

Cursor Position in TextBox

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

  • Cursor Position in TextBox

    Hello. I am filtering the characters entered into a textbox. I am doing this using callback
    on change. If any unwanted character is present I remove it from the text string and then
    control set text.

    This process losses the cursor position, setting it to the beginning of the textbox field,
    not where the cursor was when the "illegal" character was entered. I do not see any
    feature that limits chars as I desire. Yes there is numeric only, but i want digits, commas,
    and spaces. This code works fast enough, flexible in selective character rejection. But,
    it losses the cursor position. Can I get and set the cursor position in a textbox??

    The displayed code snippet is incomplete, it just shows how I filter entered characters
    in the textbox live. I guess I could break this into four textboxes for the four values I want
    to edit, but would like to learn if it is possible to locate and set cursor.

    Thanks for any suggestions.

    Code:
    IF CB.MSG = %WM_COMMAND THEN
    
        SELECT CASE AS LONG CB.CTLMSG
    
          CASE %EN_CHANGE
    
            IF CB.CTL = hCtlIniText(3) THEN
              CONTROL GET TEXT hDlgInit, hCtlIniText(3) TO PosSize
              Junk = REMOVE$(PosSize, ANY "0123456789 ,")
              IF ISNOTNULL(Junk) THEN
                PosSize = REMOVE$(PosSize, ANY Junk)
                CONTROL SET TEXT hDlgInit, hCtlIniText(3), PosSize
              END IF
            END IF          

  • #2
    See the Windows API functions getcaretPos() and SetCaretPos(). You will probably also need to use the EM_CHARFROMPOS message to translate POINTs into a character position within the text string.

    Or, you can place the caret where desired by making the desired position both the start and end of the selection range (EM_SETSEL message)
    Michael Mattias
    Tal Systems Inc.
    Racine WI USA
    mmattias@talsystems.com
    http://www.talsystems.com

    Comment


    • #3
      Or, you might want to do character filtering at a little lower level using subclassing.

      I thought I had posted a demo procedure to accept only valid hex digits but I guess I nerver posted that where it belongs (Source Code Forum).

      But I know there are other "subclass edit control to filter for valid characters" code snippets floating around here.. somewhere.

      You may also run into problems .. you are changing the control's contents on the EN_CHANGE notification... which will generate an additional EN_CHANGE notification... which will ... except in your example you will never have more than one cycle because you will not change the control contents more than once.


      [ADDED]

      I Found the subclass procedure..
      Code:
      ...
      $PROP_OLD_WNDPROC  = "OLDWNDPROC"   ' for subclassing (in toolbar edit control)
      
      FUNCTION DelimiterHexEditProc (BYVAL hWnd AS LONG, BYVAL wMSG AS LONG, BYVAL wPAram AS LONG, BYVAL lParam AS LONG) AS LONG
      
        STATIC dwProc AS DWORD, BeenHere AS LONG
        LOCAL szProp AS ASCIIZ * 64
        LOCAL  idCtrl AS LONG
        LOCAL  szText AS ASCIIZ * %MAX_PATH, lText AS LONG
        LOCAL  ichar  AS LONG
      
      
        IF ISFALSE beenHere THEN
            BeenHere = %TRUE
            szProp   = $PROP_OLD_WNDPROC
            dwProc   = getProp (hWnd, szProp)
        END IF
      
        SELECT CASE AS LONG wMSg
      
               CASE %WM_GETDLGCODE
                   'FUNCTION =  %DLGC_WANTALLKEYS     ' With DLCG_WANTCHARS all regular keys work but no WM-CHAR message
                   FUNCTION = %DLGC_WANTCHARS         ' doc says "WM_CHAR messages"
                   'FUNCTION   = %DLGC_WANTMESSAGE
                   EXIT FUNCTION
      
               CASE  %WM_CHAR                      '"An application should return zero if it processes this message. "
                  ' LOWRD wparam = "the character code of the key"
                   IF wparam = %VK_TAB THEN
                       ' standard TAB behavior
                         SetFocus       GetnextDlgTabItem(GetParent(hWnd), hWnd, (GetKeyState(%VK_SHIFT) < 0))
                         FUNCTION = 0
                         EXIT FUNCTION
      
                   ELSE  ' not tab
                      iChar  = LOWRD(WParam)
                      SELECT CASE AS LONG iChar
                          CASE &h30 TO &h39, &h41 TO &h46, _    ' OK characters 0-9 and A-F
                               &h61 TO &h66, _                  ' lower case a-f, will be uppercased when they get in
                               %VK_TAB, %VK_HOME, %VK_END, %VK_BACK, _      ' expected control characters
                               %VK_RIGHT, %VK_LEFT, %VK_INSERT, %VK_DELETE, _
                               %vK_ESCAPE
                              EXIT SELECT                      ' allow normal processing
                          CASE ELSE
                              MessageBeep  %MB_ICONHAND
                              FUNCTION = 0
                              EXIT FUNCTION    ' bypass normal processing
                      END SELECT
                   END IF
      
             END SELECT
             ' if message not handled (if handled proc was exited), call default handler
             FUNCTION = CallWindowProc (dwProc, hWnd, wMsg, wParam,lparam)
      
      END FUNCTION
      and you have to set the property at the point at which you subclass the control...

      Code:
      'limit the text length of the entry fields: char (1), dec (3) hex (2)
                 szProp = $PROP_OLD_WNDPROC
      
                 FOR idctrl =  %ID_CONTROLCHAR_FIRST TO %ID_CONTROLCHAR_LAST
                     hCtrl = GetDlgitem (hwnd, idCtrl)    ' CONTROL HANDLE  in DDT
                     SELECT CASE AS LONG idctrl
                         CASE %ID_ELSEP_CHAR, %ID_SUBELSEP_CHAR, %ID_REPSEP_CHAR, %ID_SEGTERM_CHAR
                             iLen = 1
                         CASE %ID_ELSEP_DEC, %ID_SUBELSEP_DEC, %ID_REPSEP_DEC, %ID_sEGTERM_DEC
                             iLen = 3
                         CASE %ID_ELSEP_HEX, %ID_SUBELSEP_HEX, %ID_REPSEP_HEX, %ID_SEGTERM_HEX
                           ' subclass the hex controls to allow only 0-9, A-F
                             dwProc  =  SetWindowLong (hCtrl, %GWL_WNDPROC, CODEPTR (DelimiterHexEditProc))
                            SetProp    hCtrl, szPRop, dwProc
      MCM
      Last edited by Michael Mattias; 26 May 2016, 03:09 PM. Reason: add subclass procedure code
      Michael Mattias
      Tal Systems Inc.
      Racine WI USA
      mmattias@talsystems.com
      http://www.talsystems.com

      Comment


      • #4
        Stan,
        You could use EM_GETSEL and EM_SETSEL...

        Pierre

        Code:
                  CONTROL GET TEXT hDlgInit, hCtlIniText(3) TO PosSize
                  Junk = REMOVE$(PosSize, ANY "0123456789 ,")
                  IF ISNOTNULL(Junk) THEN
                    LOCAL SelStart AS LONG
                    LOCAL SelEnd   AS LONG
                    SendMessage(CBLPARAM, %EM_GETSEL, VARPTR(SelStart), VARPTR(SelEnd))
                    PosSize = REMOVE$(PosSize, ANY Junk)
                    CONTROL SET TEXT hDlgInit, hCtlIniText(3), PosSize
                    SendMessage(CBLPARAM, %EM_SETSEL, SelStart - 1, SelEnd - 1)
                  END IF
        Last edited by Pierre Bellisle; 26 May 2016, 07:00 PM.

        Comment


        • #5
          Another way to get more grip is to subclass the control...

          Pierre

          Code:
          #COMPILE EXE '#Win 8.04#
          #INCLUDE "Win32Api.inc"
           
          GLOBAL hDlg AS DWORD
           
          %Textbox = 101
          '______________________________________________________________________________
           
          FUNCTION EditSubClassedProc(BYVAL hWnd AS DWORD, BYVAL wMsg AS LONG, BYVAL wParam AS LONG, BYVAL lParam AS LONG) AS LONG
           STATIC pEditProc AS DWORD
           LOCAL  SelStart  AS LONG
           LOCAL  SelEnd    AS LONG
           LOCAL  BufferLen AS LONG
           LOCAL  sBuffer   AS STRING
           
           SELECT CASE wMsg 'Keyboard Input Notifications
           
             CASE %WM_GETDLGCODE
               FUNCTION = %DLGC_WANTCHARS 'OR %DLGC_WANTALLKEYS OR %DLGC_WANTARROWS OR %DLGC_WANTMESSAGE
               EXIT FUNCTION
           
             CASE %WM_CHAR
               SELECT CASE wParam
           
                 CASE 8 'Backspace
                   BufferLen = SendMessage(hWnd, %WM_GETTEXTLENGTH, 0, 0) 'Zero terminator not included in lenght
                   sBuffer   = NUL$(BufferLen) 'Prepare buffer for text
                   SendMessage(hWnd, %WM_GETTEXT, BufferLen + 1, STRPTR(sBuffer)) 'Get text, len include zero terminator
                   SendMessage(hWnd, %EM_GETSEL, VARPTR(SelStart), VARPTR(SelEnd)) 'Get the caret position
                   IF ASC(sBuffer, SelStart) = 44 THEN 'If backspace is to erase a comma ","
                     SendMessage(hWnd, %EM_SETSEL, SelStart - 1, SelEnd - 1) 'Jump over it
                     EXIT FUNCTION 'Do not forward backspace key to the control
                   END IF
           
                 CASE 48 TO 57, 65 TO 70 '"0" to "9", "A" to "F"
                   'Let character pass as is
           
                 CASE 97 TO 102 '"a" to "f"
                   'Let character pass after ucase
                   wParam = wParam - 32 'Upper case the character "a"-"f" to "A"-"F"
           
                 CASE ELSE
                   EXIT FUNCTION 'Do not forward key to the control
           
               END SELECT
           
           END SELECT
           
           IF pEditProc = 0 THEN pEditProc = wParam 'Trick to get original edit proc pointer without using a global variable
           FUNCTION = CallWindowProc(pEditProc, hWnd, wMsg, wParam, lParam) 'Call original window proc.
           
          END FUNCTION
          '______________________________________________________________________________
           
          CALLBACK FUNCTION PBMainCallBack
           STATIC pEditProc AS DWORD
           STATIC hEdit     AS DWORD
           
           SELECT CASE CBMSG
           
             CASE %WM_INITDIALOG
               hEdit     = GetDlgItem(hDlg, %Textbox) 'Get edit control handle
               pEditProc = SetWindowLong(hEdit, %GWL_WNDPROC, CODEPTR(EditSubClassedProc)) 'Subclass the edit control
               SendMessage(hEdit, 0, pEditProc, 0) 'Trick to avoid pEditProc to be global
               PostMessage(hEdit, %EM_SETSEL, SendMessage(hEdit, %WM_GETTEXTLENGTH, 0, 0), -1) 'Put caret at the end
           
             CASE %WM_DESTROY
               SetWindowLong(hEdit, %GWL_WNDPROC, pEditProc) 'Unsubclass the edit control
           
           END SELECT
          END FUNCTION
          '______________________________________________________________________________
           
          FUNCTION PBMAIN
           
           DIALOG FONT "Segoe UI", 9
           DIALOG NEW %HWND_DESKTOP, "Hexadecimal character only", , , 200, 60, %WS_SYSMENU, 0 TO hDlg
           
           SetClassLong(hDlg, %GCL_HICON, LoadIcon(BYVAL %NULL, BYVAL %IDI_INFORMATION)) 'Icon from Windows
           
           CONTROL ADD TEXTBOX, hDlg, %Textbox, "1,234,567,890,ABC", 15, 15, 170, 12
           
           DIALOG SHOW MODAL hDlg CALL PBMainCallBack
           
          END FUNCTION
          '______________________________________________________________________________
          '
          Last edited by Pierre Bellisle; 30 May 2016, 08:02 PM.

          Comment


          • #6
            Thanks for the responses. I need to study these, getting into new areas. I like the idea of filtering the
            keystrokes before they go to the display. Tomorrow, as clarity of mind allows. Will get back again, then.

            Stan

            Comment


            • #7
              Hello Pierre, thanks for the code. I have waded thru the code of your second post. There is a whole world of programming, getting into the win32api. I have it patched and running. I find it interesting that control keystrokes (forward and back arrow) work without being run thru the filter code, yet one has to allow backspace! But, I figure, backspace is an original 'character' on a teletype. Origins of Ascii. I am choosing to filter characters before they reach the textbox, than messing with the cursor, for now.

              ah, teletypes. Had a model 28 when younger. It was baudot. I remember at a programming school, they had a Univac 1107 and 1108. the 1108 (filled a room, at about a 1Mhz clock freq) had a model 35 teletype as their console. That was way cool. Watch'n that typebox flying around. Figure now a Raspberry Pi 3 out performs it 3000X or more. Oh well, off topic but fun.

              So, I have been reading on the winapi32 functions being called. I would like to limit the backspace to not delete a comma.

              Hello Michael, thanks for posting your code, I am now wading into it. Your second post. Sorry to be so slow,

              Stan

              Comment


              • #8
                Hey Stan,

                Do not worry to be slow, we are all lazy on this forum. ;-)
                Easy does it,

                I'm not sure to get on how you want the backspace key to react over a comma.
                I did update my subclass code to integrate some functionality.
                Backspace won't erase a comma but jump over it.
                Have a look, maybe you can do something with it.
                If not, just clarify on what you want to do exactly...

                Pierre

                Comment


                • #9
                  Hello Pierre, I am doing a project for downloading and sequentially renumbering photos from 3 different digital cameras. I view it as a leaning project, with
                  Power Basic, DDT, and now Win32API . There is a dialog for editing an INI file. One field in this textbox. It contains 4 numeric values, for the main dialog. those values are the main dialog's x and y size, x and y position. The main dialog I can also resize, the ini file saving the last state. So, this textbox could look like:

                  [ 1250, 900, 100, 100 ] .......<------- the square brackets simulating the edge of the textbox

                  Now, my 1st posted code would removed any unwanted characters on change, and redisplay SET TEXT.
                  The cursor position was lost.

                  Your sub class code filtered all but hex character and backspace (+ something else. I modified that to allow
                  only 0 to 9, comma, space, BS. When running, I found it even better to me to eliminate the space and comma.

                  [1250,900,100,100 ]

                  This approach limited any user to place numeric only into one of the four fields, delimited by 3 commas. Left/right arrow placed
                  cursor where needed. numeric inserted where wanted. BackSpace erase as desired, Only drawback was BackSpace erases
                  any character in the textbox , would delete a comma. Now, I don't Have to do it this way, and have not been able to pursue it
                  because I have gotten stalled with new version antivirus installed this weekend.

                  I have been having issues with antivirus interference. I have upgraded to BitDefender total 2016, which allowed
                  me to declare exceptions. This in three ways, the folder where I run the program (virus scan), the file names (virus scan), and the
                  programs themselves (EXE) while running. The AVirus still grabs them and quarantines it. I have a ticket item with them to
                  solve this. Hope to resolve it tomorrow. AND it takes it a long time to tell me it stole my exe. A long time, I know it much quicker
                  because program stops.

                  Stan

                  Comment

                  Working...
                  X