Problems with the close form event (MDI)...bug?

Topics: CAB & Smart Client Software Factory
Feb 8, 2006 at 2:34 AM
originally posted by: aras03k

Hi,

I have a problem with how to handle the close event after i have showed my mdi form to the user. My form consists of 1 smart part with 2 other smartparts nested inside it. All 3 are decorated with the "SmartPart" attribute. The form is displayed by a workitem that is wired up to a menustrip through a command handler.

It works great showing the form the first time but when the form is closed by clicking on the "x" it somehow messes up the smartparts cotained inside the form. The second time i want to display the form it is nothing but a gray window!

It was my understanding that the window workspace and mdiworkspace wraps around a form and when the window close event triggers it removes the smartparts from the forms control list...thus avoiding disposal?

I tried to circumvent this by attempting to remove the smartparts from the workitems smartpart collection and reloading them the next time i had to show the window...but i quickly found out that smartparts were not meant to be removed from the WI once they were loaded!

So, is this a bug in the workspaces? I cant believe this is by design but if it is how do i handle the workspaces so i can close and show them multiple times with no problems?

I realize that i could perhaps call terminate on the WI and the whole thing would be disposed...but since the WI is wired up to the menu i cant see this as a good idea?

Any help is greatly appreciated :)

---------------------------------------------------------------------------------------------------------------------------
Here is an example of my code :

Assigning the mainworkspace from the root workitems mdiworkspace:
mainWorkspace = WorkItem.Parent.WorkspacesConstants.Workspaces.MainWorkspace;

The command handler method to activate and display the form:

CommandHandler(Constants.Commands.FindAccountHolder)
public void SearchAccountHolder(object sender, EventArgs e)
{

//I have to avoid the smartpart being loaded twice...the framework throws an exception otherwise.
if (!WorkItem.SmartParts.ContainsObject(searchView))
{
searchView = WorkItem.SmartParts.AddNew<AccountHolderSearchView>();
searchViewInfo = new WindowSmartPartInfo();
searchViewInfo.Modal = true;
searchViewInfo.Title = Properties.Resources.Search;
searchViewInfo.Width = 750;
searchViewInfo.Height = 350;
searchView.Dock = DockStyle.Fill;
searchViewInfo.MaximizeBox = false;
}
mainWorkspace.Show(searchView, searchViewInfo);
}
Feb 8, 2006 at 6:10 AM
originally posted by: DLorenz

Well, I think that the Menu Click should simply create a new WorkItem and run it. Then the new WorkItem will run its SmartParts to the appropriate Workspace. Then you can close the WorkItem to get rid of all those objects. Terminating a WorkItem will remove it from its parent's collection, so you should be able to run it again and again.

If you don't like this idea, the alternative would be to use unique keys when running your SmartParts. That way, it wouldn't think they were the same SmartPart. What I think is happening now is that when you run the SmartPart a second time, it finds one in its collection. However, the IsDisposed property is probably set to true, but CAB seems to ignore this. If you add a key to the call "searchView = WorkItem.SmartParts.AddNew<AccountHolderSearchView>(key);", this should fix your problem. However, if you have Event Subscriptions sitting there, both instances would receive them.
Feb 8, 2006 at 6:35 AM
originally posted by: aras03k

Surely there must be another workaround than simply terminating the WI or creating "copies" of it? I'm not sure i would like either of those alternatives to be honest.

I kinda like the fact that the WI are not disposed upon end of use because the next time they are called the state collection is intact. For instance, my searchview will display the same data that it did when it was closed down. This is handy in some cases - and perhaps in others not.
Feb 8, 2006 at 6:39 AM
originally posted by: DLorenz

The only other thing I can think of is that you have to call the Workspace.Close(smartpart) method somehow, but I'm not sure if that alone will fix your problem. I guess you could raise some event that the WorkItem can handle in order to remove the smartpart from the Workspace during the Closing event of the SmartPart.

