Bind to DataContext of ItemsControl from within DataTemplate - c#

I'm using an ItemsControl, boiled down, in this way:
<UserControl.Resources>
<DataTemplate x:Key="HomeItemTemplate">
<UserControl Command="{Binding DataContext.MyCommand}"/>
</DataTemplate>
</UserControl.Resources>
<ItemsControl ItemTemplate="{StaticResource HomeItemTemplate}" />
Now, the view this ItemsControl is placed in has a DataContext. In that DataContext I have my MyCommand. I have to use this ItemsControl in multiple places, so directly referencing to an ElementName within the DataTemplate is something I don't want to do.
I tried TemplatedParent and RelativeSource, different stuff, I don't know what exactly I should do. How would I have to write this so my UserControl can bind to the DataContext of the ItemsControl?

If you have a DataTemplate/ItemTemplate in a resource dictionary, you might want to use the {RelativeSource FindAncestor} binding mode to bind to properties of the parent ItemsControl:
<UserControl.Resources>
<DataTemplate x:Key="HomeItemTemplate">
<UserControl Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}}"/>
</DataTemplate>
</UserControl.Resources>

Related

How to access data from ViewModel in ItemsSource tag

I'm making TabControl that can change dynamically using ItemsSource tag.
I want to know the way to access ViewModel data in ItemsSource tag.
I searched through the Internet. but I couldn't find the answer.
CODE
public class ViewModel
{
// this will be used in ItemsSource
private ObservableCollection<ActiveButton> _allExecuteButtonInfos = new ObservableCollection<ActiveButton>();
public ObservableCollection<ActiveButton> AllExecuteButtonInfos
{
get { return _allExecuteButtonInfos; }
set {
_allExecuteButtonInfos = value;
OnPropertyChanged();
}
}
// I want to get this data in ItemsSource
private List<string> _boardNameList = new List<string>();
public string BoardNameList
{
get { return _boardNameList; }
set {
_boardNameList = value;
OnPropertyChanged();
}
}
}
XAML
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" VerticalScrollBarVisibility="Auto">
<Grid VerticalAlignment="Stretch" Margin="0,0,0,0" >
<ComboBox Width="334" Margin="0,0,0,0" Grid.Column="1" Grid.Row="1" Height="22" VerticalAlignment="Top"
<!-- I want to get data from ViewModel not in ItemsSource(AllExecuteButtonInfos) -->
<!-- eg) VM:BoardNameList, ViewModel.BoardNameList etc -->
ItemsSource="{Binding BoardNameList, Mode=TwoWay , UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedBoard, Mode=TwoWay}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I hope I can find the answer.
Thank you.
You could bind to the DataContext, i.e. the view model, of the parent TabControl using a RelativeSource:
<ComboBox ...
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource AncestorType=TabControl}}" />
Note that it's pointless to set the Mode of an ItemsSource binding to TwoWay since the control never sets the property. It's also meaningless to set the UpdateSourceTrigger to PropertyChanged in this case for the same reason.
I am not sure where you've defined the data context but I suppose that it's somewhere above the first 'Grid' markup. Something like this?
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
Then you have to somehow refer to the Datacontext of the window. You can do it this way
<ComboBox
ItemsSource="{Binding DataContext.BoardNameList, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
if the name of your view is not 'MainWindow', you have to change it to the view name where you have that code.
One of the best ways is to create a UserControl for each model and then put data templates in TabControl.Resources with DataType specified for all types you could put in ItemsSource - you get full customization of the view with nice seperation of XAML files.
<Grid>
<TabControl Background="#FF292929" ItemsSource="{Binding AllExecuteButtonInfos}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type MyViewModel1}">
<MyViewModel1_View ViewModel="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type MyViewModel2}">
<MyViewModel2_View ViewModel="{Binding}"/>
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Grid>
I'm going from memory, so the binding may be done differently, but that's the basic idea.
That, or you use some kind of ViewResolver as the only item in the TabControl (something like this)
Basically, go even more MVVM :)
Provided that the DataContext of your view is set correctly to your ViewModel and AllExecuteButtonInfos is indeed available in your view, you can use a RelativeBinding to access properties which are not in the DataContext of your current scope.
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type UserControl}}, Path=DataContext.BoardNameList}" />
With that, you are leaving the implicit DataContext of the DataTemplate, which is ActiveButton and access the object of the specified type via AncestorType. From there you can set a Path to the DataContext of the UserControl, which is, in your case, an object of the class ViewModel.
Imaging you are climbing up a ladder. From the ComboBox object up to your UserControl, from where you can access all underlying properties.

Access userctrol's datacontext does not work for me

In wpf mvvm mode, i have a usercontrol like this
<UserControl MyControl>
<Grid>
<DataGrid
ItemsSource="{Binding MySource}"
Visibility = "{Binding the usercontrol's datacontext.UserGrade}"
/>
</Grid>
</UserControl>
In my MainPageView I use it like this
<Window:MainPageView
xmlns:vm="clr-namespace:My.ViewModel"
xmlns:userCtl="clr-namespace:My.Controls"
<Window.DataContext>
<vm:MainPageViewModel/>
</Window.DataContext>
<userCtl:MyControl>
<userCtl:Mycontrol.DataContext>
<vm:MyControlViewModel/>
</userCtl:Mycontrol.DataContext>
<userCtl:MyControl>
</Window:MainPageView>
Now here's the question, how can I access the MyUserControl's datacontext.UserVisiable, and binding to the MyUserControl's datagrid visibility? I tried to use {RelativeSource FindAncestor, AncestorType={x:Type UserControl}} but it did not work, or I didi it wrong? Thanks!
You could try this:
<Grid>
<DataGrid ItemsSource="{Binding MySource}"
Visibility = "{Binding DataContext.UserGrade, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</Grid>
Explanation: Using RelativeSource for the Binding Source, helps you navigate throw the visual tree, to the first ancestor of the current control, of the type specified (UserControl). It then uses the UserControl.DataContext.UserGrade as the binding property.
If the Usercontrol.DataContext is null, then the binding will not work. As specified in the question, userControl has a DataContext that contains that property.
Also, you could try setting the AncestorType=location:MyControl, in case UserControl is not enough. (location: is the namespace where your control is located)

UserControl's DependencyProperty is null when UserControl has a DataContext

I have added a DependencyProperty to my View, binding to the DependencyProperty works, but only if I do not also set the DataContext.
GenericView.xaml
<UserControl x:Class="GenericProject.View.GenericView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Button Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<TextBox IsEnabled="False" Text="{Binding SomeProperty, Mode=OneWay}" />
</StackPanel>
</UserControl>
GenericView.xaml.cs
public partial class GenericView : UserControl
{
// The DependencyProperty for VMFactory.
public static readonly DependencyProperty VMFactoryProperty = DependencyProperty.Register("VMFactory", typeof(VMFactoryViewModel<GenericViewModel>), typeof(GenericView));
public VMFactoryViewModel<GenericViewModel> VMFactory
{
get { return (VMFactoryViewModel<GenericViewModel>)GetValue(VMFactoryProperty); }
set { SetValue(VMFactoryProperty, value); }
}
public GenericView()
{
InitializeComponent();
}
}
Here I am creating two views to illustrate the issue at hand. The VMFactory binding in the first view will fail because I have DataContext set. The second view will succeed, what is the cause of this behavior?
MainPage.xaml
<vw:GenericView DataContext="{Binding Generic}" VMFactory="{Binding GenericFactory}" />
<vw:GenericView VMFactory="{Binding GenericFactory}" />
This is a fairly common Binding "gotcha"...
In order to access VMFactory, you need to bind your UserControl to itself using...
DataContext="{Binding RelativeSource={RelativeSource Self}}"
You would not then bind DataContext on a GenericView item to anything elsewhere.
However, if you are intending to bind other values to VMFactory external to the UserControl (i.e. <vw:GenericView VMFactory={Binding ...}"/>), you should use RelativeSource with mode FindAncestor for type UserControl.
<!-- Shortened to show pertinent Binding -->
<ctrl:CommandTextBox Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
I've got a working solution, it seems as though properties of a control are bound relative to the DataContext of the control?
I was certainly aware items within the control would be bound relative to the DataContext, but I apparently have never used a control in this way before and did not understand that properties of the control would also inherit the scope of the set DataContext. Essentially everything within my View was correct, but the binding to my DependencyProperty was failing.
GenericView.xaml
<!-- Everything in here was correct. -->
<UserControl x:Class="GenericProject.View.GenericView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<Button Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" />
<TextBox IsEnabled="False" Text="{Binding SomeProperty, Mode=OneWay}" />
</StackPanel>
</UserControl>
MainPage.xaml
<!-- This is where I messed up earlier, VMFactory requires a relative binding. -->
<vw:GenericView DataContext="{Binding Generic}"
VMFactory="{Binding DataContext.GenericFactory, RelativeSource={RelativeSource AncestorType={x:Type Page}}}" />
As #toadflakz said, this is a very common issue in WPF and one that took me a while to get my head around when I was learning WPF. Luckily, the solution is simple. Let's say that we have a UserControl that has an object set as its DataContext and another set as the value of a DependencyProperty that is declared within the UserControl... your situation.
From within the UserControl XAML, you can data bind to a property of the object set as the DataContext as normal:
<TextBlock Text="{Binding PropertyFromDataContextObject}" />
If you want to data bind to an object from the object set as the value of the DependencyProperty, you can simply use a RelativeSource Binding:
<TextBlock Text="{Binding PropertyFromDependencyPropertyObject, RelativeSource={
RelativeSource AncestorType={x:Type YourPrefix:YourUserControl}}}" />
Note that both of these Bindings can be used together in the same UserControl as long as both of the DataContext and DependencyProperty properties have been set.

Can't Reach ViewModel From Within ListBox

I have a ViewModel set as the highest level DataContext for my wpf application but I can't seem to access it when I jump into a ListBox as the new DataContext is the element of the list. A simple example is below.
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<!--1D List-->
<ListBox ItemsSource="{Binding my_list}">
<ListBox.ItemTemplate>
<DataTemplate>
<!--Individual Elements-->
<TetxBlock Text="{Binding ViewModel_DisplayString}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
That example won't actually work when it comes to the button click as the ViewModel_ClickFunction isn't on the items within the class.
So is there anyway for me to do {Binding parent.selected_item} or something like that? I just need to be able to access the ViewModel from within the ListBox.
The DataContext inside ItemTemplate is actually the item itself. So in this case you have to use RelativeSource to walk up the visual tree (to the ListBox) and change the Path to DataContext.ViewModel_DisplayString:
<TetxBlock Text="{Binding DataContext.ViewModel_DisplayString,
RelativeSource={RelativeSource AncestorType=ListBox}}"/>

Changing content according to selected option in WPF

I'm interested in creating an app that displays some buttons and changes a viewport according to the selected button. The viewport in my app is a ContentControl and I thought of changing its content whenever a button is clicked. However, I believe there's a better approach, by perhaps injecting the ViewModels of each of the Views I want to present to the ContentControl and styling them using DataTemplates (Since I want to avoid having a grid with many controls and just setting their Visibility property whenever I want to show a particular view). Which of the approaches seems better to you? Do you have a different approach for this?
The view should be something similar to this:
Thanks!
Usually have a ViewModel behind the window which contains:
ObservableCollection<IViewModel> AvailableViewModels
IViewModel SelectedViewModel
ICommand SetCurrentViewModelCommand
I display the AvailableViewModels using an ItemsControl, which has its ItemTemplate set to a Button. The Button.Command is bound to the SetCurrentViewModelCommand, and it passes the current data item from the AvailableViewModels collection in through the CommandParameter
To display the content area, I use a ContentControl with ContentControl.Content bound to SelectedViewModel, and DataTemplates get used to tell WPF how to render each ViewModel.
The end result is my XAML looks something like this:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:ViewA />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<local:ViewB />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border DockPanel.Dock="Left" BorderBrush="Black" BorderThickness="0,0,1,0">
<ItemsControl ItemsSource="{Binding AvailableViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}"
Command="{Binding DataContext.SetCurrentViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }"
Margin="2,5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding SelectedViewModel}" />
</DockPanel>
You can view an example of the full code used for such a setup on my blog

Categories

Resources