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
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:
; 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
(this requires that your message numbers do not overlap the standard message numbers, which I should not have assumed)
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
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
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:
#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.
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
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
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
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
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
evDialogProcOptions(FLH, @evdeOnKeypress, -3, "27") ; detect ESC key
and in my 50ms timer event, I do
emap = evProcessEvents()
What else can I provide?
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
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
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
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
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
I think I found the problem. At least it would explain the inconsistent behavior.
http://www.jtdata.com/anonymous/dialogex_events.zip
Jim
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
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
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
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
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
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
<< rather than calling that function on every event. >>
which function are you referring to here?
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
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
Excellent. I really appreciate the testing and feedback. Thanks again.
Jim
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)
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
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)
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
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
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
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
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.
whoops, posted a debug version of the code. Here's a better version.
Please turn on the LoseFocus option as well and see if GainFocus works like you think it should. Something to do with only the one being on, I think, but I can't figure out why yet.
Jim
Okay. I decided that rather than fighting it, assuming what I did works, I would just make them both either on or off. I kept two separate initialization options so as to make the event checking clear. The Help File explains my reasoning. I couldn't see why it was failing but then decided having the option for only one being on really wasn't necessary anyway.
http://www.jtdata.com/anonymous/dialogex_events.zip
Please let me know if this resolves the problem. Thanks.
Jim
Nailed it. Both events work, either singly or together. Rock solid too -- I can't trick it.
I'm going to go back to that old program and put this event in it.
Thanks so much for your efforts on this Jim !!!!
Now we just need to convince Tony to use his spare time (har har, who has spare time?) to make a map of handles-to-names automatically available for dialog extender writers! Then you could just return the control name!
Actually I'm just amazed that any of this is even possible. Credit to Winbatch for making this sort of extensibility available.
-Kirby
Yeah...the WinBatch folks are awesome.
I did try to think of a way to get the Names but everything I considered ended up requiring the user to do something extra so stuck with the triggerlist option where one can submit the names up front.
One last, hopefully, overarching change before you start using it. I have combined the dialog extender and domdata extender. I often use both in the same project and this will also make things easier, I hope, on a number of fronts.
http://www.jtdata.com/anonymous/wbOmnibus.zip
Obviously good to go back and run your test script again to make sure I didn't break anything. Actually found a bug or two on other fronts, testing the changes, so that was good.
Jim
P.S. I GREATLY appreciate the time you have put in on helping with this project. THANKS!!!
I also meant to say that was a clever use of the status bar functions to show event status/activity.
Jim
Omnibus packaging is working fine for me.
Excellent. Thanks.
Jim