UserControl property trigger for child control - wpf

Depending on the IsEnabled property of my UserControl (true/false), I want the controls inside it have different colors. I want to do so with the 'magic' of XAML.
<UserControl.Resources>
<Style x:Key="EnableDependent" TargetType="{x:Type Shape}">
<Style.Triggers>
<Trigger Property="{Binding Path=IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="True">
<Setter Property="Stroke" Value="White" />
</Trigger>
<Trigger Property="{Binding Path=IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="False">
<Setter Property="Stroke" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
The style is applied in a ViewBox where a Path is drawn:
<Viewbox Grid.Column="3" Width="18" Margin="5,5,2,5" MouseEnter="Dispatch_MouseEnter" DockPanel.Dock="Right" Stretch="Uniform">
<Path Data="M0,1 L4,1 M2,0 L4,1 L2,2" Stretch="Fill" StrokeThickness="3" Width="12" Height="12" Style="{StaticResource EnableDependent}" />
</Viewbox>
I get a runtime exception that a binding cannot be set in the 'Property' property of a trigger.
So what is the way to do this?

Use a DataTrigger instead of a normal Trigger which is for internal property changes, it has a Binding-Property where you can do that.
<Style x:Key="EnableDependent" TargetType="{x:Type Shape}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="True">
<Setter Property="Stroke" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" Value="False">
<Setter Property="Stroke" Value="Black" />
</DataTrigger>
</Style.Triggers>
</Style>

Related

Default Content for Empty Datagrid or Listbox in WPF

Does anyone know how to add default content to an empty datagrid or listbox? Such as "No Results Returned" or something along those lines.
You can do something like this, where the ListBox is Hidden and an associated error Grid is displayed instead. The benefit of this approach is that it is a bit more flexible, as you have an entire Grid to use instead of a VisualBrush.
<Grid>
<ListBox x:Name="MyListBox">
<ListBox.Style>
<Setter Property="Visibility" Value="Visible" />
<Style TargetType="ListBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyListBox, Path=Items.Count}" Value="0">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Grid.Style>
<TextBlock Text="No Results Returned" />
</Grid>
<Grid>
<Style.Triggers>
<DataTrigger Binding="{Binding Items.Count, RelativeSource={RelativeSource Self}}" Value="0">
<Setter Property="CanUserSortColumns" Value="False" />
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="We did't find any matching records for your group..." FontSize="14" FontWeight="SemiBold" Foreground="LightCoral"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
This is what I have found and was able to to test it. Thanks to anyone who tried to help.

Show validation error on multiple rows in DataGrid

In my WPF application I have an ObservableCollection of items. Each of item must have a unique name and the name of the item must starts with a letter. I check data validation errors in base class that implements IDataErrorInfo. The problem is that when user enters the existing name the ellipse and the "!" sign appear only in one row, instead of two, but both of them have validation errors. Here is some code of my DataGrid.
<DataGrid ItemsSource="{Binding Path=IconManagerModel.ConfigurationIcons,
ValidatesOnDataErrors=True}" x:Name="IconsData">
<DataGrid.Resources>
<Style x:Key="errorStyle" TargetType="{x:Type TextBlock}" >
<Setter Property="Padding" Value="2"/>
<Style.Triggers>
//Error style for names which not starts with letter
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip" Value="{Binding RelativeSource=
RelativeSource FindAncestor,
AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
//Error style for duplicated names
<DataTrigger Binding="{Binding IsDuplicated}" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip" Value="Duplicated Name" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="ErrorEditStyle" TargetType="{x:Type TextBox}">
<Setter Property="Padding" Value="2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
//This template applies only for the row that has been edited.
//Other row with the same IconId keeps default style
<Grid ToolTip="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type DataGridRow}},
Path=(Validation.Errors)[0].ErrorContent}" >
<Ellipse StrokeThickness="0" Fill="Red"
Width="{TemplateBinding FontSize}"
Height="{TemplateBinding FontSize}">
</Ellipse>
<TextBlock Text="!" FontSize="{TemplateBinding FontSize}"
FontWeight="Bold" Foreground="White"
HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Icon Name">
</DataGridTemplateColumn>
<DataGridTextColumn ElementStyle="{StaticResource ResourceKey=errorStyle}"
EditingElementStyle="{StaticResource ResourceKey=ErrorEditStyle}"
Binding="{Binding IconId, ValidatesOnDataErrors=True,
NotifyOnValidationError=True,
UpdateSourceTrigger=PropertyChanged}"/>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Thanks in advance.

