【问题标题】:Avalon Dock 2.0 LayoutItemTemplateSelector given ContentPresenter instead of ViewModelAvalon Dock 2.0 LayoutItemTemplateSelector 给定 ContentPresenter 而不是 ViewModel
【发布时间】:2015-09-15 07:36:06
【问题描述】:

我已经为此工作了数周...我正在创建一个在主窗口中使用 Avalon Dock 2.0 的 WPF 应用程序。我正在尝试以 MVVM 方式使用对接管理器,因此我将DockingManager.DocumentsSource 绑定到我的MainViewModel 中的ObservableCollection<object> 属性。我还创建了一个自定义DataTemplateSelector 并将其绑定到DockingManager.LayoutItemTemplateSelector。我遇到的问题:

  1. 我将ViewModel 添加到文档源。
  2. 我的自定义 DataTemplateSelector.SelectTemplate() 被调用。
  3. SelectTemplate() 中的 item 参数是 System.Windows.Controls.ContentPresenter,而不是我添加的 ViewModel 对象。
  4. 即使我返回正确的DataTemplate,它最终也会绑定到ContentPresenter,而不是ContentPresenter 中包含的ViewModel

我设法在一个简单的 WPF 项目中复制了这个问题,这里是相关代码:

主窗口:

<!-- 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>

主视图模型:

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

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

数据模板选择器:

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;
    }
}

测试视图:

<!-- 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");
        }
    }
}

结果:

TestView 未正确绑定到TestViewModel,因此“TESTTESTTEST”不会出现在TextBox 中。我检查了Avalon Dock's sample MVVM project,他们的DataTemplateSelector 总是得到ViewModel 而不是ContentPresenter。我做错了什么?

【问题讨论】:

  • 顺便说一句,有人可以看看我的问题中突出显示的语法吗? c# 代码似乎没有正确突出显示。
  • 刚刚用c#扩展你的标签,现在代码高亮工作了。
  • 我怀疑 LayoutItemTemplateSelector 是按照 ContentTemplate 的完成方式建模的,如果我没记错的话,它有一些 slightly different behavior... 即它使用 .Content 来表示 .DataContext.Content 是一个包装你的 ViewModel 的 ContentPresenter
  • 如果你使用&lt;local:TestView DataContext="{TemplateBinding Content}" /&gt;之类的东西会起作用吗?
  • @Rachel,只是为了确保我尝试使用 xaml 手动绑定 DataContext 并且它有效。当然,在这种情况下,如果我可以选择手动绑定 DataContext,我们就不需要 TemplateSelector :P

标签: c# .net wpf mvvm avalondock


【解决方案1】:

在 TestTemplateSelector 上更改 SelectTemplate 的定义如下:

    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);
    }

您应该始终检查传递的项目是否是目标视图模型的实例,如果不是,则将调用委托给基类,以便 WPF 可以处理您不关心的对象。

【讨论】:

  • OP 提到问题是这个方法中的itemContentPresenter 类型,而不是预期的TestViewModel 类型
  • @Rachel,首先调用此方法,并在项目参数上传递一个 ContentPresenter 实例。在这种情况下,该方法应该调用基类方法。稍后将在 item 为 TestViewModel 的情况下进行第二次调用,它将按预期工作。
  • @KarelTamayo,刚刚尝试过,它确实有效。我调试了项目,并且 SelectTemplate 的调用确实以您描述的方式发生。您可以将该解释添加到您的答案中吗?另外,如果您有关于为什么会发生这种情况的参考资料,那也太好了,谢谢!
  • @DaveS,很高兴它成功了。对于我们为什么会看到这种行为,我没有确切的答案。实际上,如果您调试 Avalon Sources 附带的 AvalonDock.MVVMTestApp,您会发现该方法从未使用 ContentPresenter 实例调用,而是始终在项目参数 ( FileViewModelFileStatsViewModel),这让事情变得更加烦人。所以我想它必须与 Avalon Dock 内部或我们使用它的方式有关。我同意你的看法,如果我们能知道这里发生了什么,那就太好了。
  • 在此讨论中提供了对 ContentPresenter 行为的解释:wpftoolkit.codeplex.com/discussions/645009#post1447505
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-12
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 2021-10-24
  • 1970-01-01
相关资源
最近更新 更多