How to set ComboBoxItem properties when using ItemsSource and template bindings? - wpf

When I created combobox manually it was no problem, but since the combobox is now populated automatically thanks to setting ItemsSource I don't know how to set the properties of each ComboBoxItem. Currently I need to set Selected action for each item (one global value) and Tag (different value per each item).
Currently I only define how the combobox item looks like:
<ComboBox ItemsSource="{Binding Path=Modules}"
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Height="16" Width="16" Source="{Binding ObjectData.ImageSource}" />
<Label Content="{Binding ObjectData.Label}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
How to set the properties I mentioned? Note: those properties are of ComboBoxItem.
Modules is the property of my window class:
public ObservableCollection<SelectableObject<Module>> Modules { get; private set; }

You can set ComboBoxItem properties in the ItemContainerStyle of the ComboBox:
<ComboBox ...>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Tag" Value="{Binding TagValue}"/>
<Setter Property="IsSelected" Value="{Binding Selected}"/>
</Style>
</ComboBox.ItemContainerStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
When the "global" Selected property is not in the item view model, you'll have set the binding source explicitly, e.g. by RelativeSource/FindAncestor.

Related

List box modify datatemplate of items at runtime from code behind

i have this listbox
<ListBox x:Name="lbsample" ItemsSource="{Binding CurrentSettings}" SelectionMode="Extended">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" >
<Setter Property="Focusable" Value="False"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<GroupBox x:Name="gb0" Margin="0" Background="Transparent">
<GroupBox.Header>
<CheckBox x:Name="chk0" Content="xxx"
IsChecked="{Binding Path=DataContext.IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}}}" />
</GroupBox.Header>
<!---->
<StackPanel Orientation="Horizontal" IsEnabled="{Binding Path=IsEnabled}" >
<TextBlock x:Name="lbltxt" Text="yyy"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="5,5,5,10"/>
</StackPanel>
</GroupBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
when mask is loaded i want to change the text 'xxx' and 'yyy' progmatically from code behind
how can i do that? i cannot reach the properties because listbox is bound and has itemsource
i know is not best practice.. but i do not have easy alternatives..
Thanks!
if you need something view-specific, add Loaded event handler:
<CheckBox x:Name="chk0" Content="xxx" Loaded="CheckBoxLoaded" ...>
then in code-behind get CheckBox from sender argument and change properties:
private void CheckBoxLoaded(object sender, RoutedEventArgs e)
{
var chk = sender as CheckBox;
}
however you can declare special property in item class and simple bind CheckBox:
<CheckBox x:Name="chk0" Content="{Binding PropertyX}" ...>
same approaches should work for TextBlock.

How to set the text of the comboBox items with a converter?

I have a converter that binds to a collection which elements are of type MyType. I would like to set the text that it is sound withd a converter. So to start with a quick test, I am trying first to set the text of all elements with the text "Hello".
I am trying to use this code:
<ComboBox
DisplayMemberPath="MyProperty"
SelectedItem="{Binding Path=MySelectedItem, Mode=TwoWay}"
ItemsSource="{MyViewModelCollection}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="TextBlock.Text" Value="Hello"/>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
However, it is shown the value of the property set in DisplayMemeberPath.
So for the first step I would like to know how to set to all items the text "Hello" but my idea it is to use a converter that takes the comboBoxItem, and according to some checks, returns the text that I want to show.
Thanks.
Instead of DisplayMemberPath, set the ItemTemplate property to an appropriate DataTemplate with a TextBlock:
<ComboBox ItemsSource="{Binding MyViewModelCollection}"
SelectedItem="{Binding MySelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyProperty,
Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Dynamic treeview expansion not working with HeirarchicalDataTemplate

