Coproject - a RIA Caliburn.Micro demo, part 14

by Augustin Šulc

In the last part, we created LazyScreen class so that we can load our view models into memory only when they are requested and not right after the application start. In this part, I want to create LazyConductor – a conductor that will support closing of child LazyScreens and then opening new ones instead of removing them from its Items collection.

Please note that this is just a simple demo of an idea I had about lazy screens/conductors and using it in production might need additional code. But if you use it, please let me know (I plan to use it on a real project too, so this post might be updated later according to my experience).

LazyConductor

In the Framework folder, create a new file called LazyConductorWithCollectionOneActive.cs and put the following into it:

public partial class LazyConductor<TScreen, TMetadata>
{
	public partial class Collection
	{
		public class OneActive : Conductor<LazyScreen<TScreen, TMetadata>>.Collection.OneActive
		{
		}
	}
}

From now, every time I refer to LazyConductor, I mean the OneActive class.

Since the difference between the original conductor and our lazy conductor is in the way they close their children, we will override functions dealing with closing/deactivating stuff. When a child is closed, the original conductor removes it from its children collection. Lazy conductor should only call Reset() on the child and activate another one.

Add overriden DeactivateItem function:

public override void DeactivateItem(LazyScreen<TScreen, TMetadata> item, bool close)
{
	if (item == null)
	{
		return;
	}

	if (close)
	{
		CloseStrategy.Execute(new[] { item }, (canClose, closable) =>
			{
				if (canClose)
				{
					CloseItemCore(item);
				}
			});
	}
	else
	{
		ScreenExtensions.TryDeactivate(item, false);
	}
}

If you compare it to Caliburn.Micro source code, you will notice that there is no change. The reason for that is that we just need to alter CloseItemCore function and since it is private, there is no other way.

private void CloseItemCore(LazyScreen<TScreen, TMetadata> item)
{
	if (item.Equals(ActiveItem))
	{
		var next = DetermineNextItemToActivate(item);
		ChangeActiveItem(next, true);
	}
	else
	{
		ScreenExtensions.TryDeactivate(item, true);
	}

	item.Reset();
}

The last line is why we did that – original conductor said ‘Items.Remove(item)’. To make the solution build again, we must add one more function:

protected LazyScreen<TScreen, TMetadata> DetermineNextItemToActivate(
	LazyScreen<TScreen, TMetadata> currentItem)
{
	var next = Items.FirstOrDefault(x => x != currentItem && x.IsScreenCreated);
	return next;
}

Finally add these functions:

public override void CanClose(Action<bool> callback)
{
	var openedItems = Items.Where(x => x.IsScreenCreated);
	CloseStrategy.Execute(openedItems, (canClose, closable) =>
		{
			closable.Apply(CloseItemCore);
			callback(canClose);
		});
}

protected override LazyScreen<TScreen, TMetadata> EnsureItem(LazyScreen<TScreen, TMetadata> newItem)
{
	var node = newItem as IChild;
	if (node != null && node.Parent != this)
	{
		node.Parent = this;
	}

	return newItem;
}

The first line of CanClose is quite important here – we want to check only already opened children.

ShellViewModel

To use our new lazy conductor, update definition of ShellViewModel as follows:

public class ShellViewModel : LazyConductor<IModule, IModuleMetadata>.Collection.OneActive, IShell

Finally, I want to add the possibility to close opened modules. Add this to ShellViewModel:

public bool CanCloseActiveItem
{
	get
	{
		return ActiveItem != null;
	}
}

public void CloseActiveItem()
{
	DeactivateItem(ActiveItem, true);
}

To get the guard property updated, put this code into the class constructor:

this.PropertyChanged += (s, e) =>
	{
		if (e.PropertyName == "ActiveItem")
		{
			NotifyOfPropertyChange(() => CanCloseActiveItem);

		}
	};

ShellView

The last thing to do is to add a close button to ShellView. Open ShellView, add this definition into its beginning:

xmlns:local="clr-namespace:Coproject.Controls"

and put this control to the end of the LayoutRoot grid:

<local:ImageButton x:Name="CloseActiveItem" ImageName="Close" ToolTipService.ToolTip="Close current module"
					Margin="20" HorizontalAlignment="Right" VerticalAlignment="Top" />

And we are done! Open To Do module, edit an item and then try to close the whole module – you will see a warning that you have unsaved changes in the module.

image

Tags: Caliburn, Ria, Coproject, Silverlight

5 Comments

  • Andy White said

    I just read through your series of articles, and have to say this is one of the best walkthroughs on a wide range of complex technologies I've seen. Thanks for putting this together, I've learned a lot reading through it and going through your example. Looking forward to more articles in the future if you continue the series!

  • Lucky said

    Keep up the good work ! I hope to see more ! Keep writing.

  • Jitendra said

    I have just started with Caliburn Micro and Silverlight and this article is really useful.

  • Leo said

    Hey,

    Just a quick question with your implementation of LazyConductor, does this solve the problem when resetting a view model that has a backing properties mapped to a Model and a simple backing properties? So If I try to close an active item, edit changes, hit close and open again the item will it reset the Model and the normal backing properties to its original values not with the updated one like I did not click save but the view still shows the updated value. So it is not yet persisted.

    Thanks!

  • Augustin Šulc said

    When closing a child of a LazyConductor, it only calls Reset() function on the child LazyScreen. So the main logic is in LazyScreen. When you open the child again, the LazyScreen's view model gets recreated so you should see the original values (provided you use NonShared CreationPolicy so that MEF creates a new instance for you).

Add a Comment