Changing Button image when IsEnabled

I want to change button image when button IsEnabled == False.
Below is my example, bindings are fine, when I change them for False/True it is still not working.
<Button x:Name="btnBackward" Grid.Column="0" Grid.Row="2" Command="{Binding UserWorkflowManager.NavigateBackward}" IsEnabled="{Binding UserWorkflowManager.NavigateBackwardEnable}" Grid.RowSpan="2">
<Button.Template>
<ControlTemplate>
<Image Name="_image" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="Uniform">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/UserWorkflow.View;component/Images/LDC500_butX_PreviousPane_norm.bmp" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=btnBackward, Path=IsEnabled}" Value="False">
<Setter Property="Source" Value="/UserWorkflow.View;component/Images/LDC500_butX_PreviousPane_dis.bmp"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ControlTemplate>
</Button.Template>
</Button>
Try the following
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled,RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
<Setter Property="Source" Value="/UserWorkflow.View;component/Images/LDC500_butX_PreviousPane_dis.bmp"/>
</DataTrigger>
</Style.Triggers>
Or add the trigger directly in the control template like so
<ControlTemplate>
<Image Name="_image" HorizontalAlignment="Center">
..........
</Image>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="_image" Property="Source" Value="/UserWorkflow.View;component/Images/LDC500_butX_PreviousPane_dis.bmp" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Try this:
<Button x:Name="btnBackward" Grid.Column="0" Click="btnBackward_Click">
<Button.Template>
<ControlTemplate>
<Image Name="_image" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="Uniform">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Source" Value="/Images/0.png" />
</Style>
</Image.Style>
</Image>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Source" Value="/Images/1.png" TargetName="_image"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
You should specify "TargetName" property for a setter within "ControlTemplate.Triggers"
The soution is is to use RelativeSource={RelativeSource Self}} in your DataTrigger.
<DataTrigger
Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}"
Value="False">
Example
Add this style:
<Style x:Key="MainButtonStyle" TargetType="Button" BasedOn="{StaticResource ChromelessButtonStyle}">
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="Green" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Background" Value="Blue" />
</DataTrigger>
</Style.Triggers>
</Style>
Then apply this style to the button:
<Button x:Name="ActionButton" Style="{StaticResource MainButtonStyle}" Command="{Binding MyCmd}" IsEnabled="{Binding ActionButtonEnabled}"/>

Why won't IsChecked change on this toggle button?

I have the following xaml for a toggle button:
<ToggleButton Margin="0,3" Grid.Row="3" Grid.ColumnSpan="2" Command="{Binding DataContext.SelectAllCommand, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" Value="Select All"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Select None"/>
<Setter Property="Command" Value="{Binding DataContext.SelectNoneCommand, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
But the IsChecked property never gets updated when clicking the button.
If I use snoop and force the IsChecked to True then the trigger kicks in.
I tried your ToggleButton and it's working fine. The only problem I see with it is that you set Command explictly. It should be done with a Setter instead (like you did with Content) to not break the Trigger.
<ToggleButton Name="toggleButton" Margin="0,3" Grid.Row="3" Grid.ColumnSpan="2">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" Value="Select All"/>
<Setter Property="Command"
Value="{Binding DataContext.SelectAllCommand,
Mode=OneWay,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Select None"/>
<Setter Property="Command" Value="{Binding DataContext.SelectNoneCommand, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
If you're still unable to Click it, I'd check if IsHitTestVisible or similar is set to False further up the Visual Tree.
If you want to compare your version to my test app to see what's not working, I uploaded it here: http://www.mediafire.com/?ik24ftsfw2wwfwb
Fixed it by parameterising my command, binding to a new property on my viewmodel and moving Command into the style:
<ToggleButton Margin="0,3" Grid.Row="3" Grid.ColumnSpan="2" IsThreeState="False"
IsChecked="{Binding DataContext.IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" Value="Select All"/>
<Setter Property="Command" Value="{Binding DataContext.SelectAllCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Select None"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Don't you just love WPF sometimes!
Just a guess, but remove the Binding Mode=OneWay and try again.

