Reputation: 2304
I'm trying to institute nested ViewModels in my already working application which uses nested views. Here's an example of what I want to do:
MainWindow View:
<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>
Child View:
<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();
}
}
}
So, I need to hook ListBox.SelectedItem
and ListBox.ItemSource
to bind with properties in MainWindowViewModel.DirFilesViewModel
. The catch is I have to do the binding in MainWindow View
not ctlDirListBox
view.
How do i access elements inside my child view? I think that's my biggest barrier. I think all my data context is right, I just can't wrangle the child view elements.
Upvotes: 1
Views: 3409
Reputation: 37059
I'm assuming that DirFilesViewModel
is the viewmodel for that usercontrol. If that's not the case, let me know what the real situation is and we'll get it sorted out.
This is a very simple case. @JamieMarshall If the XAML you provided is all there is to your UserControl, maybe it shouldn't be a usercontrol at all. You could just write a DataTemplate with that XAML in it and use that, or you could write a Style for ListBox. If you need the events, then a UserControl makes sense, but you may not actually need the events.
But it could just be a minimal example to understand how UserControls are used, and for that purpose it's well suited.
You can assign an instance of your main viewmodel to the main window's DataContext in the window's constructor,
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
or in the XAML as
<Window.DataContext>
<vm:MainWindowViewModel />
<Window.DataContext>
Neither is particularly preferable, just don't do either one in a UserControl. Your main window is pretty much the only time a view (a window is a view, properly considered) should create its own viewmodel.
Making it a resource doesn't add anything. Your binding to Grid.DataContext is a bad idea -- it's rare that you ever bind anybody's DataContext to anything; this is related to what Will was talking about in your other question -- but even if it were a good idea, this is what the binding would look like:
<Grid
DataContext="{Binding Source={StaticResource ViewModel}}"
>
But don't do that!
One thing you can do to display that usercontrol with the correct data is create "implicit datatemplates" for your your viewmodels that'll be displayed in parents like this one.
For example:
App.xaml
<!-- No x:Key, just DataType: It'll be implicitly used for that type. -->
<DataTemplate DataType="{x:Type vm:DirFilesViewModel>
<local:ctlDirFilesListBox />
</DataTemplate>
Then in MainWindow.xaml:
<UserControl
Grid.Row="0"
Grid.Column="0"
Content="{Binding DirFilesViewModel}"
/>
XAML will go to the window's DataContext for a property named DirFilesViewModel
. What it finds there is an object that's an instance of the class also named DirFilesViewModel
. It knows it has a DataTemplate for that class, so it uses that datatemplate.
This is amazingly powerful: Imagine you have an ObservableCollection<ViewModelBase>
with thirty instances of ten different kinds of viewmodels with different views, and the user selects one or another. The selected viewmodel is in a mainviewmodel property named SelectedChildVM
. Here's the XAML to display SelectedChildVM with the correct view:
<ContentControl Content="{Binding SelectedChildVM}" />
That's it.
Moving along:
<!--
Need to access the `ItemsSource="{Binding }"` and
`SelectedItem="{Binding Path=}"` of the ListBox in
`ctlDirFilesListBox` view -->
No you don't! That's the last thing you want to do! Some UserControls have properties of their own, instead of a viewmodel. With those, you bind the properties in the parent like any control.
This is a different use case of UserControls: It's "parameterized" by inheriting a viewmodel as its DataContext. The information you give it is the viewmodel.
The controls in the UserControl should have their own bindings, where they get that stuff from properties of the UserControl's viewmodel.
Let's assume the usercontrol's viewmodel (I'm guessing that's what DirFilesViewModel
is) has a Files
property (ObservableCollection<SomeFileClass>
) and a SelectedFile
class (SomeFileClass
). You likely don't need 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>
Upvotes: 5
Reputation: 169160
How do i access elements inside my child view?
You could add two dependency properties (for example named ItemsSource
and SelectedItem
) to the code-behind class of your ctlDirFilesListBox
control and bind to these in the parent window:
<local:ctlDirFilesListBox ItemsSource="{Binding Property}" SelectedItem="{Binding Property}" />
You should also bind to these properties in the 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); }
}
}
Upvotes: 0