Removing Terminated (and disposed) WorkItems

Topics: CAB & Smart Client Software Factory
Jun 22, 2006 at 2:58 PM
originally posted by: sgro

I noticed that there was one other question reagarding this topic, but could not find an answer. So...

I would like to be able to remove terminated and disposed WorkItems, but they are not actually removed from the collections when I use WorkItems.Remove(). It seems as I create new WorkItems and complete their use-case, they simply pile up, in terminated state, in the various collections in which they exist.

What needs to be done to permit this to happen? Is there some reason that it should not? What am I missing?
Jun 24, 2006 at 11:39 AM
originally posted by: DLorenz

You should just have to call Terminate() on a WorkItem in order to remove that WorkItem from its Parent's collection... This bug was fixed in the final releaes of CAB, I think.
Jul 6, 2006 at 10:03 AM
originally posted by: sgro

Thanks for responding.

I am using the December 2005 releaase of the CAB.

I have been doing a Terminate() on the WorkItem before attempting to remove. While it is true that this prevents throwing an exception, it does not actually remove it from the list. If you traverse the parent work item list, you will find it is still there in a terminated state.

It seems that some dictionary is checked for the existence of the WorkItem, but its existence in that dictionary is removed when the WorkItem is terminated. Subsequently, when WorkItems.Remove() is called, that dictionary is consulted and the WorkItem is found to be absent. As a result, the list is actually not modified, leaving the WorkItem where it is in the list.
Jul 15, 2006 at 8:48 AM
originally posted by: glenmaroney

Further to this. If you try to dynamically load the same work item again it seems that it already exists so the Load method never fires again and the WorkItem never gets re-loaded.

Anyone got any ideas how to get around this?
Feb 21, 2007 at 10:06 PM
Hello All,

Any workaround for this issue?

Cheers,
Feb 22, 2007 at 1:39 AM
Edited Feb 22, 2007 at 1:57 AM
// I have not tried this but I read somewhere to do this
protected override void CloseView()
{
base.CloseView();
WorkItem.Terminate();
// As we are closing and re-creating this view several times,
//we need to Dispose the view
if (View is IDisposable) ((IDisposable)View).Dispose();
}

This also involves adding the iDisposable interface to your view.

I must also add I have found very good reasons not to Terminate and dispose of the view like this. I prefer to just keeping reusing it.
Mar 24, 2007 at 12:43 PM

Allan wrote:
// I have not tried this but I read somewhere to do this
protected override void CloseView()
{
base.CloseView();
WorkItem.Terminate();
// As we are closing and re-creating this view several times,
//we need to Dispose the view
if (View is IDisposable) ((IDisposable)View).Dispose();
}

This also involves adding the iDisposable interface to your view.

I must also add I have found very good reasons not to Terminate and dispose of the view like this. I prefer to just keeping reusing it.



I agree, what if the view is in the rootworkitem, I can't/shouldn't terminate that. How do you go about reusing the existing view. Now take for example, I have a tabworkspace in a details view. and it does the same thing. Is there a way I can reuse it too? Because once I open it, and close it, and try to open it once again. The main details view loads and works normally but the embedded tabworkspace shows nothing.
Mar 24, 2007 at 3:30 PM
Edited Mar 27, 2007 at 11:05 AM

gdngenericuser wrote:
originally posted by: glenmaroney

Further to this. If you try to dynamically load the same work item again it seems that it already exists so the Load method never fires again and the WorkItem never gets re-loaded.

Anyone got any ideas how to get around this?


My solution may be related, or loosely related to your issue. I have a MDI application and I wanted to ensure a form once loaded could not be loaded again. The problem I encountered was that once the form was closed - you could not open it again because my view still existed. If I attempted to Show this existing view I got a blank form.

The problem is that the presenter is removed, but it's view is left dangling. My workaround was to subscribed to the WorkItems.Items.Removed event (this event fires before any objects are actually removed) and in the Items_Removed() handler check to see if it is my presenter - if it is I manually remove the view.
private void AddViews()
{
    WorkItem.Items.Removed += new EventHandler<DataEventArgs<object>>(*Items_Removed*);
}
 
