Using Wpf RelativeSource Binding breaks Blendability - wpf

I've got the following scenario... I have a Window that contains an ItemsControl. I specify a ViewModel for the Window's DataContext. I specify a DataTemplate for the ItemControl's ItemTemplate. In the the DataTemplate I use a ComboBox and for the ComboBox's ItemsSource I use a RelativeSource Binding to its containing Window's DataContext. During Run-Time everything works fine and the Binding is resolved correctly, but during Design-Time Cider can't pick up the containing Window's ViewModel to which the ItemSource is binding.
Here is my code (I left out the xml namespace declarations at the top, but in my code they are included):
<Window d:DataContext="{Binding Source={StaticResource DesignViewModel}}">
<Window.Resources>
<designviewmodels:GenresEditorDesignViewModel x:Key="DesignViewModel" />
</Window.Resources>
<ItemsControl Grid.Row="0" Margin="3" ItemsSource="{Binding Path=CurrentState}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid DataContext="{Binding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="3,0,3,0"
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.AvailableGenres,
Mode=OneWay}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=Genre, Mode=TwoWay}" DataContext="
{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
So basically from the above piece of code the Path=DataContext.AvailableGenres can not be resolved during Design Time, but during Run Time it is resolved correctly.
Does anybody know if i'm doing something wrong or is it a problem with the Wpf xaml parser that it cannot resolve bindings to RelativeSources during design time?

I know this is an old question, but for the sake of posterity I have a solution that works for me.
I have never been able to get RelativeSource bindings to be Blendable. However, you can provide sign-posts to the design-time environment(s) if you are fortunate enough to have an ancestor without a binding set against it.
On the spare ancestor (in this case, the Grid) bind the DataContext to the same RelativeSource except with the Path set to just the DataContext. Then, apply a d:DataContext to the same ancestor, providing it with the type (or equivalent mock) you want to bind to on your actual, original element. Finally, bind your original element (the ComboBox) to the property or path as per normal.
<Grid
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext,
Mode=OneWay}"
d:DataContext="{Binding Source={StaticResource DesignViewModel}}" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="20"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="0" Margin="3,0,3,0"
ItemsSource="{Binding Path=AvailableGenres, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=Genre, Mode=TwoWay}" DataContext="
{Binding}" />
</Grid>

Related

Listbox DataTemplate field binding isn't working

I have a list of employees entities, bound to a listbox that implements a DataTemplate.
DataTemplate:
<DataTemplate x:Key="EmployeeTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Vertical"
VerticalAlignment="Top" Margin="2">
<TextBlock Foreground="Black" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Path=Test}">
</TextBlock>
<TextBlock Foreground="Black" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Path=Language.ContactNumber}">
</TextBlock>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Vertical"
VerticalAlignment="Top" Margin="2">
<TextBlock Foreground="Black" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Path=IdNumber}">
</TextBlock>
<TextBlock Foreground="Black" FontSize="12"
VerticalAlignment="Bottom" Text="{Binding Path=ContactNumber}">
</TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
Listbox:
<ListBox ItemsSource="{Binding Path=Employees}"
x:Name="ListBoxEmployees"
ItemTemplate="{DynamicResource EmployeeTemplate}"
BorderBrush="DarkGray"
Margin="5"/>
My Datacontext is a viewModel called EmployeeViewModel, it contains the collection of employees. This binding works fine, the employees gets displayed and all is good. The Problem is that the EmployeeViewModel inherits from a base abstract ViewModel that contains a static property called Language. This model has various fields that I bind labels to all over the app. The values in the data template does not work. Why?
Additional info: This listbox is in a usercontrol on the mainwindow.xaml
Edit:
xmlns:viewModels="clr-namespace:POC.DesktopClient.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModels:EmployeeViewModel}"
These XML namespaces at the top of my usercontrol allows xaml intelisense when binding. The intelisence does pick up the language object and its fields within.
As you stated Language property lies in ViewModel class. But having it in DataTemplate, will make it to search for in Employee class and not in your ViewModel.
You need to access ListBox's DataContext which you can do using RelativeSource:
<TextBlock Text="{Binding Path=DataContext.Language.ContactNumber,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListBox}}"/>
UPDATE
For comment:
Binding resolves correctly using this, but it still does not update
when I change the language model.
First of all for instance properties to be updated on GUI you need to implement INotifyPropertyChanged on your ViewModel class.
But however, this won't work for static properties. For static properties to be updated on GUI, you have to rebind them till WPF 4.5. If you are using WPF 4.5, you can do that using StaticPropertyChanged. Please refer to the details here WPF 4.5 binding and change notification for static properties.

Using multiple Treeview objects with the same (but cloned) itemssource

