Removing SmartParts from a WorkItem
A common and perhaps a little painful problem while working in CUIB (using SCSF) is disposing the smart parts from a WorkItem. If smart parts are not disposed properly one may get an exception like "Cannot access the object in its current state" and in cases where we are using TabWorkspace it is often difficult to predict the behavior.
The problem here I see is the way WorkItem is understood. A WorkItem is primarily seen as a container to manage objects. The normal tendency is to have all the smart parts added to the WorkItem and later retrieve the SmartPart from the WorkItem to accomplish certain use cases. This is not a good practice to follow; a WorkItem should be seen as something that is used to accomplish a use case e.g. In a TabWorkspace we can have creating a tab as a use case. Each use case needs to be encapsulated in a WorkItemController class. Adding and also disposing of smart parts need to be done in this class.
Let me try to elucidate how this needs to be done-Let us consider a simple use case where on click of a button a tab needs to be created in the TabWorkspace. The tab needs to display a SmartPart. Straight forward of doing this would be something like this-In the Command Handler of the button
//Add the SmartPart to the WorkItem
this.WorkItem.SmartParts.AddNew<View>("MyView1");
//Add more SmartParts as required
//Display SmartPart in TabWorkSpace
this.WorkItem.Workspaces[WorkspaceNames.LayoutWorkspace].Show(this.WorkItem.SmartParts.Get<View1>("MyView1"),new SmartPartInfo("My View","This is my view"));
The problem with this approach will be disposing of the SmartPart from the WorkItem when the use case ends - in our case closing of the tab. Of course we can remove SmartPart from the WorkItem by calling the Remove method on the WorkItem. This is not the right way to as SmartPart would not be completely disposed from the WorkItem and we will run into problems when we try add to the SmartPart again to the WorkItem and in some cases we may experience weird behavior.
The right way to tackle this problem is to encapsulate all the logic pertaining to a use case in a WorkItemController class.
Creating WorkItemController Class to define a Use Case
Create a class say MyController which inherits from WorkItemController class. WorkItemController comes with SCSF and can be found in Infrastructure.Interface Project. All the logic including adding\removing SmartParts pertaining to the use case ought to be there in this class.
class MyController : WorkItemController
{
public MyController()
{ }
public void LoadSmartParts()
{
//Add the SmartPart to the WorkItem
this.WorkItem.SmartParts.AddNew<View1>("MyView1");
//Display SmartPart in TabWorkSpace
this.WorkItem.Workspaces[WorkspaceNames.LayoutWorkspace].Show(this.WorkItem.SmartParts.Get<View1>("MyView1"),new SmartPartInfo("My View","This is my view"));
}
}
Now in the Command Handler of the button create an instance ControlledWorkItem – this can be found in Infrastructure.Interface and add this instance to the WorkItem
ControlledWorkItem<MyController> myController = this.WorkItem.WorkItems.AddNew<ControlledWorkItem<MyController>>("MyController");
myController.Controller.LoadSmartParts();
Disposing SmartParts
Subscribe to the tab close event and in the event subscription code dipose the SmartPart and finally the WorkItem itself.
//Dispose method in the Controller class
//Disposes all the SmartParts from the workItem
class MyController : WorkItemController
{
public void DisposeSmartParts()
{
IEnumerator<KeyValuePair<string, object>> smartPartCollection = this.WorkItem.SmartParts.GetEnumerator();
List<object> spCollection = new List<object>();
while (smartPartCollection.MoveNext)
{
KeyValuePair<string, object> namedValue = smartPartCollection.Current;
spCollection.Add(namedValue.Value);
}
for (Int16 i = 0; i <= spCollection.Count - 1; i++)
{
this.WorkItem.SmartParts.Remove(spCollection(i));
if (spCollection(i) is IDisposable)
{
((IDisposable)spCollection(i)).Dispose();
}
}
}
}
Call this method in the tab close event subscription code
ControlledWorkItem<MyController> myController = this.WorkItem.WorkItems.Get<ControlledWorkItem<MyController>>("MyController");
//Dispose the SmartParts from the WorkItem
myController.Controller.DisposeSmartParts();
//Dispose the WorkItem
myController.Dispose();
This ensures all the SmartParts and subsequently the WorkItem is disposed correctly.
Comments
Nice post. I knew that those guys(smartparts) were hanging somewhere around but did not know how to dispose them. We are developing stuff using CAB/SCSF and just a few comments how we are doing this. First we are loading smartparts in the Run() method of the WorkItem Controller (What is your opinion about that?)
e.g.
private IWorkspace _workspace;
private IView1 _view1;
public void Run(IWorkspace workspace)
{
//Add the SmartPart to the WorkItem
_view1 = this.WorkItem.SmartParts.AddNew("MyView1");
_workspace.SmartPartClosing += new EventHandler(_workspace_SmartPartClosing);
// We should bring our view to the front when we're activated
WorkItem.Activated += new EventHandler(WorkItem_Activated);
}
void _workspace_SmartPartClosing(object sender, WorkspaceCancelEventArgs e)
{
if (e.SmartPart == _view1)
{
_workspace.SmartPartClosing -= new EventHandler(_workspace_SmartPartClosing);
// Makes it seem faster...
_workspace.Hide(_view1);
WorkItem.SmartParts.Remove(_view1);
// If we should kill the view and the WorkItem:(I knew that the smartpart is not disposed and hanging around!!!)
WorkItem.Terminate();
// BUG: The _view1 is never disposed
}
}
void WorkItem_Activated(object sender, EventArgs e)
{
if (_workspace.ActiveSmartPart != _view1)
{
_workspace.Show(_view1, smartpartinfo);
}
}
and we are calling workitem controller (mostly from ModuleAction because of the security implementation) for example like this:
string workItemId = null;
workItemId = "MyView1";
ControlledWorkItem alWorkItem = null;
if (_workItem.WorkItems.Get(workItemId) != null)
{
alWorkItem = _workItem.WorkItems.Get>(workItemId);
}
else
{
alWorkItem = _workItem.WorkItems.AddNew>(workItemId);
alWorkItem.Controller.Run(_workItem.Workspaces[WorkspaceNames.MainWorkspace]);
}
alWorkItem.Activate();
I'll be very greatfull if you can put comment on the code above. Thanks & regards
Posted by: Tomislav Babic | December 1, 2007 12:49 AM