ContentStringFormat never changes by Setter Property in DataTrigger - c#

In my user control I want to have label displaying Text from resource depending on viewmodel property Location value and with different format.
I implemented it by defining DataTrigger for each Location value
<Style x:Key="LocationValueHelper" TargetType="{x:Type Label}">
<Style.Triggers>
<DataTrigger Binding="{Binding Location}" Value="HomeViewModel">
<Setter Property="Content" Value="" />
<Setter Property="ContentStringFormat" Value="{}{0}???"/>
</DataTrigger>
<DataTrigger Binding="{Binding Location}" Value="FirstViewModel">
<Setter Property="ContentStringFormat" Value="{}{0}+++"/>
<Setter Property="Content" Value="{localization:LanguageResource ResourceKey=First}" />
</DataTrigger>
<DataTrigger Binding="{Binding Location}" Value="SecondViewModel">
<Setter Property="ContentStringFormat" Value="{}{0}---"/>
<Setter Property="Content" Value="{localization:LanguageResource ResourceKey=Second}" />
</DataTrigger>
<DataTrigger Binding="{Binding Location}" Value="ThirdViewModel">
<Setter Property="ContentStringFormat" Value="{}{0}***"/>
<Setter Property="Content" Value="{localization:LanguageResource ResourceKey=Third}" />
</DataTrigger>
</Style.Triggers>
</Style>
<Label Foreground="White" HorizontalAlignment="Center" FontSize="40" Style="{StaticResource LocationValueHelper}" />
The problem is that format never changes from the first one (???) - HomeViewModel selected first - although text changing works.
I have
Home???
First???
Second???
Third???
instead of
Home???
First+++
Second---
Third***

There are a lot of discussions, using Labels and ContentStringFormat does not work like expected or desired:
WPF StringFormat on Label Content
StringFormat VS ContentStringFormat
...
The reason seems to be the template based implementation under the hood, since the content also could contain other data than strings. To solve your problem, my proposal is to use a TextBlock, which works quite smooth like this:
<StackPanel>
<CheckBox IsChecked="{Binding MyFlag}"/>
<TextBlock HorizontalAlignment="Center" FontSize="40">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding MyFlag}" Value="False">
<Setter Property="Text" Value="{Binding Path=MyText, StringFormat='{}{0}+++'}" />
</DataTrigger>
<DataTrigger Binding="{Binding MyFlag}" Value="True">
<Setter Property="Text" Value="{Binding Path=MyText, StringFormat='{}{0}???'}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>

Related

XAML DataTrigger Not Firing?

I have a property in my ModularClientModule class called ExistsOnDisk. When that property is set to false, I want to disable the CheckBox. For some reason, this does not seem to work despite me putting it last in the Triggers XAML block, which means it should be the last thing to fire. I.e, nothing should be interfering. I know my code-behind should be correct because as you will see below, I have another control which receives the update and fires fine.
This one here responds to the ExistsOnDisk property change without issue, and strikesthrough the text:
GridViewColumn x:Name="FileNameHeader" Header="File Name">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:ModularClientModuleUI}">
<TextBlock Text="{Binding Filename}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<DataTrigger Binding="{Binding ExistsOnDisk}" Value="False">
<Setter Property="TextDecorations" Value="Strikethrough"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
This one here does not, and the checkbox remains enabled:
<GridViewColumn x:Name="StatusHeader" Header="Status">
<GridViewColumn.CellTemplate>
<DataTemplate DataType="{x:Type local:ModularClientModuleUI}">
<StackPanel>
<CheckBox x:Name="ClientModuleStatusCheckBox" Margin="5, 0" Click="ClientModuleStatusCheckBox_Click" IsEnabled ="{Binding Client.Online}">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource MahApps.Styles.CheckBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding LoadState}" Value="Loaded">
<Setter Property="IsChecked" Value="True" />
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadState}" Value="Unloaded">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadState}" Value="Loading">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ExistsOnDisk}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding ExistsOnDisk}" Value="False">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
What could be going on here?
Set the default value of the IsEnabled using a Setter and remove IsEnabled ="{Binding Client.Online}" from the <CheckBox> element:
<CheckBox x:Name="ClientModuleStatusCheckBox" Margin="5, 0" Click="ClientModuleStatusCheckBox_Click">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource MahApps.Styles.CheckBox}">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding LoadState}" Value="Loaded">
<Setter Property="IsChecked" Value="True" />
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadState}" Value="Unloaded">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding LoadState}" Value="Loading">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ExistsOnDisk}" Value="True">
<Setter Property="IsEnabled" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
Also note that you shouldn't use more than one trigger on the same bool property.

