Validation.ErrorTemplate that accesses properties on the AdornedElementPlaceholder - wpf

I would like to have my error template look different depending on some property values on the adorned control.
Setting the TargetType like below results in a runtime exception: 'TextBox' ControlTemplate TargetType does not match templated type 'Control'. Thus, it appears that the ErrorTemplate must use a targetType of 'Control'.
<ControlTemplate x:Key="ValidationErrorTemplate" TargetType={x:Type TextBox}>
<Grid>
<AdornedElementPlaceholder HorizontalAlignment="Left" Name="placeholder"/>
<Grid Background="Yellow">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{TemplateBinding IsReadOnly}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Grid>
</ControlTemplate>
I removed the targetType and then tried this:
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource AncestorType={x:Type TextBox}}}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
And then this, which yielded no exceptions but also no effect:
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
and this, which yielded no exceptions but also no effect:
<DataTrigger Binding="{Binding (TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
and finally this, which yielded "BindingExpression path error: 'IsReadOnly' property not found on 'object' ''AdornedElementPlaceholder'":
<DataTrigger Binding="{Binding IsReadOnly, ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Green"/>
</DataTrigger>
Does anyone have any other ideas on how to reference dependency properties in the ErrorTemplate?

The correct answer was:
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
While this was one of my failed attempts early on, my test setup was flawed. I was setting the default background property on the grid instead of setting it in the style. Due to Dependency Property precedence, the value set directly on the object will always trump any value set in a style (specifically, in my triggers).
Here is a working setup:
<ControlTemplate x:Key="ValidationErrorTemplate">
<Grid>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Background" Value="Yellow"/>
<Style.Triggers>
<DataTrigger Binding="{Binding AdornedElement.(TextBox.IsReadOnly), ElementName=placeholder}" Value="True">
<Setter Property="Background" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<AdornedElementPlaceholder Name="placeholder"/>
</Grid>
</ControlTemplate>
One key here is that AdornedElement is always of type Control, so you must do the appropriate qualification (or cast?) to access properties that are not exposed on Control. This is done via the parenthesis around the class name and property. Another example is: AdornedElement.(CheckBox.IsChecked). Since IsChecked is not on Control, you must qualify it by explicitly stating the class type that owns the property.

Related

hide Canvas depending on child's content

this is my latest try to make the canvas Invisible whenever the label.Content is an empty String. Any help/advice appreciated, thanks.
<Canvas Visibility="Visible">
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, ElementName=holamouse, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:String.Empty}">
<Setter Property="Canvas.Visibility" Value="Hidden"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>
The problem here is that a local property value always has higher precedence than a value set by a Style Setter. See Dependency Property Value Precedence.
When you set Visibility="Visible" on the Canvas, any Style Setter for that property is silently ignored. You could move the property assignment to the Style, although Visible is the default value anyway:
<Canvas>
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Content, ElementName=holamouse}"
Value="{x:Static sys:String.Empty}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
<DataTrigger Binding="{Binding Content, ElementName=holamouse}"
Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>
Please note also that there is a second trigger for Value="{x:Null}" now.
You need to move the default Visibility property out of the <Canvas> tag and into the <Style>
This is because properties defined in the <Tag> take precedence over any property setters, including triggered property setters. See MSDN's Dependency Property Precedence List if you want more details.
<Canvas>
<Label Content="" Name="holamouse" />
<Canvas.Resources>
<Style TargetType="{x:Type Canvas}">
<Setter Property="Canvas.Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content, ElementName=holamouse, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static sys:String.Empty}">
<Setter Property="Canvas.Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
</Canvas>

wpf vb.net Datagrid image in row depending on value in cell

This is the code i use but it is notworking
<Window.Resources>
<Style x:Key="PinkRow" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Rank}" Value="Master">
<Setter Property="Source" Value="A_Cancel.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding RANK}" Value="Bosun">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding RANK}" Value="">
<Setter Property="Background" Value="yellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
If field rank has the value "Master" i want the image A_cancel.png
The C# is case sensitive, so the Rank and RANK are different properties. Use proper property name in your bindings. Also you must mention in your Setter the TargetName for Image element.
<DataTrigger Binding="{Binding Rank}" Value="Master">
<Setter TargetName="ImageElementName" Property="Source" Value="A_Cancel.png"/>
</DataTrigger>

Same setters for different triggers

