I'm trying to use both AvalonDock 2.0 (MVVM-compliant) and Caliburn Micro in my WPF application. All works fine, except for a couple of issues connected with closing document panes or hiding tool panes.
My main viewmodel derives from Conductor<IScreen>.Collection.OneActive
and exposes two BindableCollection
's of Screen
-derived viewmodels for Tools
and Documents
; the corresponding relevant XAML is like:
<xcad:DockingManager Grid.Row="1"
AnchorablesSource="{Binding Path=Tools}"
DocumentsSource="{Binding Path=Documents}"
ActiveContent="{Binding Path=ActiveItem, Mode=TwoWay}">
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
<Setter Property="Title" Value="{Binding Model.DisplayName}" />
</Style>
</xcad:DockingManager.LayoutItemContainerStyle>
<xcad:DockingManager.LayoutItemTemplateSelector>
<views:AutobinderTemplateSelector>
<views:AutobinderTemplateSelector.Template>
<DataTemplate>
<ContentControl cal:View.Model="{Binding . }" IsTabStop="False" />
</DataTemplate>
</views:AutobinderTemplateSelector.Template>
</views:AutobinderTemplateSelector>
</xcad:DockingManager.LayoutItemTemplateSelector>
<xcad:LayoutRoot>
<xcad:LayoutPanel Orientation="Horizontal">
<xcad:LayoutAnchorablePane DockHeight="150" DockMinWidth="200">
</xcad:LayoutAnchorablePane>
<xcad:LayoutDocumentPane/>
</xcad:LayoutPanel>
</xcad:LayoutRoot>
</xcad:DockingManager>
The template selector is as simple as that:
public class AutobinderTemplateSelector : DataTemplateSelector
{
public DataTemplate Template { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return Template;
}
}
1. Closing Documents
The first issue comes when handling document-pane close. AD has its document handling mechanism, which should be synchronized with CM's one. CM is based on a screen conductor; when a screen needs to be closed, the method TryClose
is used to close it if possible (i.e. unless a guard method tells the framework that the screen cannot be closed, e.g. because the document is dirty). To let AD play with CM I'm using a workaround similar to that described in Prevent document from closing in DockingManager, where the main view code directly calls this method handling the docking manager closing event: when AD is closing the document, call the underlying VM guard method and cancel if required; if not cancelled, then AD goes on closing thus firing the DocumentClosed
event.
To see if this could work, I first created a public TryClose
method in the document viewmodel base, essentially duplicating the code in the CM TryClose
override, like (IsDirty
is a protected virtual method overridden by descendant viewmodels):
public bool CanClose()
{
if (!IsDirty()) return true;
MessageBoxAction prompt = new MessageBoxAction
{ ...prompt message here... };
bool bResult = true;
prompt.Completed += (sender, args) =>
{
bResult = prompt.Result == MessageBoxResult.Yes;
};
prompt.Execute(null);
return bResult;
}
This is the method called by the main view code behind in the handlers for AD document closing and document closed:
private void OnDocumentClosing(object sender, DocumentClosingEventArgs e)
{
DocumentBase doc = e.Document.Content as DocumentBase;
if (doc == null) return;
e.Cancel = !doc.CanClose();
}
private void OnDocumentClosed(object sender, DocumentClosedEventArgs e)
{
DocumentBase editor = e.Document.Content as DocumentBase;
if (doc != null) doc.TryClose();
}
Note that I cannot directly call TryClose
in OnDocumentClosing
, as this would cause null object reference errors in AD. This is really ugly but it works. I can now close documents and my guard methods are called appropriately before proceeding. Anyway, it would be nice to get suggestions for a less hacky solution here.
2. Hiding Tools
Another issue arises from hiding the tools panes. In this case, AD should just hide them. The AD control visibility can be bound to an IsVisible
boolean property in my viewmodels implementing tool panes, using a BooleanToVisibility
converter. To this end I just add the binding in the XAML:
<xcad:DockingManager.LayoutItemContainerStyle>
<Style TargetType="{x:Type xcad:LayoutItem}">
...
<Setter Property="Visibility"
Value="{Binding Model.IsVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityCvt}}"/>
...
Now, if I hide a tool pane by clicking on its X button, I can see that my VM IsVisible
property is set to false
as expected, and the pane is hidden. Then, if I programmatically set this property back to true
the pane is not shown. Even restoring the layout does not work: I can see that when the application starts and the object corresponding to the hidden VM is being added to the Tools
collection its IsVisible
is already false
. To have it back, I must set this to true
and then restore the layout. If I miss any of the two steps, the pane remains hidden. Clearly I'm not following the intended implementation strategy here. Could anyone point me in the right direction?