How to bind to a SelectedValue data context property in WPF?

I have a listbox that display some model items.
I would like to hide some control below the listbox based on the boolean value of the model item associated with the selected listbox item.
I tried the following but it did not work:
1) set the ListBox SelectedValuePath="MyModelBooleanProperty"
2) add a data trigger to the control that I want to hide as follows
<DataTrigger Binding="{Binding ElementName=FolderList, Path=SelectedValue}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FolderList, Path=SelectedValue}" Value="True">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
I used this and it worked. Also make sure you don't explicitly set Visibility of TextBlock that overrides whatever the Style does.
<ListBox
x:Name="FolderList"
ItemsSource="{Binding Source={StaticResource ViewModel}, Path=List}"
SelectedValuePath="SomeBooleanProperty">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding SomeStringProperty}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FolderList, Path=SelectedValue}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FolderList, Path=SelectedValue}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
My Text Block
</TextBlock>

Reusing style trigger in wpf

I have done some research on this but failed to find out on how to reuse the style trigger.
I have style trigger set on Label and I am using Data trigger to set content. I have multiple labels in same view and also in different view. The Data bound is of same type except its different property of same data context.
Consider following 2 different labels where I need to display performance of 2 person - PersonA and PersonB. The value displayed for both the labels will be based on Performance format selected.
<Label Grid.Row="5" Grid.Column="9"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center">
<Label.Style>
<Style BasedOn="{StaticResource SomeGlobalStaticStyle}" TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Fractional">
<Setter Property="Content" Value="{Binding DataModel.PersonA.Performance.Value.Fractional}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Decimal">
<Setter Property="Content" Value="{Binding DataModel.PersonA.Performance.Value.Decimal}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="US">
<Setter Property="Content" Value="{DataModel.PersonA.Performance.Value.US}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
<Label Grid.Row="6" Grid.Column="9"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center">
<Label.Style>
<Style BasedOn="{StaticResource SomeGlobalStaticStyle}" TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Fractional">
<Setter Property="Content" Value="{Binding DataModel.PersonB.Performance.Value.Fractional}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Decimal">
<Setter Property="Content" Value="{Binding DataModel.PersonB.Performance.Value.Decimal}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="US">
<Setter Property="Content" Value="{DataModel.PersonB.Performance.Value.US}" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
If you see, the only difference is the first trigger takes PersonA and the second trigger takes PersonB. I have 10 instances of such label spread in same and multiple views. Is there is way I can define this trigger once in Resources and bind whatever data I want to from each Label.
Looking forward for the solution.
Regards,
Abdyax
configurate setters to obtain values not from Label DataContext, but another Label property - Tag
<Style BasedOn="{StaticResource SomeGlobalStaticStyle}" TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Fractional">
<Setter Property="Content" Value="{Binding Tag.Performance.Value.Fractional, RelativeSource={RelativeSource Self}}}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="Decimal">
<Setter Property="Content" Value="{Binding Tag.Performance.Value.Decimal, RelativeSource={RelativeSource Self}}" />
</DataTrigger>
<DataTrigger Binding="{Binding PersonModel.PerformanceFormat}" Value="US">
<Setter Property="Content" Value="{Binding Tag.Performance.Value.US, RelativeSource={RelativeSource Self}}}" />
</DataTrigger>
</Style.Triggers>
</Style>
and bind labels Tag to different properties:
<Label Grid.Row="5" Grid.Column="9"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" Tag="{Binding DataModel.PersonA}"/>
<Label Grid.Row="6" Grid.Column="9"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center" Tag="{Binding DataModel.PersonB}"/>
both labels should pick our default style for TargetType="Label" with triggers
Is there is way I can define this trigger once in Resources and bind whatever data I want to from each Label.
Short answer: No, I am afraid not. You cannot replace the Value of a Setter but reuse the rest of the Style in XAML.
You could create the styles programmatically using the XamlReader.Parse method. Then you can simply replace the "PersonA" with "PersonB" in the string that you pass to the method.
But there is no way to do this generally in pure XAML.

