Removing WorkItems and Views

Topics: CAB & Smart Client Software Factory
Mar 18, 2006 at 1:23 PM
originally posted by: aadams99

I have the requirement of removing (unloading, disposing, taking it out of memory so that the next time i need it i'll have to load it again, however you want to say it) a 'View' when I move to another and doing the same thing with a 'WorkItem'.

I have a nagivation side bar that allows the user move from one 'View' to another. Each 'button' within this navigation bar corresponds to a 'WorkItem' (so when I select a category the WorkItem for that category loads). Then when I select another 'button' on the navigation bar corresponding to a subcategory within this category the 'View' gets loaded.

(Application):
Navigation Bar for Configuration Module
(Category)
(WorkItem Loads) Sales ->
(SubCategory)
(View Loads and is Shown) Calculate Sales
(Category)
(WorkItem Loads) Billing ->
(SubCategory)
(View Loads and is Shown) Do Billing Stuff

I'm in the Sales->Calculate Sales view and want to move to Billing->Do Billing Stuff view.
THe Calculate Sales 'View' needs to be (unloaded, removed, disposed) and the Sales 'WorkItem' needs to be (unloaded, removed, disposed).

Could I get code examples of how to do this?

Thanks --
Mar 19, 2006 at 8:35 AM
originally posted by: Diesel31

What about storing the active workitem in a variable and whenever you need to activate a new workitem, you simply terminate the workitem you stored in the variable. I'm not sure or not, but CAB may provide a method or event to help with this, allowing you to get the active workitem.
Mar 21, 2006 at 6:46 AM
originally posted by: aadams99

I'm using the BAT methodology for loading WorkItems and Views.

//This load the initial workitem:
public class ConfigurationModuleInit : ModuleInit
{
private WorkItem _rootWorkItem;

InjectionConstructor
public ConfigurationModuleInit(ServiceDependency WorkItem rootWorkItem)
{
_rootWorkItem = rootWorkItem;
}

public override void Load()
{
base.Load();
_rootWorkItem.WorkItems.AddNew<ControlledWorkItem<MainController>>("MainController");
}
}


//The other WorkItems are loaded on demand (as stated in my original post):
EventPublication(EventTopicNames.MemberCategorySelected, PublicationScope.Global)
public event EventHandler<EventArgs> MemberCategorySelected;
private void MemberSelected()
{
RemoveAllOtherControllerWorkIItems();

if (_workItem.Parent.WorkItems.Contains("MemberController") != true)
_workItem.Parent.WorkItems.AddNew<ControlledWorkItem<MemberController>>("MemberController");

MemberCategorySelected(this, new EventArgs());
}

//I tried to remove existing WorkItems when another WorkItem (Category as stated in my original post) is chosen by the user on the Navigation bar:
// As you see below, I tried to do both a terminate and a dispose -- both throwing exceptions.
// The line when I find the WorkItems and Remove them doesn't throw an exception, but it doesn't remove them either (as shown by the "CAB Viewer" application attached to the Shell).
private void RemoveAllOtherControllerWorkIItems()
{
foreach (System.Collections.Generic.KeyValuePair<string, WorkItem> wiName in _workItem.Parent.WorkItems)
{
if (wiName.Key != "MainController")
{
WorkItem wi = _workItem.Parent.WorkItems.Get(wiName.Key);
_workItem.Parent.WorkItems.Get(wiName.Key).Items.Remove(wiName.Value);
_workItem.Parent.WorkItems.Remove(wi);

//_workItem.Parent.WorkItems.Get(wiName.Key).Dispose();
//_workItem.Parent.WorkItems.Get(wiName.Key).Terminate();
}
}
}
Mar 21, 2006 at 6:47 AM
originally posted by: aadams99

Continuation:

// This is a partial look at teh MemverController (called above):
public class MemberController : WorkItemController, IDisposable
{
SourcesView _sourcesView;
InterestView _interestView;
MemberCategoryView _memCatView;
ActionView _actionView;
BaseUIExtensionToolBar _uiExtensionToolBar = new BaseUIExtensionToolBar();

public override void Run()
{
AddViews();
AddDescriptionToolStrip();
}

private void AddViews()
{
_sourcesView = WorkItem.Items.AddNew<SourcesView>();
_memCatView = WorkItem.Items.AddNew<MemberCategoryView>();
_interestView = WorkItem.Items.AddNew<InterestView>();
_actionView = WorkItem.Items.AddNew<ActionView>();
}

private void AddDescriptionToolStrip()
{
CloseAllOtherDescriptionToolStrip();

BuildUIExtensionToolBar(Properties.Resources.MemberCategory);
this.WorkItem.UIExtensionSitesUIExtensionSiteNames.MainDescription.Add(_uiExtensionToolBar);
}

CommandHandler(CommandNames.ViewMemberSources)
public void ViewMemberSources(object sender, EventArgs e)
{
AddDescriptionToolStrip(Properties.Resources.Member_Sources);
WorkItem.WorkspacesWorkspaceNames.RightWorkspaceTriple.Show(_sourcesView);
}
:
:
~MemberController()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
//don't do anything here yet.
}
}
}
//As stated above -- I'm currently following the BAT methodology
Mar 21, 2006 at 12:43 PM
originally posted by: ForeignerCR

