C# AssociatedObject.DataContext在ContentPresenter中的行为为空

C# AssociatedObject.DataContext在ContentPresenter中的行为为空,c#,wpf,C#,Wpf,我尝试使用一个行为从VM的视图中触发一个方法。为此,我在VM中使用了一个触发器类型的对象和一个事件和调用者,并将其绑定到行为中的依赖属性。该行为不会在其加载的回调中对事件进行订阅。这与预期的一样,当VM中的事件被调用时,我可以使用AssociatedObject调用视图中的方法 但是,当行为在ContentPresenter的DataTemplate中时,我会看到以下奇怪的行为(没有双关语…)。给定一个ContentPresenter,它有两个数据模板,根据内容的类型(Tab1或Tab2)使用,

我尝试使用一个行为从VM的视图中触发一个方法。为此,我在VM中使用了一个触发器类型的对象和一个事件和调用者,并将其绑定到行为中的依赖属性。该行为不会在其加载的回调中对事件进行订阅。这与预期的一样,当VM中的事件被调用时,我可以使用AssociatedObject调用视图中的方法

但是,当行为在ContentPresenter的DataTemplate中时,我会看到以下奇怪的行为(没有双关语…)。给定一个ContentPresenter,它有两个数据模板,根据内容的类型(Tab1或Tab2)使用,当内容从Tab1更改为Tab2时,一切正常。但是,当它从Tab1的一个实例更改为Tab1的另一个实例时,AssociatedObject.DataContext突然为null(因此,我尝试在视图上调用的方法失败)

我试图创建一个最小的示例来演示这一点。下面的代码应该具有在新WPF应用程序中运行的所有内容。观察以下单击路径中的调试输出以进行复制:

选项卡->选项卡1->调用->按预期输出

Tab->Tab1a->Tab1->Invoke->DataContext为空

Tab->Tab1->再次按预期进行

我可以想办法解决这个问题,但我想了解它。我假设它与内容更改为相同类型时未重建的DataTemplate有关,但我仍然希望AssociatedObject指向正确的网格(我认为不是这样,因为实际显示的网格中的DataContext很好)。任何想法都将受到高度赞赏

MainWindow.xaml

<Window x:Class="EmptyWpfApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:EmptyWpfApp"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <local:ViewModel />
</Window.DataContext>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <ContentPresenter Grid.Row="0" Content="{Binding Tab}">
       <ContentPresenter.Resources>
           <DataTemplate DataType="{x:Type local:Tab1}">
               <Grid>
                   <i:Interaction.Behaviors>
                       <local:TestBehavior Trigger="{Binding T}"/>
                   </i:Interaction.Behaviors>
                   <TextBlock>With behavior</TextBlock>
               </Grid>
           </DataTemplate>
           <DataTemplate DataType="{x:Type local:Tab2}">
               <Grid>
                   <TextBlock>Empty</TextBlock>
               </Grid>
           </DataTemplate>
       </ContentPresenter.Resources>
   </ContentPresenter>
    <Button Grid.Row="1" Click="ButtonBase_OnClick">Tab</Button>
    <Button Grid.Row="1" Grid.Column="1" Click="ButtonBase_OnClick1">Tab1</Button>
    <Button Grid.Row="1" Grid.Column="2" Click="ButtonBase_OnClick1a">Tab1a</Button>
    <Button Grid.Row="2" Grid.ColumnSpan="3" Click="ButtonBase_OnClick2">Invoke on VM</Button>
</Grid>

举止
空的
标签
表1
表1A
在虚拟机上调用

MainWindows.xaml.cs:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using EmptyWpfApp.Annotations;

namespace EmptyWpfApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window

{
    private Tab2 _tab = new Tab2();
    private Tab1 _tab1 = new Tab1();
    private Tab1 _tab1a = new Tab1();

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        ((ViewModel)DataContext).Tab = _tab;
    }
    private void ButtonBase_OnClick1(object sender, RoutedEventArgs e)
    {
        ((ViewModel)DataContext).Tab = _tab1;
    }
    private void ButtonBase_OnClick1a(object sender, RoutedEventArgs e)
    {
        ((ViewModel)DataContext).Tab = _tab1a;
    }

    private void ButtonBase_OnClick2(object sender, RoutedEventArgs e)
    {
        (((ViewModel) DataContext).Tab as Tab1)?.T.OnE();
    }
}

public class ViewModel : INotifyPropertyChanged
{
    private ITab _tab;