How to control visibility of text in all listboxitems on selection of one item in dynamic Listbox Menu?

I generate ListBox menu from XML. I use datatemplate to style the behavior of listboxitems on selection and other states. I need to hide all textblocks in all listboxitems on selection of the item which gets a value ‘retract’ from XML. Now, I am able to hide texblock only in listboxitem which has this value but cannot hide textblocks in other listboxitems. I am wondering if someone can help. Thank you in advance.
<DataTemplate x:Key="ListBoxItemDataTemplate">
<Grid x:Name="DataItem">
<Image x:Name="IconImage" Source="{Binding XPath=#icon}" Height="16" Margin="16,0,0,0" Stretch="None" VerticalAlignment="Center" HorizontalAlignment="Left" />
<TextBlock x:Name="ListboxIemtextBlock" Text="{Binding XPath=#name}" />
<Image x:Name="ArrowImage" Height="10" Source="Resources/Images/arrow_collapsed_grey.png" Visibility="{Binding XPath=#state}"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True">
<Setter TargetName="ListboxIemtextBlock" Property="Foreground" Value="White"/>
<Setter TargetName="IconImage" Property="Source" Value="{Binding XPath=#iconSelected}"/>
<Setter TargetName="IconImage" Property="Height" Value="16"/>
<Setter TargetName="ArrowImage" Property="Source" Value="Resources/Images/arrow_collapsed_white.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True">
<Setter TargetName="ListboxIemtextBlock" Property="Foreground" Value="#FF6dacbe"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True" />
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource AncestorType={x:Type ListBoxItem}, Mode=FindAncestor}}" Value="True" />
</MultiDataTrigger.Conditions>
<Setter TargetName="ListboxIemtextBlock" Property="Foreground" Value="White"/>
</MultiDataTrigger>
<DataTrigger Binding="{Binding XPath=#retract}" Value="True" >
<Setter TargetName="ListboxIemtextBlock" Property="Visibility" Value="Hidden"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
It looks like I cannot control the visibility of all texblocks from a datatemplate. I think it should be done in the ListBox style. I was thinking to switch datatemplates with a second datatemplate that does not have texblock at all. I wanted to use multitrigger for conditions with values isSelected and XML-Binding to Binding="{XPath=#retract}. However, I cannot assign XPath binding for multitrigger in Listbox style. Perphaps, you might help to bind it correctly or have a better idea on how to hide texblocks.
<Style x:Key="ListBoxItemContainerStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate" Value="{StaticResource ListBoxItemDataTemplate}"/>
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter x:Name="contentPresenter"/>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Binding="{XPath=#retract}" Value="true"/>
</MultiTrigger.Conditions>
<Setter Property="ContentTemplate" Value="{StaticResource SelectedListBoxItemDataTemplate}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I populated XML with XMLDataProvider. I reference to xml this way:
<XmlDataProvider x:Key="PagesData" XPath="/Pages" Source="Data/DataSource.xml" />
XML:
<Pages xmlns="">
<page name="Item 1" icon="Resources/Iocn1.png" retract="False" />
<page name="Item 2" icon="Resources/Iocn2.png" retract="False" />
<page name="Item 3" icon="Resources/Iocn3.png" retract="True" /></Pages>
You can bind to the SelectedItem.retract for the Parent ListBox. This is a working example using Path instead of XPath (since I don't have your XML source) but you should be able to get it to work the same way
Add this trigger in the DataTemplate
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type ListBox}}, Path=SelectedItem.retract}" Value="True" >
<Setter TargetName="ListboxIemtextBlock" Property="Visibility" Value="Hidden"/>
</DataTrigger>

Resources