[CommandHandler(CommandNames.ViewTeam)]
public void ViewTeamHandler(object sender, EventArgs e)
{
    TeamView teamView = WorkItem.Items.Get<TeamView>("TeamView");
    // Only display view if not already loaded
    if (teamView == null) { 
        teamView = WorkItem.Items.AddNew<TeamView>("TeamView");
        IWorkspace mainWorkspace = WorkItem.Workspaces["mainWorkspace"];
        mainWorkspace.Show(teamView);
    }
}
 
void Items_Removed(object sender, DataEventArgs<object> e)
{
    if (e.Data is TeamViewPresenter)
        WorkItem.Items.Remove( WorkItem.Items.Get<TeamView>("TeamView"));
}
Mar 25, 2007 at 9:31 PM
Edited Mar 25, 2007 at 9:32 PM
nuchild,

I create all my view as follows:

In the ModuleController or the presenter Class that wants to display/open a view I put
private void ShowReportView()
        {
            const string workItemID = "ReportViewController";
            ControlledWorkItem<ReportViewController> workItem;
            if (!this.WorkItem.WorkItems.Contains(workItemID))
            {
                workItem = this.WorkItem.WorkItems.AddNew<ControlledWorkItem<ReportViewController>>(workItemID);
                workItem.Controller.Run();
            }
            else
            {
                workItem = WorkItem.WorkItems[workItemID] as ControlledWorkItem<ReportViewController>;
            }
            workItem.Controller.Activate();
        }

And then I also for every view I have a WorkItemController

class ReportViewController : WorkItemController
    {
        private ReportView reportView = null;
 
        public override void Run()
        {
            base.Run();
            if (reportView == null)
                reportView = WorkItem.SmartParts.AddNew<ReportView>();
        }
 
        public void Activate()
        {
            WorkItem.Workspaces[Constants.WorkspaceNames.RightWorkspace].Show(reportView);
        }
    }

And that’s all that required. The code above works out if the view exists and if it does reuses it otherwise it creates if for the first time.
Mar 27, 2007 at 12:05 PM

Allan wrote:
nuchild,

I create all my view as follows:
...
...
And that’s all that required. The code above works out if the view exists and if it does reuses it otherwise it creates if for the first time.



Very nice Allan! Didn't see that one in the tutorials, HOWEVER - I'm still having the same problem - please reference the following link:
http://www.global-webnet.net/CodePlex/ControllerTest.htm

I only execute the .Run() line once - upon load. After that it always runs the else statement - I wont' be able to debug why until later this evening but if it is consistent I will find that when I closed my form my presenter was released however the view (workItemId) still remains. It appears I'll still have to manually remove the workItemId object reference....

        [CommandHandler(CommandNames.ViewTeam)]
        public void ViewTeamHandler(object sender, EventArgs e)
        {
            const string workItemId = "TeamView";
            ControlledWorkItem<TeamContactViewController> workItem;
 
            if (!this.WorkItem.WorkItems.Contains(workItemId))
            {
                workItem = WorkItem.WorkItems.AddNew<ControlledWorkItem<TeamContactViewController>>(workItemId);
                workItem.Controller.Run();
            }
            else
            {
                workItem = WorkItem.WorkItems[workItemId] as ControlledWorkItem<TeamContactViewController>;
            }
            workItem.Controller.Activate();
        }
 

    public class TeamContactViewController : WorkItemController
    {
        private TeamContactView teamContactView = null;
 
        public override void Run()
        {
            base.Run();
            if (teamContactView == null)
                teamContactView = WorkItem.SmartParts.AddNew<TeamContactView>();
        }
        public void Activate()
        {
            IWorkspace mainWorkspace = WorkItem.Workspaces["mainWorkspace"];
            WindowSmartPartInfo formInfo = new WindowSmartPartInfo();
            formInfo.Title = "Team Contact Manager";
 
            // In case form was minimized - restore to normal
            formInfo.WindowState = FormWindowState.Normal;
            WorkItem.Workspaces["mainWorkspace"].Show(teamContactView,formInfo);
        }
    }
Mar 27, 2007 at 10:05 PM
Bill,

Yes the solution as I presented it does only execute Run() once.
That is because I am not destroying the view, but instead reusing the same view each time.

I’m guessing here, but what might be happening is this….
You are most probably populating your view on OnViewReady()

The first time your view works as expected.

The second time you where only restoring the minimised view, so all is well.

The third time when you created the view, it did NOT call OnViewReady(), where I am guessing you are populating the view with data……Change your logic there and it will work. ( I overcame the problem with sending parameters to my controller, which passed them onto the view each time…which I admit is not the correct way, but it solved the problem at the time)

>> It appears I'll still have to manually remove the workItemId object reference....
That’s the call you have to make.. Either keep reusing the view or destroy it and create again each time.

Are you using the iDisposable interface which I made mention of on Feb 22 (above) and if so what is the code you are using to destroy the view?
Mar 28, 2007 at 2:41 PM
Edited Nov 8, 2007 at 12:19 PM

what is the code you are using to destroy the view?
...
You are most probably populating your view on OnViewReady()
...


Hi Allan,

In my research I have found that the WindowWorkSpace has legacy code which is being utilized (at least by the MdiWorkspace which is derived from it). I have to go out of town this evening but upon my return I will fix the problem at the source. I do have a good workaround (below) that is cleaner than subscribing to the events.

Dispose method of the View (teamContactView.Designer.cs)
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_presenter != null)
                {
                    _presenter.OnCloseView(); // <<<<<<<<======= Workaround
                    _presenter.Dispose();
                }
 
                if (components != null)
                    components.Dispose();
            }
 
            base.Dispose(disposing);
        }

Presenter code follows
    public class TeamContactViewPresenter : Presenter<ITeamContactView>
    {
        private TeamService _teamContactService;
        [ServiceDependency]
        private TeamService TeamContactService
        {
            set { _teamContactService = value; }
        }
 
        /// <summary>
        /// Close the view
        /// </summary>
        public void OnCloseView()
        {
            TeamContactView teamContactView = 
                WorkItem.RootWorkItem.SmartParts.Get<TeamContactView>("TeamContactView");
 
            if (teamContactView != null)
                WorkItem.RootWorkItem.SmartParts.Remove(teamContactView);
 
            base.CloseView();
        }
 
        public List<TeamContact> GetTeamContacts()
        {
            return _teamContactService.GetTeamContacts(this, new TeamServiceEventArgs());
        }
 
    }

As to the question on how I am loading my data
    [SmartPart]
    public partial class TeamContactView : UserControl, ITeamContactView
    {
        private TeamService _teamService;
        [ServiceDependency]
        public TeamService TeamService { set { _teamService = value; } }
 
        public TeamContactView(){
            InitializeComponent();
        }
 
        /// <summary>
        /// Sets the presenter. The dependency injection system will automatically
        /// create a new presenter for you.
        /// </summary>
        [CreateNew]
        public TeamContactViewPresenter Presenter
        {
            set
            {
                _presenter = value;
                _presenter.View = this;
            }
        }
 
        protected override void OnLoad(EventArgs e)
        {
            _presenter.OnViewReady();
 
            Dock = DockStyle.Fill;
            dgvTeamContacts.DataSource = _teamService.GetTeamContacts(
                this, new TeamServiceEventArgs());
        }
    }
Mar 29, 2007 at 12:04 AM
Bill

On your OnCloseView() for the TeamContactPresenter you write:

...
           TeamContactView teamContactView = 
                WorkItem.RootWorkItem.SmartParts.Get<TeamContactView>("TeamContactView");
 
            if (teamContactView != null)
                WorkItem.RootWorkItem.SmartParts.Remove(teamContactView);
...

Why can you not just simply write?
 
	If (View != null)
	 	WorkItem.RootworkItem.SmartParts.Remove(View);

What I’m asking is isn’t your view the TeamContactView object?


