0

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?

Community
  • 1
  • 1
Naftis
  • 4,393
  • 7
  • 63
  • 91
  • Have a look at Gemini... I know there are some ugly hacks in there to deal with Avalon Dock issues. https://github.com/tgjones/gemini – Meirion Hughes Jan 28 '15 at 14:03
  • Thanks this is not a bad idea! Unfortunately, Gemini looks like a rather big framework, so digging through its code is no easier than trying to dig in the AD/CM source looking for a hint. Anyway, given that this seems a rather common question and I found no complete guidance, I created a public GitHub for this: feel free to look at it and contribute: https://github.com/Myrmex/WpfCalava/ – Naftis Jan 28 '15 at 21:47
  • Keep in mind the bit at the end. https://github.com/tgjones/gemini/blob/master/src/Gemini/Modules/Shell/ViewModels/ShellViewModel.cs – Meirion Hughes Jan 28 '15 at 21:59
  • Thanks again, this anyway unless I'm wrong seems more related to how Gemini chose to handle documents synchronization between CM and AD, using data binding binding from CM ActiveItem to AD ActiveContent. In my case I'm not experiencing such an issue; rather, it seems documents handling is OK, but tools handling is not, unless probably I recur to some ugly hack like forcing layout update in the docking manager after changing a tool visibility. Interested readers can find more in the readme at the github project pointed above. – Naftis Jan 29 '15 at 09:45
  • It seems that with some more googling and experimenting I managed to fix at least the most evident issues. A key post was http://stackoverflow.com/questions/23617707/binding-to-layoutanchorableitem-visibility-in-avalondock-2, even if I had to slightly modify its solution. I have now updated my GitHub repo, still I'm open to any suggestion to improve it or fix some other annoyances. Please read the updated Readme in the repo for more. Thanks to all – Naftis Jan 29 '15 at 17:13

0 Answers0