Conditional overwrite of style property - wpf

I'm trying to alter the background of a textbox if the value of the field contains a specified text. The problem that I encounter is that I already have a style applied to the field and I try to overwrite a property of the style like in the following example but with no success. Any ideas how could I achieve this?
<TextBox Grid.Column="1"
HorizontalAlignment="Right"
Text="{Binding CustomerType}" >
<TextBox.Style BasedOn="{DynamicResource SelectableTextStyle}">
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding CustomerType}" Value="Unknown">
<Setter Property="TextBox.Background" Value="Tomato"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

Your style definition, as written should probably not even compile because you are using an attribute on a property element (the TextBox.Style tag).
Update your code so that the inner style definition has the 'BasedOn' attribute like so:
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{DynamicResource SelectableTextStyle}">
<Style.Triggers>
....
And everything will be gravy.

Related

wpf visibility based on a condition

I want to show a StackPanel based on a particular condition. In this example I've used the BorderThickness property:
<ContentControl x:Name="gridDati" VirtualizingPanel.VirtualizationMode="Recycling" VirtualizingPanel.ScrollUnit="Item" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items}" Value="{x:Null}">
<Setter Property="BorderThickness" Value="0" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="0">
<Setter Property="BorderThickness" Value="12" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="pnlLoading" Visibility="Visible">
<Label Content="">
<Label.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=BorderThickness, ElementName=gridDati, UpdateSourceTrigger=PropertyChanged}" Value="0">
<Setter Property="TextBlock.Text" Value="" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=BorderThickness, ElementName=gridDati, UpdateSourceTrigger=PropertyChanged}" Value="12">
<Setter Property="TextBlock.Text" Value="STAND BY" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</StackPanel>
Basically when in the code behind I apply a template on gridDati, while the item counter is still zero, the border is set correctly to 12. After that it turns to zero (item binded) and this behevior is what I want.
So, I also would like to show a StackPanel at the same condition, so I used a DataTrigger but seems that is not fired at all. How can I "link" these two condition? so show a stackpanel when I have items in the datagrid?
This is the proper way to declare the Label so you get the desired result.
<Label>
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Items.Count}" Value="0">
<Setter Property="Content" Value="STAND BY"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
But there are a few points I need to explain to make sure you understand what I changed and why. I'm going to go through the XAML from the inside out.
First, I changed the Setter to use the right property name. You're using a Label, and your old Setter had Property="TextBlock.Text". TextBlock.Text is not a valid property name for a Label (no such property exists), so that wasn't going to work. The property you want is called Content.
Moving up one level to the DataTrigger. Instead of binding to gridDati that is binding to Items, I just bound directly to Items. You could do it the other way, but in my opinion it would be unusual and it might cause unforeseen bugs.
Next, you'll notice I removed the first DataTrigger. WPF dependency properties can be set in a number of different ways, and there is an order of precedence for which value will be taken over others. The default value (lowest precedence) for a Label's content is for it to be empty. When the DataTrigger applies the Setter, it overrides that value (it has higher precedence). When the DataTrigger condition is no longer fulfilled (Items.Count != 0), WPF stops applying the Setter and the value reverts back to the default, because there is no longer any value of higher precedence overriding it. So you don't need to add a second DataTrigger resetting to default, wit ill do that automatically.
Moving up further you'll see I changed the opening Style tag to <Style TargetType="Label">. It's common practice to set the TargetType of a Style. Doing this also gives you IntelliSense options for Setters in that Style, which might have helpped you catch the mistake you made by trying to use TextBlock.Text as a property name.
Finally, I removed Content="" from the opening Label tag. Setting the value of a property directly on an element in XAML has a very high precedence, which overrides all Styles and DataTriggers. As long as this was there, nothing you did in any Style would change anything for the Label's Content.

Why style targettype has to be rebinded back to the original properties?