Hi, I'm running into the same problem. I have the same need to get rid of the child workItems of my main workItem before loading some others.
Iterating through the WorkItems collection with a foreach and calling "Terminate" or "Dispose" produces and exception in the next iteration.

The idea of having a WorkItem variable that stores the last active one sounds ok, but since we can have many "last active" child workitems we want to get rid of we'd have to keep an array of them. This sounds like too much work for something that sounds as easy as removing items from a collection that we already have.

Any other idea of how to achieve this?

Thanks in advance.
Mar 22, 2006 at 6:38 AM
originally posted by: ChrisHolmes

I am doing something similiar with a project I'm working on. I have a Payroll WorkItem that can spawn several child WorkItems, each a unique employee from the DB.

How are you all creating your WorkItems? Are you using a unique key that can be saved and referenced? When I generate a Workitem it gets a unique ID, like so:

string key = string.Format("Employee{0}". employeeID);
EmployeeWorkItem workItem = this.WorkItems.AddNew<EmployeeWorkItem>(key);

A navigation sidebar (basically a ListView) also gets the key. Clicking on an Employee's name in the ListView causes the parentWorkItem to fetch the childWorkItem based on the key value. I can close a childWorkItem anytime by getting the WorkItem via the key and calling Terminate()

EmployeeWorkItem workItem = this.WorkItems.Get<EmployeeWorkItem>(key)
workItem.Terminate()

I've tested this by creating and terminating several workItems (many of the same employee) and I never get exceptions.

Some events/commands in my project target the currently active workItem, so I store that workItem's key in a variable in the parentWorkItem for reference. It's only a string, so easy enough to hang onto.


Also note - Terminating the full WorkItem list might cause problems if you have one WorkItem in the collection that shouldn't be killed. You can fetch only a certain type of workItem by calling the FindByType<Type>() method:

ICollection<EmployeeWorkItem> workItems = this.WorkItems.FindByType<EmployeeWorkItem>();
Mar 22, 2006 at 10:18 AM
originally posted by: aadams99

After talking with the architect on this project he suggested the following:
What if I kept the WorkItem in memory, but just found the active View in a WorkItem and removed it from Memory and the Shell Workspace (so it’s no longer seen). And when I needed that View again just add it back to the WorkItem it’s associated with and load it?

Does the View need a custom dispose method?

Is there a 'standard/proper' way to accomplish this? Could I get code for this?
Mar 22, 2006 at 12:52 PM
originally posted by: ChrisHolmes

Why not just use a DeckWorkspace?

If you don't need to dispose of the WorkItem then you don't need to Dispose of the View - you just need to hide it. The DeckWorkspace accomplishes this.

If it is a memory issue - if memory is tight for some reason - then you need to Terminate the WorkItem and the View. If you've given the WorkItem a key name this is easy to do.
Mar 22, 2006 at 1:02 PM
originally posted by: aadams99

Further along this line:

