Monday, November 30, 2009

Using messages to notify events

Lately, i've been using frames extensively in my projects and recently i faced a little problem while working with them: how to notify the parent control (often a TForm) changes in the state of the frame controls/data?

I tried some approaches:

1) Add an event handler to the TDatasource.OnDataChange event.
This has the disadvantage of not being a generic solution since not always the frame has a TDataSource. Also this event is fired in changes to all fields of the respective TDataset, and often only a few fields need to be monitored, leading to some overhead. Yet is not possible to set the event at design time due to bug 14947.

2)Create handlers for events of child controls.
Works by setting in the parent form handlers for events, e.g. OnChange, of child controls of the frame. It has the drawback of needing more than one event handler in the case where is necessary to monitor more than one control/field.
Another problem is that such event handlers can be cleared by the IDE due to bug 14835. Not to say that sometimes the changes in the frame are not propagated to frame instances. In this case the workaround i found to sync the frames was to remove and add the frame again, loosing all properties set in the frame parent control.

So, i decided to implement a specific event for the frame. The first approach i thought was adding a TNotifyEvent property to the frame but this has almost the same drawbacks of approach 2. At this point the idea of using messages came in my mind.

AFAIK messages are used mostly in LCL and seldom in user projects so i had doubts if would work. Here is what i did:

1) Declare a custom message id constant
I declared the following constant in a shared unit:

2) Create a method in the frame to send the message
I created a method SendDataChangeMsg with the following code:

Form: TCustomForm;
Form := GetFirstParentForm(Self);
if Form <> nil then
Form.Perform(LM_CHILDDATACHANGED, 0, 0);

The code is pretty straight. It get the parent form and then send the message through Perform method

3) Hook the events of the controls that should be monitored
Mostly OnChange events. Just called SendDataChangeMsg inside them

4) Add a property ValidData
This property checks if the data in the monitored controls are valid

5) Intercept the message at the frame parent (TForm)
In the TForm i put the frame, i created a method to intercept the message declared as follow:

procedure ChildDataChanged(var Msg: TLMessage); message

I'm using that to allow saving or not the data so i put a code to enable/disable the save button:

SaveButton.Enabled := Frame.ValidData;
That's all. It's working fine for me. In the end i got a pretty clear notify system. Now i can remove/add the frame as necessary without the need to reconnect the event handlers every time.

No comments: