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