How do I set the style and a style trigger in WPF - wpf

I am trying to set the style for a WPF content control to a dynamic resource. I can very easily do this:
<ContentControl Style="{DynamicResource RibbonGroup}">
...
</ContentControl>
If I want to add a style trigger to a content control I can do this:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
</ContentControl>
If I try to combine these to style the control and have a trigger like this:
<ContentControl Style="{DynamicResource RibbonGroup}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="True">
...
This XAML creates an error that says the property "Style" is set more than once.
What does the XAML look like to set the style to a dynamic resource and include a style trigger.
Thanks for any help you can offer.

As it says, you set the style twice. Don't do that, use basedon to add to the style you have as a resource.
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl" BasedOn="{StaticResource RibbonGroup}">

You cannot add two different styles to one singele control yo have to do it in one!
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Style.Triggers>
<DataTrigger Binding="{Binding Path=ShowImport}" Value="True">
...
</ContentControl>
However it seems you want to make two styles that share some attributes what you can do in this case is create a BaseStyle and a secund style where you add the BasedOn property like so:
<Style TargetType="ContentControl" BasedOn="{StaticResource <X:Key_From_Base_Style>}">

Related

How to set ContentPresenter default content to Unset in WPF XAML

I'm trying to display status icons in status bar. The icons are defined as ViewBox static resources and displayed via ContentPresenter style with DataTriggers.
I would like no icon displayed if none of the triggers are matched, so I've tried setting the default Setter Content to x:Null or an empty string or remove the line at all, but the other icons stop displaying at all then.
Any ideas please?
My XAML code is as follows
<StatusBarItem Grid.Column="2">
<ContentPresenter>
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Setter Property="Content" Value="{x:Null}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=State}" Value="Ok">
<Setter Property="Content" Value="{StaticResource StatusOK}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=State}" Value="Invalid">
<Setter Property="Content" Value="{StaticResource StatusInvalid}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=State}" Value="Warning">
<Setter Property="Content" Value="{StaticResource StatusWarning}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
</StatusBarItem>
Update
I've tried using visibility as suggested by Ed Plunkett but the icons stopped showing up at all. Here is the code.
<Style TargetType="ContentPresenter">
<Setter Property="Content" Value="{StaticResource StatusOK}"/>
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=State}" Value="Ok">
<Setter Property="Content" Value="{StaticResource StatusOK}"/>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=State}" Value="Invalid">
<Setter Property="Content" Value="{StaticResource StatusInvalid}"/>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=State}" Value="Warning">
<Setter Property="Content" Value="{StaticResource StatusWarning}"/>
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
Your problem was misusing a ContentPresenter. There's no reason to use a ContentPresenter outside of a ControlTemplate. The only features it has which differ from ContentControl are things that only make sense inside a ControlTemplate. Now, as it happens, StatusBarItem is a subclass of ContentControl, so you could just style the StatusBarItem and set its Content. Either version in your question will work that way.

Validation.ErrorTemplate that accesses properties on the AdornedElementPlaceholder

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.

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

Best way to have a TextBox show a message based on a bool in the ModelView

I have a TextBox that needs to have its border change color and then display a message below the text box. This message should be displayed/hidden based on a bool value in the model. What is the best way to achieve this?
There are a ton of different ways of doing this. If you're only going to do this once, the simplest way is to add the TextBlock to the layout and use a style to hide it, e.g.:
<StackPanel>
<TextBox Text="{Binding Text, Mode=TwoWay}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="BorderBrush" Value="Lightgray"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBlock Text="This is the message displayed if IsValid is false.">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
If this is something you want to be able to repeat, you'll want to make this into a template or a user control.
Also, note that changing the visibility from collapsed to visible will change the overall layout, which could have all kinds of undesirable effects. Depending on your design, you might make the visibility default to hidden.
You can use a DataTrigger to set the text, visibility, and appearance of the textbox based on the value in the ViewModel. This seems like the simplest solution.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TheBoolean}" Value="True">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
...
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
Another option is to create an IValueConverter to convert the bool to get the text, visibility, and color.

Xamly determine if a ListBox.Items.Count > 0

Is there a way in XAML to determine if the ListBox has data?
I wanna set its IsVisibile property to false if no data.
The ListBox contains a HasItems property you can bind to. So you can just do this:
<BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
...
<ListBox
Visibility="{Binding HasItems,
RelativeSource={RelativeSource Self},
Converter=BooleanToVisibility}" />
Or as a Trigger so you don't need the converter:
<ListBox>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger
Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}"
Value="False">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
I haven't tested the bindings so there might be some typos but you should get the idea.
Do it in a trigger and you won't need a ValueConverter:
<ListBox>
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<DataTrigger
Binding="Items.Count, {Binding RelativeSource={RelativeSource Self}}"
Value="0">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
So that shows the ListBox by default, but if Items.Count is ever 0, the ListBox is hidden.
<ListBox.Style>
<Style TargetType="ListBox">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<Trigger Property="HasItems" Value="False">
<Setter Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
You can probably make this work using a ValueConverter and normal binding.
Set Visibility to be:
Visibility = "{Binding myListbox.Items.Count, Converter={StaticResource VisibilityConverter}}"
Then set up your converter to return Visibility.Collapsed etc based on the value of the count.

Resources