Avalon Dock 2.0 LayoutItemTemplateSelector given ContentPresenter instead of ViewModel
Asked Answered
S

1

4

I've been at this for weeks...I am creating a WPF application that uses Avalon Dock 2.0 in the the Main Window. I am trying to use the Docking Manager in a MVVM way, so I have DockingManager.DocumentsSource bound to an ObservableCollection<object> property in my MainViewModel. I also created a custom DataTemplateSelector and bound it to DockingManager.LayoutItemTemplateSelector. The problem I am having:

  1. I add a ViewModel to the documents source.
  2. My custom DataTemplateSelector.SelectTemplate() is called.
  3. The item parameter in SelectTemplate() is a System.Windows.Controls.ContentPresenter instead of the ViewModel object that I added.
  4. Even if I return the correct DataTemplate, it ends up getting bound to the ContentPresenter instead of the ViewModel contained within the ContentPresenter.

I managed to replicate the problem in a bare-bones WPF project, here is the relevant code:

MainWindow:

<!-- MainWindow markup DataContext is bound to
      I omitted the usual xmlns declarations -->
<Window 
        xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"
        xmlns:local="clr-namespace:AvalonTest"
        Title="MainWindow">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>
        <xcad:DockingManager DocumentsSource="{Binding Docs}">
            <xcad:DockingManager.LayoutItemTemplateSelector>
                <local:TestTemplateSelector>
                    <local:TestTemplateSelector.TheTemplate>
                        <DataTemplate>
                            <local:TestView/>
                        </DataTemplate>
                    </local:TestTemplateSelector.TheTemplate>
                </local:TestTemplateSelector>
            </xcad:DockingManager.LayoutItemTemplateSelector>

            <xcad:LayoutRoot>
                <xcad:LayoutPanel Orientation="Vertical">
                    <xcad:LayoutAnchorablePane/>
                    <xcad:LayoutDocumentPane/>
                </xcad:LayoutPanel>
            </xcad:LayoutRoot>
        </xcad:DockingManager>
    </Grid>
</Window>

MainViewModel:

class MainViewModel
{
    //Bound to DockingManager.DocumentsSource
    public ObservableCollection<object> Docs { get; private set; }

    public MainViewModel()
    {
        Docs = new ObservableCollection<object>();
        Docs.Add(new TestViewModel());
    }
}

DataTemplateSelector:

class TestTemplateSelector : DataTemplateSelector
{
    public TestTemplateSelector() {}

    public DataTemplate TheTemplate { get; set; }

    //When this method is called, item is always a ContentPresenter
    //ContentPresenter.Content will contain the ViewModel I add
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        //Just return the only template no matter what
        return TheTemplate;
    }
}

TestView:

<!-- TestTemplateSelector will always return this TestView -->
<UserControl x:Class="AvalonTest.TestView"
             xmlns:local="clr-namespace:AvalonTest">
    <Grid>
        <StackPanel Orientation="Vertical">
            <TextBox Text="{Binding TestText}"/>
            <Button Content="A Button"/>
        </StackPanel>
    </Grid>
</UserControl>

TestViewModel:

//TestView.DataContext should be set to this, but instead
//it gets set to a containing ContentPresenter
class TestViewModel : ObservableObject
{
    private string testText = "TESTTESTTEST";
    public string TestText
    {
        get { return testText; }
        set
        {
            testText = value;
            RaisePropertyChanged("TestText");
        }
    }
}

The Result:

enter image description here

TestView is not properly bound to the TestViewModel and therefore "TESTTESTTEST" does not show up in the TextBox. I have checked out Avalon Dock's sample MVVM project and their DataTemplateSelector always gets the ViewModel instead of ContentPresenter. What am I doing wrong?

Solemnize answered 15/9, 2015 at 7:36 Comment(5)
By the way, can somebody take a look at the syntax highlighting in my question? The c# code does not seem to be highlighted properly.Solemnize
Just extended your tags with c#, now the code highlighting works.Rosefish
I suspect LayoutItemTemplateSelector was modeled after the way ContentTemplate was done, and if I recall correctly it had some slightly different behavior... namely that it used the .Content for the .DataContext, and the .Content is a ContentPresenter that wraps your ViewModelCroquet
Does it work if you use something like <local:TestView DataContext="{TemplateBinding Content}" />?Croquet
@Rachel, just to make sure I have tried to bind the DataContext manually using xaml and it works. Of course in this case if I had the option of manually bind the DataContext we wouldn't need a TemplateSelector :PSolemnize
S
5

Change the definition for SelectTemplate on TestTemplateSelector as follows:

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        //check if the item is an instance of TestViewModel
        if (item is TestViewModel)
            return TheTemplate;

        //delegate the call to base class
        return base.SelectTemplate(item, container);
    }

You should always check if the item passed is an instance of your target view model and if isn't, delegate the call to the base class so WPF can handle the objects you don't care about.

Stalagmite answered 22/9, 2015 at 17:38 Comment(5)
OP mentioned the problem is the item in this method is of type ContentPresenter, not type TestViewModel as expectedCroquet
@Rachel, this method is invoked first passing a ContentPresenter instance on item param. In that case, the method should call the base class method. A second call will be made later where item is TestViewModel and it'll work as expected.Stalagmite
@KarelTamayo, just tried this out and it indeed works. I debugged the project and the invocation of SelectTemplate does happen in the way you describe it. Can you add that explanation to your answer? Also if you have references as to why this happens that would be great too, thanks!Solemnize
@DaveS, glad it worked. I don't have an exact answer to why are we seeing this behavior. Actually, if you debug the AvalonDock.MVVMTestApp that comes with Avalon Sources you'll see that this method is never invoked with a ContentPresenter instance but always using a view model instance on item param (FileViewModel or FileStatsViewModel) which makes things even more annoying. So I guess it has to be something with Avalon Dock internals or the way we use it. I agree with you that it'll be great if we could know what is happening here.Stalagmite
An explanation for the ContentPresenter behavior has been provided in this discussion: wpftoolkit.codeplex.com/discussions/645009#post1447505Solemnize

© 2022 - 2024 — McMap. All rights reserved.