So, I have a listview with different datatemplates as seen here:
<ListView Panel.ZIndex="0" x:Name="FilterList" Margin="10,0" Grid.Row="2"
Grid.ColumnSpan="3" Background="White" ItemTemplateSelector="{StaticResource
ReportFilterTemplateSelector}" ItemsSource="{Binding reportParameters,
Mode=TwoWay}" ScrollViewer.CanContentScroll="False">
One of my sample datatemplates can be seen below. Everything shows up great. My problem, is that for this (and other) datatemplates, I can have multiple instances of the same one. In this particular instance, the treeview itemssource is bound to DataContext.OfficeListText to populate all the elements.
<DataTemplate x:Key="office">
<Grid MinHeight="35" MaxHeight="250">
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding rpName}" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
<Expander HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
Header="{Binding Path=DataContext.OfficeListText, RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
VerticalAlignment="Top" ExpandDirection="Down">
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding
Path=DataContext.OfficeList, RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type UserControl}}, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>
</Expander>
</Grid>
</DataTemplate>
The main problem with this is one, for instance, if I select an office in say the first treeview, the 2nd treeview shows the same. Essentially I want them to have the same itemssource initially but have separate instances. Since they are generated dynamically that is where I'm getting stuck. Any help would be appreciated.
I'm not sure what other code would be necessary, since I'm sure most of it will be irreverent based on what I'll need to do to make this work, but if you would like more I will gladly provide. Thanks!
Currently your TreeView binds to a single instance of OfficeList that belongs to your UserControl's DataContext. This means that every TreeView points to the same list. If I understand your question correctly, what you really want is to have a different instance of OfficeList for each TreeView.
I don't recommend instantiating a new OfficeList each time the DataTemplate is applied to a reportParameter. You could do that with a ValueConverter, but it would be pretty hacky.
The cleaner solution would be to have a class that contains the data for your reportParameter, plus an instance of OfficeList, and then bind to instances of that class instead of binding to the UserControl. Depending on how the reportParameters are structured (I am going to assume that reportParameters is a list of objects of type ReportParameter), there are two ways you might want to do this:
1) If ReportParameter is a ViewModel, you can simply add an OfficeList property to the ReportParameter class, and initialize it when you instantiate each ReportParameter.
Then your TreeView would look like this:
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding Path=OfficeList, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>
2) If ReportParameter is not a ViewModel (and thus it would not be appropriate to add an OfficeList parameter), then create a new class called something like ReportParameterViewModel that contains 2 properties: ReportParameter and OfficeList. Instead of binding your ListView to a list of ReportParameter, bind to a list of ReportParameterViewModel.
Then your ListView will look something like this:
<ListView Panel.ZIndex="0" x:Name="FilterList" Margin="10,0" Grid.Row="2"
Grid.ColumnSpan="3" Background="White" ItemTemplateSelector="{StaticResource
ReportFilterTemplateSelector}" ItemsSource="{Binding reportParameterViewModels,
Mode=TwoWay}" ScrollViewer.CanContentScroll="False">
Your TextBlock will look like this:
<TextBlock Text="{Binding ReportParameter.rpName}" VerticalAlignment="Center" Grid.Row="0" Grid.Column="0" />
Your TreeView will look like this:
<TreeView Tag="{Binding rpParameter}" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ItemsSource="{Binding Path=OfficeList, Mode=TwoWay}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"/>

Binding to viewmodel from inside a datatemplate

I have multiple videos displayed they are bound with a videocollection in Mainviewmodel. Everything works fine untill I try to bind the enter command to Mainviewmodel. I Don't know the syntax for this. As it stands the binding is set to Video and not Mainviewmodel.
Errormessage:
'StartVideoCommand' property not found on 'object' ''Video'
Xaml:
<Window.Resources>
<local:MainViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModel}">
<ListBox ItemsSource="{Binding Videos}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.InputBindings>
!!! <KeyBinding Key="Enter" Command="{Binding StartVideo}" /> !Bound to Video not to Mainviewmodel grrr
</Grid.InputBindings>
... layout stuff
<TextBlock Text="{Binding Title}" Grid.Column="0" Grid.Row="0" Foreground="White"/>
<TextBlock Text="{Binding Date}" Grid.Column="0" Grid.Row="1" Foreground="White" HorizontalAlignment="Left"/>
<TextBlock Text="{Binding Length}" Grid.Column="1" Grid.Row="1" Foreground="White" HorizontalAlignment="Right"/>
... closing tags
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.StartVideo}"
Another approach would be to use ElementName binding instead of RelativeSource.
Example:
<Window x:Name="root" ... >
...
Command="{Binding ElementName=root, Path=DataContext.StartVideo}"
...
A possible advantage over RelativeSource is that this is explicit; if someone changes the XAML hierarchy then relative references could break unintentionally. (Not likely in this specific example of binding to a Window however).
Also if your "root" element already is named then so much the better it is easy to take advantage of.
It is also somewhat more readable.

Binding to viewmodel's property from a template

