Dialog (Pseudo)Events - Experimental

Started by JTaylor, March 16, 2021, 09:17:41 PM

Previous topic - Next topic

JTaylor

I will refer to this as experimental rather than beta.  The event functions require WinBatch 2021B or higher.   I think it will end up being released but would love to have some feedback on how well it works before I commit.   Hopefully the Help file is helpful.

This provides events, in a manner of speaking, for OnEnter, OnExit and KeyPress.  Use at your own risk.  All I can say is my computer didn't blow up testing it.


      http://www.jtdata.com/anonymous/dialogex_events.zip


Jim

kdmoyers

I've had only a few minutes to read the code, but here are my unimportant thoughts:

the on-enter and on-exit events can be simulated with already existing tech, but it's a hassle.  This looks way easier, bravo!

the key events look really really great.  I don't know any good way of getting this effect and have wished for it for years.

I think it needs a little helper map to get the control names.  If you knew the names, you could map handles to names, and return the name. You'd have to sneakily collect the control names from the dialog variables.  kinda tricky... I'll take a crack at it over the weekend.

It would be cool to structure it so that it emulates the standard event loop behavior, simplifying the code.  Imagine this:
Code (winbatch) Select
; main dialog switch statement
switch 1

    case MyDialog_Message==@deInit
      . . .
      Handles_to_Names = CreateControlsMap(SID_Handle) ; this might be tricky
      . . .
      break

    case MyDialog_Message==@timer
   
      ; if there was a special event, set the normal vars and continue the Switch
      emap = evProcessEvents(0,0,0)
      If ArrInfo(emap,1) > 0
        MyDialog_Message = emap["DMsg"]
        MyDialog_Name = Handles_to_Names[emap["DCHwnd"]]
        continue
      endif
      break
   
   
    case MyDialogMessage == @evdeOnKeyPress
      message("key was",emap["VK_KEY"])
      break

    case MyDialogMessage == @dePbPush
      message("pushed button",MyDialog_Name)
      break

    case MyDialogMessage == @evdeOnExit && MyDialog_Name == "eb_SID_cname"
      message("text is", DialogControlGet(SID_Handle,MyDialog_Name,@dcText))
      break



The mind is everything; What you think, you become.

kdmoyers

(this requires that your message numbers do not overlap the standard message numbers, which I should not have assumed)
The mind is everything; What you think, you become.

JTaylor

Thanks.   Yeah.   I'm not doing anything that can't be done to some degree already but hopefully this will be smoother and easier.

I have winbatch KeyPress code that works okay but was never happy with it even though it was probably the best I could do.


Assuming I understand, I am already providing a way to get what you want...

ctrl_name = DialogProcOptions(SID_Handle,@dpoCtlName,emap["DCtrl"])

"DCtrl" is the control number that WinBatch sees/uses and which can be used to get the Name and vice-versa.

This only works well though if there are no Group Boxes, which tend to mess up the control numbers as it is tied to TAB order.  I remember this issue when using DLL calls against the dialogs before things became much easier.   One can work around it but is a bit of hassle.  I can try to clarify this more in the Help file if needed.  One could build that Map in WinBatch, if needed, but I can't do much more than I have already from the Extender side. 

So if you changed:

         MyDialog_Name = Handles_to_Names[emap["DCHwnd"]]
to
        MyDialog_Name = DialogProcOptions(SID_Handle,@dpoCtlName,emap["DCtrl"])

You would accomplish what you want without the extra map and with caveat above.

The other would work as well if you didn't want that structure within the timer.   You would just need to remove the "Break" from the @Timer Case statement so it fell on through and, of course, break if the Map size was zero.

Thanks again for the feedback.


Jim


JTaylor

I actually started this project as a way to learn about events in C++ and whether I could use them in the context of an extender.   I got it [sort of] working and then realized that in this situation it made no sense to use Events  as I would simply be triggering an event from a pseudo event which was being triggered by another Event so dropped that part of the code :)

Jim

kdmoyers

