我尝试使用行为来从VM触发视图中的方法。为此,我在VM中使用类型为Trigger的对象以及事件和调用程序,并将其绑定到行为中的依赖项属性。该行为将在其Loaded回调中订阅事件。可以按预期工作,当调用VM中的事件时,我可以使用AssociatedObject在视图中调用该方法。
但是,当Behavior在ContentPresenter的DataTemplate内部时,我看到以下奇怪的行为(没有双关语的意图……)。给定一个具有两个DataTemplate的ContentPresenter(根据模板的类型(Tab1或Tab2)使用),当将Content从Tab1更改为Tab2时,一切都很好。但是,当将它从Tab1的一个实例更改为Tab1的另一个实例时,AssociatedObject.DataContext突然为空(由于这个原因,我尝试在View上调用的方法失败了。)
我试图创建一个最小的例子来说明这一点。下面的代码应具有可以在新的WPF应用程序中运行的所有功能。在以下单击路径中观察调试输出以重现:
Tab-> Tab1->调用->预期输出
Tab-> Tab1a-> Tab1->调用-> DataContext为空
Tab-> Tab1->再次如预期
我可以想出解决此问题的方法,但是我想理解它。我认为这与当Content更改为相同类型时未重建DataTemplate有关,但是我仍然希望AssociatedObject指向正确的Grid(我认为不是这样,因为实际显示的Grid中的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>
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);
}
}
}