How can you bind to a ViewModel property from a DataTemplate - c#

So I have a DataTemplate that I'm trying to bind to. The idea is that there is a checkbox in the app that can be used to turn what are considered "Advanced Values" on or off. I figured I could use an IValueConverter and pass in whether or not the value is advanced and whether or not the CheckBox is checked. The problem is that I can't figure out how to bind anything outside of the DataTemplate's x:DataType. Below is an approximation of the code:
<CheckBox x:Name="ShowAdvancedCheckBox" IsChecked="{x:Bind ViewModel.ShowAdvanced}"/>
...
<DataTemplate x:Key="StringParameterTemplate" x:DataType="datatypes:BatchParameter">
<TextBox
Header="{x:Bind Name}"
Text="{x:Bind Value, Mode=TwoWay}"
Visibility="{x:Bind IsAdvanced, Converter={StaticResource IsAdvancedToVisibility}, ConverterParameter=???}" />
</DataTemplate>
I'm unsure what to put into the ConverterParameter though. Any advice?

When you are in DataTemplate, Your DataContext is not your ViewModel.
To bind the ViewModel property, Set x:Name for your page or UserControl (XAML) and Bind using ElementName eg:
<UserControl
...
x:Name = ShowAdvancedView>
...
<DataTemplate x:Key="StringParameterTemplate" x:DataType="datatypes:BatchParameter">
<TextBox
Header="{x:Bind Name}"
Text="{x:Bind Value, Mode=TwoWay}"
Visibility="{Binding ViewModel.ShowAdvanced,ElementName = ShowAdvancedView, Mode = OneWay, Converter={StaticResource IsAdvancedToVisibility}}" />
</DataTemplate>
</UserControl>
When using this code UserControl is the source of Binding and Its DataContext is your ViewModel.

Related

how to bind IsEnabled to property of vm in xaml for button in DataTemplate?

I have the button with DataTemplate in my telerik:GridViewDataColumn.CellTemplate xaml.
I want to bind the IsEnabled Property in xaml to my prop in viewmodel.
my prop is filled with correct value in viewmodel but didn't show changes in xaml. How to do this?
<telerik:GridViewDataColumn.CellTemplate>
<DataTemplate>
<Button Content="Edit"
VerticalAlignment="Center"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type telerik:RadGridView }},Path=DataContext.SendDateCommand}"
CommandParameter="{Binding}" IsEnabled="{Binding IsSetSendDate,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</telerik:GridViewDataColumn.CellTemplate>
First of all, changing the Mode to TwoWay for IsEnabled should help. I would also check to ensure that IsSetSendDate is declared as public and has a Getter and Setter in your class. Also make sure that DataContext is set for the xaml, either by declaring with <Window.Resources> tag or by setting DataContext in the xaml.cs constructor.

How to bind to a source inside a ListBox different from the ItemsSource already specified

