How to avoid an event being overwritten by a general one? - event-handling

I have a main application, with following event:
ON ESCAPE ANYWHERE
I've just created a frame within a window, where I'd like to add an event:
ON ESCAPE ...
This, however, seems not to work as the main application already has an ON ESCAPE ANYWHERE. Is there something like ON ESCAPE OVERWRITES DEFAULT to create an event for my subwindow?
Thanks in advance

From the documentation notes:
A trigger defined with the ON statement remains in effect until one of
the following occurs:
Another ON statement defines another trigger (or REVERT) for the same event and widget
For a non-persistent trigger, the procedure or trigger block in which the ON statement appears terminates
So I think your main application trigger is defined after your own trigger, overwriting it. If you can be specific enough, you should be fine:
on escape anywhere do:
message 'escaping from anywhere' view-as alert-box.
end.
define frame fr1
cc1 as char
cc2 as char
with side-labels
.
do with frame fr1:
enable all.
on escape of cc1 do:
message "escaping from cc1 in frame 1" view-as alert-box.
end.
end.
view frame fr1.
wait-for close of frame fr1.

Related

Override 'Cancel' in event procedures

There is data validation in my MS Word user form which returns the focus to the textbox where the user entered something incorrect. Now I am trying to accommodate the user's change of mind: instead of correcting the entry, I want him to be able to exit the form (click the Exit command button), in which case the entry would be discarded. I suppose that a solution would start with not using the text box's exit event. I little help from someone who knows the answer would save me a lot of testing time, perhaps to find out that I can't do it.
Does anyone know?
I understand that you are handling the Exit event of the Textbox, setting the Cancel output parameter if the data is not valid.
There's a tricky but simple solution that permits to keep that working and still have an Exit button. It permits to activate the handler of the Exit button without requiring the focus to leave the Textbox. This way you can unload the Form safely in this handler.
Try this it works pretty smoothly:
1- Set the property TakeFocusOnClick of the Exit command button to False. You can do that at design time in the property-sheet, or at run-time i.e. at UserForm_Activate
2- just unload the form when the Exit button is clicked:
Private Sub ExitButton_Click()
Unload Me
End Sub
#A.S.H provided the key to the solution below. His point is that it is possible to call another event procedure while Cancel is active in the Exit procedure of a control. That other procedure can be used to rectify the condition in the first control which is triggering the Cancel, thereby enabling an orderly exit. The all-enabling condition is that the control on whose click event the rectifying procedure is to run must not take the focus when clicked (meaning it can run without triggering an exit from the control stuck on Cancel). I have added code to the exit procedure to set CmdExit.TakeFocusOnClick = False when a Cancel condition arises there. Now, ...
Private Sub CmdExit_Click()
' 12 May 2017
' if CmdExit can't take the focus it can't be the ActiveControl
If Not ActiveControl Is CmdExit Then
Select Case ActiveControl.Name
Case "Cbx107"
Cbx107.Value = ""
Case "Tbx53"
Tbx53.Value = "0"
End Select
With CmdExit
If Not .TakeFocusOnClick Then
.TakeFocusOnClick = True
.SetFocus
End If
End With
End If
' now CmdExit is the ActiveControl
MsgMe "Cmd Exit: ActiveControl = " & ActiveControl.Name
Me.Hide
End Sub

Why Seting the SetMode to orbit disables custom KeyPressFcn event handlers, the callback

