2017-10-06 85 views
0

我试图在我已经在使用嵌套视图的工作应用程序中创建嵌套的ViewModels。下面是我想要做的一个例子:MVVM将嵌套的子视图挂接到子视图模型

主窗口视图:

<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> 

MainWindowViewModel

using System; 
using System.Text; 

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

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

所以,我需要挂接ListBox.SelectedItemListBox.ItemSourceMainWindowViewModel.DirFilesViewModel中的属性绑定。赶上是我在MainWindow View不是ctlDirListBox查看绑定。

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

+0

UserControls应该为您的模型或您的视图模型设计。你不应该为你的UserControl设计一个视图模型。 TextBox是否有TextBoxViewModel? **不,**,并有一个很好的理由。对于这种反模式的真实生活中的例子,以及为什么它不能很好地阅读[这个答案](https://stackoverflow.com/a/44729258/1228)。 – Will

回答

3

我假设DirFilesViewModel是该usercontrol的视图模型。如果情况并非如此,请告诉我真实情况是什么,我们会把它整理出来。

这是一个非常简单的情况。 @JamieMarshall如果你提供的XAML都是你的UserControl,也许它不应该是一个usercontrol。你可以用它写一个带有XAML的DataTemplate并使用它,或者你可以写一个ListBox的样式。如果你需要这些事件,那么UserControl是有意义的,但你可能并不需要这些事件。

但它可能只是一个理解用户控件如何使用的最简单的例子,为此它非常适合。

你可以在窗口的构造你的主视图模型的实例分配到主窗口的DataContext的,

public MainWindow() 
{ 
    InitializeComponent(); 

    DataContext = new MainWindowViewModel(); 
} 

或XAML作为

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

也不是特别理想,只是不” t在UserControl中执行任一操作。您的主窗口几乎是唯一一个视图(窗口是一个视图,正确考虑)应该创建自己的视图模型。

使其成为资源不会添加任何内容。你对Grid.DataContext的绑定是一个坏主意 - 你很少将任何人的DataContext绑定到任何东西上;这是关系到究竟会是谈论你的另一个问题 - 但即使它是一个好主意,这是结合会是什么样子:

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

但不这样做!

你可以用正确的数据显示usercontrol的一件事就是为你的视图模型创建“隐含的数据模型”,这些数据模型将显示在像这样的父项中。

例如:

的App.xaml

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

然后。xaml:

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

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

这是惊人的强大:想象一下你有一个ObservableCollection<ViewModelBase>与十个不同种类的viewmodels具有不同视图的30个实例,用户选择一个或另一个。所选视图模型位于名为SelectedChildVM的mainviewmodel属性中。以下是XAML以正确的视图显示SelectedChildVM:

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

就是这样。

一起移动:

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

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

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

UserControl中的控件应该有它们自己的绑定,它们从UserControl的viewmodel的属性中获取这些东西。

假设该用户控件的视图模型(我猜这就是DirFilesViewModel是)具有Files属性(ObservableCollection<SomeFileClass>)和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> 
+0

谢谢@Ed Plunkett,这是一个非常全面的答案。其中一个问题 - DataTemplate技巧很酷,但如果我想要基本的话,该怎么办?我如何显式引用子视图模型而没有绑定数据上下文?这仅仅是为了我自己的熏陶。我最终可能会用到你的诀窍,如果你从未来过平托,很难欣赏法拉利。:) –

+0

@JamieMarshall我不会称它为“诡计”,确切地说;这就好像说用方向盘绕过曲线而不是漂移是“诀窍”。 DataTemplates不一定是隐含的:你可以给它们一个'x:Key'属性,并将它们用作ListBox或ItemsControl的'ItemTemplate',或者明确地告诉contentControl使用哪一个:''。 –

+0

如果你有一个隐式数据模式的视图模型(比如编辑它的属性),也许你也有一个或多个明确的数据模板,例如,如果你有一个ListBox中的视图模型列表,而你只是想显示'Name'属性。或者你可能有一个只读视图,一个简洁的视图等等。WPF是从winforms/MFC/etc等角度考虑UI的一种不同方式。你必须学会​​像WPF一样思考。 –

0

如何访问我的孩子视图内元素?

你可以在父窗口中添加两个依赖属性(例如名为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); } 
    } 
}