What if we had somekind of dispose on the View that cleaned itself up and removed it from the view of the user (in the shell) and allowed for me to reload it when I need to show it again. What about having this in an Event -- so that only the views that are open (loaded in memory and displayed) get closed. That way I don't have to parse through the WorkItems looking for an active View?

What should I consider with this approach?
Has anyone done this before?
Does this sound reasonable?

Could I get code examples on this?
Mar 22, 2006 at 1:06 PM
originally posted by: aadams99

I agree with you, it would be easier just to keep them loaded and in a DeckWorspace. Actually, we are using DeckWorspaces. I asked the same question to the architect. At this point he wants to do it this way. The reaon behind it I believe is that this is going to be a LARGE application, and we would end up with a HIGH number of views loaded if we went that route.

Thanks for your response -- I need all the help/suggestions I can get.
Mar 22, 2006 at 1:11 PM
originally posted by: ChrisHolmes

If you're anticipating that many views loaded then I suggest terminating the WorkItem (and thus its view) when it's not being used, as long as you don't have to restore it again to the same state.

Giving all of your WorkItems a string ID when you create them will give you a measure of control over the multitude of WorkItems you'll be using. Then Terminating one becomes a cinch, and no looping through collections is necessary.
Mar 22, 2006 at 1:38 PM
originally posted by: aadams99

As i'm sure you notice from the code postings, I am currently giving them a string Id when I create them. And I have code posted that looks for all the WorkItems that are not the "MainController" WorkItem and remove it from the "Parent" WorkItem. The problem is that when I look at the Viewer that shows the objects in memory -- the WorkItems still exists. and If i try a Dispose or Terminate -- i get an exception.
Mar 22, 2006 at 1:44 PM
originally posted by: ChrisHolmes

Are you trying Terminate() without the Remove()?


Terminate should remove it from the Items collection. What exception do you get?
Mar 23, 2006 at 8:15 AM
originally posted by: ChrisHolmes

Ah, that makes sense. An enumerator expects the collection it is enumerating over to be static and not change, hence the exception when a WorkItem is terminated. I was under the assumption you could just iterate over the WorkItem collection with a trusty for-statement, like so:


for(int i = 0; i < WorkItems.Count; i++)
{
WorkItemsi.Terminate();
}


But as I checked it this morning you cannot! They are only accessible via the string key or the enumerator. Bummer.
Mar 23, 2006 at 8:54 AM
originally posted by: ForeignerCR

Ok Guys, I finally worked out my problem, which seems to be the same of the original poster.
My app is a large one, which will load several workitems that must be all terminated before loading a new set of them.

As I said, iterating through the WorkItems collection doing the Terminate() for each item produces a "Collection was modified; enumeration operation may not execute." exception.

So, I came up with this idea while I was sleeping last night (yes, Sleeping! - don't ask :-))
Basically I iterate through the Worktems collection without using the Enumerator to iterate, and I get the Enumerator in each iteration. This way, I can get rid of of the complete collection of Workitems that belong to my MainController.

The code:

System.Collections.Generic.IEnumerator < KeyValuePair < string, WorkItem > > workItem;
while (this.WorkItem.WorkItems.Count > 0)
{
workItem = this.WorkItem.WorkItems.GetEnumerator();
workItem.MoveNext();
workItem.Current.Value.Terminate();
}

Notice that if I move out the GetEnumerator() line to the workItem variable definition, the mentioned exception happens again.

I don't know what the problem is, but well, this does the trick for me.

Maybe it can be of some help for you.

Cheers.
Mar 24, 2006 at 5:32 AM
originally posted by: aadams99

Thanks for the feedback! -- I'll give your idea a shot.
Mar 24, 2006 at 10:20 AM
originally posted by: aadams99

private void RemoveAllOtherControllerWorkIItems()
{
int workItemCount = _workItem.Parent.WorkItems.Count;
System.Collections.Generic.IEnumerator<KeyValuePair<string, WorkItem>> workItem;
while (workItemCount > 0)
{
workItem = _workItem.Parent.WorkItems.GetEnumerator();
workItem.MoveNext();
if (workItem.Current.Key != "MainController")
workItem.Current.Value.Terminate();
workItemCount--;
}
}

When I iterate over the _workItem.Parent.WorkItems collection in this function, even though I know that there is more than one WorkItem, the GetEnumerator method only sends back the "first" WorkItem (the MainController WorkItem). It's not walking to the next WorkItem in the collection. I'm I missing something here?
Mar 24, 2006 at 12:10 PM
originally posted by: ChrisHolmes

I may be missing something really obvious here, so smack me over the head if I am, BUT - it seems like you're fetching the parent WorkItem of the current WorkItem and then trying to Terminate it's children (of which this workItem would then be one). Wouldn't that cause a problem when, in essence, it tries to Terminate itself?

I am seeing this hierarchy of WorkItems:


MainWorkItem
ChildWorkItem1
ChildWorkItem2
ChildWorkItem3
ChildWorkItem4 (you are calling Terminate from here?)

I would think you'd want the logic for terminating child WorkItems in the parent WorkItem, not in one of the children. Unless, as I said, there's something really obvious I'm missing. It's late in the day =)
Mar 24, 2006 at 1:01 PM
originally posted by: aadams99

