Wednesday, October 31, 2007

Implementing OnActivate / OnDeactivate for MDI Child Forms

Find out what MDI Child WAS Active

In an MDI application, more than one document or child window can be opened within a single parent window. If you need to react when a certain MDI child form becomes active you can handle the OnActivate event of the child form. The OnActivate event is fired when the form (MDI child in this case) receives the input focus. If you need to react when a certain MDI child is being deactivated you can handle the OnDeactivate event. OnDeactivate is fired when the form transitions from being the active form to another form in the same application becoming the active form.

What if you need to extract some data from the MDI child form being deactivated from the form being activated?

You need to handle a special MDI Windows message: WM_MDIACTIVATE.

Note: every (MDI parent) Delphi form exposes the ActiveMDIChild property.

ActiveMDIChild basically returns the active MDI child form.

What we are after here is implementing something like OnActiveMDIChildChange event!

What MDI Child is Being Deactivated?

The WM_MDIACTIVATE message can be handled on the MDI parent level and on the MDI child level. MDI parent level means writing code to handle the message in the MDI parent form. MDI child level means handling the message on the child level - inside every MDI child form.

On the MDI parent level the message can be used to identify the MDI child window being activated.

More interestingly, on the MDI child level the WM_MDIACTIVATE message caries the HWND (handle) of the MDI child form being deactivated. This means that from the active MDI child form handling the WM_MDIACTIVATE message you can get your hands on the previously active MDI child window!

Note: in any MDI application you should have one base form for all your MDI child forms - then use form inheritance (VFI) to extend the functionality for each particular MDI child type form.

You will then write the code to handle the WM_MDIACTIVATE message only once - inside the base MDI child form class.


To handle WINDOWS message write a message method for this message:
//MDI child form
___TMDIChild = class(TForm)
___procedure WMMDIACTIVATE(var msg : TWMMDIACTIVATE) ; message

In the implementation get the MDI child that was previously activate ("now" deactivated) and get some data from it:
___deactivated : TWinControl;
___deactivatedChild : TMDIChild;
//find the control (form) being deactivated
___deactivated := FindControl(msg.DeactiveWnd) ;

//if deactivated is a TMDIChild form .. do something ...
___if Assigned(deactivated) AND (deactivated is TMDIChild) then
______deactivatedChild := TMDIChild(deactivated) ;

______Caption := 'deactivated: ' + deactivatedChild.Caption;

The msg procedure parameter holds the info related to the WM_MDIACTIVATE message.

Note that WM_MDIACTIVATE is first sent to the child window being deactivated and then to the child window being activated.

The above code uses FindControl to get the TWinControl descendant whose window handle is identified by the DeactiveWnd HWND value. Since DeactiveWnd identifies the MDI child window being deactivated, FindWindow will locate the MDI child that was previously active.

When you make sure the previously active MDI child was found, you might need to check its type and do some special processing ... I'll leave this up to you...

No comments: