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)
Related
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.
Is there a keyword to directly bind to the DataContext and not to an attribute of it?
I heard about the workaround with a Self Object. My problem is that I open a Window, and give an ObservableCollection as argument, which is set to the DataContext.
Here the WPF(xaml.cs) ctor
public Depot(ObservableCollection<ObservableCollection<ItemManager.Item>> totalDepot)
{
this.FullDepotList = totalDepot;
this.DataContext = FullDepotList[1];
InitializeComponent();
}
The XAML Code snippet where I would preferably bind to the DataContext directly or to "this":
<WrapPanel>
<ListBox
ItemsSource="{Binding this, UpdateSourceTrigger=PropertyChanged}"
ItemTemplate="{DynamicResource ItemWithCoolTooltipTemplate}"
Focusable="False">
</ListBox>
</WrapPanel>
To bind directly to the DataContext and not to an attribute of it, don't write any binding Path. Make it just {Binding}. UpdateSourceTrigger=PropertyChanged is not needed because ItemsSource doesn't change from view.
<ListBox
ItemsSource="{Binding}"
ItemTemplate="{DynamicResource ItemWithCoolTooltipTemplate}"
Focusable="False">
</ListBox>
alternatively use Path=. to code "bind entire DataContext here" requirement
<ListBox
ItemsSource="{Binding Path=.}"
ItemTemplate="{DynamicResource ItemWithCoolTooltipTemplate}"
Focusable="False">
</ListBox>
any tricks with RelativeSource/ElementName are usually necessary to change binding source. In this case DataContext (binding source) is simply inherited from parent Window.
You can try the following trick.
Add name property to your Window - <Window ... Name="myWindow" ...>
Use such a construction to bind to the property or whatever you need - <ListBox ItemsSource="{Binding Path=DataContext, ElementName=myWindow}" ... />
I have xaml inside an xaml. The inside xaml has some bindings which are giving some problems. To explain here is my code
Main xaml
<TabItem Header="Configuration" DataContext="{Binding ComponentsVM}">
<Grid>
<ListBox ItemsSource="{Binding SomeList}" DisplayMemberPath="Name" SelectedItem="{Binding SomeComponent}" SelectedIndex="0"/>
<ig:MyInsideXamlElement Content="{Binding MyUserControl}" DataContext="{Binding}" />
</Grid>
</TabItem>
The inside xaml is
<Grid>
<TextBox Text="{Binding MySearchPath}"/>
</Grid>
The MyUserControl property displays my binding of UserControl without problems. But the MySearchPath property does not get updated with entity framework class. I suspect the binding of my inner xaml(MySearchPath) does not get resolved because the whole tab item's datacontext is ComponentsVM. Is there any way to give a second datacontext to the inner xaml?
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.
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>