I made a view for my GameViewModel
I have some xaml like that
<UserControl.Resources>
<DataTemplate DataType="{x:Type ViewModels:PlayerViewModel}">
<StackPanel>
<Button
Content="{Binding ?????}"
Command="{Binding WrongAnswerCommand}" />
<TextBlock
Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
ItemsSource="{Binding Players}"
IsSynchronizedWithCurrentItem="True">
</ListBox>
</Grid>
So, here it is an observable collection Players.
I need the button content to be binded to GameViewModel's property.
What should I use?
Basically, you need to go up to a higher level to get the full view model.
Something like
Content="{Binding DataContext.MyContentPropertyName,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
Or
Content="{Binding DataContext.MyContentPropertyName,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}}"
Where MyContentPropertyName is the property in GameViewModel that represents the content of the button.
Note: from the snippet it seems that both bindings will produce the same result, adding them both to make an example that you can choose how high you want to go to find the right context.

MVVM Binding in Silverlight3 ItemsControl to get the parent controls DataContext

I have the following ItemsControl in Silverlight 3.
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="MyBtn" Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding ElementName=MyBtn, Path=Tag}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The Problem is that I have the ItemsControl bound to the Collection in my ViewModel, but I need the button to trigger a command on the ViewModel which is of course not Available in the DataContext of the button since it only contains the collection.
I can make the command fire by setting my ViewModel as a Resource and then binding to it as a StaticResource, but I want to know why the element to element binding won't work in this scenario. I would prefer not to use the StaticResource binding because that requires the default constructor on the ViewModel and so I can't inject my data easily.
UPDATE
I'm working through this slowly... Looking at the suggestions from Peter I realized that I may have more serious binding issues because of my page setup. I have two possible road blocks, but first things first.
My Items control above is wrapped in another items control that is bound to an observable collection. I moved my items control so that its a direct child of the root items control. It was wrapped in another control that I'll get to. So I tried the element binding to the items control ControlItemList, but its a collection so it can't find my ButtonCommand method in that Collection. What I need to do is bind to an item within that collection. How do I bind to a single item within the collection?
<Grid x:Name="LayoutRoot" Background="White"><!-- DataContext="{Binding Path=., Source={StaticResource lvvm}}">-->
<StackPanel Orientation="Vertical">
<ItemsControl x:Name="ControlItemList" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="100" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="ControlName" Grid.Column="0" Text="{Binding Name}" VerticalAlignment="Center" />
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
So, Assuming I can get the above to work the other road block is that my items control is wrapped in another usercontrol that I'm using to get DataTemplateSelector type functionality. I think this control may be blocking me from getting to the parent DataContext. Is there a limit as to how far up the tree you can go?
<common:DeviceControlTemplateSelector Grid.Column="1" FieldType="{Binding ValueType}" Margin="0,2,0,2">
<common:DeviceControlTemplateSelector.StringTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, Mode=TwoWay}" Width="100"/>
</DataTemplate>
</common:DeviceControlTemplateSelector.StringTemplate>
<common:DeviceControlTemplateSelector.DateTimeTemplate>
<DataTemplate>
<TextBox Text="this is date time binding" Width="100"/>
</DataTemplate>
</common:DeviceControlTemplateSelector.DateTimeTemplate>
<common:DeviceControlTemplateSelector.BooleanTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Value, Mode=TwoWay}" />
</DataTemplate>
</common:DeviceControlTemplateSelector.BooleanTemplate>
<common:DeviceControlTemplateSelector.IntegerTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ValueCollection}" DisplayMemberPath="Value" SelectedIndex="{Binding Value, Mode=TwoWay}" >
</ComboBox>
</DataTemplate>
</common:DeviceControlTemplateSelector.IntegerTemplate>
<common:DeviceControlTemplateSelector.MultiButtonTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding ValueCollectionList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button x:Name="MyBtn"
Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=ControlItemList, Path=DataContext.ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding Value}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</common:DeviceControlTemplateSelector.MultiButtonTemplate>
<Button x:Name="MyBtn"
Height="40" Content="{Binding Name}"
Tag="{Binding Value}"
cmd:ButtonBaseExtensions.Command="{Binding ElementName=ControlItemList, Path=ButtonCommand}"
cmd:ButtonBaseExtensions.CommandParameter="{Binding Value}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Thanks again everyone for your help!
the best solution i have found so far to deal with this situation is by Dan Wahl where he uses a DataContextProxy.
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx
Try to change
from
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=ButtonCommand}"
to
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot, Path=DataContext.ButtonCommand}"
OR
cmd:ButtonBaseExtensions.Command="{Binding ElementName=LayoutRoot.DataContext, Path=ButtonCommand}"
In MVVM messaging is a strong concept for cummunication between ViewModels. You could use PRISM Eventaggregator or the Messenger class of MVVM Light Toolkit. On the button command you can publish a message and subscribe to it in the viewmodel.

Resources