How to assign DataContext from parent View's ViewModel (Property) - wpf

EDIT:
Here is a small sample Solution (VS 2013) showing my exact problem: nakkvarr.net/TestApp.zip
I have two views:
MainView
EmployeeView
I want my EmployeeView to reference the MainViewModel property "employees".
I tried this:
<DockPanel DataContext="{Binding DataContext.MainViewModel,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type EmployeeLisViewModel}}}">
But all I get is the Error: EmployeeListViewModel ist not supported in WPF ?!
(EmployeeListViewModel is also the name of the property of my MainViewModel)
The thing is:
My MainView has some menu items. One is for sorting the employee list, which is inside my UserControl. My UserControl ViewModel contains the Commands for sorting.
EDIT:
I changed it to vm:EmployeeListViewModel ... now the error is gone...but now I don't seem to get access to the ViewModel :/
<ia:InvokeCommandAction Command="{Binding LoadEmployeesCommand}"
Does not trigger, no emplyees get loaded. When I use the ViewModel directly, it works just fine.
MainWindow.xaml
<Window x:Class="de.XXX.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:de.XXX.Views"
xmlns:vm="clr-namespace:de.XXX.ViewModel"
Icon="/Images/App.ico"
Style="{StaticResource MainWindowStyle}"
Title="MainWindow">
<DockPanel DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
<Menu DockPanel.Dock="Top">
...
</Menu>
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding EmployeeListViewModel}" />
</DockPanel>
</Window>
EmployeeListView.xaml //not model, copy & paste error x_X
<UserControl x:Class="de.XXX.Views.EmployeeListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:de.XXX.ViewModel"
xmlns:ia="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="350">
<DockPanel DataContext="{Binding MainViewModel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type vm:EmployeeListViewModel}}}">
<ListBox DockPanel.Dock="Bottom" x:Name="EmployeeList" ItemsSource="{Binding EmployeeList}" ScrollViewer.CanContentScroll="False" />
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding LoadEmployeesCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</DockPanel>
</UserControl>

If you modify your SubControl to below, this should work.
<Grid DataContext="{Binding Path=DataContext.SubVM, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">
I have a hint on what you are trying to achieve and I guess you have missed some details on Binding especially on other underlying properties needed to be set. This part is working based on the given code. But then, there are numerous ways in making it better. Good luck.

I think there might be a combination of misunderstandings here! Your UserControl has
x:Class="de.XXX.Views.EmployeeListView"
defined as the class name in xaml. However, you're also saying that the file is named EmployeeListViewModel.xaml? A .xaml file should never be named a viewmodel. I believe you should have MainWindow.xaml, EmployeeListView.xaml, MainWindowViewModel.cs, and EmployeeListViewModel.cs, is this the case?
Also, in your MainWindow.xaml, you're already setting the datacontext of your EmployeeListView usercontrol:
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding EmployeeListViewModel}" />
This implies to me that your class structure is that MainViewModel contains an instance of the EmployeeListViewModel... is this true?
If all the above is true, then this part of your UserControl xaml does not make sense:
<DockPanel DataContext="{Binding MainViewModel,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type vm:EmployeeListViewModel}}}">
Whether EmployeeListViewModel is actually your view and not your viewmodel, the property "MainViewModel" does not exist on EmployeeListViewModel, so the binding will never work.
If you're trying to have a reference to your Window's DataContext, MainViewModel, within your UserControl, a possible way to do this is:
MainWindow.xaml:
<controls:EmployeeListView DockPanel.Dock="Bottom" DataContext="{Binding}" />
This should set the DataContext of your EmployeeListView to MainViewModel. Then, in your EmployeeListView, you can do this:
<UserControl x:Class="de.XXX.Views.EmployeeListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:de.XXX.ViewModel"
xmlns:ia="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DesignHeight="350" d:DesignWidth="350">
<DockPanel
<ListBox DockPanel.Dock="Bottom" x:Name="EmployeeList" ItemsSource="{Binding EmployeeList}" ScrollViewer.CanContentScroll="False" />
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding EmployeeListViewModel.LoadEmployeesCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
</DockPanel>
</UserControl>
There's a lot of assumptions about your views and viewmodels I made for this answer. I hope this at least helps. If it doesn't make any sense, please post your viewmodels as well :)

Related

Assign different ViewModels to one UserControl

I have MainWindow.xaml page with it's MainViewModel
and would like to add 2 SidePanels using one UserControl, but it should have different ViewModels.
MainViewModel alredy has 2 properties with created SidePanelViewModels:
public MainViewModel()
{
LeftSidePanel = new SidePanelViewModel(PanelSides.Left);
RightSidePanel = new SidePanelViewModel(PanelSides.Right);
}
How to set objects in this properties as DataContext for each UserControl in xaml?
Something like this doesnot work:
<Window DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
...
<Grid Visibility="{Binding RightSidePanel.PanelVisibility}" Grid.Column="4" Grid.Row="2" >
<v:SidePanelViev DataContext="{Binding RightSidePanel}" />
</Grid>
</Window>
I broke all the brains thinking how to do it, Please help
ps.
Or please suggest any other approach to reach the same target..
I have solved my problem by adding both instances of SidePanelViewModel as Content of ContentControl
<Window DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">
...
<Grid Visibility="{Binding RightSidePanel.PanelVisibility}" Grid.Column="4" Grid.Row="2" >
<ContentControl Content="{Binding RightSidePanel}"></ContentControl>
</Grid>
</Window>
and adding new Window.Resource what binds all classes of type SidePanelViewModel to be visualised using SidePanelViev
<Window.Resources>
<DataTemplate DataType="{x:Type vm:SidePanelViewModel}">
<v:SidePanelViev></v:SidePanelViev>
</DataTemplate>
</Window.Resources>
This works, but if somebody has better ideas, please do not hesitate to post them, I am not sure that my decision is good one

How to find Button's DataContext in this sample code?

I was going through the excellent blog written by Rachel. Here is the link.
She mentions in "The View" section that " As Button’s DataContext is the PageViewModel, she used a RelativeSource binding to find the ChangePageCommand".
Could any one explain me, how is that Button's DataContext is PageViewModel?
She has written another blog explaining about DataContext here. From this article it seemed to me that DataContext of the Button would be "ApplicationViewModel", because if the element's DataContext is not specified it will inherit DataContext of it's Parent. And as none of the elements specify any DataContext, it seems like DataContext of Button should be of Window element DataContext (which is "ApplicationViewModel" as defined in App.xaml.cs).
Obviously I am wrong here, but what is that I am not thinking correctly?
Other Code snippets can be found in the article, below is the XAML code.
<Window x:Class="SimpleMVVMExample.ApplicationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SimpleMVVMExample"
Title="Simple MVVM Example" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type local:HomeViewModel}">
<local:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ProductsViewModel}">
<local:ProductsView />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
Because you're inside of an ItemsControl's ItemTemplate. The DataContext is implicitly defined as the binding of each object provided by the ItemsSource binding collection.
The ItemsControl creates an ItemTemplate for each item in the ItemsSource collection. The DataContext of each ItemTemplate will be bound to the individual object that is being iterated in the collection. You can read more about datatemplate behavior here. (See Remarks)
So, in order to get to the ChangePageCommand provided by the window's DataContext , you have to provide a relative source lookup.

Binding one dependency property to another

