WPF ViewModel 实例化影响子继承

标签 wpf xaml mvvm binding datacontext

我有一个子用户控件 (Page1),当它在 XAML 中声明为内联时,它无法继承在我的 WPF 窗口的 DataContext 上设置的 ViewModel (WizardPageViewModel):

<Window x:Class="WPFToolkitWizard.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:common="clr-namespace:WPFToolkitWizard"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
    <common:WizardPageViewModel/>
</Window.DataContext>
<Grid>
    <common:MyWizardControl>
        <common:MyWizardControl.Pages>
            <common:Page1 Title="Page 1" Description="First page" IsValid="true" />
        </common:MyWizardControl.Pages>
    </common:MyWizardControl>
</Grid>

但是,如果我将其更改为静态资源并按如下方式引用它:
<Window.Resources>
<common:WizardPageViewModel x:Key="vm" />
</Window.Resources>

<common:Page1 Title="Page 1" Description="First page" IsValid="true" DataContext="{StaticResource vm}" />

在 Page1 中声明的依赖于注入(inject)的 VM 的绑定(bind)可以正常工作。问题是我想从 Window 的 DataContext 属性中引用 ViewModel,因为这是我们在整个团队中声明我们的 VM 的方式:
<Window.DataContext>
<common:WizardPageViewModel/>
</Window.DataContext>

我尝试按如下方式设置绑定(bind),但没有这样的运气:
<common:Page1 Title="Page 1" Description="First page" IsValid="true" DataContext="{Binding}" />

我也读过关于 DataContext 自动继承的帖子,但在这种情况下它不起作用。

查看详细的调试输出如下:
System.Windows.Data Warning: 60 : BindingExpression (hash=26218178): Default mode resolved to TwoWay
System.Windows.Data Warning: 61 : BindingExpression (hash=26218178): Default update trigger resolved to LostFocus
System.Windows.Data Warning: 62 : BindingExpression (hash=26218178): Attach to     System.Windows.Controls.TextBox.Text (hash=35377412)
System.Windows.Data Warning: 67 : BindingExpression (hash=26218178): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=26218178): Found data context element: TextBox (hash=35377412) (OK)
System.Windows.Data Warning: 71 : BindingExpression (hash=26218178): DataContext is null
System.Windows.Data Warning: 65 : BindingExpression (hash=26218178): Resolve source deferred

它指出 DataContext 为空,但我只是不知道为什么它不能自动找到我的 VM 实例。

MyWizardControl 是一个用户控件

Page1 是一个 ContentControl

最佳答案

似乎对我来说按预期工作。

根据您的代码结构,我假设 common:MyWizardControlItemsControl ,因为它包含一个 Pages属性(property)。

如果这是真的,那么 ItemsControl元素(在您的情况下是您的页面)从 ItemControl 继承 ViewModel的来源ItemsSource属性,这将是 ObservableCollection<WizardPageViewModel>在你的情况下。

由于您没有设置 ItemSource ItemsControl 上的属性(property),它是空的,WPF 不能为其分配任何数据上下文。

静态的有效,因为您明确设置它,覆盖任何可能的 DataContextItemSource 设置.

话虽如此,您有三个选择:

  • 静态实例化它并自己分配它,就像您在示例中所做的那样(不是很优雅)
  • 使用带有 ViewModelLocator 的 MVVM 框架(即 Microsoft 的 Prism 框架),它将显式解析 View每次实例化 View 时(通过代码或从 XAML)
  • 创建 MyWizardViewModel其中包含 ObservableCollection<WizardPageViewModel> Pages {get;set;}并将其绑定(bind)到 ItemsSourceWizardPageViewModel .

  • 第一个选项感觉有点脏,但对于演示或教学项目可能没问题。

    第二个适用于可重用的 View / View 模型,如果是 Prims,ViewModelLocator 将为每个 Page1 创建一个 View 模型的新实例。查看您在 XAML 中插入的内容。您可以在多个 Page1 中使用 ViewModel 的单个实例控制是否使用依赖注入(inject)框架(如 Unity、MEF、Autofac 等)

    第三个选项是首选,因为 ViewModel 旨在与 View 和您的 View 一起使用。是 Page1 ,而不是 MyWizardControl (实际上 MyWizardControl 是一个带有 subview 的 View ,每个 View 都有自己的 View 模型,代 TableView 所需的数据)。

    第三种选择是最容易实现的,无需使用 MVVM 框架或依赖注入(inject)

    编辑:一个例子
    // Prism ViewModel base class, adjust for your framework or whatever you use as base
    public class MyWizardViewModel : BindableBase 
    {
        public ObservableCollection<WizardPageViewModel> Pages { get; set; }
    
        public MyWizardViewModel()
        {
            Pages = new ObservableCollection<WizardPageViewModel>()
            {
                new WizardPage1ViewModel(), // public class WizardPage1ViewModel : WizardPageViewModel
                new WizardPage2ViewModel() // public class WizardPage2ViewModel : WizardPageViewModel
            }
        }
    }
    

    XAML:
    <Window x:Class="WPFToolkitWizard.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:common="clr-namespace:WPFToolkitWizard"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <common:WizardPageViewModel/>
    </Window.DataContext>
    <Grid>
        <common:MyWizardControl ItemsSource="{Binding Pages}" />
    </Grid>
    

    然后您只需为每个 ViewModel 制作 DataTemplates,然后 ContenControl 将根据 DataContext 的类型显示正确的模板。见 here

    关于WPF ViewModel 实例化影响子继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31121566/

    相关文章:

    wpf - 区分 TouchUp 和 TouchLeave 以及 TouchDown 和 TouchEnter 的策略?

    wpf - WP8.1 中的 XAML : Child Element Styles

    c# - 将色带控件与 Caliburn Micro 接口(interface)的好方法是什么?

    c# - 将属性 PropertyChange 与实现 ICommand 的命令绑定(bind)

    c# - ListBox 中的 ComboBox 不会触发 SelectionChanged 事件

    c# - 打开包含大量项目的组合框时 wpf UI 卡住

    .net - 在没有服务定位器的情况下注入(inject) ViewModel

    wpf - 在 WPF 应用程序中禁用右键单击(按住)。

    c# - 使用超过 2^31 个元素的虚拟化集合进行 UI 虚拟化

    visual-studio - 键盘命令摆脱自动生成的双引号