I am referring to the code block here, on Data triggers
<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleDataTriggerSample" Height="200" Width="200">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox Name="cbSample" Content="Hello, world?" />
<TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="48">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No" />
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbSample, Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Yes!" />
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
To me, from programming language design point of view, the line <Style TargetType="TextBlock"> is completely unnecessary, because it is already nested inside the <TextBlock>, so of course whatever setter property should be applied to the TextBlock type. So why need the line <Style TargetType="TextBlock">?
Can <Style TargetType> be of other type except TextBlock?
So why need the line ?
A Style may be defined as a resource, i.e. not inline, and if doesn't have a TargetType specified, how is the XAML parser supposed to be able to parse it and set the properties specified by the setters? It can't. Or at least it doesn't.
Just because you can define a Style inline you are still creating an instance of the exact same class that may be used as a (global) resource and then setting a TargetType is indeed required.
Can be of other type except TextBlock?
No, apart from a type that is derived from TextBlock. If you specify another type you will get an exception at runtime when the BAML (the compiled XAML) is parsed.
You could use any class TextBlock derives from (for example FrameworkElement).
If you implement your own CustomizedTextBlock for example you are able to use styles defined for TextBlock in your project.
You find an example for this here.

How to apply StaticResource using Textbox.Style

I have a textbox using AddItemsTextBoxStyle (defined in resource dictionary), as below:
<TextBox x:Name="txtItems" Style="{StaticResource AddItemsTextBoxStyle}" />
However, if I want to apply DataTrigger to my textbox, then I can't use the self-closing tags format. Instead, I need to reformat my textbox to something like this:
<TextBox x:Name="txtItems">
<TextBox.Style>
<Style>
<Style.Triggers>
...
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Sorry if this sounds silly. But how can I apply "static resource" for my textbox by using "TextBox.Style" tag?
You can use the Style.BasedOn property to combine your pre defined Style and your Trigger like this:
<TextBox x:Name="txtItems">
<TextBox.Style>
<Style BasedOn="{StaticResource AddItemsTextBoxStyle}">
<Style.Triggers>
...
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

How to hide texblock if the text string value is equal to zero?

I’d like to have the texblock is not visible if the value that generated dynamically is equal to zero. I think it can be done solely in XAML using DataTrigger. I am wondering if someone can help to find the proper solution for this.
Thank you in advance.
If you just want to hide the textbox based on the value of that same textbox you could use something like the code below.
<TextBox Text="0">
<TextBox.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Text}" Value="0">
<Setter Property="UIElement.Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
You can add in a binding if you wanted. Also if you need to check the value of a different textbox to determine if this one should be shown you can alter the binding on the DataTrigger.

Databinding to XML in a DataTrigger in WPF

In a WPF application, I have correctly bound a DataTemplate to an XML node that looks like:
<answer answer="Tree", correct="false" score="10" />
In my application, I have a TextBlock with the answer in it. At first, I want it invisible, but when the correct attribute in the XML file changes to "true", it must become visible.
My DataTemplate is hooked up correctly, because everything else works. For example, if I change the answer attribute in the XML file (just for testing), it changes in my WPF view. But I'm having troubles with the visibility. This is my XAML:
<TextBlock Text="{Binding XPath=#answer}" Visibility="Hidden">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
I'm guessing the Databinding in the DataTrigger isn't working correctly. Anyone have a clue?
I have run into the same problem with databound ToggleButtons. Try removing the Visibility="False" and replacing it with another DataTrigger that handles the incorrect case.
I think the issue is that the Visibility property is hard-coded. Try setting the Visibility in the style:
<TextBlock Text="{Binding XPath=#answer}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding XPath=#correct}" Value="true">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Sure, it works if you give a specific else case instead of just false. As in my case, it was {x:Null} and value. So when its value to bind is present, it will be true and TextBlock.Visibilty will be set using setters value and when binding path does not have any value inside it, i.e. null in my case, its simply {x:Null} :)

Resources