As for the state, you could do a couple of things. First, you can inject the parent's state into the child workitem's state. That way, you could store the data you need in at a higher level and keep passing it down as needed. Another option would be to use the IStatePersistanceService provided by CAB. It will save the WorkItem's state to the hard drive and reload it if the key is the same, assuming you call WorkItem.Save() before you close it.
Feb 8, 2006 at 6:49 AM
originally posted by: aras03k

Yes I tried hooking in to the closing event from the smartpart:

mainWorkspace.SmartPartClosing += new EventHandler<WorkspaceCancelEventArgs>(OnMainWorkspaceSmartPartClosing);

private void OnMainWorkspaceSmartPartClosing(object sender, WorkspaceCancelEventArgs e)
{
mainWorkspace.SmartPartClosing -= new EventHandler<WorkspaceCancelEventArgs>(OnMainWorkspaceSmartPartClosing);
e.Cancel = true;
mainWorkspace.Close(e.SmartPart); //Calling close manually seems to do the trick
WorkItem.Deactivate();
}

This actually works fine. But as you can imagine, cancelling the event (to avoid stack overflow) has the effect of stopping any further event handling. If I close the shell application it only closes the child window and nothing further happens ;)
Feb 8, 2006 at 6:52 AM
originally posted by: DLorenz

I wouldn't do it thru that kind of event. Instead, I would use an EventPublication. You could raise the event in the Closing event and have the WorkItem catch it in an Event Subscription. Then you can cast the sender to the SmartPart and close it on the Workspace there. That way, you don't have to worry about messing up the closing call.
Feb 16, 2006 at 1:04 PM
originally posted by: jjgoodfella

We had this same exact problem. We were creating composite smart part CAB forms within the mdi and then tried to close the form. When reshown, the controls were stripped out of the CAB generated form. What we ended up doing, which worked for us, was to create a "BaseView" class that has the SmartPart attribute and handles all CAB closing functionality. The base class also allows us to handle reactivation of the same view if the developer decides that he/she wanted the view to persist upon clicking the close button.

Within our BaseView class, we have all logic routed to the parent form's FormClosing event. So, when a user clicks on the close button in the title bar, the FormClosing subscriber method is called which in turn calls a private "Close(FormClosingEventArgs closingArgs)" method. Within the Close method, we first fire an event that enables the derived classes to circumvent the closing of the form. The event uses the CancelEventArgs class to allow the derived class to cancel the closing if desired. This is useful if the derived class has dirty data, etc. that needs to be saved first. Next, we check to see if the CancelEventArgs Cancel property is not true and the FormClosingEventArgs CloseReason is not "MdiFormClosing." We check for the MdiFormClosing because we also want to ensure the correct logic is handled when clicking on the shell's close button. After we check for those items, we then check for a bool property value to see if the developer decided to persist the item. If persisted, we then cancel the form closing, and hide the item in the workspace. If not persisted, we subsequently remove the item from the workitem.

Also, within the CAB's WindowWorkspace class, we changed the logic within the OnClosing of the WindowForm class to be handled by a subscriber FormClosing method. This enables the BaseView's FormClosing to be hit before the WindowForm FormClosing. We also created a new CancelEventArgs class and passed in the FormClosingEventArgs Cancel property value. This essentially bubbles up whatever was set in the base class. By the way - this is where the controls are being stripped out of the form. We also call the base OnClosing event as the last call in the FormClosing subscriber method in which we pass the CancelEventArgs that we created.

To make the above work, we needed to also create properties within our BaseView class for the WorkItem, SmartPartID, and WorkSpace. Without these items, we would not be able to properly remove the BaseView instance from the WorkItem or hide the instance within the workspace.

The above logic also works if the view has a "Close" button or something similar where if clicked, the view is closed. For this situation, we added a public Close method in which the ParentForm.Close method is called. This will trigger the FormClosing event to fire.

Hope this helps.
Feb 28, 2006 at 4:34 AM
originally posted by: plombaer

The code of the BaseView class would be very useful.

Thanks,
Philippe
Apr 17, 2006 at 1:00 PM
originally posted by: GregShaw

jjgoodfella,
I'm with Philippe. Any chance of the code for BaseView?

Thanks, Greg.