My fault, from my explanation it seems like this is the case.

When the "Module" loads from the Init file it's called like so:
public class ConfigurationModuleInit : ModuleInit
{
private WorkItem _rootWorkItem;

InjectionConstructor
public ConfigurationModuleInit(ServiceDependency WorkItem rootWorkItem)
{
_rootWorkItem = rootWorkItem;
}

public override void Load()
{
base.Load();
_rootWorkItem.WorkItems.AddNew<ControlledWorkItem<MainController>>("MainController");
}
}
At this point the only 'WorkItem' that is loaded is the named WorkItem "MainController"

From now on all the 'WorkItems' are loaded/unloaded on demand with the exception of the "MainController" which is not a controller but a 'WorkItem' (it is done this way in the BAT project).

Now when from the Navigation bar -- the user selects other categories and a corresponding 'WorkItem' is loaded -- but just before this I want to unload any other 'WorkItem' that may have been loaded (with the excpetion of the "MainController" which olds the NavigationBar View -- I need this to stay active. But when I call this function we have been talking about earlier -- I'm calling it from within a view that is loaded from the "MainController" -- so -- your right in a sense -- because the _rootWorkItem that is loaded in the init is the "Parent" WorkItem -- and stays active -- but the "MainController" WorkItem (which is where all these termination calls are made) never will get terminated -- so it should be ok.

Thanks for your response by the way -- observations like that are the only way I will figure out what is going on.

I got the Terminate() to work with the following:
int workItemCount = _workItem.Parent.WorkItems.Count;
System.Collections.Generic.IEnumerator<KeyValuePair<string, WorkItem>> workItem;
workItem = _workItem.Parent.WorkItems.GetEnumerator();
while (workItemCount > 0)
{
workItem.MoveNext();
if (workItem.Current.Key != "MainController" && workItem.Current.Key != null)
workItem.Current.Value.Terminate();
workItemCount--;
}
... And when I look at the module in the Viewer the 'WorkItem' now looses its ID and its Status changes to Terminated. BUT it's still there -- and it I look at its SmartParts collection -- I still see the views.
Mar 24, 2006 at 1:06 PM
originally posted by: aadams99

By the way -- thanks ForeignerCR for your code snipplet. It seems i'm almost there.
Mar 24, 2006 at 1:17 PM
originally posted by: Sankar74

More on the code i just posted...

I added the New Lines of code to the code posted just above:

int workItemCount = _workItem.Parent.WorkItems.Count;
System.Collections.Generic.IEnumerator<KeyValuePair<string, WorkItem>> workItem;
workItem = _workItem.Parent.WorkItems.GetEnumerator();
while (workItemCount > 0)
{
workItem.MoveNext();
if (workItem.Current.Key != "MainController"
&& workItem.Current.Key != null)
{
_workItem.Parent.WorkItems.Get(workItem.Current.Key).Items.Remove(workItem.Current.Value); // NEW LINE!!!
workItem.Current.Value.Terminate();
workItem.Current.Value.Dispose(); //NEW LINE!!!
}
workItemCount--;
}

The Remove and Terminate don't change anything about the now Terminated WorkItem -- I can still see it in the Viewer --

What gives?
Mar 24, 2006 at 1:29 PM
originally posted by: ChrisHolmes

So lemme get this straight - you can Terminate the WorkItem, but the View associated with it is still active in the WorkSpace?
Mar 24, 2006 at 1:38 PM
originally posted by: Sankar74

Well -- I have that CAB Visualizer attached to the Shell.

After I Terminate the WorkItem:
1) I can still see it in the CAB Visualizer
2) Its ID changes in the CAB Visualizer from some ID to blank
3) the Status changes in the CAB Visualizer from Inactive (I'm not activating the WorkItem -- it still functions properly without doing this -- the BAT project never did this so..) to Terminated.
4) The State in the CAB Visualizer is now nothing
5) When I click on the SmartParts item collection in the CAB Visualizer - I can still see the Views

