MenuItem.Icon not greyed out with DataTrigger - c#

I try to show a different image as menuitem.icon if the menuitem is disabled (just a greyed out version). This is what I tried:
<tk:DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="_Divorce" Command="{Binding Path=DivorceCommand}">
<MenuItem.Icon>
<Image>
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem, AncestorLevel=3}}" Value="true">
<Setter Property="Image.Source" Value="eraser.ico" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem, AncestorLevel=3}}" Value="false"
<Setter Property="Image.Source" Value="eraser_grey.ico" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tk:DataGrid.ContextMenu>
</tk:DataGrid>
Error message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.MenuItem', AncestorLevel='1''. BindingExpression:Path=IsEnabled; DataItem=null; target element is 'Image' (Name=''); target property is 'NoTarget' (type 'Object')
What is wrong with my current XAML datatrigger binding?
I appreciate any help!

You could try the following:
Add a Trigger like CubaLibre mentioned
But instead of using a target name, which will not work. Use the following.
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Image">
<Setter.Value>
<Image Source="eraser_grey.ico"/>
</Setter.Value>
</Setter>
</Trigger>
if that will not work (sorry can't test it for myself at the moment). You could also do it the hard way:
Create a Style for menuitem, you can just completely copy the original style
Now in the control template, of the desired role template, you just add under ControlTemplate.Triggers. Your trigger like
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="eraser_grey.ico"/>
</Trigger>
to make it a little bit cleaner. I would add a service class, which contains an attached property named "DisabledIcon" or similiar and use that as an value, instead the hard coded one.
Unfortunately changing the whole style is the only working way i can think of right now.
[Edit]
Ok after some further investigation, here is a solution that works fine, even though i'm not happy with it because it is quiet ugly :( But sorry i have no better idea at the moment.
<MenuItem Header="_Divorce" Command="...">
<MenuItem.Icon>
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<Image x:Name="iconImage" Source="arrowDown.png"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}}" Value="False">
<Setter TargetName="iconImage" Property="Source" Value="close.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</MenuItem.Icon>
</MenuItem>

<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" TargetName="Image" Value="eraser_grey.ico"/>
</Trigger>
Does it work when you try to do so

Related

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.

DataTrigger on an Image that should change the Image.Source property does not work

I reproduced this question/answer as given below:
<Button Command="Play" ToolTip="Execute Macro">
<Image DataContext="{Binding ElementName=UserControlMacroEdit}" Source="/ParametricStudySharedGui;component/Image/arrowRight32x32.png" Height="24" Width="24">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsMacroRunning, Converter={StaticResource FakeTransparentConverter}, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/Run24.png"></Setter>
<Setter Property="Opacity" Value=".5"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Button>
However, there is a strange behavior.
The "Opacity" changed but the image itself stay the same. It look like that I can't modify the Image.Source from a DataTrigger. Why?
Your original code does not work because of how dependency properties are handled. In particular, since they can be set from multiple places, WPF implements the idea of "priority" for such properties. "Locally set" properties (i.e. where they are set as part of the declaration of the element in XAML) have higher precedence over any setters found in styles.
Please see Dependency Property Value Precedence for more details.
You can work around the problem by initializing the Source property in the Style itself, rather than as part of the Image declaration:
<Image DataContext="{Binding ElementName=UserControlMacroEdit}" Height="24" Width="24">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/arrowRight32x32.png"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsMacroRunning, Converter={StaticResource FakeTransparentConverter}, Mode=OneWay}" Value="True">
<DataTrigger.Setters>
<Setter Property="Source" Value="/ParametricStudySharedGui;component/Image/Run24.png"></Setter>
<Setter Property="Opacity" Value=".5"></Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>

How to bind with a relative source that is Ancestor and Self?

I have the following code:
<ItemsControl ItemsSource="{Binding MyDictionary}" >
<Image Width="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Value}" Value="False">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.SecDic}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Value}" Value="True">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.ThirdDic}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ItemsControl>
MyDictionary - Is a dictionary that the key is of type enum
Now This binding works if I change the SecDic and ThirdDic from dictionaries to BitmapImages. But I want that for each key (enum) in MyDictionary there will be a different True/False image.
So I tried to write:
<Setter Property="Source" Value="{Binding Path=DataContext.SecDic[Key],
RelativeSource={AncestorType={x:ItemsControl}}}"/>
But it doesn't work since the "Key" belongs to the data context of the image (of MyDictionary) and I changed the relative source to be the items control so there is no "Key".
Any suggestions?
What I did to solve this is a multibinding + a custom multibinding converter.
There was one binding for the dictionary and one for the key.