I have a treeview control that picks item controls dynamically through a template selector. I have bound the property IsExpanded on my model to IsExpanded on TreeViewItem using a Setter. (I know this is hooked up, because if I set IsExpanded to true in the model constructor, the entire tree is expanded, as expected.)
Here's the problem. After the I load the tree, just the root node is visible (as expected), but if I set IsExpanded on a node to true, the tree is supposed to expand to the node that changed, but it does not. (I've put debug markers in to make sure the property is actually changing. )
Here is my xaml:
<Window.Resources>
<HierarchicalDataTemplate x:Key="RegularNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<StackPanel Orientation="Horizontal">
<Border Width="8" Height="15" >
<Label Content="*" Padding="0" HorizontalAlignment="Right" Visibility="{Binding ModifiedCueVisibility}" />
</Border>
<TextBlock Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate x:Key="RootNodeTemplate"
ItemsSource="{Binding Path=Children}" >
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Text="{Binding Path=ModelDisplayName}"/>
</StackPanel>
</Grid>
</HierarchicalDataTemplate>
<local:ManifestNodeTemplateSelector x:Key="manifestNodeTemplateSelector"/>
</Window.Resources>
<Grid>
<TreeView Name="TheManifestTreeView" Grid.Row="0" ItemsSource="{Binding ManifestRoot}"
ItemTemplateSelector="{StaticResource manifestNodeTemplateSelector}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
Here is The code for IsExpanded:
private bool _isExpanded;
public bool IsExpanded
{
get => _isExpanded;
set
{
_isExpanded = value;
NotifyPropertyChanged("IsExpanded");
}
}
My problem is that I was expecting the tree to automatically expand to a leaf node if that node expanded. If I want the tree to expand to a node, I need to walk up the parentage and expand those.
It seems like ItemContainerStyle only applies for first level of TreeViewItem.Try to put the style inside TreeView.Resources instead
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
</Style>
</TreeView.Resources>

WPF TreeView IsSelected Binding Throwing Null Reference Exception

I've got a tabcontrol that is bound to a tab collection in my view model. Right now there are 2 types of tabs and one of them has a treeview in it. When the tab is first created, the treeview selection works. When I switch tabs and come back to the tab with the treeview in it, the treeview items seem to become unbound. When I try to select one I get the following error:
System.Windows.Data Error: 8 : Cannot save value from target back to source.
BindingExpression:Path=IsSelected; DataItem='NavigationItem' (HashCode=50956576);
target element is 'TreeViewItem' (Name=''); target property is 'IsSelected' (type
'Boolean') NullReferenceException:'System.NullReferenceException:
Object reference not set to an instance of an object.
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(
Object component, Object value)
at MS.Internal.Data.PropertyPathWorker.SetValue(Object item, Object value)
at MS.Internal.Data.ClrBindingWorker.UpdateValue(Object value)
at System.Windows.Data.BindingExpression.UpdateSource(Object value)'
I'm not sure why this is occurring. When debugging through the application my model still has all of the correct data, and the treeview even displays the information from the model meaning it has to have been bound correctly when the tab was initially changed. It's just when I try to select an item after having left the tab and come back. Here's the source for my tab control and the tab in question with a treeview in it:
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
...
</TabControl.ItemTemplate>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type tabs:ArchitectureTabViewModel}">
<views:ArchitectureTab/>
</DataTemplate>
<DataTemplate DataType="{x:Type tabs:TestOrderTabViewModel}">
<views:TestOrderTab/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
And the tab in question:
<Grid DataContext="{Binding GraphNavigationModel}">
...
<TreeView ItemsSource="{Binding NavigationTree}" Background="#00ffffff" Margin="5" BorderThickness="0" MinWidth="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=OneWayToSource}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Grid>
</Grid>

WPF Error: Cannot find governing FrameworkElement for target element

