Hi all,
I'm having some EditBox and a PushButton in a DialogBox
The PushButton is used to go ahead in the script and to validate the value entered in every EditBox
So, this validation is always done after all the EditBoxes have been filled
Now, how is it possible to check the complete value (not Char by Char) in every EditBox while leaving it and before entering value in the next EditBox ?
Thanks for your infos
jm
You might want to look at
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/Dialog~Editor/Dialog~Editor~version~6.X/Samples+Field~validation~with~WinBatch~Dialog.txt
or
http://techsupt.winbatch.com/webcgi/webbatch.exe?techsupt/tsleft.web+WinBatch/Dialog~Editor/Dialog~Editor~version~6.X/Samples+Field~Validation~Example.txt
but in any case you will probably require a dynamic dialog with a callback.
[EDIT]: and also, you can consider using validating drop-downs rather than regular edits, for example a field that says "Select State" - then there is always the 'wizard' concept where you step through a series of validations.
I've always wished there was an event for focus loss, so that your code could run when the user moves the cursor away from the field. Here's the hack I've been using for the last several years:
;WasSimpleFocusLossEvent.wbt
;
; Sometimes new people are suprised to find that the EditBox
; event (number 5) fires for every keystroke -- what they
; really wanted to know is when the user is DONE changing
; the EditBox. This can be detected by watching when the
; focus moves away from the control - the "focus lost" event.
; Then we can look at the data and see if any action is needed.
;
; Also demoed here is a simple and handy StatusBar function.
;
;
#DefineFunction DProc(DH,DM,DN,DE,DC)
; Demonstration of WasFocusLossEvent and StatusBar
; detect the focus loss pseudo-event
if WasFocusLossEvent(DH,&Controlname)
; only if the control name starts with "EditBox"
if strindex(controlname,"EditBox",1,0)
; what did it contain?
t = dialogcontrolget(DH, controlname, 3)
;; here is where you do act upon the event, checking field
;; contents (because you know the field name) and possibly
;; steering the focus back onto the field, or whatever
; in this case, just put all that in the status bar
StatusBar(2, controlname:" lost focus, contains ":t)
endif
endif
; normal event processing Switch
switch DM
case 0 ; initialization event
DialogProcOptions(DH,1,200) ; timer events every 200 ms
DialogProcOptions(DH,2,@TRUE) ; pushbutton events
StatusBar(DH, "75,") ; set up a two-section statusbar
return(-1)
case 1 ; timer event
; display the time in the status bar
StatusBar(1, strsub(timeymdhms(),12,8))
return(-1)
case 2 ; pushbutton
message(DN,'exiting')
return(-1)
endswitch
return(-1)
#EndFunction
#definefunction WasFocusLossEvent(DiaHan,ptrControlname)
; Detects a focus loss pseudo event
;
; if function returns @TRUE, then Controlname will be
; set to the control that just lost focus. Controlname
; must be passed as a pointer (using the ampersand).
;
; Be sure dialog has a timer event of 500ms or less.
; Add to dialog callback BEFORE normal event handling.
; set up to remember control
ptrLastFocus = PtrPersistent(LastFocus,"")
; see what control currently has focus
DCSTATE_GETFOCUS = 5
ThisFocus = DialogControlState(DiaHan,0,DCSTATE_GETFOCUS,0)
; same control?
if ThisFocus == *ptrLastFocus
; return nothing, no focus change
return @false
else
; no previous known control?
if *ptrLastFocus == ""
*ptrLastFocus = ThisFocus ; reset for this new control
return @false
endif
terminate(isdefined(*ptrControlname)==-1,'WasFocusLossEvent','Controlname must be pointer')
*ptrControlname = *ptrLastFocus ; control that lost focus
*ptrLastFocus = ThisFocus ; reset for this new control
return @true
endif
#endfunction
;
; Note: the WasFocusLossEvent function requires a timer
; event because it is essentially polling to discover the
; change in focus. It is not necessary to do anything
; with the timer event if you do not want to. Fire the
; event every half second or less to get a snappy response
; to focus changes.
;
; Note: the WasFocusLossEvent function should be called
; ahead of the normal dialog event processing in order to
; detect and process the focus change before processing the
; event that changed it. For example, if clicking a Save
; button changes the focus, you want to first deal with the
; focus change, then deal with the Save operation.
#definefunction StatusBar(N,text)
; Super easy status bar on winbatch dialogs
; (repackaged from article W15451 in the database)
;
; on first call, N is the Dialog Handle and
; text is "width,width,width..."
; which establishes the number of sections
; and gives the width in pixels for each section
; on subsequent calls, N is the section number (1 based) and
; text is content for that section
;
user32=strcat(dirwindows(1),"user32.dll")
WM_USER=1024
SB_SETPARTS=WM_USER+4
SB_SETTEXT=WM_USER+1
h = ptrpersistent(handle,0) ; remember the status bar handle between calls
if *h == 0 ; create mode?
hinst=dllhinst("")
style = 0
*h = dllcall(user32,long:"CreateWindowExA",long:512,lpstr:"msctls_statusbar32",lpstr:"",long:1073741824|268435456|style,long:0,long:0,long:0,long:0,long:N,long:0,long:hinst,long:0)
n = itemcount(text,',')
awidths=binaryalloc(n*4)
t = 0
for i = 0 to n-2
x = itemextract(i+1,text,',')
if !isnumber(x) then x = 0
binarypoke4(awidths,i*4,t+x)
t = t + x
next i
binarypoke4(awidths,i*4,-1)
dllcall(user32, long:"SendMessageA", long:*h, long:SB_SETPARTS, long:n, lpbinary:awidths)
else
dllcall(user32,long:"SendMessageA",long:*h,long:SB_SETTEXT,long:N-1,lpstr:text)
endif
#endfunction
MyDialogFormat=`WWWDLGED,6.2`
MyDialogCaption=`Testing FocusLoss Event`
MyDialogX=167
MyDialogY=052
MyDialogWidth=146
MyDialogHeight=127
MyDialogNumControls=007
MyDialogProcedure=`DProc`
MyDialogFont=`DEFAULT`
MyDialogTextColor=`DEFAULT`
MyDialogBackground=`DEFAULT,DEFAULT`
MyDialogConfig=0
MyDialog001=`051,081,036,012,PUSHBUTTON,"PushButton_OK",DEFAULT,"OK",1,10,@csDefButton,DEFAULT,DEFAULT,DEFAULT`
MyDialog002=`011,017,036,012,EDITBOX,"EditBox_1",ebVariable1,"A",DEFAULT,1,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog003=`075,017,036,012,EDITBOX,"EditBox_2",ebVariable2,"BB",DEFAULT,2,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog004=`013,051,036,012,EDITBOX,"EditBox_3",ebVariable3,"CCC",DEFAULT,3,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog005=`077,049,036,012,EDITBOX,"EditBox_4",ebVariable4,"DDDD",DEFAULT,4,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog006=`003,105,138,012,STATICTEXT,"StaticText_1",DEFAULT,"current time focus loss info",DEFAULT,60,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog007=`001,001,138,006,STATICTEXT,"StaticText_2",DEFAULT,"Use Tab key or mouse to move between fields",DEFAULT,70,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
ButtonPushed=Dialog("MyDialog")
exit
In the case of multiple edit boxes, it is not absolutely necessary to track the input focus to validate boxes one at a time. All that's needed is to use the edit box's "@deEdText" event to note which box is currently being filled. Then when the even switches to the next edit box, validate the previous edit box and put the input focus back on the previous box if the entry was not correct using the "DialogControlState" function's "@dcsSetFocus" request code.
That's true, but you still end up looking at the box many times, at each keystroke, which can get expensive if what you are doing is a database lookup or something.
Here's another version of the same thing, but coded in a way that makes it more like a real event.
;WasFullFocusLossEvent.wbt
;
; This sample shows the same idea, only treating the pseudo event more
; like a real event, which can appear more confusing, but is actually
; simpler to code in practice.
;
;
#DefineFunction DProc(DH,DM,DN,DE,DC)
; Demonstration of WasFocusLossEvent and StatusBar
; detect the focus loss pseudo-event
if WasFocusLossEvent(DH,&DN)
DM = 99 ; my pseudo focus loss event code
endif
; normal event processing Switch
switch 1
case DM==@deInit ; initialization event
DialogProcOptions(DH,1,200) ; timer events every 200 ms
DialogProcOptions(DH,2,@TRUE) ; pushbutton events
StatusBar(DH, "75,") ; set up a two-section statusbar
return(-1)
case DM==@deTimer ; timer event
; display the time in the status bar
StatusBar(1, strsub(timeymdhms(),12,8))
return(-1)
case DM==@dePbPush ; pushbutton
message(DN,'exiting now')
return(-1)
case DM==99 && DN=="EditBox_1" ;first box lost focus
t = dialogcontrolget(DH, DN, 3) ;
StatusBar(2, "first box contains ":t)
return(-1)
case DM==99 && DN=="EditBox_2" ;second box lost focus
t = dialogcontrolget(DH, DN, 3) ;
StatusBar(2, "second box contains ":t)
return(-1)
case DM==99 && DN=="EditBox_3" ;third box lost focus
t = dialogcontrolget(DH, DN, 3) ;
StatusBar(2, "third box contains ":t)
return(-1)
case DM==99 && DN=="EditBox_4" ;fourth box lost focus
t = dialogcontrolget(DH, DN, 3) ;
StatusBar(2, "fourth box contains ":t)
return(-1)
endswitch
return(-1)
#EndFunction
#definefunction WasFocusLossEvent(DiaHan,ptrControlname)
; Detects a focus loss pseudo event
;
; if function returns @TRUE, then Controlname will be
; set to the control that just lost focus. Controlname
; must be passed as a pointer (using the ampersand).
;
; Be sure dialog has a timer event of 500ms or less.
; Add to dialog callback BEFORE normal event handling.
; set up to remember control
ptrLastFocus = PtrPersistent(LastFocus,"")
; see what control currently has focus
DCSTATE_GETFOCUS = 5
ThisFocus = DialogControlState(DiaHan,0,DCSTATE_GETFOCUS,0)
; same control?
if ThisFocus == *ptrLastFocus
; return nothing, no focus change
return @false
else
; no previous known control?
if *ptrLastFocus == ""
*ptrLastFocus = ThisFocus ; reset for this new control
return @false
endif
terminate(isdefined(*ptrControlname)==-1,'WasFocusLossEvent','Controlname must be pointer')
*ptrControlname = *ptrLastFocus ; control that lost focus
*ptrLastFocus = ThisFocus ; reset for this new control
return @true
endif
#endfunction
;
; Note: the WasFocusLossEvent function requires a timer
; event because it is essentially polling to discover the
; change in focus. It is not necessary to do anything
; with the timer event if you do not want to. Fire the
; event every half second or less to get a snappy response
; to focus changes.
;
; Note: the WasFocusLossEvent function should be called
; ahead of the normal dialog event processing in order to
; detect and process the focus change before processing the
; event that changed it. For example, if clicking a Save
; button changes the focus, you want to first deal with the
; focus change, then deal with the Save operation.
#definefunction StatusBar(N,text)
; Super easy status bar on winbatch dialogs
; (repackaged from article W15451 in the database)
;
; on first call, N is the Dialog Handle and
; text is "width,width,width..."
; which establishes the number of sections
; and gives the width in pixels for each section
; on subsequent calls, N is the section number (1 based) and
; text is content for that section
;
user32=strcat(dirwindows(1),"user32.dll")
WM_USER=1024
SB_SETPARTS=WM_USER+4
SB_SETTEXT=WM_USER+1
h = ptrpersistent(handle,0) ; remember the status bar handle between calls
if *h == 0 ; create mode?
hinst=dllhinst("")
style = 0
*h = dllcall(user32,long:"CreateWindowExA",long:512,lpstr:"msctls_statusbar32",lpstr:"",long:1073741824|268435456|style,long:0,long:0,long:0,long:0,long:N,long:0,long:hinst,long:0)
n = itemcount(text,',')
awidths=binaryalloc(n*4)
t = 0
for i = 0 to n-2
x = itemextract(i+1,text,',')
if !isnumber(x) then x = 0
binarypoke4(awidths,i*4,t+x)
t = t + x
next i
binarypoke4(awidths,i*4,-1)
dllcall(user32, long:"SendMessageA", long:*h, long:SB_SETPARTS, long:n, lpbinary:awidths)
else
dllcall(user32,long:"SendMessageA",long:*h,long:SB_SETTEXT,long:N-1,lpstr:text)
endif
#endfunction
MyDialogFormat=`WWWDLGED,6.2`
MyDialogCaption=`Testing FocusLoss Event`
MyDialogX=167
MyDialogY=052
MyDialogWidth=146
MyDialogHeight=127
MyDialogNumControls=007
MyDialogProcedure=`DProc`
MyDialogFont=`DEFAULT`
MyDialogTextColor=`DEFAULT`
MyDialogBackground=`DEFAULT,DEFAULT`
MyDialogConfig=0
MyDialog001=`051,081,036,012,PUSHBUTTON,"PushButton_OK",DEFAULT,"OK",1,10,@csDefButton,DEFAULT,DEFAULT,DEFAULT`
MyDialog002=`011,017,036,012,EDITBOX,"EditBox_1",ebVariable1,"A",DEFAULT,1,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog003=`075,017,036,012,EDITBOX,"EditBox_2",ebVariable2,"BB",DEFAULT,2,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog004=`013,051,036,012,EDITBOX,"EditBox_3",ebVariable3,"CCC",DEFAULT,3,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog005=`077,049,036,012,EDITBOX,"EditBox_4",ebVariable4,"DDDD",DEFAULT,4,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog006=`003,105,138,012,STATICTEXT,"StaticText_1",DEFAULT,"current time focus loss info",DEFAULT,60,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog007=`001,001,138,006,STATICTEXT,"StaticText_2",DEFAULT,"Use Tab key or mouse to move between fields",DEFAULT,70,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
ButtonPushed=Dialog("MyDialog")
exit
If you need to process editboxes BEFORE pushbuttons, then the events loop can be rearranged in various ways.
Not necessarily. Chances are the event is running anyway. You don't have to check the values of the entries for what Tony suggests (assuming I understand). You just maintain two variables.
current_focus and previous_focus
When current no longer matches previous then you know you need to validate the previous_focus control.
Jim
Quote from: kdmoyers on April 12, 2018, 07:53:24 AM
That's true, but you still end up looking at the box many times, at each keystroke, which can get expensive if what you are doing is a database lookup or something.
<< You just maintain two variables. current_focus and previous_focus When current no longer matches previous then you know you need to validate the previous_focus control >>
Which is precisely what the code I posted does. It just does it in a way that lets your program treat it like an ordinary event.
Okay. Didn't read your code closely. Just responded to what you said. :)
Jim
Quote from: kdmoyers on April 12, 2018, 07:53:24 AM
That's true, but you still end up looking at the box many times, at each keystroke, which can get expensive if what you are doing is a database lookup or something.
The assertion that checking which edit box is producing a text input event is any more expensive than kicking off every two seconds to check which control has the focus seems a bit questionable In fact, it could even be less expensive because the UDC would be called less often.
However, either way will work.
Thanks -- good points Tony and Jim. You guys always make me think. I do like the timer-based thing though.
By unwrapping the function call, and switching to DefineSubroutine, I get a shorter and clearer version below.
I'll have to think about a no-timer version.
;WasOtherFocusLossEvent.wbt
;
LostFocus = 0
#DefineSubroutine DProc(DH,DM,DN,DE,DC)
; Demonstration of WasFocusLossEvent and StatusBar
; detect the focus loss pseudo-event
DCSTATE_GETFOCUS = 5
ThisFocus = DialogControlState(DH,0,DCSTATE_GETFOCUS,0)
if ThisFocus != LostFocus
switch 1
case LostFocus=="EditBox_1" ;first box lost focus
t = dialogcontrolget(DH, LostFocus, 3) ;
StatusBar(2, "first box contains ":t)
if strupper(t)==t then break ; is good
StatusBar(2, "ERROR: must be upper case")
DialogControlState(DH,LostFocus,@dcsSetFocus,0)
return(-2) ; don't process this event call further
case LostFocus=="EditBox_2" ;second box lost focus
t = dialogcontrolget(DH, LostFocus, 3) ;
StatusBar(2, "second box contains ":t)
if strlower(t)==t then break ; is good
StatusBar(2, "ERROR must be lower case")
DialogControlState(DH,LostFocus,@dcsSetFocus,0)
return(-2) ; don't process this event call further
case LostFocus=="EditBox_3" ;third box lost focus
t = dialogcontrolget(DH, LostFocus, 3) ;
StatusBar(2, "third box contains ":t)
break
case LostFocus=="EditBox_4" ;fourth box lost focus
t = dialogcontrolget(DH, LostFocus, 3) ;
StatusBar(2, "fourth box contains ":t)
break
endswitch
LostFocus = ThisFocus
endif
; normal event processing Switch
switch 1
case DM==@deInit ; initialization event
DialogControlState(DH,"EditBox_1",@dcsSetFocus,0)
DialogProcOptions(DH,1,200) ; timer events every 200 ms
DialogProcOptions(DH,2,@TRUE) ; pushbutton events
StatusBar(DH, "75,") ; set up a two-section statusbar
return(-2)
case DM==@deTimer ; timer event
; display the time in the status bar
StatusBar(1, strsub(timeymdhms(),12,8))
return(-2)
case DM==@dePbPush ; pushbutton
display(1,DN,'exiting now')
return(1)
endswitch
return(-2)
#EndSubroutine
#definefunction StatusBar(N,text)
; Super easy status bar on winbatch dialogs
; (repackaged from article W15451 in the database)
;
; on first call, N is the Dialog Handle and
; text is "width,width,width..."
; which establishes the number of sections
; and gives the width in pixels for each section
; on subsequent calls, N is the section number (1 based) and
; text is content for that section
;
user32=strcat(dirwindows(1),"user32.dll")
WM_USER=1024
SB_SETPARTS=WM_USER+4
SB_SETTEXT=WM_USER+1
h = ptrpersistent(handle,0) ; remember the status bar handle between calls
if *h == 0 ; create mode?
hinst=dllhinst("")
style = 0
*h = dllcall(user32,long:"CreateWindowExA",long:512,lpstr:"msctls_statusbar32",lpstr:"",long:1073741824|268435456|style,long:0,long:0,long:0,long:0,long:N,long:0,long:hinst,long:0)
n = itemcount(text,',')
awidths=binaryalloc(n*4)
t = 0
for i = 0 to n-2
x = itemextract(i+1,text,',')
if !isnumber(x) then x = 0
binarypoke4(awidths,i*4,t+x)
t = t + x
next i
binarypoke4(awidths,i*4,-1)
dllcall(user32, long:"SendMessageA", long:*h, long:SB_SETPARTS, long:n, lpbinary:awidths)
else
dllcall(user32,long:"SendMessageA",long:*h,long:SB_SETTEXT,long:N-1,lpstr:text)
endif
#endfunction
MyDialogFormat=`WWWDLGED,6.2`
MyDialogCaption=`Testing FocusLoss Event`
MyDialogX=167
MyDialogY=052
MyDialogWidth=146
MyDialogHeight=127
MyDialogNumControls=007
MyDialogProcedure=`DProc`
MyDialogFont=`DEFAULT`
MyDialogTextColor=`DEFAULT`
MyDialogBackground=`DEFAULT,DEFAULT`
MyDialogConfig=0
MyDialog001=`051,081,036,012,PUSHBUTTON,"PushButton_OK",DEFAULT,"OK",1,10,@csDefButton,DEFAULT,DEFAULT,DEFAULT`
MyDialog002=`011,017,036,012,EDITBOX,"EditBox_1",ebVariable1,"A",DEFAULT,1,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog003=`075,017,036,012,EDITBOX,"EditBox_2",ebVariable2,"BB",DEFAULT,2,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog004=`013,051,036,012,EDITBOX,"EditBox_3",ebVariable3,"CCC",DEFAULT,3,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog005=`077,049,036,012,EDITBOX,"EditBox_4",ebVariable4,"DDDD",DEFAULT,4,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog006=`003,105,138,012,STATICTEXT,"StaticText_1",DEFAULT,"current time focus loss info",DEFAULT,60,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
MyDialog007=`001,001,138,006,STATICTEXT,"StaticText_2",DEFAULT,"Use Tab key or mouse to move between fields",DEFAULT,70,DEFAULT,DEFAULT,DEFAULT,DEFAULT`
ButtonPushed=Dialog("MyDialog")
exit
<< I'll have to think about a no-timer version >>
Duh -- just ask for the @deEdText event instead of a timer event. That was simple.
Thanks guys!
For the sake of accuracy, I typed "every 2 seconds" above when I should have typed "every one-half second."
With the last code of KdMoyers, it's exactly what I was looking for
Now it's time to study it :)
Thanks a lot for your advices
jm
All this still begs the question as to what is the goal. Is the dialog merely a form to fill out, or a process, i.e. an update/install. The latter would fit a wizard approach, the former more of a form validation. Then the former can be broken down as : validate-field is null, validate field has to be filled in [upper/lower case, or lookup table], validate field filed with mask, i.e. SSAN, Phone #.....
I'm sure all contributors have experienced the above. But, just curious