    public ITab Tab
    {
        get => _tab;
        set
        {
            _tab = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

public interface ITab {}

public class Tab1 : ITab
{
    public Trigger T { get; } = new Trigger();
}

public class Tab2 : ITab {}

public class Trigger
{
    public event EventHandler E;

    public virtual void OnE()
    {
        E?.Invoke(this, EventArgs.Empty);
    }
}

public class TestBehavior : Behavior<Grid>
{
    public static readonly DependencyProperty TriggerProperty = DependencyProperty.Register(
        "Trigger",
        typeof(Trigger),
        typeof(TestBehavior),
        new PropertyMetadata(default(Trigger)));

    public Trigger Trigger {
        get => (Trigger)GetValue(TriggerProperty);
        set => SetValue(TriggerProperty, value);
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Loaded += OnLoaded;
        AssociatedObject.Unloaded += Cleanup;
    }

    private void Cleanup(object sender, RoutedEventArgs e)
    {
        Cleanup();
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        Cleanup();
    }

    private void Cleanup()
    {
        AssociatedObject.Loaded -= OnLoaded;
        if (Trigger != null)
            Trigger.E -= TriggerOnE;
    }

    private void OnLoaded(object sender, RoutedEventArgs e)
    {
        Subscribe();
    }

    private void Subscribe()
    {
        Trigger.E += TriggerOnE;
    }

        private void TriggerOnE(object sender, EventArgs e)
        {
            Debug.WriteLine("DC:" + AssociatedObject.DataContext);
        }
    }
}
使用系统;
使用系统组件模型;
使用系统诊断;
使用System.Runtime.CompilerServices;
使用System.Windows;
使用System.Windows.Controls;
使用System.Windows.Interactive;
使用EmptyWpfApp.Annotations;
命名空间EmptyWpfApp
{
/// 
///MainWindow.xaml的交互逻辑
/// 
公共部分类主窗口:窗口
{
private tab2u tab=new Tab2();
私有tab1u Tab1=新Tab1();
私有tab1u tab1a=新Tab1();
公共主窗口()
{
初始化组件();
DataContext=新的ViewModel();
}
private void按钮base_OnClick(对象发送方,RoutedEventTarget e)
{
((ViewModel)DataContext).Tab=\u Tab;
}
private void按钮base_OnClick1(对象发送方,RoutedEventTargets e)
{
((ViewModel)DataContext).Tab=\u tab1;
}
private void按钮base_OnClick1a(对象发送方,RoutedEventTargets e)
{
((ViewModel)DataContext).Tab=_tab1a;
}
private void按钮base_OnClick2(对象发送方,路由目标)
{
(((ViewModel)DataContext).Tab作为Tab1)?.T.OnE();
}
}
公共类视图模型:INotifyPropertyChanged
{
私人ITab_选项卡;
公共ITab选项卡
{
获取=>\u选项卡;
设置
{
_tab=值;
OnPropertyChanged();
}
}
公共事件属性更改事件处理程序属性更改;
[NotifyPropertyChangedInvocator]
受保护的虚拟void OnPropertyChanged([CallerMemberName]字符串propertyName=null)
{
PropertyChanged?.Invoke(这是新的PropertyChangedEventArgs(propertyName));
}
}
公共接口ITab{}
公共类表1:ITab
{
公共触发器T{get;}=新触发器();
}
公共类Tab2:ITab{}
公共类触发器
{
公共事件处理程序E;
公共虚拟void OnE()
{
E?.Invoke(this,EventArgs.Empty);
}
}
公共类TestBehavior:行为
{
公共静态只读DependencyProperty触发器属性=DependencyProperty.Register(
“触发器”,
类型(触发器),
类型(测试行为),
新属性元数据(默认(触发器));
公共触发器{
get=>(触发器)GetValue(TriggerProperty);
set=>SetValue(TriggerProperty,value);
}
受保护的覆盖无效附加()
{
base.onatached();
AssociatedObject.Loaded+=已加载;
AssociatedObject.Unload+=清理;
}
专用无效清除(对象发送器、RoutedEventArgs e)
{
清理();
}
附加时受保护的覆盖无效()
{
base.OnDetaching();
清理();
}
私有空间清理()
{
AssociatedObject.Loaded-=已加载;
if(触发器!=null)
Trigger.E-=TriggerOnE;
}
已加载专用void(对象发送方,RoutedEventArgs e)
{
订阅();
}
私有无效订阅()
{
Trigger.E+=TriggerOnE;
}
私有void触发器(对象发送方,事件参数)
{
Debug.WriteLine(“DC:+AssociatedObject.DataContext”);
}
}
}

我认为这种设计比实际需要复杂得多。我的猜测是,您希望用户能够为窗口的主要内容从三种不同的viewmodel中进行选择,然后您就有了另一个按钮来调用所选viewmodel上的方法。对吗?我认为解决方案是从传统的MVVM设计开始,这更容易在第一次正确使用,也更容易调试……我还可以补充一点,如果您在ControlTemplate之外使用ContentPresenter,您对ContentPresenter的了解不够,无法在ControlTemplate之外使用它。在ControlTemplate之外,始终使用ContentControl。ContentPresenter没有添加任何不正确的内容