I have a ListBox inside a HubSection, whose Items are bound to a class "players" added to my DefaulViewModel via code behind.
First I simply put a TextBox bound to the property "PlayerName" of my class "players".
Now I would like to add a ComboBox with some items that are NOT part of the class players.
Is it possible ? I thought that definind an ItemsSource in the ComboBox would sort of override the ItemsSource of the ListBox, but nothing displays.
The DataContext of the whole page is defined like so:
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
Then the HubSection is like so:
<HubSection x:Name="HubSec1">
<DataTemplate>
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<ComboBox ItemsSource="{Binding Path=ListOfElements}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</HubSection>
If I define the ComboBox in the same way but outside the ListBox, it will display the string elements of "ListOfElements" properly.
But in this ListBox, the ComboBox is empty. So my guess is that having defined an ItemsSource for the ListBox, it is not possible to override it.
I have tried to define a DataTemplate but was not successful doing so, but it might be the good solution (and I did not proceed properly)
What am I missing ?
Edit :
The ComboBox items is an ObservableCollection. It is not part of the "players" class.
Here is how I added these elements to the DefaultViewModel
DefaultViewModel.Add("players", players);
DefaultViewModel.Add("MyItemsList", ListOfElements);
You can walk up the visual tree and bind to an ancestors datacontext:
{Binding Path=PathToProperty, RelativeSource={RelativeSource AncestorType={x:Type typeOfAncestor}}}
EX:
{Binding Path=ListOfItems, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
that should give you the datacontext that the listbox has, so assuming your ListOfItems exists in that data context.
Or you can name your control, and then bind to its datacontext by element name:
{Binding ElementName=mySourceElement,Path=ListOfItems}
It can be a little bit tricky to create a good working binding in Windows Apps. A widely used work around is to use the Tag property.
<ListBox x:Name="ListBox1" ItemsSource="{Binding players}" Margin="0,184,0,0" Tag="{Binding Path=ListOfElements}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBox Text="{Binding Path=PlayerName, Mode=TwoWay}"/>
<TextBox Text="{Binding Path=Tag, ElementName=ListBox1}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
A binding to an element wirh the specific name will work always. And the ListOfElements should be in the scope of the ListBox so you can use the Tag property as a proxy. If you need to bind more than one property, you can also use dummy XAML elements:
<Border Tag="{Binding ...}" Name="dummy1"/>

Binding to a second property

I have two properties in my viewmodel, called Premises and Towns.
I'm binding my ListViewItems to Premises, and in the itemtemplate I want to bind to Towns, but when I use the following XAML it tries to bind to Premises.Towns instead of Towns.
How can I bind to Towns directly?
Viewmodel:
public class MainWindowViewModel
{
public ObservableCollection<Premise> Premises;
public List<Town> Towns;
}
XAML:
<ListView x:Name="PremisesList" Margin="195,35,10,10"
ItemContainerStyle="{StaticResource OverviewListViewItemStyle}"
ItemsSource="{Binding Premises}" HorizontalContentAlignment="Stretch">
And this is what's in my OverviewListViewItemStyle.
<ComboBox ItemsSource="{Binding Towns}" Grid.Row="2" Grid.ColumnSpan="3">
<ComboBox.ItemTemplate>
<DataTemplate>
<ComboBoxItem>
<TextBox Text="{Binding Name}" />
</ComboBoxItem>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I'd like to be able to select a Town for a Premise via XAML.
You are correct in your assumption. ComboBox looks for Towns in Premise class, which is the class behind each ListViewItem If you want to refer to same context as ListView you need to use RelativeSource binding.
<ComboBox
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Towns}"
Grid.Row="2"
Grid.ColumnSpan="3"
DisplayMemberPath="Name"/>
Not related to your problem but you also don't need to specify DataTemplate to display single property. DisplayMemberPath will work as well. If you do specify DataTemplate you don't need to use ComboBoxItem as ComboBox will wrap DataTemplate content in ComboBoxItem so effectively you'll end up with ComboBoxItem inside another ComboBoxItem
You bind the ItemsSource to the Premises property therefore if you bind to the Towns in the OverviewListViewItemStyle the binding engine will look up in the Premise object for a property called Towns.
If you want to select a town for a premises you should tell to the combobox where to look from that property. You can try to set the combobox's datacontext to the main viewmodel with relative source in the binding. Something like that:
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.Towns}"

WPF Binding a Dependency Property