WPF binding not applied - why does it happen

I wanted to update a stringformat of my binding via a datatrigger. So I thought of just doing this:
<TextBlock Text="{Binding Foo.Name}" Margin="3">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Path=., StringFormat='Start {0}'}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Foo.IsEnabled}" Value="True">
<Setter Property="Text" Value="{Binding Path=., StringFormat='Stop {0}'}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
now this just shows the value of Foo.Name and not with the string format applied('start'/'stop' string).
I've modified the code, to get it working. This is the working code:
<TextBlock Margin="3">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding Foo.Name, StringFormat='Start {0}'}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Foo.IsEnabled}" Value="True">
<Setter Property="Text" Value="{Binding Foo.Name, StringFormat='Stop {0}'}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Now i'm looking for the reason why the first piece of code doesn't work and why the second piece of code works.
I've seen similar behavior in DataTemplates where I could not set or alter a property value via a Setter in a DataTrigger because the property was already set directly via the property on the object itself. Only when I removed the property from the object itself and set it as a style it worked.
Or is this just a limitation in WPF?
I wouldn't call it a limitation, it's documented. Dependency properties have a value precedence. https://msdn.microsoft.com/en-us/library/ms743230(v=vs.110).aspx

DataTrigger for IconTemplate in RadWindow

I have a WPF application and I use Telerik.
I'm trying to set the Icon Template so that it has a default value and only on a certain condition will it bind the image source:
<telerik:RadWindow.Resources>
<Style x:Key="CustomIconStyle" TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsConditionMet, ElementName=MyWindow, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="Source" Value="{Binding Path=IconImageSource, ElementName=MyWindow, UpdateSourceTrigger=PropertyChanged}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadWindow.Resources>
<telerik:RadWindow.IconTemplate>
<DataTemplate>
<Image Style="{StaticResource CustomIconStyle}" Source="/MyAssembly;Component/Resources/myIcon.ico" Height="16" Margin="0,0,5,0"/>
</DataTemplate>
</telerik:RadWindow.IconTemplate>
For some reason it always show the default icon.
I would also like to mention that I did implement the property changed - and I copied the same style just to a control inside the window and not in the template and it worked - so the problem isn't with the property changed
Any ideas?
You can use Triggers like that :
<telerik:RadWindow.Resources>
<Style TargetType="Image" x:Key="Style1">
<Setter Property="Source" Value="default.ico"/>
<Style.Triggers>
<DataTrigger Binding="{Binding MyCondition}" Value="true">
<Setter Property="Source" Value="custom.ico"/>
</DataTrigger>
</Style.Triggers>
</Style>
</telerik:RadWindow.Resources>
<telerik:RadWindow.IconTemplate>
<DataTemplate>
<Image Style="{StaticResource Style1}" Height="16" Margin="0,0,5,0"/>
</DataTemplate>
</telerik:RadWindow.IconTemplate>
So the problem was that once the RadWindow was loaded it didn't change the Icon.
The solution:
<telerik:RadWindow.IconTemplate>
<DataTemplate>
<Image Height="16" Margin="0,0,5,0">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/MyAssembly;Component/Resources/myIcon.ico" />
<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Path=IsConditionMet, ElementName=MyWindow}">
<Setter Property="Source" Value="{Binding Path=IconImageSource, ElementName=MyWindow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</DataTemplate>
</telerik:RadWindow.IconTemplate>
But the trick is to give the correct value of IsConditionMet in the windows constructor before the load.
Thanks for the help everyone.

Categories