I have a custom Tab Control that I have created, but I am having an issue. I have an Editable TextBox as part of the custom TabControl View.
<Controls:EditableTextControl x:Name="PageTypeName"
Style="{StaticResource ResourceKey={x:Type Controls:EditableTextControl}}" Grid.Row="0" TabIndex="0"
Uid="0"
AutomationProperties.AutomationId="PageTypeNameTextBox"
AutomationProperties.Name="PageTypeName"
Visibility="{Binding ElementName=PageTabControl,Path=ShowPageType}">
<Controls:EditableTextControl.ContextMenu>
<ContextMenu x:Name="TabContextMenu">
<MenuItem Header="Rename Page Type" Command="{Binding Path=PlacementTarget.EnterEditMode, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
AutomationProperties.AutomationId="RenamePageTypeMenuItem"
AutomationProperties.Name="RenamePageType"/>
<MenuItem Header="Delete Page Type" Command="{Binding Path=PageTypeDeletedCommand}"
AutomationProperties.AutomationId="DeletePageTypeMenuItem"
AutomationProperties.Name="DeletePageType"/>
</ContextMenu>
</Controls:EditableTextControl.ContextMenu>
<Controls:EditableTextControl.Content>
<!--<Binding Path="CurrentPageTypeViewModel.Name" Mode="TwoWay"/>-->
<Binding ElementName="PageTabControl" Path="CurrentPageTypeName" Mode ="TwoWay"/>
</Controls:EditableTextControl.Content>
</Controls:EditableTextControl>
In the Content section I am binding to a Dependency Prop called CurrentPageTypeName. This Depedency prop is part of this custom Tab Control.
public static DependencyProperty CurrentPageTypeNameProperty = DependencyProperty.Register("CurrentPageTypeName", typeof(object), typeof(TabControlView));
public object CurrentPageTypeName
{
get { return GetValue(CurrentPageTypeNameProperty) as object; }
set { SetValue(CurrentPageTypeNameProperty, value); }
}
In another view, where I am using the custom TabControl I then bind my property, with the actual name value, to CurrentPageTypeName property as seen below:
<Views:TabControlView Grid.Row="0" Name="RunPageTabControl"
TabItemsSource="{Binding RunPageTypeViewModels}"
SelectedTab="{Binding Converter={StaticResource debugConverter}}"
CurrentPageTypeName="{Binding Path=RunPageName, Mode=TwoWay}"
TabContentTemplateSelector="{StaticResource tabItemTemplateSelector}"
SelectedIndex="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.SelectedTabIndex}"
ShowPageType="Hidden" >
<!--<Views:TabControlView.TabContentTemplate>
<DataTemplate DataType="{x:Type ViewModels:RunPageTypeViewModel}">
<RunViews:RunPageTypeView/>
</DataTemplate>
</Views:TabControlView.TabContentTemplate>-->
</Views:TabControlView>
My problem is that nothing seems to be happening. It is grabbing its Content from the Itemsource, and not from my chained Dependency props. Is what I am trying even possible? If so, what have I done wrong.
Thanks for looking.
Unless I'm missing something this is definitely possible. Here is a simplified working example.
User control with a dependency property named TestValue, containing a TextBox bound to this property:
<UserControl x:Class="TestApp.TestControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"
x:Name="TestControlName">
<Grid>
<TextBox Text="{Binding ElementName=TestControlName, Path=TestValue, Mode=TwoWay}"/>
</Grid>
</UserControl>
A different view using this user control, binding the above mentioned dependency property to something:
<Window x:Class="TestApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestApp="clr-namespace:TestApp" Title="MainWindow"
Height="350" Width="525">
<StackPanel>
<TestApp:TestControl TestValue="{Binding ElementName=SourceTextBox, Path=Text, Mode=TwoWay}" />
<TextBox Name="SourceTextBox" />
</StackPanel>
</Window>
It sounds that the issue is somewhere in the part of the code you have not posted (e.g. wrong name used in Content binding).
I think you already solved this yourself for the "SelectedIndex" property. Just do the same thing for the "CurrentPageType" property i.e. use RelativeSource

WPF ListBox ItemsSource StaticResource/Binding question

Given the following code:
<Window x:Class="WpfApplication76.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<CollectionViewSource x:Key="myCol">
<CollectionViewSource.Source>
<col:ArrayList>
<ListBoxItem>Uno</ListBoxItem>
<ListBoxItem>Dos</ListBoxItem>
<ListBoxItem>Tres</ListBoxItem>
</col:ArrayList>
</CollectionViewSource.Source>
</CollectionViewSource>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{StaticResource myCol}" />
<ListBox ItemsSource="{Binding Source={StaticResource myCol}}" />
</Grid>
</Window>
In this example, the
<ListBox ItemsSource="{StaticResource myCol}" />
Gives me an error complaining that it cannot bind to a "CollectionViewSource" object.
But the other listbox:
<ListBox ItemsSource="{Binding Source={StaticResource myCol}}" />
binds perfectly fine.
So my question is why does one work and the other one does not? AT the end, aren't both ItenSources being set to the same "CollectionViewSource" object?
Thank you.
The ItemsSource property is of type IEnumerable. A CollectionViewSource is not an IEnumerable. CollectionViewSource's View property will give you an IEnumerable.
When you Bind to a CollectionViewSource the Binding is smart enough to grab the View property and actually bind to that. Maybe CollectionViewSource has a [DefaultBindingProperty] on it.
It boils down to the fact that when you go through the Binding you don't actually bind to the CollectionViewSource, but its View property.

Binding to a data template control property

Is it possible to bind something to a property of a control in a data template entirely in XAML? The following code is a simplified version of the problem I'm running into. I'd like the text of the TextBlock (displayName) to be updated as the user types in the TextBox located in the DataTemplate.
<Window x:Class="WpfApplication4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication4="clr-namespace:WpfApplication4"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type WpfApplication4:Foo}">
<TextBox Text="{Binding Path=Name}" />
</DataTemplate>
<WpfApplication4:Foo x:Key="testObject" Name="This is a test" />
</Window.Resources>
<StackPanel>
<TextBlock x:Name="displayName" Margin="5" />
<ContentControl x:Name="contentControl" Margin="5" Content="{StaticResource testObject}" />
</StackPanel>
No, at least, not from XAML. You could write code to traverse the visual tree and find the element you want to bind to, but that would be nasty.
But in your particular example, would it not make sense to just bind the TextBlock to the same data object (Foo instance)?

Resources