I am having an issue binding a dependency property in a UserControl. When it initializes it gets a value but then it will not update. I've probably missed something obvious, here are some code snippets:
This is where I bind the BalanceContent dependency property:
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4"
BalanceContent="{Binding Source={StaticResource UserData}, Path=SelectedUser.Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Game:PlayerDetails>
Here is the TextBox in the UserControl:
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}"
Grid.Row="7"></TextBox>
Here is the Dependency Property:
public static readonly DependencyProperty BalanceContentProperty = DependencyProperty.Register(
"BalanceContent", typeof(string), typeof(PlayerDetails));
public string BalanceContent
{
get
{return (string) GetValue(BalanceContentProperty);}
set
{SetValue(BalanceContentProperty, value);}
}
Here is the list where the selected user is updated, which is in a view that uses the UserControl:
<ListView x:Name="lstAccounts" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Grid.RowSpan="4"
ItemsSource="{Binding Source={StaticResource UserData}, Path=CurrentUserSearch}"
SelectedItem="{Binding Source={StaticResource UserData}, Path=SelectedUser}"
And SelectedUser is defined here in a class that implements INotifyPropertyChanged:
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedUser"));
}
}
The idea is that the TextBox should update when a new user is selected in the list but at the moment it is not doing so. I've put the binding on local TextBox and it updates fine, just not on a DependencyProperty. Any help appreciated.
There are some possibilities you could try:
First, your ListView may not be updating yours ViewModel's SelectedUser property. Try setting the binding in your ListView to "TwoWay" mode:
<ListView x:Name="lstAccounts" Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Grid.RowSpan="4"
ItemsSource="{Binding Source={StaticResource UserData}, Path=CurrentUserSearch}"
SelectedItem="{Binding Source={StaticResource UserData}, Path=SelectedUser, Mode=TwoWay}"/>
You can organize better the way the DataContext's are defined. Remember that all the child controls of your UserControl will have access to its DataContext without the use of relative binding (they inherit it). As your PlayerInfo control depends on the SelectedUser, consider setting it's DataContext to the SelectedUser, either binding it to the SelectedUser of the ListView or the SelectedUser in the UserData viewmodel.
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{Binding Source={StaticResource UserData}, Path=SelectedUser}"
BalanceContent="{Binding Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Game:PlayerDetails>
The source of the current SelectedUser could also be the ListView:
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{Binding SelectedItem, ElementName=lstAccounts}"
BalanceContent="{Binding Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Game:PlayerDetails>
Either way, you will then be able to do the following on the TextBox, because its DataContext will be the same as the one of its parent:
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding Balance}"
Grid.Row="7"></TextBox>
If the usercontrol depends on the root viewmodel for things like commands and other high level logic, then set the DataContext to it in a way you can easily access the SelectedUser.
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{StaticResource UserData}"
BalanceContent="{Binding SelectedUser.Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Game:PlayerDetails>
So you can do this:
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding SelectedUser.Balance}"
Grid.Row="7"></TextBox>
In this second approach, however, you will have to check one thing I'm not sure about. I know that when the DataContext of a control changes, it will update all the dependent bindings allright. For example, if you changed the PlayerDetails DataContext to another UserData instance, the BalanceContent property would update as well. However, in this case the BalanceContent depends on property of the SelectedUser of the UserData. So it will listen to Property changes of that instance of User. If the SelectedUser.Balance changes (and User implements INotifyPropertyChanged, or it is a DependencyProperty), BalanceContent will update. Now, if the SelectedUser instance in the UserData changes, I'm not sure BalanceContent will update, because I think that a binding does not listen to changes of every object in its path.
EDIT
The last point was perhaps the first problem I hit when developing with xaml. I had a DataGrid in Silverlight whose entity type had a property of a complex type. One of the columns depended on a property of the complex type. If I changed the value of the complex type, the column would update fine (it implemented INPC). If I changed the complex type instance of an entity, the column would not... The solution was to cascade DataContexts: I created a template column, set the binding of the column for the complex type, instead of its property. Then I bound the text of the TextBox of my template to the property of the complextype, because it was now the TextBox's DataContext.
In your case you can do it for the TextBox.Text, but not for the PlayerDetails.BalanceContent. You can bind the TextBox.DataContext to the SelectedUser of the UserData and then bind Text to the Balance property.
<TextBox VerticalAlignment="Center" DataContext="{Binding SelectedUser}" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding Balance}"
Grid.Row="7"></TextBox>
Please try testing after changing your binding for text box inside your user control to bind to BalanceContent which is User Controls Dependency Property (your original binding source seems to be data context property)
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}"
EDIT: Please try the following code
public User SelectedUser
{
get
{
return _selectedUser;
}
set
{
_selectedUser = value;
OnPropertyChanged(new PropertyChangedEventArgs("SelectedUser"));
OnPropertyChanged(new PropertyChangedEventArgs("SelectedUserBalance"));
}
}
public string SelectedUserBalance
{
get
{
return _selectedUser.Balance;
}
}
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4"
BalanceContent="{Binding Source={StaticResource UserData}, Path=SelectedUserBalance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
</Game:PlayerDetails>
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}"
Grid.Row="7"></TextBox>
The Textbox is trying to bind to the UserControls DataContext. give an x:Name to the Usercontrol definition and set the TextBox's DataContext to DataContext="{Binding ElementName=[YourControlName]}"
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2"
Text="{Binding SelectedItem.BalanceContent, ElementName=lstAccounts}" Grid.Row="7"></TextBox>
forget binding of UserControl , Directly bind TextBox to SelectedItem . I hope this will help.
You are binding a StaticResource to the property. StaticResources, as opposed to DynamicResources, are loaded only once, when the Window (or UserControl) is being initialised. DynamicResources are loaded only when needed (and each time when needed). Any change that is made to a DynamicResource is picked up immediately and the property is updated accordingly. Simply replace the keyword "StaticResource" with "DynamicResource", and the property binding should update each time the resource changes.
Note: If the StaticResource is an object that derives from the Freezable class, it will also behave somewhat like a DynamicResource. For example, if your resource is a Brush, the bound property will update each time you change the Brush object in any way (by changing its opacity, for example), but this is due to the behaviour inherited from the Freezable class. However if you replace the Brush object in the StaticResource by a new Brush object, this change won't be picked up, because the change has been made to the Resource, which is Static, not the Brush.
Also Note: Even resources that are only available as StaticResources, such as data SystemColors, SystemFonts (which provide access to system settings) can be wrapped in a DynamicResource to ensure that the property is updated at each change. This can be done this way:
<Control Property="{DynamicResource {x:Static SystemColors.XXX}}"></Control>.

