2017-10-06 115 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); } 
    } 
}