(Design-)Error with dc_guitypeahead()

This forum is for eXpress++ general support.
Message
Author
User avatar
Markus Walter
Posts: 54
Joined: Thu Jan 28, 2010 12:49 am
Location: Germany

(Design-)Error with dc_guitypeahead()

#1 Post by Markus Walter »

Hello Roger,

there is a error in your design of the handling of dc_guitypeahead(). I worked several days on a problem, my customers complained about.

I try to explain in my poor english...

My Situation: My software opens new windows very often. Some of my customers write very fast, so i use dc_guitypeahead(). My Customers complain about missing characters or that characters are not in the correct order.
Example: User writes to a dcget, press Enter, a new window opens with a new dcget and he enter 1234. But the new get shows sometimes 234 or 2134 or 124

I looked for your mechanism for dc_guitypeahead() and found the reason (at least a part of it). You work like this (in :readgui()):
1. adding keyboard-events to :keyboardQueue
2. in your :eventloop() you use postappevent to fire these events back to the xbase eventqueue

Here we have two problems:
a. in the period between 1. and 2. there can be new keyboard events from the user
b. if there are more events in :keyboardQueue there can be new keyboard events from the user "between" firing the events

So it comes sometimes to a wrong order of keyboard events.

I found a way to correct this (in :eventloop(), base is express build 252:

was:

Code: Select all

    IF snEventSpy > 0
      nEvent := DllCall( snEventSpy, DLL_XPPCALL, 'SpyAppEvent', ;
                @mp1, @mp2, @oXbp, IIF( Empty(nTimeOut), 0, 10 ) )
    ELSE
      nEvent := AppEvent( @mp1, @mp2, @oXbp, ;
               IIF( Empty(nTimeOut), IIF(Empty(::keyboardQueue),nWait,1), Min(nWait,10) ) )
    ENDIF

    IF nEvent = 0 ...
now:

Code: Select all

    IF snEventSpy > 0
      nEvent := DllCall( snEventSpy, DLL_XPPCALL, 'SpyAppEvent', ;
                @mp1, @mp2, @oXbp, IIF( Empty(nTimeOut), 0, 10 ) )
    ELSE
      nEvent := AppEvent( @mp1, @mp2, @oXbp, ;
               IIF( Empty(nTimeOut), IIF(Empty(::keyboardQueue),nWait,1), Min(nWait,10) ) )
    ENDIF

    // if there are Keyboard-Events in :keyboardQueue (dc_readguitypeahead()), "new" Keyboard-Events have to add to the :keyboardQueue...
    if nEvent == xbeP_Keyboard .and. !empty(::keyboardQueue)
      aadd(::keyboardQueue, mp1)
      loop
    endif


    IF nEvent = 0 ...

and i don't use postappevent "for firing the :keyboardQueue:

was:

Code: Select all

    IF nEvent = 0 .OR. (nEvent = xbeM_Motion .AND. mp1 == NIL) .OR. Valtype(oXbp) # 'O'
      IF IsFunction('xbpHandleOcxEvents')
        &('xbpHandleOcxEvents()')
      ENDIF
      IF lExitWhenQueueEmpty
        RETURN .T.
      ELSEIF nWait == 0
        IF !Empty(::keyboardQueue)
          PostAppEvent( xbeP_Keyboard, ::keyboardQueue[1],nil,SetAppFocus() )
          ARemove(::keyboardQueue,1)
        ENDIF
        LOOP
      ENDIF

    ELSEIF Valtype(oGetList:loopWhenBlock) = 'B' .AND. !Eval(oGetList:loopWhenBlock)
      LOOP
    ELSE
      nSeconds := Seconds()
    ENDIF
now:

Code: Select all

    IF nEvent = 0 .OR. (nEvent = xbeM_Motion .AND. mp1 == NIL) .OR. Valtype(oXbp) # 'O'
      IF IsFunction('xbpHandleOcxEvents')
        &('xbpHandleOcxEvents()')
      ENDIF
      IF lExitWhenQueueEmpty
        RETURN .T.
      ELSEIF nWait == 0
        IF !Empty(::keyboardQueue)
          nEvent := xbeP_Keyboard
          mp1    := ::keyboardQueue[1]
          oXbp   := setappfocus()
          ARemove(::keyboardQueue,1)
        ENDIF
      ENDIF
       
    ELSEIF Valtype(oGetList:loopWhenBlock) = 'B' .AND. !Eval(oGetList:loopWhenBlock)
      LOOP
    ELSE
      nSeconds := Seconds()
    ENDIF

With this changes, is much more better, but not perfect. Sometimes characters are still missing (but not more in wrong order) and i found no reason why.

Can you have a look to my changes and take them to the next release?
Perhaps you have an idea, why sometimes characters are missing?

This is a small test programm, which can show the problem:

Code: Select all

#include "dcdialog.ch"
#include "appevent.ch"

function Appsys()
return NIL

procedure main()
local getlist := {}, getoptions := {}, lRet, oButt

  DC_ReadGuiTypeAhead(.t.)

  @ 3, 3 dcpushbutton CAPTION "Test" SIZE 10, 2 OBJECT oButt ACTION {|| wDoSomething(), wTest(), setappfocus(oButt)}

  DCGETOPTIONS TITLE "Test"
  DCREAD GUI TO lRet OPTIONS getoptions ADDBUTTONS FIT eval {|| setappfocus(oButt)}
RETURN


function wTest()
local getlist := {}, getoptions := {}, cVar := space(20)

  @ 5, 5 DCGET cVar

  DCGETOPTIONS TITLE "Test"
  DCREAD GUI TO lRet OPTIONS getoptions ADDBUTTONS FIT ENTEREXIT

return

function wDoSomething()
local atest := {}, i

  for i := 1 to 1000
    aadd(aTest, "0000")
  next

return NIL
Press Enter and very quickly some characters (exp. 1234). And perhaps you will see that the characters come sometimes in wrong order. I used the software "autohotkey" for automation...
-----------------
Greetings
Markus Walter

User avatar
rdonnay
Site Admin
Posts: 4745
Joined: Wed Jan 27, 2010 6:58 pm
Location: Boise, Idaho USA
Contact:

Re: (Design-)Error with dc_guitypeahead()

#2 Post by rdonnay »

Markus -

Thank you for doing my work for me.

I will look into this.

Roger
The eXpress train is coming - and it has more cars.

User avatar
Markus Walter
Posts: 54
Joined: Thu Jan 28, 2010 12:49 am
Location: Germany

Re: (Design-)Error with dc_guitypeahead()

#3 Post by Markus Walter »

Hallo Roger,

thank you for looking into this.
Please have a look especially to the "loosing" characters. I use CLEAREVENTS in DCREADGUI, but also if i replace the dc_clearevents() at this point with DC_ClearEvents(,,,{xbeP_Keyboard}), sometimes one character is lost.
Is there another function which takes events out of the eventqueue?
-----------------
Greetings
Markus Walter

User avatar
rdonnay
Site Admin
Posts: 4745
Joined: Wed Jan 27, 2010 6:58 pm
Location: Boise, Idaho USA
Contact:

Re: (Design-)Error with dc_guitypeahead()

#4 Post by rdonnay »

Markus -

It appears that, with your changes, the typeahead is not working at all now.

I will have to spend some time on this.

Roger
The eXpress train is coming - and it has more cars.

User avatar
rdonnay
Site Admin
Posts: 4745
Joined: Wed Jan 27, 2010 6:58 pm
Location: Boise, Idaho USA
Contact:

Re: (Design-)Error with dc_guitypeahead()

#5 Post by rdonnay »

Ok, I think I have it now.

This appears to be the solution. It passes my tests :

Code: Select all

    IF nEvent == xbeP_Keyboard .and. !Empty(::keyboardQueue) .AND. Empty(mp2)
      AAdd(::keyboardQueue, mp1)
      LOOP
    ENDIF

    IF nEvent = 0 .OR. (nEvent = xbeM_Motion .AND. mp1 == NIL) .OR. Valtype(oXbp) # 'O'
      IF IsFunction('xbpHandleOcxEvents')
        &('xbpHandleOcxEvents()')
      ENDIF
      IF lExitWhenQueueEmpty
        RETURN .T.
      ELSEIF nWait == 0
        IF !Empty(::keyboardQueue)
          PostAppEvent( xbeP_Keyboard, ::keyboardQueue[1],'QUEUE',SetAppFocus() )
          ARemove(::keyboardQueue,1)
        ENDIF
        LOOP
      ENDIF
    ELSEIF Valtype(oGetList:loopWhenBlock) = 'B' .AND. !Eval(oGetList:loopWhenBlock)
      LOOP
    ELSE
      nSeconds := Seconds()
    ENDIF
The eXpress train is coming - and it has more cars.

User avatar
Markus Walter
Posts: 54
Joined: Thu Jan 28, 2010 12:49 am
Location: Germany

Re: (Design-)Error with dc_guitypeahead()

#6 Post by Markus Walter »

Hello Roger,

your solution seem to work for me, too.

But it's only a solution for not having events in wrong order. I still have the problem (with your and mine solution), that sometimes characters are lost. And i can see, that they never come in the :keyboardQueue (not at the first point where :keyboardQueue is build and not at the new second point). There are "lost" before the :keyboardQueue is build.

Does you have an idea, where this could be?
-----------------
Greetings
Markus Walter

User avatar
rdonnay
Site Admin
Posts: 4745
Joined: Wed Jan 27, 2010 6:58 pm
Location: Boise, Idaho USA
Contact:

Re: (Design-)Error with dc_guitypeahead()

#7 Post by rdonnay »

You have to be careful that there are no calls to DC_ClearEvents() or DC_CompleteEvents(). That's the only way I can imagine events getting lost. Also, If you are spawning the new window in a new thread, the events may be going to the queue in the calling thread.
The eXpress train is coming - and it has more cars.

User avatar
Markus Walter
Posts: 54
Joined: Thu Jan 28, 2010 12:49 am
Location: Germany

Re: (Design-)Error with dc_guitypeahead()

#8 Post by Markus Walter »

Hello Roger,

i have no separate thread.

I found (with help of my debugging_system) at least 2 points which make trouble (both in :readgui()):

Code: Select all

IF !::isExit .AND. Valtype(::saveAppFocus) = 'O' .AND. ::isDestroy .AND. ::saveAppFocus:status() > 0
  DC_CompleteEvents() // - removed this to fix Cliff's problem
  SetAppFocus(::saveAppFocus)
ENDIF

Code: Select all

      IF DC_EnterExitMode()=2 .OR. ; // Enter Exit mode exits any time ENTER is hit
          (lHitBottom .AND. (oGetList:isExitEnter .OR. oXbp == SetAppFocus()))

        IF !DC_GetValidate( oXbp )
          BREAK
        ENDIF
        IF oGetList:isExitEnter
          IF Valtype(DC_GetObject(aGetList,'DCGUI_BUTTON_OK')) = 'O' .AND. ;
              DC_EnterExitMode()=1 // Enter Exit mode sets focus to OK button when ENTER is hit
            SetAppFocus(DC_GetObject(aGetList,'DCGUI_BUTTON_OK'))
          ELSE // Exit on ENTER if Enter Exit mode is 0 or 2

            DC_ReadGuiExit(aGetList)
            IF oGetList:isDialogActive
              BREAK
            ENDIF
            nEvent := xbeP_Close
            oGetList:IsOkStatus := .t.
            DC_ClearEvents()
            BREAK
          ENDIF
        ENDIF
      ENDIF

These both points are SOMETIMES the reason for lost events. But not allways. I have put a debug-print after each appevent() in your lib, but i lost characters even without a debug-print... :( :( :(

Any idea?
-----------------
Greetings
Markus Walter

User avatar
rdonnay
Site Admin
Posts: 4745
Joined: Wed Jan 27, 2010 6:58 pm
Location: Boise, Idaho USA
Contact:

Re: (Design-)Error with dc_guitypeahead()

#9 Post by rdonnay »

If I had a test app that would faithfully reproduce the problem I may be able to find a fix, but this is working good for me.
The eXpress train is coming - and it has more cars.

User avatar
Markus Walter
Posts: 54
Joined: Thu Jan 28, 2010 12:49 am
Location: Germany

Re: (Design-)Error with dc_guitypeahead()

#10 Post by Markus Walter »

Hello Roger,

here you are:

Code: Select all

#include "dcdialog.ch"
#include "appevent.ch"

function Appsys()
return NIL

procedure main()
local getlist := {}, getoptions := {}, lRet, oButt

  set deleted on
  set exact on
  SET OPTIMIZE OFF
  SET RUSHMORE OFF
  SET SMARTFILTER OFF

  DC_ReadGuiTypeAhead(.t.)

  @ 10, 10 dcsay "MainWindow" SIZE 100, 20
  @ 40, 10 dcpushbutton CAPTION "Start" size 100, 40 ACTION {|| doSubwindow() } OBJECT oButt

  DCGETOPTIONS TITLE "Test" WINDOWWIDTH 400 WINDOWHEIGHT 200 PIXEL
  DCREAD GUI TO lRet OPTIONS getoptions ADDBUTTONS SETAPPWINDOW SETFOCUS oButt MODAL

RETURN

function doSubWindow()

  do while .t.
    if !Subwindow()
      exit
    endif
  enddo

RETURN NIL


function SubWindow()
local getlist := {}, getoptions := {}, cVar := space(20), oGet, lRet

    @ 30, 30 dcget cVar OBJECT oGet

    DCGETOPTIONS TITLE "Sub" WINDOWWIDTH 300 WINDOWHEIGHT 150 PIXEL
    DCREAD GUI TO lRet OPTIONS getoptions ADDBUTTONS ENTEREXIT SETAPPWINDOW MODAL SETFOCUS oGet // CLEAREVENTS

    if lRet
      wTest()
    endif

RETURN lRet


function wTest()
local getlist := {}, getoptions := {}, cVar := space(20), oGet, lRet

  @ 20, 20 DCGET cVar OBJECT oGet

  DCGETOPTIONS TITLE "wTest" WINDOWWIDTH 300 WINDOWHEIGHT 100 PIXEL
  DCREAD GUI TO lRet OPTIONS getoptions ADDBUTTONS MODAL SETFOCUS oGet  // CLEAREVENTS 

return lRet
- Press Start-Button
- Enter a Character ("A" for example)
- Press Enter and very fast "12345"
and sometimes "1" or "12" is missing

You must be very fast or use a Automation-Tool. I use AutoHotkey with this Script:

Code: Select all

; Test Typeahead
Var3 = 80
!+s::
;SetKeyDelay 0
nWait:= 100
while GetKeyState("Control") = false
{
    Send 1
    Sleep, Var3
    Send 2
    Sleep, Var3
    Send 3
    Sleep, Var3
    Send {Enter}
    Sleep, Var3
    Send 1
    Sleep, Var3
    Send 2
    Sleep, Var3
    Send 3
    Sleep, Var3
    Send 4
    Sleep, Var3
    Send 5
    Sleep, Var3
    Send 6
    Sleep, Var3
    Send 7
    Sleep, 1500
    Send {ESC}
    Sleep, 200
} 
return
After starting the button "Start" you can press Shift-Alt-S and the script will run until you press Ctrl (for some seconds). With Var3 you can set the sleep-Time and you will see: more fast -> more errors...

I hope you found a solution.


But the 2 Code-Parts which i have shown in my last message, are definitly responsable for some missing characters (not for all). How can i handle this? I need ENTEREXIT in some dialogs and what about the line:

Code: Select all

DC_CompleteEvents() // - removed this to fix Cliff's problem
It seems to me that Cliff had a problem with this, too?!
-----------------
Greetings
Markus Walter

Post Reply