Loading Data
Well my theory was kinda right. You are using OnLoad() which is only being called once. But I suspect you have the resolved the issue now that you are disposing of the View.
Mar 29, 2007 at 1:28 AM

Allan wrote:
Bill

Why can you not just simply write?
 
	If (View != null)
	 	WorkItem.RootworkItem.SmartParts.Remove(View);

What I’m asking is isn’t your view the TeamContactView object?

Loading Data
Well my theory was kinda right. You are using OnLoad() which is only being called once. But I suspect you have the resolved the issue now that you are disposing of the View.



Because I'm new at this ;) Thanks for taking the time to help me - I learned a lot and my code will be a lot cleaner now!

Allan Well my theory was kinda right.

Your theory was right. What I found as I debugged the code was that the views components successfully disposed - at a point where it was supposed to remove the view from the smartparts collection it was an empty form! Since it wasn't removed from the smartparts and its reference was still there it loaded all that remained - the empty form.

My workaround basically complies with the intent of the baseclass process so when the dust settles the code you started me with will work all of the time.

Aug 30, 2007 at 1:31 PM
I am using 2007 may releas of the cab and I can't get he removed event to fire. Is it realy true that they have fixed this bug?
Aug 31, 2007 at 3:48 AM
ckwik I am using 2007 may releas of the cab and I can't get he removed event to fire. Is it realy true that they have fixed this bug?


The last release I worked with (sorry can't remember if it was may) still had the issue... I have since abandoned the SCSF, as it will not be supported in the future. I am now learning Acropolis and SharePoint (not to mention Silverlight). Disappointing as I really enjoyed working with SCSF - problem is that by the time I completed my application, or any application, it would be obsolete....

Good luck!
Aug 31, 2007 at 6:43 AM
Thx for the input. I have missed that part that they will not support the SCSF, where did you hear that? Lol, you I like to get a framework that will stay supported for some time too. Trying to start to read up about Acropolis...
Aug 31, 2007 at 10:42 PM
Note: On the "releases" tab there is a "Proposed" work item labeled "WPF C# View is not being disposed correctly" - looks like our problem still exists (to answer your question :)

cjwik I have missed that part that they will not support the SCSF, where did you hear that?

Unfortunately at what appears to be a very reliable source. Unfortunate because I had just spent months getting it under my belt, really was impressed by it (had work-arounds for the issues) and was ready to start development. In my research to see if Acropolis should be a candidate for the application I stumbled upon the following....

For your convenience I provide an excerpt of the following link:

http://blogs.msdn.com/kathykam/archive/2007/06/06/acropolis-vs-scsf-cab-faq.aspx

Acropolis vs SCSF/CAB FAQ
Glenn Block from the Patterns and Practices team (the guys who made CAB) blogged about some common asked questions relating to Acropolis and their project.

Check it out here: http://blogs.msdn.com/gblock/archive/2007/06/06/acropolis-the-future-of-smart-client.aspx

Here is a quick snippet of the most common question asked:

What happens to SCSF when Acropolis ships? Will p&p still support it?

With the announcement of Acropolis, we currently have no further plans for SCSF releases. That being said, our customers should rest assured that we are not dropping support for SCSF. We will continue to support the forums, provide fixes and assist customers in their implementations. Additionally the newly launched SCSFContrib project is an ongoing community effort to extend CAB/SCSF which will continue. We will continue to look at ways to help customers build smart client applications including providing pure WPF guidance as well as guidance for building Acropolis applications.

I have an existing SCSF / CAB applicaiton, will you help me migrate to Acropolis?

Another yes. We will be working with David Hill and the Acropolis team to create a migration path for existing CAB/SCSF customers. This will include looking at hosting existing CAB components in Acropolis as well as prescriptive guidance. We are committed to helping our customers make this transiton.

Sep 1, 2007 at 3:08 AM

cjwik wrote:
Thx for the input. I have missed that part that they will not support the SCSF, where did you hear that?


It's not a complete drop in support. They are simply not planning any future releases for the framework. Quote from Glenn's blog:

http://blogs.msdn.com/gblock/archive/2007/06/06/acropolis-the-future-of-smart-client.aspx


What happens to SCSF when Acropolis ships? Will p&p still support it?

With the announcement of Acropolis, we currently have no further plans for SCSF releases. That being said, our customers should rest assured that we are not dropping support for SCSF. We will continue to support the forums, provide fixes and assist customers in their implementations. Additionally the newly launched SCSFContrib project is an ongoing community effort to extend CAB/SCSF which will continue. We will continue to look at ways to help customers build smart client applications including providing pure WPF guidance as well as guidance for building Acropolis applications.


The other thing to note is that CAB is completely open source; you have the source code, you can alter it if it doesn't meet your needs. I understand the desire to have "official" libraries from Microsoft, but this is not closed source code. You can change it. And the CAB isn't tough to alter. Where I work we've made modifications (about two is all) as well as written extensions to the CAB to bend it to our will. For instance, we found a bug in the WindowWorkspace so we just changed the source code. Bug gone.

If the CAB is what you want to work with then use it. We've invested the time and really like the framework for what it does. I think abandoning it now to try and use Acropolis instead would be wasteful (especially for us, because our code isn't going to need to see Vista for quite some time). I'd say the CAB is 99% fine. The 1& bugs can be fixed pretty easy with a dollop of code from your own hands. To me, that's worth it for the productivity I get out of the framework.


