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.