This behavior seems incredibly odd to me, and I assume I am doing something wrong to get it. I have a ContentControl that uses a DataTemplete to render an TabControl. I want an image to display when there are no tabs open, and hide when there are. But here is the problem:
<Image Name="image1" Stretch="Uniform" Visibility="Hidden" Source="/Affinity;component/Images/affinity_logo.png">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, ElementName=tabcontrolworkspaces}"
Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
This doesn't work... sort of.
I have tested this on Visiblity and Margin (just to be sure). This trigger will alter the property, unless that property is defined in the Image tags. If it is, the trigger will not update that property. So, if I don't define a visibility for the image, and the trigger hides it, it works. The problem is, the default is Visible and the trigger needs to show it when value=0 and hide it otherwise.
Why won't the trigger override properties that are explicitly defined? Isn't that its purpose?
This is the normal Dependency Property Value Precedence. Setting it on Image is at #3, while in the Style trigger is at a lower precedence of #6.
You can do this instead:
<Image Name="image1" Stretch="Uniform" Source="/Affinity;component/Images/affinity_logo.png">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, ElementName=tabcontrolworkspaces}"
Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
Set your Visibility in the Style in addition to in the Trigger
I've encountered this weird behavior with DataTriggers many times, where sometimes DataTrigger Setters won't take effect unless the Setter is also defined in the Style.
Won't work
<Image Visibility="Collapsed">
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding Something}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Image.Style>
</Image>
Will work
<Image>
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Something}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Image.Style>
</Image>
Edit: See the accepted answer for an explanation on why this doesn't work. It has to do with the order in which dependency properties are determined, where properties defined in the <Tag> always take precedence over triggered values.
When a trigger is true it changes the value to the desired value. When it is no longer true it returns the value to the previous value. It won't change it to a value it wans't.
This means that if the original value is visible, and you change it to visible, when the trigger is no longer active, the value will revert back to visible.
Related
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.
i'm having a small issue with an style trigger on an image. The trigger does change the source of the image, but when it does i lose the streght="fill" property. I've also tried setting it in the trigger, but it seem to ignore it. What am i missing here?
<Image x:Name="xBackground"
Height="99.5"
Width="271"
Stretch="Fill">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="Green.png" />
<Style.Triggers>
<DataTrigger Binding="{Binding HasAlarm}" Value="true">
<Setter Property="Source" Value="Red.png" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
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
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.
I have a Command bound to a Button in XAML. When executed, the command changes a property value on the underlying DataContext. I would like the button's Content to reflect the new value of the property.
This works*:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}"
Content="{Binding Path=TestProperty, Mode=OneWay}" />
But this doesn't:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Content" Value="Yes"/>
</DataTrigger.Setters>
</DataTrigger>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="False">
<DataTrigger.Setters>
<Setter Property="Content" Value="No"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Why is that?
* By "works" I mean the Content gets updated whenever I click the button. I don't know if it's important or not, but just in case it is, I'm using .NET 3.5 SP1.
UPDATE:
I also tried another variation (removing the second DataTrigger in favor of default value), still to no avail:
<Button Command="{x:Static Member=local:MyCommands.TestCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content" Value="No"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=TestProperty, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Content" Value="Yes"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
TIA
Try to set your default Content to No and to use only the first DataTrigger. Avoid the second one.
I solved the problem myself. It turned out I was looking in the wrong place. The bound CLR property was of different type than what I'd assumed in XAML. My stupidity, really.