I have several elements and all of them should look the same if "X". The problem is, "X" is different for each element, so I can't easily use a shared style for them. But in all cases, the setters are the same, so I would like to have them all in one place. How can I achieve that?
More concretely, I have this code:
<TextBlock Name="text1">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedMonth, Converter={conv:IsNullConverter}}"
Value="False">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters -->
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Name="text2">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters, same ones as above -->
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Here, I would like to put the two Setters to some common place, so that I wouldn't have to always change them one by one. Is there some way to do that?
I tried putting a collection of Setter together and then using something like DataTrigger.Setters="{StaticResource SharedSetters}". This doesn't work, but if it did, it would be exactly what I want.
First of all, you could have a common style which would be the base (BasedOn attribute) for all the other styles. This style would contain a normal Trigger which is controlled by a custom boolean attached property, and the setters you want to reuse. Then, in the other styles, which are based on the base style, your DataTriggers would only set the attached property, and let the base style set all the other properties.
Assuming you have created the attached property IsSelected on the type Foo, the base style would look something like:
<Style x:Key="IsSelectedStyle" TargetType="TextBlock">
<Style.Triggers>
<Trigger Property="local:Foo.IsSelected" Value="True">
<Setter Property="Background" Value="RoyalBlue" />
<!-- possibly other setters -->
</Trigger>
</Style.Triggers>
</Style>
And then you would use it like this:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource IsSelectedStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<Setter Property="local:Foo.IsSelected" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Move value of your setters to static resources and set key in setters.
<SolidColorBrush x:Key="keyName" Color="RoyalBlue" />
and
<Setter Property="Background" Value="{StaticResource keyName}" />
in your code.
Now you can change brush of "keyName" and it affects all setters.

Data template switching based on trigger

I'm having the same problem as described here :
ContentTemplateSelector is only called one time showing always the same datatemplate
I've attempted the same solution as Simon Weaver suggests (although his answer is somewhat abbreviated so i'm guessing it looks like the following):
<ContentControl >
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="true">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentControl.ContentTemplate"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="false">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentControl.ContentTemplate">
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl>
However when i run this, i just get 'System.Windows.Style' displayed in my content control.
As an aside, i've (sort of) got it working using an overridden DataTemplateSelector class, but the problem there is that the selector only gets evaluated on start up. I need this switching behaviour based on the data bound IsSelected property - which i was hoping the above snippet achieves. BTW the actual data templates themselves just contain UI stuff - no data triggers etc so i'm not including them in my post.
Your ContentControl is setting it's Content to the Style since you are missing your ContentControl.Style tag.
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Value="{StaticResource UnSelectedDataTemplate}" Property="ContentTemplate" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Value="{StaticResource SelectedDataTemplate}" Property="ContentTemplate" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentContro.Style>
</ContentControl>

Set to visible when item in a combobox is selected

As the title says, I have a hidden border with some controls inside, and I would like to show it when a particular item in a combobox is selected.
I tried the following
<ComboBox Name="cmbRequiredRule" SelectedValuePath="Content"
SelectedValue="{Binding Path=ClientValidation.NarrativeRequiredRule}">
<ComboBoxItem>All</ComboBoxItem>
<ComboBoxItem>Matching</ComboBoxItem>
</ComboBox>
<Border Visibility="Collapsed">
<Border.Resources>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ClientValidation.NarrativeRequiredRule}" Value="Matching">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
....
</Border>
and this property in the view model:
public string NarrativeRequiredRule
{
get...
set...
}
but the trigger doesn't seem to be working
Try setting Visibility=Collapsed in your Style Setters, not as part of the Border Tag. I've had issues in the past where a DataTrigger would not apply when the value was specified as part of the Tag.
<Border>
<Border.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test}" Value="Matching">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Resources>
...
</Border>
Locally assigned value takes precedence over styles. Hence you need to have
<Setter Property="Visibility" Value="Collapsed" />
in Style as #Rachel has pointed out.
Also I tried debugging the binding using a dummy converter and found that the value turned out to be System.Windows.Controls.ComboBoxItem: Matching instead of Matching.
Hence your final style is:
<Style TargetType="{x:Type Border}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ClientValidation.NarrativeRequiredRule}" Value="System.Windows.Controls.ComboBoxItem: Matching">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
could be binding issue. In your example below:
<DataTrigger Binding="{Binding Path=ClientValidation.NarrativeRequiredRule}" Value="Matching">
where is the ClientValidation located ? because if the whole View's DataContext is bound to VM, you will need to include these hierarchies. Check your Output log, it should throw some errors if binding failes

Resources