1-The code below displays the properties of the pressed key.Try it by pressing a key and observe the results.
figure('Name','Press keys to put event data in Command Window',...
'KeyPressFcn',#(obj,evt)disp(evt));
you will see outputs like this( e.g upon pressing space bar)
Character: ' '
Modifier: {1x0 cell}
Key: 'space'
2-Now simply add the following line of code to above ( or simply execute it before clearing the workspace)
cameratoolbar('SetMode','orbit');
Now press any key and nothing happens! the control will no longer be transferred to your costume call back function! ( here:#(obj,evt)disp(evt)).
same thing happens for WindowButtonDownFcn, WindowButtonUpFcn too.
How can I get around this? I wanna be able to handle KeyPressFcn or WindowButtonDownFcn after executing cameratoolbar('SetMode','orbit').
I found the answer: Once the cameratoolbar('SetMode','orbit') is called one of these two happens:the handle to the figure is lost or the event handler gets its default value. I am not sure which one though. Therefore we can add the following code to re-assign the lost handler back to our own call back function:
set(gcf,'KeyPressFcn',#(obj,evt)disp(evt))

Using REPEAT a block till an event occurs?

This is my issue:
On triger of a START button i wan to execute one block. And it should stop executing when I
press STOP button.
Eg
on triger of start button:
REPEAT:
message "hai".
END.
and when I press STOP button It should stop. What additional condition should I give to REPEAT block?Preferably I dont want to write the condition in STOP button Triger..Please give suggestion .
Progress is not multi-threaded.
So you have to carefully think through which bits of code need to execute in what context in order to fake it. (More carefully than my initial "it cannot be done" response.)
As "firhang" points out "PROCESS EVENTS" can be used to check for events queued and ready to be acted on.
So you could run your loop inside an event handler and have that event handler listen for additional events.
I believe that the following satisfies the original question and works in both GUI and Character clients:
define variable i as integer no-undo.
define variable bStop as logical no-undo.
define button butStart label "Start".
define button butStop label "Stop".
form butStart butStop with frame but-frame row 1.
on choose of butStop bStop = true.
on choose of butStart do:
bStop = false.
do while bStop = false:
i = i + 1.
process events.
message i.
end.
message "Paused".
end.
enable butStart butStop with frame but-frame.
pause 0 before-hide.
wait-for window-close of current-window.
PROCESS EVENTS statement
Processes all outstanding events without blocking for user input.
Syntax
PROCESS EVENTS
Example
This procedure counts to 1,000 until you choose STOP:
r-proevs.p
DEFINE VARIABLE ix AS INTEGER NO-UNDO.
DEFINE VARIABLE stop-sel AS LOGICAL NO-UNDO.
DEFINE BUTTON stop-it LABEL "STOP".
DISPLAY stop-it.
ON CHOOSE OF stop-it
stop-sel = TRUE.
ENABLE stop-it.
DO ix = 1 TO 1000:
DISPLAY ix VIEW-AS TEXT.
PROCESS EVENTS.
IF stop-sel THEN LEAVE.
END.
On each pass through the loop, the procedure displays the new value of ix and then checks whether any events are waiting to be processed. If no events have occurred, execution continues and the loop iterates. If the STOP button has been chosen, that event is processed changing the value of stop-sel. When execution continues, the program exits the loop.
If the loop does not contain the PROCESS EVENTS statement, the choose event never processes and the loop iterates until ix equals 1,000.
Notes
The WAIT-FOR statement processes all pending events and blocks all other execution until a specified event occurs. The PROCESS EVENTS statement processes all pending events and immediately continues execution with the next statement.
If there are any asynchronous requests for which PROCEDURE-COMPLETE events have been received but not yet processed, this statement processes these events as described for the WAIT-FOR statement.
You cannot call the .NET method system.Windows.Forms.Application:DoEvent( ) in ABL. The PROCESS EVENTS statement performs the function of this method.
.NET can raise exceptions in the context of an ABL session when this statement executes.
DEFINE VARIABLE bStop AS LOGICAL NO-UNDO.
DEFINE BUTTON butStop LABEL "Stop".
FORM butStop WITH FRAME but-frame ROW 1.
ON CHOOSE OF butStop bStop = TRUE.
ENABLE butStop WITH FRAME but-frame.
REPEAT:
PROCESS EVENTS.
MESSAGE "hai".
IF bStop THEN LEAVE.
END.
MESSAGE "stoped".
WAIT-FOR WINDOW-CLOSE OF CURRENT-WINDOW.

Using pstimer to run an event

I want to execute a function or a procedure or a block in regular intervals of 60seconds.
Do i have to use PS timer for that?
let this be my block
MyString = myEditor.Screen-Value.
Message MyString.
//myEditor is my Editor widget in the frame.
//My String is a string which i will use to display
I want to repeat this in each 60 seconds.. So that everytime it should display whatever I typed
inside the editor.? How can I do this with using PSTimer or without using it?
You must generate a Tick trigger block.
In this block can you write your code.
PSTimer has a property "Interval", which means interval for Tick event in miliseconds.
It has also other property "Enabled", witch starts and stopts timer.
When you run some longer code from your Tick event, schould be better switch that property "Enabled" on FALSE and on end of the trigger over on TRUE. Otherwise you can become a conflict with your code and new Tick event resp. your program will making never other, only showing runing your trigger script.

Getting Updated Value while using lock?

this is my first Procedure.
define frame LockFrame Customer.Name Customer.CreditLimit Customer.Balance.
pause.
DO TRANSACTION:
for each Customer exclusive-lock:
assign Customer.CreditLimit = Customer.CreditLimit + 5.
pause 1 no-message.
display Customer.Name Customer.CreditLimit Customer.Balance.
end.
end.
and thsi is my second Procedure.
define frame LockFrame Customer.Name Customer.CreditLimit Customer.Balance.
pause.
DO TRANSACTION:
for each Customer exclusive-lock:
assign Customer.Balance= Customer.Balance + 2.
pause 1 no-message.
display Customer.Name Customer.CreditLimit Customer.Balance.
end.
end.
When I run first Procedure and just after second procedure, I have to get the value Updated by first (here CrditLimit).(and vice versa)
But I am not able to run second since the record is locked by the first.It is showing an error message.
I think problem is with my locking.Please help on this.
This is exactly what you should expect.
The record is locked, exclusively for the benefit of the first procedure, until the transaction commits. The commit will occur at the 2nd END statement.
I'm not sure why you have PAUSE in there -- you should never block on IO inside a transaction block -- that will only lead to problems (like users locking each other while they get up and go get coffee...)
You almost NEVER really want to enclose an update of an entire table in a transaction (which is what your DO TRANSACTION block is doing). Even if you think that is what you want to do, or have been told that that is what you must do it is probably wrong. This is usually the result of confusing a "business transaction" with a "database transaction" -- they are not the same thing -- especially when large amounts of data are in play.
A better way to write your code (apply the same concept to both samples):
define buffer updCustomer for customer.
for each customer no-lock /* where whatever */:
/* maybe some no-lock logic... */
do for updCustomer transaction:
find updCustomer exclusive-lock where recid( updCustomer ) = recid( customer ).
assign
updCustomer.creditLimit = customer.creditLimit + 5.
.
display
updCustomer.name
updCustomer.creditLimit
updCustomer.balance
.
end.
pause 1 no-message.
end.
Using NO-LOCK on the FOR EACH allows selection logic or other logic to run without needing the lock. Using the update buffer and the DO FOR ... TRANSACTION block tightly scopes the record lock and the transaction to that single block. Placing the PAUSE outside the block prevents the "user goes to get coffee" problem.
I hesitate to comment on any answer that Tom has given, since I consider him authoritative. But I want to point out two small things.
First of all, the use of RECID might be better as ROWID. ROWID is recommended for use by Progress (see http://documentation.progress.com/output/OpenEdge102a/oe102ahtml/wwhelp/wwhimpl/common/html/wwhelp.htm?context=dvref&file=dvref-15-48.html) since RECID is "supported for backward compatibility. For most applications, use the ROWID function, instead."
But that's minor, really. What's also important in my opinion is what Tom did in his example for you - he defined a buffer ("define buffer updCustomer for customer.") that he used in the update. I want to encourage you to use buffers EVERY time you work with a record, especially if you get into using persistent or super procedures, or if you are using functions or internal procedures.
Why? Defining a buffer ensures that the scope of the buffer you are updating is limited to the place where you defined it. As an example, Progress will "leak" the default buffer into your super procedure if you aren't careful. Imagine this scenario...a program that finds a record, calls a function in a super procedure to do "some stuff" and then deletes the record.
FIND MyTable WHERE MyTable.fk = fkValue NO-LOCK NO-ERROR.
UpdateOtherStuff(MyTable.fkValue).
DeleteMyRecord(MyTable.fkValue).
But in "UpdateOtherStuff", it does some work including this...
FOR EACH MyTable:
If MyTable.Thing = 'ThingOne' THEN LEAVE.
/* other processing here... */
END.
You might be surprised when you find that the super procedure shares the default "MyTable" buffer with your program, and ends up repositioning the record somewhere you don't want...so that the call to "DeleteMyRecord()" has a different record than you expect.
The problem would be solved if "UpdateOtherStuff" had a "DEFINE BUFFER ... FOR MyTable" at the top, even if it was "DEFINE BUFFER MyTAble for MyTable" (strange as that looks...).
That's why Tom's example, including a DEFINE BUFFER..., should be a template for the work you do in the ABL.
This question was asked previously - see https://stackoverflow.com/a/5490130/1433147.