Trigger a binding update in XAML? - c#

My XAML set up is a bit complex code:
<UserControl.Resources>
<local:SuperCoolObject x:Key="firstObject"/>
<local:TotallyHotObject x:Key="secondObject"/>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=MyItems, Source={StaticResource firstObject}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<lol:MyConverter x:Key="myConverter" Equals="{Binding Path=SelectedItem, Source={StaticResource secondObject}}" />
<Style x:Key="On" TargetType="local:ThemedImage">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource myConverter}}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
(I've omitted the actual DataTemplate)
Notice how the ItemsControl's ItemsSource binds to firstObject and myConverter binds to secondObject. What I need to do is have the ItemsSource binding refresh when the myConverter binding (secondObject.SelectedItem) property changes.
All the appropriate properties are set up as binding properties but I need some way or forcing ItemsSource to refresh in XAML.

Related

Collapse CheckBox if ComboBox has no element selected

I'm trying to collapse a CheckBox through Visibility if the selected item of a ComboBox is null or empty. The source is a list of objects having two string properties: Code and Name.
I'm using a trigger binded to the ComboBox text.
<ComboBox x:Name="VideoSub" SelectedItem="{Binding SubSelection, Mode=TwoWay}"
ItemsSource="{Binding Path=SubsSource}"
IsEnabled="{Binding HasItems, RelativeSource={RelativeSource Self}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<CheckBox Width="80" IsEnabled="{Binding ElementName=VideoSub, Path=IsEnabled}"
HorizontalAlignment="Right" Margin="0,10,0,0">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Text.Length, ElementName=VideoSub, UpdateSourceTrigger=PropertyChanged}" Value="0">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
It is more directly to use event SelectionChanged to control whether components are displayed.
I wrote some demo code, hope it helps:
MainWindow.xaml
<Window x:Class="TestWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
<StackPanel>
<ComboBox Name="MyComboBox" SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem Content="Code"></ComboBoxItem>
<ComboBoxItem Content="Name"></ComboBoxItem>
</ComboBox>
<CheckBox Name="MyCheckBox" Visibility="Collapsed" />
</StackPanel>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
namespace TestWpfApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (this.MyComboBox.SelectedItem == null)
{
this.MyCheckBox.Visibility = Visibility.Collapsed;
}
else
{
this.MyCheckBox.Visibility = Visibility.Visible;
}
}
}
}
Furthermore, sometimes it's easier to use procedure (such as functions/methods) than definition (such as triggers etc) in programming :)
If you are using a DataTemplate, you should not trigger on elements inside it, but its data. If you want to hide the CheckBox when the SelectedItem is null, use the x:Null markup extension for comparison.
<DataTrigger Binding="{Binding SelectedItem, ElementName=VideoSub}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
As you bind Name to the TextBlock, you can add a DataTrigger to your style, that compares it to the static empty string. There is no need to reference the TextBlock itself.
<DataTrigger Binding="{Binding SelectedItem.Name, ElementName=VideoSub}" Value="{x:Static system:String.Empty}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
Note that you have to add an XML namespace for the String type.
.NET Core: xmlns:system="clr-namespace:System;assembly=System.Runtime"
.NET Framework: xmlns:system="clr-namespace:System;assembly=mscorlib"
Here the complete style that checks for null and emptyness.
<Style TargetType="{x:Type CheckBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=VideoSub}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem.(local:PersonModel.Name), ElementName=VideoSub}" Value="{x:Static system:String.Empty}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
By the way, you probably do not need a DataTemplate at all, if you just want to display a property as text in the ComboBox. Set the DisplayMemberPath instead.
<ComboBox x:Name="VideoSub" SelectedItem="{Binding SubSelection, Mode=TwoWay}"
ItemsSource="{Binding Path=SubsSource}"
IsEnabled="{Binding HasItems, RelativeSource={RelativeSource Self}}"
DisplayMemberPath="Name"/>
Just bind to SelectedItem:
<CheckBox Width="80" IsEnabled="{Binding ElementName=VideoSub, Path=IsEnabled}"
HorizontalAlignment="Right" Margin="0,10,0,0">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, ElementName=VideoSub}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
An object itself "having two string properties: Code and Name" cannot be "empty".
There either is an item selected or there isn't.

