对于完全加载后新设置的 ContentControl 内容,不会触发 Loaded 事件

Loaded event is not fired for newly set content of ContentControl after it has been fully loaded

提问人:Pavel Dubsky 提问时间:10/23/2020 更新时间:10/23/2020 访问量:829

问:

我希望能够将 的属性绑定到 ViewModel 属性,以便当 ViewModel 更改其属性的值时,将根据 确定 ,并为新呈现的 View 触发事件(即根据 选择)。下面的代码示例演示了该问题,即更改时不会触发事件。ContentContentPresenterContentContentPresenterDataTemplateLoadedDataTemplateLoadedContent

据我了解,更改时不会触发事件,我对此感到满意。问题是新加载的上下文的事件不会被触发,但它仍然会被呈现出来。有什么方法可以实现这种行为吗?LoadedContentContentPresenterLoaded

主窗口

<Window x:Class="WpfLoadProblem.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfLoadProblem"
        Height="450"
        Title="MainWindow"
        Width="800">
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:ContentViewModel}">
            <local:ContentUserControl/>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="5"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Command="{Binding Path=ChangeContentCommand}" Grid.Row="0">Change Content</Button>
        <ContentPresenter Content="{Binding Path=Content}" Grid.Row="2"/>
    </Grid>
</Window>

MainWindowView模型

internal sealed class MainWindowViewModel : INotifyPropertyChanged
{
    private object content;

    public MainWindowViewModel()
    {
        ChangeContentCommand = new DelegateCommand(ChangeContent);
    }

    public ICommand ChangeContentCommand { get; }

    public object Content
    {
        get => content;
        private set
        {
            if (content != value)
            {
                content = value;
                OnPropertyChanged(new PropertyChangedEventArgs(nameof(Content)));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void ChangeContent()
    {
        // After setting this property Loaded event is not fired for the 2nd and consecutive times.
        Content = new ContentViewModel();
    }

    private void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        PropertyChanged?.Invoke(this, e);
    }
}

ContentUserControl

<UserControl x:Class="WpfLoadProblem.ContentUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:i="clr-namespace:Microsoft.Xaml.Behaviors;assembly=Microsoft.Xaml.Behaviors">
    <i:Interaction.Triggers>
        <i:EventTrigger x:Name="Loaded">
            <i:InvokeCommandAction Command="{Binding Path=LoadedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <ListBox ItemsSource="{Binding Path=Numbers}"/>
</UserControl>

ContentView模型

internal sealed class ContentViewModel
{
    private readonly ICollection<int> numbers = new ObservableCollection<int>();

    public ContentViewModel()
    {
        LoadedCommand = new DelegateCommand(Load);
    }

    public ICommand LoadedCommand { get; }

    public IEnumerable<int> Numbers => numbers;

    private void Load()
    {
        int start = numbers.Count == 0 ? 0 : numbers.Max();

        numbers.Clear();

        for (int number = start; number < start + 5; ++number)
        {
            numbers.Add(number);
        }
    }
}
C# WPF MVVM

评论


答:

0赞 Keith Stein 10/23/2020 #1

这只是一个猜测,但由于类型永远不会改变,因此使用的类型也可能永远不会改变。相反,它只是继续使用相同的 .但是,要改变的是 .尝试使用 DataContextChanged 事件,看看它是否有效。ContentDataTemplateDataTemplateDataContextLoaded

评论

0赞 Pavel Dubsky 10/23/2020
实际上,这是真的,因为如果我在不同的命令执行上设置不同类型的视图模型,它就会起作用。但是,如果我将它们一个接一个地设置为“重置”数据模板,它仍然不起作用。我想这与它何时选择重用现有数据模板有关。 很好,但并没有真正解决我的问题,因为它必须被设置,但被设置为它的内容。DataContextChangedContentPresenterLoadedUserControl
0赞 Pavel Dubsky 10/23/2020
我想,作为一种非常讨厌的解决方法,当它触发事件时,应该调用其内容。DataContextChangedContentPresenterLoaded
0赞 Keith Stein 10/23/2020
@PavelDubsky我假设(希望)它会沿着树传播。DataContextChanged
1赞 mm8 10/23/2020 #2

除非卸载控件,否则不会触发该事件。而且不是在变化的时候。将缓存应用的内容,并且仅更新数据上下文和绑定。LoadedContentDateTemplate

在设置属性后,应直接在调用方法中调用 或调用,而不是依赖于引发的事件:LoadedLoadedCommandLoad()ChangeContent()Content

private void ChangeContent()
{
    var viewModel = = new ContentViewModel();
    Content = viewModel;
    viewModel.LoadedCommand.Execute(null);
}

评论

0赞 Pavel Dubsky 10/23/2020
好吧,这是最接近实现我想要的,没有任何额外的黑客。谢谢,我会将其标记为接受的答案。