WPF: Accessing two DataContexts in the same control

I am using an MVVM approach, and I have an object from my ViewModel called DatabasesSubFrame which is DataTemplated to show a ListBox. I want to display a Button below the ListBox, which binds to both the currently SelectedItem, and a property on the DatabasesSubFrame object which is being DataTemplated.
I know how to refer to the currently selected item, by setting the DataContext on a shared ancestor with the ListBox and use {Binding /}. In this example the shared ancestor is a StackPanel. And if the DataContext wasn't explicitly set there I could easily bind to a property on the DatabasesSubFrame object by just doing {Binding SomeProperty}. However, if I do {Binding SomeProperty} within the explicitly set DataContext, it refers to the wrong DataContext.
How do I access the "original" DataContext here? I tried messing with RelativeSources and TemplatedParents but couldn't figure out how to fit them in.
<DataTemplate DataType="{x:Type VM:DatabasesSubFrame}">
<StackPanel DataContext="{Binding Databases}" >
<ListBox Name="DbInfoBox"
ItemsSource="{Binding}"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding ShortName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Problem: The Command and V:CreateCommandBinding.Command are set incorrectly here. How do I access OpenDbCommand from the top-level DataTemplate's DataContext? -->
<Button Content="Open Database"
CommandParameter="{Binding /}"
Command="{Binding ???, Path=OpenDbCommand.Command}"
V:CreateCommandBinding.Command="{Binding ???, Path=DataContext.OpenDbCommand}"/>
</StackPanel>
</DataTemplate>
I think this question will help you to find the answer to yours. Another trick is to set the Name of the Window to something like "Root". You can then get at the window's original datacontext by using:
{Binding ElementName=Root, Path=DataContext.MyViewModelsProperty}

Categories