I've got a DataGrid with a row that has an image. This image is bound with a trigger to a certain state. When the state changes I want to change the image.
The template itself is set on the HeaderStyle of a DataGridTemplateColumn. This template has some bindings. The first binding Day shows what day it is and the State changes the image with a trigger.
These properties are set in a ViewModel.
Properties:
public class HeaderItem
{
public string Day { get; set; }
public ValidationStatus State { get; set; }
}
this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
this.HeaderItems.Add(new HeaderItem()
{
Day = i.ToString(),
State = ValidationStatus.Nieuw,
});
}
Datagrid:
<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >
<DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid>
Datagrid HeaderStyleTemplate:
<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Day}" />
<Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger >
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding State}" Value="Nieuw"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now when I startup the project the images doesn't show and I get this error:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderItems[0]; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=26950454); target property is 'Header' (type 'Object')
Why is this error showing?
Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid. So bindings do not work with their properties such as Visibility or Header etc (although these properties are valid dependency properties!).
Now you may wonder how is that possible? Isn't their Binding property supposed to be bound to the data context? Well it simply is a hack. The binding does not really work. It is actually the datagrid cells that copy / clone this binding object and use it for displaying their own contents!
So now back to solving your issue, I assume that HeaderItems is a property of the object that is set as the DataContext of your parent View. We can connect the DataContext of the view to any DataGridColumn via something we call a ProxyElement.
The example below illustrates how to connect a logical child such as ContextMenu or DataGridColumn to the parent View's DataContext
<Window x:Class="WpfApplicationMultiThreading.Window5"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="Window5" Height="300" Width="300" >
<Grid x:Name="MyGrid">
<Grid.Resources>
<FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
</Grid.Resources>
<Grid.DataContext>
<TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
</Grid.DataContext>
<ContentControl Visibility="Collapsed"
Content="{StaticResource ProxyElement}"/>
<vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
<vb:DataGrid.ItemsSource>
<x:Array Type="{x:Type TextBlock}">
<TextBlock Text="1" Tag="1.1"/>
<TextBlock Text="2" Tag="1.2"/>
<TextBlock Text="3" Tag="2.1"/>
<TextBlock Text="4" Tag="2.2"/>
</x:Array>
</vb:DataGrid.ItemsSource>
<vb:DataGrid.Columns>
<vb:DataGridTextColumn
Header="{Binding DataContext.Text,
Source={StaticResource ProxyElement}}"
Binding="{Binding Text}"/>
<vb:DataGridTextColumn
Header="{Binding DataContext.Tag,
Source={StaticResource ProxyElement}}"
Binding="{Binding Tag}"/>
</vb:DataGrid.Columns>
</vb:DataGrid>
</Grid>
</Window>
The view above encountered the same binding error that you have found if I did not have implemented the ProxyElement hack. The ProxyElement is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as ContextMenu or DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View.
I hope this guides you in correct direction.
A slightly shorter alternative to using a StaticResource as in the accepted answer is x:Reference:
<StackPanel>
<!--Set the DataContext here if you do not want to inherit the parent one-->
<FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn
Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
Binding="{Binding ...}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
The main advantage of this is: if you already have an element which is not a DataGrid's ancestor (i.e. not the StackPanel in the example above), you can just give it a name and use it as the x:Reference instead, hence not needing to define any dummy FrameworkElement at all.
If you try referencing an ancestor, you will get a XamlParseException at run-time due to a cyclical dependency.
The way without a proxy is to set bindings in the constructor:
var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
{
Source = ViewModel,
Converter = converter,
});
}
The Proxy Element didn't work for me, for a tooltip. For an infragistics DataGrid I did this, you might change it easily to your kind of grid:
<igDP:ImageField Label="_Invited" Name="Invited">
<igDP:Field.Settings>
<igDP:FieldSettings>
<igDP:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igDP:CellValuePresenter}">
<Setter Property="ToolTip">
<Setter.Value>
<Label Content="{Binding DataItem.InvitationSent, Converter={StaticResource dateTimeConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</igDP:FieldSettings.CellValuePresenterStyle>
</igDP:FieldSettings>
</igDP:Field.Settings>
</igDP:ImageField>

Resources