ToolTip to show validation errors works for TextBlock bound to POCO but not Property within POCO. Why?

In my WPF MVVM project I use INotifyDataErrorinfo to handle validation within a DataGrid. I can successfully style error cells in my "Operator" column thus:
<DataGridTemplateColumn Header="Operator" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Operator}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding OperatorId, Converter={StaticResource IsOperatorIdNullConverter}}" Value="False">
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent"/>
</ToolTip>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Salmon"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<controls:AutoCompleteBox Text="{Binding Operator, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"
ItemsSource="{Binding Path=Data.OperatorNames, Source={StaticResource proxy}}"
IsTextCompletionEnabled="True"
FilterMode="Contains"
MinimumPrefixLength="3"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In my "OperatorType" column however this same technique doesn't work. Errors are being detected and the system default error styling is shown but my custom styling isn't. The code is:
<DataGridTemplateColumn Header="Operator type" Width="140">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OperatorType.OperatorTypeName}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip DataContext="{Binding RelativeSource={RelativeSource Self}, Path=PlacementTarget}">
<ItemsControl ItemsSource="{Binding Path=(Validation.Errors)}" DisplayMemberPath="ErrorContent"/>
</ToolTip>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Salmon"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<controls:AutoCompleteBox ItemsSource="{Binding Path=Data.OperatorTypeNames, Source={StaticResource proxy}}"
ItemTemplate="{StaticResource AutoCompleteBoxItemOperatorTypeTemplate}"
SelectedItem="{Binding OperatorType, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
ValueMemberPath="OperatorTypeName"
IsTextCompletionEnabled="True"
FilterMode="Contains"
MinimumPrefixLength="3"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The only differences as far as I can see are that:
The Text in "Operator" is using bound to a POCO (Operator) while for "OperatorType" it is bound to a POCO property (OperatorType.OperatorTypeName)
The AutoCompleteBox declarations are slightly different
I've tried numerous settings for the ToolTip DataContext but nothing seems to work.
Question
What do I need to change to get the "OperatorType" customised error styling to work?
Well, it was a bit of a journey, but the solution was to set a DataContext on the TextBlock:
<TextBlock DataContext="{Binding OperatorType}" Text="{Binding OperatorTypeName}">
This causes the Trigger to be pointed at the OperatorType POCO whereas the Text in the box is taken from OperatorType.OperatorTypeName.

DataTrigger with Property = ItemControl.ItemTemplate

I Am having some trouble getting this binding to work. I have several objects within SettingCollection that all have an enum property. I want to generate a control based on what the value of this is. But when I check this value with a data trigger it does not work.
Can anyone provide some insight into how i can accomplish this?
<Window.Resources>
<DataTemplate x:Key="CheckboxNode">
<CheckBox IsChecked="{Binding Status}" Margin="0,5,0,0">
<ContentPresenter Content="{Binding DisplayName}"/>
</CheckBox>
</DataTemplate>
<DataTemplate x:Key="TextboxNode">
<TextBox Text="Badgers"></TextBox>
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding SettingCollection}">
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="checkbox">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource CheckboxNode}" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="textbox">
<Setter Property="ItemsControl.ItemTemplate" Value="{StaticResource TextboxNode}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsControl.Style>
</ItemsControl>
If Type property is in SettingNode class, and SettingCollection is a collection of SettingNode objects, then your binding in the Datatriggers is incorrect. DataTriggers will look for Type property in ItemsControl DataContext (class with SettingCollection). Try to use DataTemplateSelector https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx

DataTemplate Based on Value of Bound Property in ItemsSource of TabControl

Ok. So I have a TabControl object in my xaml that has an ItemsSource value of ItemsSource={Binding OpenTabs} where OpenTabs is an ObservableCollection of type ClosableTab (public ObservableCollection<ClosableTab> OpenTabs { get; set; }) which extends TabItem. I found ClosableTab from here and then have adapted it's view for my own needs.
Primarily I have added a property (and sorry for the confusion in names here) isProperty. This is for a real estate program. Then in my xaml I have the following lines:
<DataTemplate x:Key="PropertyTemplate">
<Grid>
<TextBlock Text="{Binding address}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TennantTemplate">
<Grid>
<TextBlock Text="{Binding name}"/>
</Grid>
</DataTemplate>
//... That's in <Windows.Resources>
<TabControl ItemsSource="{Binding OpenTabs}" Grid.Column="1" x:Name="Tabs">
<TabControl.Resources>
<DataTemplate x:Key="DefaultTab">
<ContentControl>
<ContentControl.Triggers>
<DataTrigger Binding="{Binding isProperty}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource PropertyTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding isProperty}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource TennantTemplate}" />
</DataTrigger>
</ContentControl.Triggers>
</ContentControl>
</DataTemplate>
</TabControl.Resources>
</TabControl>
I've done some research and found that this is what I need to do if I want to have a certain DataTemplate dependant on the property in ClosableTab called isProperty.
It's not giving me what I want. Can someone please explain to me what I'm doing wrong here? And tell me what I should do? And/or possibly give me an alternative method? I can't think of what I need to change to get the functionality that I need. Thanks in advance.
You need to set DataType on DataTemplate to get it applied automatically to underlying data objects in case you are defining DataTemplate under Resources section.
<DataTemplate DataType="local:ClosableTab">
<ContentControl>
<ContentControl.Triggers>
<DataTrigger Binding="{Binding isProperty}" Value="True">
<Setter Property="ContentTemplate"
Value="{StaticResource PropertyTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding isProperty}" Value="False">
<Setter Property="ContentTemplate"
Value="{StaticResource TennantTemplate}" />
</DataTrigger>
</ContentControl.Triggers>
</ContentControl>
</DataTemplate>
Make sure to declare local namespace at root level to the one where ClosableTab is declared.
OR
Instead of adding DataTemplate in resources, set it explicitly as ItemTemplate of TabControl.
<TabControl>
<TabControl.ItemTemplate>
<DataTemplate x:Key="DefaultTab">
.....
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
UPDATE
Ideal case would be to have single DataTemplate and apply dataTrigger on TextBlock instead.
<TabControl ItemsSource="{Binding OpenTabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding address}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding isProperty}"
Value="False">
<Setter Property="Text" Value="{Binding name}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>

GridViewColumn with ComboBox and UserControl

I have a GridViewColumn with Combobox BAsed on ComboBox selected I want to dynamically put UI elements.Below given is the code.OperatorList has three values "Between","After","Before".Based on this selection the template has to be loaded.If "Between" Multiple TextBox else Single textbox.This part works fine.But to get the content of this and store in Model Class for each row is where i am encountering problem.
<StackPanel>
<ComboBox Grid.Column="0" ItemsSource="{Binding Path=OperatorList}" IsEditable="True"
IsSynchronizedWithCurrentItem="True"
SelectedValue="{Binding ReferenceOperatorSelected}" />
<UserControl x:Name="MyControl">
</UserControl>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ReferenceOperatorSelected}" Value="Between">
<Setter TargetName="MyControl" Property="ContentTemplate" Value="{StaticResource MultipleTextBoxTemplate}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ReferenceOperatorSelected}" Value="After">
<Setter TargetName="MyControl" Property="ContentTemplate" Value="{StaticResource SingleTextBoxTemplate}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ReferenceOperatorSelected}" Value="Before">
<Setter TargetName="MyControl" Property="ContentTemplate" Value="{StaticResource SingleTextBoxTemplate}"></Setter>
</DataTrigger>
</DataTemplate.Triggers>
<DataTemplate x:Key="MultipleTextBoxTemplate">
<StackPanel>
<TextBox Text="{Binding Path=BetweenValue1,Mode=TwoWay,NotifyOnSourceUpdated=True}" MinWidth="40"></TextBox>
<TextBox Text="{Binding Path=BetweenValue2,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" MinWidth="40"></TextBox>
</StackPanel>
</DataTemplate>
In my Model class I have two properties BetweenValue1 and BetweenVAlue2 ...These are not updated when i key in value in textboxes..
You have not set the Content for your UserControl. just try doing
<UserControl x:Name="MyControl" Content="{Binding}"/>

Categories