You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF ReactiveUI中主窗口显示ContainerView时ChildView不显示的原因?

问题

我正在学习WPF和ReactiveUI,现通过简化示例说明问题:我创建了绑定到ContainerViewModel的ContainerView,该ViewModel包含Child属性,与ChildView控件的ViewModel绑定;ContainerView的样式中设置了包含名为“Child”的ViewModelViewHost控件的ControlTemplate。但在主窗口中显示ContainerView时,ChildView并未显示。

ContainerView/ViewModel 代码

C# 代码

public class ContainerView : Control, IViewFor<ContainerViewModel>
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
        nameof(ViewModel), typeof(ContainerViewModel), typeof(ContainerView), new PropertyMetadata(default(ContainerViewModel)));

    public ContainerViewModel ViewModel
    {
        get { return (ContainerViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }
    object? IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = (ContainerViewModel)value;
    }

    public ContainerView()
    {
        this.WhenActivated(d =>
        {
            this.OneWayBind(ViewModel, vm => vm.Child, view => view.Child.ViewModel);
        });
    }

    private ViewModelViewHost Child { get; set; }

    public override void OnApplyTemplate()
    {
        Child = GetTemplateChild(nameof(Child)) as ViewModelViewHost;
    }
}

public class ContainerViewModel : ReactiveObject
{
    private ChildViewModel _child;

    public ChildViewModel Child
    {
        get => _child;
        set => this.RaiseAndSetIfChanged(ref _child, value);
    }

    public ContainerViewModel()
    {
        Child = new ChildViewModel();
    }
}

XAML 样式

<Style TargetType="{x:Type views:ContainerView}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type views:ContainerView}">
                <Grid>
                    <reactiveUi:ViewModelViewHost x:Name="Child"></reactiveUi:ViewModelViewHost>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

ChildView/ViewModel 代码

C# 代码

public class ChildView : Control, IViewFor<ChildViewModel>
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(
        nameof(ViewModel), typeof(ChildViewModel), typeof(ChildView), new PropertyMetadata(default(ChildViewModel)));

    public ChildViewModel ViewModel
    {
        get { return (ChildViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }
    object? IViewFor.ViewModel
    {
        get => ViewModel;
        set => ViewModel = (ChildViewModel)value;
    }

    public ChildView()
    {
        DefaultStyleKey = nameof(ChildView);
    }
}

XAML 样式

<Style TargetType="{x:Type views:ChildView}">
    <Setter Property="Background" Value="Brown"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type views:ChildView}">
                <Ellipse Width="200" Height="200" Fill="Yellow"></Ellipse>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

原因分析与解决方法

核心原因

  1. 绑定时机错误:WhenActivated在控件构造阶段执行,此时OnApplyTemplate还没调用,Child字段是null,绑定无法正确关联到ViewModelViewHost控件。
  2. 缺少视图映射注册:ReactiveUI不知道ChildViewModel对应的视图是ChildView,如果没有自动匹配的命名约定,需要手动注册映射关系。
  3. 遗漏基类方法调用:OnApplyTemplate中没调用base.OnApplyTemplate(),会干扰WPF的模板加载流程。

修复步骤

1. 调整绑定时机,确保控件加载完成后再绑定

修改ContainerView的代码,监听Child控件的加载状态,完成后再建立绑定:

public class ContainerView : Control, IViewFor<ContainerViewModel>
{
    // ... 保留原有DependencyProperty和ViewModel属性 ...

    private ViewModelViewHost? Child { get; set; }

    public ContainerView()
    {
        this.WhenActivated(disposables =>
        {
            // 等待Child控件加载完成后再创建绑定
            this.WhenAnyValue(v => v.Child)
                .Where(child => child != null)
                .Subscribe(child =>
                {
                    this.OneWayBind(ViewModel, vm => vm.Child, view => view.Child!.ViewModel)
                        .DisposeWith(disposables);
                }).DisposeWith(disposables);
        });
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate(); // 必须调用基类方法
        Child = GetTemplateChild(nameof(Child)) as ViewModelViewHost;
    }
}

2. 注册视图与ViewModel的映射

在应用启动时(比如App.xaml.cs的OnStartup方法)添加注册代码:

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    // 方式一:手动注册单个映射
    Locator.CurrentMutable.Register(() => new ChildView(), typeof(IViewFor<ChildViewModel>));

    // 方式二:自动注册当前程序集中所有符合命名约定的视图(ViewModel对应[ViewModel名]View)
    // Locator.CurrentMutable.RegisterViewsForViewModels(Assembly.GetExecutingAssembly());
}

完成以上修改后,ChildView就能正常显示了。


内容的提问来源于stack exchange,提问作者dba12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 17:45:28