WPF: Accessing two DataContexts in the same control - c#

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}

Related

How can you bind to a ViewModel property from a DataTemplate

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.

Multiple DataContext bindings in UWP Page and CommandBar

I need to bind some specific elements in my UWP page to a different VIewModel than what the page has.
So in my page I bind to one DataContext:
<Page
DataContext="{Binding RootViewModel, Source={StaticResource Locator}}">
...
and I am now trying to bind the bottomAppBar CommandBar to a different ViewModel:
<Page.BottomAppBar>
<CommandBar DataContext="{Binding OtherViewModel, Source={StaticResource Locator}}">
<CommandBar.Content>
<TextBlock Text="{Binding Status}"></TextBlock>
But it doesn't work, the binding does not occur in the CommandBar.
Why?
By design, the DataContext of the CommandBar doesn't get passed to its Content. Actually, only the DataContext property of a child element will automatically get the same DataContext of its parent, unless you specifically break the flow by assigning another value to it.
In your case, the DataContext of your CommandBar will be OtherViewModel, but the Content property will remain null.
So instead of manually setting the DataContext, you should set its Content instead.
<CommandBar Content="{Binding OtherViewModel, Source={StaticResource Locator}}">
<CommandBar.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Status}"></TextBlock>
</DataTemplate>
</CommandBar.ContentTemplate>
...
</CommandBar>

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}"

Propagate DataContext to Dependency Property from ContentControl

I have a styled ContentControl with a DependencyProperty which, among others, has a DataTemplate property (StatusTemplate) that I have to assign to a DataTemplate I got in resources (StatusTemplate1).
The ContentControl is shown and the 'Binding:: ' text from StatusTemplate1 is also displayed, but the binding is empty.
If I'm not mistaken, ContentControl doesn't propagate its DataContext to their Content, so the question is: is there any workaround I could use in order StatusTemplate1 receives the DataContext ? I'd prefer to use XAML only for this, but I've got no problem if code-behind is the way to go.
EDIT: the problem here is how to propagate the DataContext to the dependency property StatusTemplate. I changed the question title as it seems to be misleading.
The DataTemplate:
<DataTemplate x:Key="StatusTemplate1">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Binding:: " />
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
And the ContentControl:
<ContentControl Style="{Binding Path=Status, Converter={StaticResource StatusToStyleConverter}}"
dp1:AddOn.StatusTemplate="{StaticResource StatusTemplate1}">
</ContentControl>
Thanks
I have a very similar scenario, and if I recall correctly,
Content="{Binding}"
As an attribute of the contentcontrol actually works.

Categories