FWIW, I've had to avoid control numbers because I use lots of group boxes.  When the need arises (rarely) I use control handles instead.  This routine will create a handle-to-name map:
Code (winbatch) Select
#definesubroutine CreateControlsMap(_DH, _VP)

  _n = _VP:"NumControls"               ;name of the var we want
  terminate(vartype(%_n%) != 1, "CreateControlsMap", "bad dialog var prefix")
  _n = %_n%                            ;the actual number of controls
  _map = mapcreate()                   ;we'll store the controls here
  for _i = 1 to _n
    _v = _VP : strfixLeft(_i, "0", 3)       ;name of the ith var
    _v = itemextract(6,%_v%,",")            ;value of the 6th field is name
    _v = %_v%                               ;quotes removed
    _h = dialogcontrolget(_DH, _v, @dcHwnd) ;get the handle of that control
    _map[_h] = _v                           ;map handle to name
  next _i
  return _map

#endsubroutine


and you can just call this in your @deInit event.
Code (winbatch) Select
      Handles_to_Names = CreateControlsMap(FLH, "FL")

Note that this requires your call back procedure to be a subroutine, not a function.  This works for me.

I'll try it out with evProcessEvents this weekend.

-Kirby
The mind is everything; What you think, you become.

JTaylor

Thanks.  Will take a look and add to the Help file for the edification of all.

You will want to update.   You mentioning variable overlap reminded me I forgot that possibility.  I have adjusted the CONSTANT values accordingly.  I try to keep that in mind but easy to forget. 

Plus I added a few more features along with some cleanup on the Help file.

     http://www.jtdata.com/anonymous/dialogex_events.zip

Jim

JTaylor

I updated the above to handle Menus but in working on that I discovered that menus screw up the CtrlIDs I retrieve in the Extender.   

Jim

JTaylor

After the helpful feedback and pondering this issue I have made some changes.

I created a script along the lines of what Kirby suggested but which produces a name/hwnd list that can be submitted as a parameter to the extender function.  It also allows one to limit by ControlType.  If an extra script had to be run it made sense to fold this into something I was already doing anyway in being able to submit a specific control list to the function.   This eliminates the need for an extra map as the name will be returned in the map from the function return.

If one is not using Menus or Group Boxes the script would not be needed as the DCtrl key would allow access to the Controls and/or control name.

Example script in the zip file and the Help file.


http://www.jtdata.com/anonymous/dialogex_events.zip

Thanks again Kirby.   Please let me know after taking a look this weekend if this fulfills the need.

Jim

kdmoyers

The evDialogProcOptions seems to execute, but evProcessEvents crashes immediately on very first execute:
Runtime Error!
Program: C:\Program Files (x86)\WinBatch\System\WinBatch.exe
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

In the Init event, I do
Code (winbatch) Select
evDialogProcOptions(FLH, @evdeOnKeypress, -3, "27") ; detect ESC key

and in my 50ms timer event, I do
Code (winbatch) Select
emap = evProcessEvents()

What else can I provide?
The mind is everything; What you think, you become.

JTaylor

I am not sure.  I don't get that error but something is wrong.   Will let you know once I figure it out.  Thanks.

Jim

JTaylor

I posted an update but nothing that really should affect what you are seeing but it does generate an error now if no Events have been initialized and you run evProcessEvents().  I was having a weird problem and it seems to have gone away so maybe this fixed something or I was just missing something in my script.

I tested "8" rather than "27" because Esc closes my script.  With or without this active.

http://www.jtdata.com/anonymous/dialogex_events.zip


If this doesn't help and your script isn't super secret please post that.   PM if needed or happy to send you my email if you don't want it on the forum.

Jim

kdmoyers

still crashes.

I would post, but it's a bit tricky. Not because its secret but because it's complex -- 1200 lines.  That makes too confusing a picture.

Instead, let me construct a little test program and see if I can make it work with that.

-Kirby
The mind is everything; What you think, you become.

kdmoyers

Code (winbatch) Select
  AddExtender(shortcutdir("desktop"):"\dlgex44i_events.dll")
  gosub localdefs

