【问题标题】:MVVM Hooking up nested child views to child view modelMVVM 将嵌套子视图连接到子视图模型
【发布时间】:2018-03-17 17:56:33
【问题描述】:

我正在尝试在我已经使用嵌套视图的应用程序中建立嵌套视图模型。这是我想做的一个例子:

主窗口视图:

<Window x:Name="FCTWindow" x:Class="CatalogInterface.MainWindow"
        xmlns:local="clr-namespace:CatalogInterface"
        xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="532">

    <Window.Resources>
        <vm:MainWindowViewModel x:Key="ViewModel" />
    </Window.Resources>

    <Grid DataContext="{Binding Path=ViewModel.DirFilesListBoxViewModel}" x:Name="BodyGridLeft" Grid.Row="0" Grid.Column="0">
        <local:ctlDirFilesListBox>
            <!--
                Need to access the `ItemsSource="{Binding }"` and
                 `SelectedItem="{Binding Path=}"` of the ListBox in 
                 `ctlDirFilesListBox` view -->
        </local:ctlDirFilesListBox>
</Window>

子视图:

<UserControl x:Class="CatalogInterface.ctlDirFilesListBox"
         xmlns:local="clr-namespace:CatalogInterface"
         xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">

<Grid x:Name="MainControlGrid">           
    <ListBox SelectionChanged="ListBoxItem_SelectionChanged" 
                         HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF"
                         Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0">
        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
                <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
                <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</Grid>
</UserControl>

主窗口视图模型

using System;
using System.Text;

namespace CatalogInterface.ViewModels
{
    class MainWindowViewModel
    {
        public DirFilesViewModel DirFilesViewModel { get; set; }

        public MainWindowViewModel()
        {
            DirFilesViewModel = new DirFilesViewModel();
        }
    }
}

所以,我需要挂钩ListBox.SelectedItemListBox.ItemSource 以绑定MainWindowViewModel.DirFilesViewModel 中的属性。问题是我必须在MainWindow View 而不是ctlDirListBox 视图中进行绑定。

如何访问子视图中的元素?我认为这是我最大的障碍。我认为我所有的数据上下文都是正确的,我只是无法处理子视图元素。

【问题讨论】:

  • UserControls 应该为您的模型或视图模型设计。您不应该为您的 UserControl 设计视图模型。 TextBox 有 TextBoxViewModel 吗? 不, 而且有一个很好的理由。对于这种反模式的真实示例以及为什么它如此难以阅读this answer

标签: c# wpf mvvm nested


【解决方案1】:

我假设DirFilesViewModel 是该用户控件的视图模型。如果不是这样,请告诉我真实情况,我们会解决的。

这是一个非常简单的案例。 @JamieMarshall 如果您提供的 XAML 是您的 UserControl 的全部内容,那么它可能根本不应该是用户控件。您可以只编写一个包含该 XAML 的 DataTemplate 并使用它,或者您可以为 ListBox 编写一个样式。如果您需要这些事件,那么 UserControl 是有意义的,但您可能实际上并不需要这些事件。

但这可能只是一个了解如何使用 UserControl 的最小示例,并且非常适合此目的。

您可以在窗口的构造函数中将主视图模型的实例分配给主窗口的 DataContext,

public MainWindow()
{
    InitializeComponent();

    DataContext = new MainWindowViewModel();
}

或在 XAML 中为

<Window.DataContext>
    <vm:MainWindowViewModel />
<Window.DataContext>

两者都不是特别可取,只是不要在 UserControl 中做任何一个。您的主窗口几乎是视图(正确考虑的窗口是视图)应该创建自己的视图模型的唯一时间。

使其成为资源不会添加任何内容。您绑定到 Grid.DataContext 是一个坏主意——您很少将任何人的 DataContext 绑定到任何东西;这与 Will 在您的另一个问题中所说的有关 - 但即使这是一个好主意,这也是绑定的样子:

<Grid
    DataContext="{Binding Source={StaticResource ViewModel}}"
    >

但不要那样做!

使用正确数据显示该用户控件可以做的一件事是为您的视图模型创建“隐式数据模板”,该视图模型将显示在像这样的父级中。

例如:

App.xaml

<!-- No x:Key, just DataType: It'll be implicitly used for that type. -->
<DataTemplate DataType="{x:Type vm:DirFilesViewModel>
    <local:ctlDirFilesListBox />
</DataTemplate>

然后在 MainWindow.xaml:

<UserControl 
    Grid.Row="0"
    Grid.Column="0"
    Content="{Binding DirFilesViewModel}" 
    />

XAML 将转到窗口的 DataContext 以获取名为 DirFilesViewModel 的属性。它发现有一个对象是该类的实例,也名为DirFilesViewModel。它知道它有该类的 DataTemplate,因此它使用该数据模板。

这非常强大:假设您有一个ObservableCollection&lt;ViewModelBase&gt;,其中包含十种不同视图模型的三十个实例和不同的视图,并且用户选择一个或另一个。选定的视图模型位于名为 SelectedChildVM 的 mainviewmodel 属性中。这是使用正确视图显示 SelectedChildVM 的 XAML:

