Activation and Deactivation: A suggested approach
byon 28-Dec-2009 at 09:00 AM (2721 Views)
The DataFlex activation and deactivation mechanism has a long history. It was built as part of the original object-oriented user interface and has gone through many changes. It had to be extended to support modal and non-modal objects. It was then expanded to work with data aware objects (DEOs). Later it was altered to support Windows controls. It should come as no surprise that the end result is that there are a lot of ways to make this work and even more ways to make this not work. Sooner or later everyone bumps into some kind of issue with activating and deactivating dialogs.
The purpose of this article is to provide a set of guidelines for using the activation and deactivation process. Iíve revisited this and tried to come up with the most consistent and simplest ways to control it. I will be telling you which messages to send and which ones to augment. I will also be telling you which messages to leave alone and some of these may surprise you. This is not the only way you can control activation and deactivation and you may have some other way of approaching this. If so, I will try to explain enough about the process so you can work more effectively with whatever method you choose.
To start with, letís define our terms:
A dialog is a Windows dialog. A view is a modeless dialog such as dbView or View. A modal dialog is a dialog that is modal such as a dbModalPanel or ModalPanel. The class determines if a dialog is modal or modeless. You cannot make a view modal and you cannot make a modal dialog modeless.
A DEO object is a data-aware object meaning that it understands the DataFlex Data Dictionary / Data Entry Object framework (DD/DEO). A DEO object may be a container or a control. Examples of DEO containers are dbView, dbModalPanel and dbContainer3d. Examples of DEO controls are dbForm and cdbTextEdit.
A non-DEO object is an object that does not understand the DD/DEO framework. Examples of non-DEO containers are View, ModalPanel and Container3d. Examples of non-DEO controls are Button, Form and cTextEdit.
A DEO dialog is a dialog that understands the DD/DEO framework. A DEO dialog may contain a mix of DEO and non-DEO objects. A non-DEO dialog may only contain non-DEO objects.
Activation refers to displaying a dialog that is currently not paged. A dialog is inactive if it is not displayed and active if it is displayed.
The focus object is the control that has the input focus.
Focus-change refers to switching the focus between active objects. This can occur within a dialog or across dialogs. Often the term activate is used to refer to activation and focus-change. In this article, activate only refers to activation.
Deactivation refers to closing a dialog and making it inactive.
From a userís perspective activation makes a dialog visible and usable. From a technical perspective activation creates and displays the Windows dialog object and all of the child Windows objects within the dialog, it places these objects in the DataFlex focus-tree, and it gives one of these objects the focus.
The Active_state property determines if an object is already active meaning that it has already been added to the focus tree. The Window_Handle property determines if a Windows control is created. Normally these two properties should change in tandem. If an object is in the focus tree Active_State should be True and Window_Handle should be non-zero. The Focus property determines which object has the focus.
The activation process is a little different for views and modal dialogs. While much of their behavior is the same there are some critical differences. We will start with views.
View activation can be represented with this simplified pseudo-code. This code assumes that the view is not yet active (this is an activation and not a focus-change process).
Activate_View is the message you should send to activate a view. The Activate message is what actually performs the activation. This is an important and useful message and will be discussed shortly. Add_Focus creates the Windows control and adds it to the focus tree. It does this by sending Page_Object, which adds the object to the focus tree, calls the event Activating and calls Page, which creates the Windows control. Add_Focus then sends Add_Focus to all of its children. Finally Activate will move the focus to the first focusable child object. It does this by sending Activate to that object (where Activate is now used for focus-change).Code:Activate_View Activate Add_Focus Page_Object (object added to focus-tree) Activating Page (Windows control created) Broadcast Add_Focus to all children Give the focus to the first focusable object (focus-change)
From a customization point of view we are only interested in two messages: Activate and Activating.
The Activate Message
For activation purposes you should never need to send Activate. It is however a very useful message for augmentation.
If you wish to disallow activation, augment and just donít forward it.
If you wish to make any changes in your dialog before activating it, you can do so before forwarding this message because the process has not really started yet.
Remember that the Activate method is used for activation and focus-change. You want to make sure that your augmentations are used for the right task and you use the Active_State property to determine this. Here is a sample of how this would be done:
The functions CanActivate and PreActivateDialog are samples and donít exist Ė you would have to create them yourself.Code:Procedure Activate Returns Integer Boolean bActive bCanActivate Integer iRet Get Active_State to bActive // if being used for Activation If (not(bActive)) Begin // call a custom method to determine if we will allow activation Get CanActivate to bCanActivate If not bCanActivate Begin Procedure_Return 1 End // call a custom method to perform any customizations // before the activate commences Send PreActivateDialog Forward Get msg_Activate to iRet End // if being used for focus-change Else Begin Forward Get msg_Activate to iRet End Procedure_Return iRet End_Procedure
Now here is a very important point. Once you have forwarded Activate to the system classes you have passed the point of no return and the dialog should be activated. If activation is stopped after this point it is considered a programming error and things will not be good.
You can place augmentations after you forward Activate but you can only do this with views and not with modal dialogs (to be explained shortly). At this point the view is activated and some object within the view has the focus. You could change properties or even change the focus by sending Activate (focus-change) to some other object within the dialog. Note that while you should never need to send Activate for activation you might send it for focus-change.
The Activating Event
Activating is called for every single object in the dialog in a top down order. It is called before the Windows object is created. It is called before the child controls are created (i.e., it is called before Add_Focus is sent to the child objects). Therefore, at this point you can change properties, change Windows styles and perform even more aggressive customizations to the child objects. You should not try to do anything that changes the focus, deactivates objects or alters the existing focus tree. You cannot use this message to cancel the activation Ė itís not designed to do that. Therefore, donít return a value from this event.
Modal Dialog Activation
The activation of modal dialogs is almost the same as views, but there are some key differences. You activate a modal dialog by sending it the Popup message. It does the following:
The major difference here is that Create_Dialog is called between Activate and Add_Focus. This enforces modality by disabling the parent objects and starts a new UI level. Starting a new UI level means that the Create_Dialog procedure is not completed until the modal dialog is closed. This completely changes the post-augmentation behavior of the Activate message. It is not executed until the modal dialog is closed. Therefore, donít use Activate for post augmentation purposes. You still use it for the same pre-augmentation techniques discussed with view activations.Code:Popup Activate Create_Dialog (dialog Windows control created!) ----New UI level is created---- Add_Focus Page_Object (object added to focus tree) Activating Page (Windows control created - except dialog) Broadcast Add_Focus to all children Give the focus to the first focusable object (focus-change)
There is another technical difference. For internal reasons the modal dialog Windows control is created in Create_Dialog before Add_Focus is called. This means that the Windows control already exists when Activating is called and that Page actually doesnít need to create this control. Normally this does not matter but if you need to do something with the outer dialog control before it is created, you would do so in Activate. Only this dialog control is created early - all of the children of the modal dialog are created in a normal fashion (i.e., in Page following Activating).
You may notice that we are not using the Popup_Modal message for activation. This will work but it is not needed. Popup_Modal is a legacy message that calls Popup.
You should never need to send Activate to activate a dialog.You can send Activate to change the focus. This is often done within a view or modal dialog to change the focus from one object to another. When changing the focus between views it is recommended that you treat this as a view change. First activate the view and, if needed, send Activate to an object within the view. Remember that focus-change occurs when the dialog's Active_State is true.
Deactivation is the process of closing an active dialog. From a userís perspective the view or dialog is closed and no longer visible or usable. Internally, the dialog and all of its children are removed from the DataFlex focus tree, all Windows objects in the dialog are destroyed and the focus is moved to some other part of the application.
Unlike the activation process where views and modal dialogs have slightly different behaviors, the deactivation process for these two types of dialogs are the same. However, there are significant differences between DEO (dbView, dbModalPanel) and non-DEO (View, ModalPanel) dialogs.
Deactivation and DEO Dialogs
We will start with DEO dialogs because these are what you will use most often.
As you can see, this process is a bit complicated and in reality it is far more complicated. Therefore, we will start with the simple part. Here is what you need to know to control deactivation.Code:Close_Panel Exit_Function Request_Cancel Verify_Exit Execute message in Verify_Exit_Msg. If non-zero is returned, cancel Deactivate (sent to the dialog object) Send Activate to another dialog (focus-change) Release_Focus Broadcast Release_Focus to focus children Remove_Object (remove object from focus tree) Deactivating Page_Delete (destroy Windows control)
1. Send Close_Panel to close a dialog. You can send this to the dialog or any object in the dialog
2. Create your own "can-close" function which returns non-zero to stop the deactivation. Set its message ID in the Verify_Exit_Msg property in the dialog object.
3. Use the Deactivating event to handle any "is closing" logic. Deactivating is sent to every active object in the dialog.
4. Do not augment or send any of the other messages.
If you follow these steps you donít have to worry about the complications I am about to describe. The main complication is that DEO objects try to do too much. You might think that all of these messages (Close_Panel, Exit_Function, Request_Cancel, etc.) are all defined and handled by the dialog object via delegation. With DEO child objects, this is not the case. Each individual DEO's class understands and handles these messages directly. To make matters worse, these objects might send different messages to close the dialog (Close_Panel, Exit_Function, or Request_Cancel). To add one more complication, non-DEO objects (such as buttons) donít understand any of these messages and they do delegate. This makes it rather difficult to know which messages should be augmented. Depending on where the focus is, different close messages are being sent to different objects. The good news is if you donít augment any of these messages, this will all work out. As long as you place your can-close function and its Verify_Exit_Msg in the dialog, you will get consistent behaviors. The Verify_Exit message searches through parent DEOs looking for a non-zero Verify_Exit_Msg. If you set Verify_Exit_Msg in the outer dialog, all objects will find it and use the same can-close function.
As an example, adding the following code to a dbView or a dbModalPanel would only allow you to close a dialog when the current time has an odd seconds value (a nice technique that will drive anyone crazy).
Code:Object oDbAwareModalDialog is a dbModalPanel Set Label to "Label..." Set Size to 89 211 Set Border_Style to Border_Thick Function CancelClose Returns Boolean DateTime dDT Integer iSec Boolean bStopIt // silly test that returns true to stop deactivation // if the current seconds value is even. Move (CurrentDateTime()) to dDT Move (DateGetSecond(dDT)) to iSec Move (Integer(iSec/2) = (iSec/2.0)) to bStopIt Function_Return bStopIt End_Function Set Verify_Exit_Msg to (RefFunc(CancelClose))
Deactivation and Non-DEO Dialogs
The deactivation of a non-DEO dialog is much simpler. Since you are not allowed to nest DEO objects inside of a non-DEO container, we know that none of the objects are DEOS. Deactivation looks like this:
Just like in DEO dialogs, you send Close_Panel to deactivate the object. In this case, sending Close_Panel to any object in the dialog will result in the message delegating to the dialog object. If you wish to stop the deactivation, augment Close_Panel and donít forward send.Code:Close_Panel Deactivate Send Activate to another dialog (focus-change) Release_focus Broadcast Release_Focus to focus children Remove_Object (remove object from focus tree) Deactivating Page_Delete (destroy Windows control)
Here is an example of how you could control whether a non-DEO dialog can be closed.
Code:Object oNonDbAwareModalDialog is a ModalPanel Set Label to "Label..." Set Size to 89 211 Set Border_Style to Border_Thick Function CancelClose Returns Boolean DateTime dDT Integer iSec Boolean bStopIt // silly test that returns true to stop deactivation // if the current seconds value is even. Move (CurrentDateTime()) to dDT Move (DateGetSecond(dDT)) to iSec Move (Integer(iSec/2) = (iSec/2.0)) to bStopIt Function_Return bStopIt End_Function Procedure Close_Panel Boolean bStop Get CancelClose to bStop If not bStop Begin Forward Send Close_Panel End End_Procedure
The Deactivation Process
Once deactivation has begun (once the Deactivate message is sent) the process is the same for all dialogs. Once deactivation has started it cannot be stopped and if it is stopped this is a programming error and your application will not be stable.
The Deactivate message works in two modes. It is used to find the outer "area" object to deactivate and it is then used to actually deactivate an object. You have to know how to send this message with the right parameters and you have to know how to augment it properly. Leave it alone! Don't send or augment it.
When you close a dialog you want to make sure there is somewhere else for the focus to move to. With views, this is rarely an issue. With modal dialogs, the system will attempt to give the focus back to the object that had the focus when the modal dialog was invoked. If for some reason this object cannot retake the focus, it is a programming error and your program will not be stable.
Child objects are deactivated before the parent object is deactivated.
The Deactivating event is sent to every active object in the dialog in a bottom up fashion. You cannot use this event to cancel a deactivation. When Deactivating is called, the object is already removed from the focus tree (Active_State is zero) but the Windows control still exists (Window_Handle is non-zero). In addition, all child objects are already deactivated (removed from focus tree and Windows control destroyed). The Page_Delete method actually destroys the Windows control.
If you augment Deactivating, you are most likely to augment it only in the dialog object.
Working with Modal Dialogs
Modal dialogs are modal for a reason. You want to invoke them at a particular moment and suspend the rest of your application until this modal dialog process provides you with the information you need to continue. While you could use some of the messages weíve discussed here such as Popup and Deactivating to handle pre- and post-processing needs that is usually not the best strategy. Refer to the article Communication between views and dialogs for a thorough discussion of this topic and a suggested approach.
How things go wrong
Several times, I've mentioned that "bad things" happen when activation or deactivation is not properly completed. There are three things that must be properly synchronized for everything to work. These are the focus-tree, Windows objects and the UI-level.
The focus-tree is a DataFlex structure that controls how next and previous object navigation works. Objects are added and removed from this tree during activation and deactivation. Windows objects are the controls that are created during activation and destroyed during deactivation. When an object is added to the focus-tree, a Windows object should be created and vice versa. When activation or deactivation is improperly halted, you can end up with conditions where an object is in the focus-tree but there is no Windows control, or the object is not in the focus-tree but a Windows control exists. You can test for this by looking at Active_State, which reflects the focus-tree status, and Window_Handle which indicates if a Windows control exists.
The UI-level is only an issue with modal dialogs. The UI-level is the level at which the DataFlex user interface processing loop executes. Normally there is only one level and this is used by all views. When a modal dialog is activated, the Create_Dialog method creates a new UI-level and suspends the previous level. The previous UI-level remains suspended until the new UI-level ends, which occurs during deactivation. If something goes wrong during the deactivation of a modal dialog the new UI-level may not end. When this happens the dialog may appear closed but you will be processing UI events in the wrong UI-level. You can see this in the debugger. Pause your program. If your modal dialog appears closed but you still see a "create_dialog" in the call-stack, you have a problem. At this point there is a pretty good chance that Active_State and Windows_Handle are no longer synchronized.
In all of these cases, once these are not synchronized, there is no easy way for the application to recover. Even if things appear to work, they are not working properly. Normally you don't need to worry about any of this because the DataFlex activation and deactivation process handles this properly. If you are having a problem, you are doing something wrong. Correct it and everything will work properly.
That's pretty much it. Let's review what you need to know:
1. Send Activate_View to activate a view and Popup to activate a modal dialog.
2. Augment the Activate message before you forward it to control if a dialog can be activated and to handle any pre-activation needs.
3. Inside Activate test the Active_State property to determine if this is an activation or a focus-change process.
4. Once you forward Activate, the activation process should not be stopped.
5. Activating is called right after an object is added to the focus tree and right before the Windows control is created (see modal dialog exception in 8).
6. With views, you can augment Acivate after the forward to handle any post-activation needs.
7. Modal Dialogs should not have post-activation augmentations because the code is not called until after the dialog is closed.
8. Modal dialogs create the outer dialog Windows control right after you forward send Activate and not following the Activating event.
1. Close a dialog by sending Close_Panel.
2. Create your own can-close function at the dialog level that determines if a dialog can be closed. Return zero to close, non-zero to cancel.
3. With DEO dialogs, set this can-close function in the Verify_Exit_Msg property.
4. With non-DEO dialogs, augment Close_Panel to send this can-close message. If it returns non-zero, do not forward Close_Panel.
5. The Deactivating event is called after an object is removed from the focus tree and before its Windows control is destroyed.
As I stated at the top of this article, there is a forest of history in these processes. What I've tried to do here is to create the shortest path through that forest. I actually have selfish reasons for writing this. I've gone through this process multiple times and by the time I actually need to use this knowledge, I've lost the path and I have to start all over. I've now got this link bookmarked.