I'm assuming all this adds up to:
The WorkItem is now Terminated, but still resident in memory, and the Views associated with this WorkItem are still in memory?
Mar 24, 2006 at 2:06 PM
originally posted by: ChrisHolmes

Could this be because in .NET the Garbage collector has not deemed it necessary to collect that memory yet, so that is why you are seeing it in the Visualizer?
Mar 24, 2006 at 2:10 PM
originally posted by: BradWilsonMSFT

You may be seeing a bug in the visualization. Some people have reported that the WorkItem visualization accidentally holds a strong reference to WorkItems and keeps them from disappearing.
Mar 25, 2006 at 10:05 AM
originally posted by: aadams99

I don't think this is the case. I have overwritten the Dispose() in all the WorkItems. When I call the terminate for that WorkItem using the code posted above, the breakpoints in these WorkItems located in the Dispose methods never run. They only get called when I shut the application down. -- So basically the WorkItems never get disposed for some reason.
Mar 25, 2006 at 11:04 AM
originally posted by: BradWilsonMSFT

I have a simple repro that seems to show everything is working as expected. The output is:

26 > csc test.cs /r:Microsoft.Practices.CompositeUI.dll /r:Microsoft.Practices.ObjectBuilder.dll
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.62
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

27 > .\test.exe
Pre creation, WorkItems count = 0
Post creation, WorkItems count = 1
In Dispose()
Post Terminate(), WorkItems count = 0

The source is:

using System;
using Microsoft.Practices.CompositeUI;

public class Test : CabApplication<WorkItem>
{
public static void Main()
{
new Test().Run();
}

protected override void Start()
{
Console.WriteLine("Pre creation, WorkItems count = {0}", RootWorkItem.WorkItems.Count);
MyWorkItem mwi = RootWorkItem.WorkItems.AddNew<MyWorkItem>();
Console.WriteLine("Post creation, WorkItems count = {0}", RootWorkItem.WorkItems.Count);
mwi.Terminate();
Console.WriteLine("Post Terminate(), WorkItems count = {0}", RootWorkItem.WorkItems.Count);
}
}

public class MyWorkItem : WorkItem
{
protected override void Dispose(bool disposing)
{
Console.WriteLine("In Dispose()");
base.Dispose(disposing);
}
}
Mar 25, 2006 at 12:42 PM
originally posted by: aadams99

Ok -- found the problem -- I was inheriting from WorkItemController but not IDisposable in the WorkItem. Once I added the interface, when I terminated the WorkItem -- it went throught its dispose method.

The CAB Visualizer still shows the WorkItem/Views, but I'll just have to assume that there is a bug in this application.

THANKS FOR EVERYONES HELP!!!!!

Now -- it should be just as straight forward to remove a 'View' SmartPart from 'WorkItem' right?

I'm going to look into that next -- any code would be appreciated.
Mar 26, 2006 at 9:23 AM
originally posted by: BradWilson

Yes, simply call WorkItem.Items.Remove(view) and it will be removed.

When you remove something from a WorkItem by hand, you are telling it that you are taking control of the lifetime of the thing, so when you remove an item from a WorkItem and you're done with it, you should dispose it yourself. Do the code might look like:

workItem.Items.Remove(view);
view.Dispose();

Good luck!