Listbox DataTemplate field binding isn't working - wpf

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.

Related

How to pass the right Context to a DataTemplate within an ItemTemplate of a WPF ListView [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
in my WPF app there is a ListView defined as follows:
<ListView
HorizontalContentAlignment="Stretch"
DockPanel.Dock="Top"
Grid.IsSharedSizeScope="True"
ItemsSource="{Binding Path=ListSource}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="ParameterIconSize" />
<ColumnDefinition
Width="Auto"
MaxWidth="100"
SharedSizeGroup="ParameterDescriptionSize" />
<ColumnDefinition
Width="Auto"
MinWidth="40"
SharedSizeGroup="ParameterValueSize" />
</Grid.ColumnDefinitions>
<Image
Grid.Column="0"
MaxHeight="50"
Source="{Binding Path=Icon}" />
<ContentPresenter
Grid.Column="2"
Content="{Binding Path=RenderValue, Converter={StaticResource RenderObjToValueConverter}}"
ContentTemplateSelector="{StaticResource ResourceKey=RenderTemplateSelector}"
/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I've defined everything in the Resources as follows:
<!-- TEXT -->
<DataTemplate x:Key="TextValueTemplate">
<DockPanel DataContext="{StaticResource VM}">
<TextBlock
MaxWidth="75"
Margin="5,0"
VerticalAlignment="Center"
Background="Red"
DockPanel.Dock="Top"
FontSize="14"
Text="{Binding Path=Description, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
TextTrimming="CharacterEllipsis">
<TextBlock.ToolTip>
<TextBlock
MaxWidth="100"
Text="{Binding Path=Description, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</TextBlock.ToolTip>
</TextBlock>
</DockPanel>
</DataTemplate>
<!-- IMAGE -->
<DataTemplate x:Key="ImageValueTemplate">
<DockPanel DataContext="{StaticResource VM}">
<Image MaxHeight="50" Source="{Binding Path=CurrentValueImage}" />
</DockPanel>
</DataTemplate>
<!-- COMBO BOX -->
<DataTemplate x:Key="ComboValueTemplate">
<DockPanel DataContext="{StaticResource VM}">
<TextBlock
Grid.Column="1"
MaxWidth="75"
Margin="5,0"
VerticalAlignment="Center"
FontSize="14"
Text="{Binding Path=Description, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
TextTrimming="CharacterEllipsis">
<TextBlock.ToolTip>
<TextBlock
MaxWidth="100"
Text="{Binding Path=Description, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
TextWrapping="Wrap" />
</TextBlock.ToolTip>
</TextBlock>
<ComboBox/>
</DockPanel>
</DataTemplate>
<sel:ParameterVisualizationTemplateSelector
x:Key="RenderTemplateSelector"
ComboTemplate="{StaticResource ResourceKey=ComboValueTemplate}"
GraphicTemplate="{StaticResource ResourceKey=ImageValueTemplate}"
TextTemplate="{StaticResource ResourceKey=TextValueTemplate}" />
Everything compiles and runs, but then I am not able to correctly pass the ListViewItem to the inner datatemplate.
What I want to do is to be able to change what is shown based on some logic (not shown here) between an image, some text and a combobox. What's going inside those controls is defined in the ListViewItem. As you can see, I tried to write a converter that return the corresponding type (image, text, list of data), but I am missing the link between the various DataTemplates.
FINAL EDIT: Based on the answer from #mm8 I found that the real issue was not passing the context, but that the property passed wasn't correctly processed and then no value would be set up whilst not giving any binding error.
When you set DataContext="{StaticResource VM}" in your template, you are effectively "overriding" the default DataContext which is the current item in the ListSource source collection of the ListView.
So, if I understand correctly, the "dependency" chain is something like control <- textItemtemplate <- ListViewItemTemplate <- Viewmodel, so the final control automatically "inherits" the object?
You bind the ItemsSource of the ListView to a source collection property called ListSource:
<ListView ... ItemsSource="{Binding Path=ListSource}">
The default DataContext of the root element (the Grid in this case) in the ListView's ItemTemplate will then be an element in ListSource.
This means that if ListSource returns an IEnumerable<T> you can bind directly to any public property of the type T in the ItemTemplate provided that you don't explictly set the DataContext property somewhere in the template.
So Icon is supposed to be a property of T (whatever your type T is) in your example:
<Image Grid.Column="0"
MaxHeight="50"
Source="{Binding Path=Icon}" />
If you for example remove DataContext="{StaticResource VM}" from the TextValueTemplate, the binding to Description should work provided that there is a Description property defined in the object returned from the converter of the RenderValue property.

How can I set the binding for a control's property (which is inside DataTemplate and UserControl) to use the ItemSource's given property?

I would like to make a UserControl which have a DataTemplate, and inside that DataTemplate there are controls. I would like to bind to those nested (inside the DataTemplate) controls' properties so I can set them when I reuse this UserControl. The nested controls will use the ItemSource's properties but the property names of the ItemSource's properties could be different.
The UserControl:
<UserControl x:Class="ContextMenu.BaseFilterUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="Self">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="10"
Text="Owners" />
<Button Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10"
Click="FilteButtonClicked"
Width="40"
Height="40"
x:Name="FilterButton">
<Popup x:Name="FilterBoxPopup"
PlacementTarget="{Binding ElementName=FilterButton}"
Placement="Bottom"
StaysOpen="False">
<Border BorderBrush="Black"
Background="White"
Margin="2">
<ListView ItemsSource="{Binding ElementName=Self, Path=FilterList}"
x:Name="FilterListView"
Height="300"
Width="150">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<CheckBox IsChecked="{Binding IsChecked}" />-->
<!--<TextBlock Text="{Binding Name}" />-->
<!--This is where I don't know how to properly bind eg. the above control, things I tried:-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=Self, Path=DataContext.FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=DataContext.FilterElementName}" />-->
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Button>
<TextBlock Grid.Column="3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10"
Text="{Binding ElementName=Self, Path=SelectedNames}" />
</Grid>
</UserControl>
This how the UserControl is used, the FilterElementName="Name" is what I would like to set, depending on the list binded with FilterList list:
<local:BaseFilterUserControl FilterList="{Binding Owners}"
FilterElementName="Name"
SelectedNames="{Binding SelectedNames}"/>
In this case the Owners is a simple IReadOnlyList of an Owner class. The Owner class has a string Name property. But I will use this UserControl again with different list eg. where I would like to use the Versions list's Release property (for the TextBlock inside the UserControl):
<local:BaseFilterUserControl FilterList="{Binding Versions}"
FilterElementName="Release"
SelectedNames="{Binding SelectedReleases}"/>
The ListView is properly populated with items, so the FilterList DependencyProperty is working. But the nested controls are only working when I hard code the bindings:
<TextBlock Text="{Binding Name}" />
For this to work you would need to bind the Path-property of your TextBlocks Text-Binding to the FilterElementName property of your UserControl. Unfortunately the Path property of the Binding class is not a DependencyProperty and therefore not bindable.
One way to to achieve your goal would be to use the DisplayMemberPath property of the ListView, which is bindable:
<ListView x:Name="FilterListView"
Width="150"
Height="300"
ItemsSource="{Binding ElementName=Self, Path=FilterList}"
DisplayMemberPath="{Binding ElementName=self, Path=FilterElementName}"/>
If this approach does not work because you need to specify a more complex ItemTemplate, another way would be create a property of type DataTemplate in your UserControl, use that as ItemTemplate in the ListView and specify it from outside like so:
<local:BaseFilterUserControl FilterList="{Binding Versions}"
SelectedNames="{Binding SelectedReleases}">
<local:BaseFilterUserControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Release}" />
</DataTemplate>
</local:BaseFilterUserControl.ItemTemplate>
</local:BaseFilterUserControl>

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