Sep 1, 2007 at 3:17 PM
ChrisHolmes It's not a complete drop in support. They are simply not planning any future releases for the framework.

In addition they are providing migration paths (for the sake of those that are already embedded in SCSF). My delimna (and disappointment as SCSF was/is a great foundation) was that I can't afford to spend six months (or longer) writing an application only to have to rewrite it (or migrate it) as soon as it is completed; I still have .NET 1.1 applications that have to be converted or rewritten - I don't want to add to the list.... So I'll extend the architecture period and use the time it takes Acropolis and Silverlight to stabilize to learn best practices and patterns for them.
Sep 7, 2007 at 8:45 AM
Edited Sep 7, 2007 at 8:49 AM
Found following workaround in re-using existing smartparts:

...
RootWorkItem.Items.Add(new MdiWorkspace(Shell), WorkspaceNames.ContentWorkspace);
((MdiWorkspace) RootWorkItem.ItemsWorkspaceNames.ContentWorkspace).SmartPartClosing += new EventHandler<WorkspaceCancelEventArgs>(ShellApplication_SmartPartClosing);
...
void ShellApplication_SmartPartClosing(object sender, Microsoft.Practices.CompositeUI.SmartParts.WorkspaceCancelEventArgs e)
{
((IWorkspace) RootWorkItem.WorkspacesWorkspaceNames.ContentWorkspace).Hide(e.SmartPart);
e.Cancel = true;
}

In other words make workspace hiding smartparts instead of disposing them. Seems to work for me. Your thoughts?
Sep 7, 2007 at 6:23 PM
DeLight In other words make workspace hiding smartparts instead of disposing them. Seems to work for me. Your thoughts?

I trust there could be arguments on boths sides so I'll simply share a recent experience - "GDI+ error"; seemed to only happen in production. If you Google it you'll find what I did - I was not alone and there were no easy answers. After weeks of research it turned out that it was caused by a user control that was assigning an image to it; this control was used by many pages and as those pages closed the memory allocated for the image was not release - like a memory leak the system just stole more and more resources as the application was used. Since the production machines were operating in 512k and the users were in the system all day we would have users that would get a big red X where a component should be; they had to exit the application and re-enter it to use the application again. Very hard to track down, isolate and repair. In the end it was the Task Manager and debugger that assisted us (we activated the GDI object column) and watched the numbers grow and never release.

What this lesson taught me is that, like SQL Server Connections, it is best to develop for scalability, extensibility as well as reusability - to do so means to make no assumptions (it could end up in an enterprise level app with hundreds of views) and to dispose resources when your done.

This workaround solution probably will work great for those with small projects but I would exercise caution about making this a "blanket statement" easy fix as it could send unwary developers down a road that could cause them headaches when their enterprise level applications hit production.