Make xaml datatrigger work in c# code

I am new to xaml, i have following code below, my question is how can i call InvalidForeground from c# code to change the color of checkbox text?
<ControlTemplate x:Key="ItemTemplate"
TargetType="ListViewItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CkBoxVisual">
<CheckBox.IsChecked>
<Binding Path="IsSelected"
Mode="TwoWay">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent" />
</Binding.RelativeSource>
</Binding>
</CheckBox.IsChecked>
<DataTrigger Binding="{Binding InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="true">
<Setter TargetName="CkBoxVisual" Property="Foreground" Value="Red"/>
</DataTrigger>
</CheckBox>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
In your code, you are not calling anything. In it, you're hoping a change of dependency property..
But a control of type Window does not have a dependency property with the name: "InvalidForeground".
This trigger will never be triggered.
What is your goal, change a property or be notified of a change (trigger)?
Edit: There are a number of rules you must follow:
1) The control referenced in Binding property from DataTrigger (RelativeSource):
Binding="{Binding Path=InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Must have a dependency property called "InvalidForeground", which works correctly, based on this, will not work:
{x:Type Window}
its type must be declared, will work, for example:
{x:Type my:ControlName}
2) A property changed by a Trigger or DataTrigger can never be explicitly stated, for example. Will not work:
<TextBlock Text="{Binding Any}" Foreground="#FFCCCCCC">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Will work:
<TextBlock Text="{Binding Any}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FFCCCCCC"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
3) Check the operation of Bindings using RelativeSource they have some peculiarities (you should know a little about the visual tree of WPF).
http://msdn.microsoft.com/en-us/library/ms743599.aspx
4) You should check that the binding is working properly and can follow these steps:
http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings

How to use the same DataTrigger for multiple DataTemplate?

I want to set a DataTrigger for multiple DataTemplate but I am not sure if there is way to do that?
I have view structured like below
<ItemsControl ItemsSource="{Binding Collection}"
AlternationCount="2"
ItemTemplateSelector="{StaticResource RowSelector}" >
<local:HeaderTemplateSelector x:Key="RowSelector"
NormalTemplate="{StaticResource NormalTemplate}"
ExpanderTemplate1="{StaticResource ExpanderTemplate1}"
ExpanderTemplate2="{StaticResource ExpanderTemplate2}"
ExpanderTemplate3="{StaticResource ExpanderTemplate3}" />
<DataTemplate x:Key="NormalTemplate" ...>
<stackpanel x:Name="main"
<DataTemplate x:Key="ExpanderTemplate1" ...>
<Grid x:Name="main" ..>
<DataTemplate x:Key="ExpanderTemplate2" ...>
<Expander x:Name="main"
<DataTemplate x:Key="ExpanderTemplate3" ...>
<Textblock x:Name="main" ...>
I want this data trigger which provides alternative background for rows, the trigger defined below
<DataTemplate.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="Blue" TargetName="main"/>
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="black" TargetName="main"/>
</Trigger>
</DataTemplate.Triggers>
I can add this to the end of each DataTemplate and it works fine but it mean that I have to duplicate the some code 4 times, is there any way to avoid that? (I know I can use datagrid but it is overkill for this simple structure)
You can try to set the trigger with the style:
<Style TargetType="Panel" x:Key="AlternatelyPainted">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
(code thankfully stolen from this answer) and use it in your templates:
<DataTemplate x:Key="NormalTemplate" ...>
<StackPanel Style="{StaticResource AlternatelyPainted}">
...
</StackPanel>
</DataTemplate>
etc.
Edit: if you want the style to be universal, not Panel-specific, you can try the following trick:
<Style x:Key="AlternatelyPainted">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="0">
<Setter Property="Panel.Background" Value="Red"/>
<Setter Property="Control.Background" Value="Red"/>
<Setter Property="TextBlock.Background" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=(ItemsControl.AlternationIndex)}" Value="1">
<Setter Property="Panel.Background" Value="Blue"/>
<Setter Property="Control.Background" Value="Blue"/>
<Setter Property="TextBlock.Background" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
(removed the TargetType and added explicit types to the property names). Note that TextBlock doesn't inherit its Background, so we need an additional line for it.

Categories