#DefineSubroutine HappyProc(FLH,FLE,FLN,FLEInfo,FL_ChangeInfo)

  switch 1

    case FLE==@deInit
      DialogProcOptions(FLH,@dePbPush,@TRUE)  ; push button events
      DialogProcOptions(FLH,@deTimer,50)      ; ms per timer tick
      dialogprocoptions(FLH,@deEdText,@true)  ; editbox events
      DialogProcOptions(FLH,@deClose,@TRUE)   ; close box clicks
      DialogProcOptions(FLH,@dpoSysMenu,1)    ; just close

      ; contruct map of control-Hwnd-to-control-name
      Handle_to_Name = CreateControlsMap(FLH, "MD")

      ; set up to capture the F1 key
      evDialogProcOptions(FLH, @evdeOnKeypress, -3, "112")

      break

    case FLE==@deTimer                        ; TIMER event
      emap = evProcessEvents()                ; request extended events
      x = 1/0                                 ; never gets here
      if ArrInfo(emap,1) > 0                  ; we got one?
        FLE = emap["DMsg"]                    ; set up to
        FLN = Handle_to_Name[emap["DCHwnd"]]  ;   pretend this was
        FLEInfo = emap                        ;     a normal event
        continue                              ; continue normal event processing
      endif
      break

    case FLE==@dePbPush && FLN=="swap"              ; BUTTON event, swap button
      a = dialogcontrolget(FLH, "freddy", @dctext)  ; swap the
      b = dialogcontrolget(FLH, "sally",  @dctext)  ;   contents of
      dialogcontrolset(FLH, "freddy", @dctext, b)   ;     the two
      dialogcontrolset(FLH, "sally",  @dctext, a)   ;       text controls
      break

    case FLE==@evdeOnKeyPress                       ; KEYPRESS event
      message(FLN,FLEInfo['VK_KEY'])                ; tell what key was pressed
      break

    case FLE==@deClose                              ; CLOSEBOX event
    case FLE==@dePbPush && FLN=="exit"              ; BUTTON event, exit button
      return(1)

  endswitch         
  return(@retNoExit)

#EndSubroutine       


MDFormat=`WWWDLGED,6.2`

MDCaption=`Happy Dialog`
MDX=500
MDY=077
MDWidth=342
MDHeight=170
MDNumControls=005
MDProcedure=`HappyProc`
MDFont=`DEFAULT`
MDTextColor=`DEFAULT`
MDBackground=`DEFAULT,DEFAULT`
MDConfig=0