<ContentControl Content="{Binding SelectedChildVM}" />

就是这样。

继续前进:

        <!--
            Need to access the `ItemsSource="{Binding }"` and
             `SelectedItem="{Binding Path=}"` of the ListBox in 
             `ctlDirFilesListBox` view -->

不,你没有!这是你想做的最后一件事!一些用户控件有自己的属性,而不是视图模型。有了这些,您可以像任何控件一样绑定父级中的属性。

这是 UserControls 的一个不同用例:它通过继承视图模型作为其 DataContext 来“参数化”。你给它的信息是视图模型。

UserControl 中的控件应该有自己的绑定,它们从 UserControl 的视图模型的属性中获取这些内容。

让我们假设用户控件的视图模型(我猜这就是DirFilesViewModel)有一个Files 属性(ObservableCollection&lt;SomeFileClass&gt;)和一个SelectedFile 类(SomeFileClass)。您可能不需要ListBoxItem_SelectionChanged

<UserControl x:Class="CatalogInterface.ctlDirFilesListBox"
         xmlns:local="clr-namespace:CatalogInterface"
         xmlns:vm="clr-namespace:CatalogInterface.ViewModels"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="MainControlGrid">           
    <ListBox 
        ItemsSource="{Binding Files}"
        SelectedItem="{Binding SelectedFile}"
        SelectionChanged="ListBoxItem_SelectionChanged" 
        HorizontalAlignment="Stretch" 
        VerticalAlignment="Stretch" 
        Background="#FFFFFF"
        Grid.Row="2" 
        Grid.Column="1" 
        Grid.ColumnSpan="3" 
        BorderThickness="0"
        >
        <ListBox.ItemContainerStyle>
            <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
                <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
                <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
            </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>
</Grid>
</UserControl>

【讨论】:

  • 感谢@Ed Plunkett,这是一个非常全面的答案。一个问题虽然 - DataTemplate 技巧很酷,但如果我想去基本的怎么办。如何在不绑定数据上下文的情况下显式引用子视图模型?这只是为了我自己的熏陶。我可能最终会使用你的把戏,如果你从来没有开过平托,就很难欣赏法拉利。 :)
  • @JamieMarshall 确切地说,我不会称其为“技巧”;这就像说用方向盘绕过弯道而不是漂移是一种“技巧”。 DataTemplates 不一定是隐式的:您可以给它们一个 x:Key 属性并将它们用作 ListBox 或 ItemsControl 的 ItemTemplate,或者明确告诉内容控件使用哪一个:&lt;ContentControl ContentTemplate="{StaticResource MyDataTemplate}" /&gt;
  • 如果你有一个视图模型的隐式数据模板(比如,编辑它的属性),也许你也有一个或多个显式数据模板,例如,如果你有一个视图模型列表ListBox,而您只想显示Name 属性。或者您可能有只读视图、紧凑视图等。WPF 是一种与 winforms/MFC/等不同的 UI 思考方式。你必须学会​​像 WPF 一样思考。
  • @JamieMarshall 如果您提供的 XAML 是您的 UserControl 的全部内容,那么它可能根本不应该是一个用户控件。您可以只编写一个包含该 XAML 的 DataTemplate 并使用它,或者您可以为 ListBox 编写一个样式。如果您需要这些事件,那么 UserControl 是有意义的,但您可能实际上并不需要这些事件。
  • alight 我想我在船上。总而言之-您说我应该始终使用DataTemplate 设置我的DataContext,并且永远不要(有例外)设置直接绑定DataContext
【解决方案2】:

如何访问子视图中的元素?

您可以将两个依赖属性(例如名为 ItemsSourceSelectedItem)添加到 ctlDirFilesListBox 控件的代码隐藏类并在父窗口中绑定到它们:

<local:ctlDirFilesListBox ItemsSource="{Binding Property}" SelectedItem="{Binding Property}" />

您还应该绑定到UserControl 中的这些属性:

<ListBox SelectionChanged="ListBoxItem_SelectionChanged" 
                 HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="#FFFFFF"
                 Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3" BorderThickness="0"
                 ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
                 SelectedItem="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType=UserControl}}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
            <EventSetter Event="MouseDoubleClick" Handler="ListBoxItem_MouseDoubleClick"/>
            <EventSetter Event="KeyDown" Handler="ListBoxItem_KeyDown"/>
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

public class ctlDirFilesListBox : UserControl
{
    //...

    public static readonly DependencyProperty ItemsSourceProperty =
         DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ctlDirFilesListBox));

    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.Register("ItemsSource", typeof(object), typeof(ctlDirFilesListBox));

    public object SelectedItem
    {
        get { return GetValue(SelectedItemProperty); }
        set { SetValue(SelectedItemProperty, value); }
    }
}

【讨论】:

    猜你喜欢
    • 2011-05-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 1970-01-01
    • 2021-08-11
    • 2014-05-17
    • 2011-07-29
    相关资源
    最近更新 更多