datatemplate listbox selected item

here's my xaml:
<Window x:Class="WpfTest.Search"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Search" Height="600" Width="1024">
<Window.Resources>
<DataTemplate x:Key="listBoxTemplate" xmlns:ns="clr-namespace:MyConverters">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ns:ImageConverter x:Key="MyImageConverter" />
</StackPanel.Resources>
<Image Source="{Binding Path=thumb, StringFormat=/WpfTest;component/Images/{0}, Converter={StaticResource MyImageConverter}}" Height="100" Width="130" Margin="5"></Image>
<StackPanel Orientation="Vertical" Width="247">
<TextBlock Text="{Binding recipeName}" Height="60" Padding="15" FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Center"></TextBlock>
<TextBlock Text="{Binding cuisine}" Height="60" Padding="15" FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox Margin="12,96,0,0" Name="lstSearchResult" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="704" Height="445" ItemsSource="{Binding Tables[0]}" ItemTemplate="{StaticResource listBoxTemplate}" SelectionChanged="lstSearchResult_SelectionChanged">
</ListBox>
<TextBox Height="31" HorizontalAlignment="Left" Margin="12,49,0,0" Name="txtSearchRecipe" VerticalAlignment="Top" Width="518" FontSize="16" />
<Button Content="Search" Height="31" HorizontalAlignment="Left" Margin="555,49,0,0" Name="btnSearchRecipe" VerticalAlignment="Top" Width="161" Click="btnSearchRecipe_Click" />
</Grid>
</Window>
now i want to open a new form based on the item I clicked on the listbox, passing the new form the data from the selected item's textblock. how do i do that?
If I'm reading this correctly you don't need to do anything special to access the TextBlock controls in the DataTemplate. You just need to take advantage of your bindings by adding a SelectedItem binding using TwoWay binding to your ListBox.
If you're using MVVM, I would suggest you map a Command on your button click event to your View Model passing the SelectedItem as the command parameter. Then, in the command handler, cast the SelectedItem to the type you expect (this should be the type of object in the collection you've bound to your ListBox ItemsSource). You can then send the properties bound to the TextBlocks to the view model of your new window. If setup propertly these items should exist in your SelectedItem. Then set the window's DataContext to the corresponding window viewmodel and show it.
If you're just using code behind, simply cast the SelectedItem to the type used in the ListBox ItemsSource collection. Then pass the properties bound to the TextBlocks to the new window. Then show the window.

How can I create a silverlight combobox that drops down a treeview?

I'm trying to create a user control that is a combobox that, when opened, presents a treeview of heirarchal data.
I created the user control and replaced a portion of the template in Popup with:
<ScrollViewer x:Name="ScrollViewer" BorderThickness="0" Padding="1">
<sdk:TreeView x:Name="Tree">
</sdk:TreeView>
</ScrollViewer>
However, I'm not sure how to enable binding on this. The treeview needs to be bound to a different datacontext than the combobox. I tried implementing a DependencyProperty on the user control that would allow me to set the datacontext, but I'm definitely not going about it the right way. At this point, all I get is an empty treeview.
Any help on this would be greatly appreciated.
P.S. One additional caveat is that I need to template the treeview like so:
<sdk:TreeView x:Name="Tree">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding ChildUnits}">
<StackPanel Orientation="Vertical" Width="200">
<TextBlock x:Name="name" TextWrapping="Wrap" Text="{Binding Name}" FontWeight="Bold" />
<TextBlock x:Name="type" Text="{Binding Id}" FontStyle="Italic" FontSize="10" Foreground="Gray" />
</StackPanel>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>

Resources