MD001=`063,121,036,012,PUSHBUTTON,"swap",DEFAULT,"Swap",1,10,@csDefButton,DEFAULT,DEFAULT,DEFAULT`
MD002=`205,125,036,012,PUSHBUTTON,"exit",DEFAULT,"Exit",0,20,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MD003=`049,053,116,026,EDITBOX,"Freddy",ebVariable1,DEFAULT,DEFAULT,30,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MD004=`199,053,116,026,EDITBOX,"Sally",ebVariable1,DEFAULT,DEFAULT,30,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MD005=`023,021,140,010,STATICTEXT,"StaticText_1",DEFAULT,"Some Happy Text",DEFAULT,50,DEFAULT,DEFAULT,DEFAULT,DEFAULT`

ButtonPushed=Dialog("MD")

EXIT


;---------------------------------------------------------------------------
:localdefs

#definesubroutine CreateControlsMap(_DH, _VP)

  _et = "CreateControlsMap"                     ;error message title text
  _n = _VP:"Format"                             ;name of the outer scope var we want
  terminate(vartype(%_n%) != 2,                    _et, "bad dialog var prefix")
  terminate(itemextract(1,%_n%,",") != "WWWDLGED", _et, "bad dialog var prefix")
  terminate(itemextract(2,%_n%,",") < 6.2,         _et, "bad dialog version")

  _c = _VP:"NumControls"                        ;name of the outer scope var we want
  _c = %_c%                                     ;the actual number of controls
  _map = mapcreate()                            ;we'll store the controls here
  for _i = 1 to _c
    _v = _VP : strfixLeft(_i, "0", 3)           ;name of the ith dialog var
    _t = itemextract(5,%_v%,",")                ;value of the 5th field is type
    if strindexnc(_t,"MENU",1,0) then continue  ;skip menu type controls
    _n = itemextract(6,%_v%,",")                ;value of the 6th field is name
    _n = %_n%                                   ;remove quotes from value
    _h = dialogcontrolget(_DH, _n, @dcHwnd)     ;get the handle of that control
    _map[_h] = _v                               ;map handle to name
  next _i
  return _map

#endsubroutine


return
The mind is everything; What you think, you become.

JTaylor

Hmmmmmmmmm...It errors out on  x = 1/0, as it should, for me.   Something about your approach vs mine brought an issue to light though, that I wasn't seeing, so that was good.   Let's see if fixing that fixes your issue.  I wouldn't think so but I am uncertain why it errors out for you and runs fine for me.   Will ponder it while I wait to see if this makes a difference.    I might need to post a version which pops up message each step of the way to see at what point it dies on you.

http://www.jtdata.com/anonymous/dialogex_events.zip

@dbVersion should show as 1021.

Thanks.  I really do appreciate the feedback and different perspective.

Jim

JTaylor

I think I found the problem.  At least it would explain the inconsistent behavior.

          http://www.jtdata.com/anonymous/dialogex_events.zip

Jim

kdmoyers

It was me.  I thought I was on 2121B but I was really on 2121A. duh.
OK, works as expected so far... Makes the key event as expected... I'll continue to test.
You might insert a check for that version thing, in case I'm not the last fool to make that mistake.
-Kirby
The mind is everything; What you think, you become.

JTaylor

I am doing a check but apparently I did something wrong.   I have the try/catch in place and have tested the code before.  Apparently something I changed interfered.  Will get it sorted.  In any event, that problem brought to light a couple of other things so that was helpful, other than being annoying for you.  Thanks.

Jim

kdmoyers

So Jim,

attached is my latest test program.  Remarks:

[1] I had a typo in CreateControlsMap, now fixed.

[2] just a comment: I'm not sure what "Dialog Control
Numbers" are useful for.  I seem to get away with
ignoring them completely.  Maybe I'm missing something.

[3] the OnEnter and OnExit events seem to be flawless.
Very handy, much appreciated.

[4] the OnKeypress event is tricky. It seems more like a
"key is presently being held down" event, so it repeats
if you are not quick.  This might be pretty useful in
certain circumstances, like for a game.

[5] I seem to get an odd event from the shift keys, even
thought I did not ask for them.  They report key number 0.
This is easy enoough to ignore, but I thought I should
report it.

[6] If capslock is on, I get a continuous stream of events.


...testing continues...

-Kirby
The mind is everything; What you think, you become.

JTaylor

Found it.   Thanks again.  It should now error out with a helpful message.

http://www.jtdata.com/anonymous/dialogex_events.zip

#2   Assuming we are in the same context, That is the number that was used before names were implemented.   ex. SID001=....   001 would be the number.   There is also the numbers that CtrlId that windows recognizes(different than hwnd) that can be used in DLL Calls.

#3  Excellent.   That was my experience so glad I am not the only one.

#4.  Yeah.  I note this in the Help file.  Still looking at how to keep that from happening.   I know how, just not sure if it will impact performance very much or not.

#5.  Hmmmmm...I think this might be a feature and not a bug but in review maybe a bad one?   I was thinking that perhaps someone might like to trap the Shift, Ctrl and/or Alt keys without other keys being pressed.  What are your thoughts.  Need to also make sure it isn't a bug.  Will double-check.

#6.  I think that is related to #5.   If CapsLock is on I report Shift as being pressed.  Will take a look.   That would be a bug.

Thanks again.  This is a HUGE help.


Jim

kdmoyers

Quote from: JTaylor on March 22, 2021, 11:24:28 AM#2   Assuming we are in the same context, That is the number that was used before names were implemented.   ex. SID001=....   001 would be the number.   There is also the numbers that CtrlId that windows recognizes(different than hwnd) that can be used in DLL Calls.

Yeah.  I did some checking and remembering.  The 2008E update brought in dialog version 6.2 and that's the last time I used a control number.  I feel fortunate to have avoided any cases where I needed them since then. 

I still have a running program that uses the old 6.1 dialogs! Looking through the code I see the crazy scheme I used to "name" the controls back then.  Embarrassing.   The new 6.2 method makes my code so much easier to write and to read.  Thanks Wilson!

-Kirby
The mind is everything; What you think, you become.

JTaylor

Agreed.  The names were an excellent addition.

I think I have all the concerns nailed down on the keypress stuff.  It should no longer register multiple events if you hold the key down.  The light bulb came on and made that easy.   I changed the SHIFT/CTRL/ALT stuff.   Those will no longer trigger events unless you specify option 1 or include them in the keylist.  They, of course, will still be part of the Map so you can check to see if a combination was used.

I would suggest you consider using the TriggerIDList option for handling the "Names", rather than calling that function on every event.  I think it will be far more efficient.   Script for building the list is in the Help file along with an example file in the zip.

http://www.jtdata.com/anonymous/dialogex_events.zip

Thanks again.  I think we are getting close.

Any other event suggestions?   These are the only ones I remember being requested over the years but may be forgetting something.


Jim

kdmoyers

<<  rather than calling that function on every event. >>

which function are you referring to here?
The mind is everything; What you think, you become.

JTaylor

Nevermind.  I was misreading the code.   Other than not having the extra map I don't think anything would be gained.   Is the KeyPress stuff working better?

Jim

kdmoyers

Yes!  Key events look great!  I get one event per keypress, unless I hold it down long enough for the regular key repeat to happen, as expected.  Its perfect.

I've not tested for multiple combos of keys yet.  Let me think a couple hours on the idea of other events
The mind is everything; What you think, you become.

JTaylor

Excellent.  I really appreciate the testing and feedback.  Thanks again.

Jim

kdmoyers

Jim, I have a question about what extenders can and can not do.
(this will show you how little I know about extenders!!)

Can an extender function call modify a passed variable ThisVar?

      someExtenderFunction(ThisVar)


If not, what if we pass it as pointer?

      someExtenderFunction(&ThisVar)
 
The mind is everything; What you think, you become.

kdmoyers

Jim,

The other handy events I can think of would be entire-window-focus-loss and entire-window-focus-gain. 
These are a hassle to simulate reliably.  My attempts only work 50% of the time.

-Kirby
The mind is everything; What you think, you become.

JTaylor

Assuming I understand, Yes, with qualifications.   It would need to know the variable name rather than the value.    It can read the variable value and change it.   The option to create the key variables in the onkeypress event, is an example of initializing a WinBatch variable from an Extender.

               someExtenderFunction("ThisVarName")

Jim

Quote from: kdmoyers on March 23, 2021, 11:34:13 AM
Jim, I have a question about what extenders can and can not do.
(this will show you how little I know about extenders!!)

Can an extender function call modify a passed variable ThisVar?

      someExtenderFunction(ThisVar)


If not, what if we pass it as pointer?

      someExtenderFunction(&ThisVar)


JTaylor

Please explain a bit more with an example of what you are doing (words, not code).

Jim

Quote from: kdmoyers on March 23, 2021, 11:48:55 AM
Jim,

The other handy events I can think of would be entire-window-focus-loss and entire-window-focus-gain. 
These are a hassle to simulate reliably.  My attempts only work 50% of the time.

-Kirby

kdmoyers

Around here, everyone has eleventy nine overlapping windows open, competing for attention.  Browser windows, application programs, emails, images, more application windows. etc.

Sometimes, I want my  program to do something when the user switches back to it.  My case was I wanted to refresh a SQL query when the user brings the window up to focus, so it displays freshest data, but I don't care to do that when the window is sitting idle.  So the event to look for is when the window itself (not an individual control) gains focus.

My hacked worked mostly, but sometimes not.  I wrote it some years ago.

-Kirby
The mind is everything; What you think, you become.

JTaylor

Okay.  Figured that was what you meant but thought it wise to ask before I started chasing the wrong rabbit.  Will see what I can do.

Jim

JTaylor

Everything seems to be playing nice with each other.  Hopefully I tested enough variations that I am not wasting your time with silly errors.   DCHwnd will contain the window that has the focus for the two new events.  It and DMsg are the only two relevant fields for them.

    http://www.jtdata.com/anonymous/dialogex_events.zip

Jim

kdmoyers

Hmmm...

I get the initial event OK, but not another one.

I'm expecting this set of behaviors:

  • start my dialog, get initial @evdeOnGainFocus event
  • flip over to another window, say, a browser
  • read latest XKCD comic on the browser
  • flip back to my dialog
  • dialog should get @evdeOnGainFocus event
It's that last thing that does not happen.
code attached.
The mind is everything; What you think, you become.

kdmoyers

whoops, posted a debug version of the code.  Here's a better version.
The mind is everything; What you think, you become.