在ContentPresenter中的Behavior中,AssociatedObject.DataContext为null

时间:2019-06-12 13:33:56

标签: c# wpf

我尝试使用行为来从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);
        }
    }
}

0 个答案:

没有答案