Modify expander header children when expanded - wpf

I can't figure out how to do it in XAML. I came up with workarounds that use Expanded/Collapsed events, but they just don't feel right.
I have a DataGrid, with groups, that are templated as expanders. I have a button inside expander, that's hidden by default, and only needs to be shown when an expander is expanded.
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Some Text"/>
<Button Name="MyButton" Visibility="Collapsed" Content="Add All"/>
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
So basically the code in question is right in the middle:
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Some Text"/>
<Button Name="MyButton" Visibility="Collapsed" Content="Add All"/>
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
I'm trying to set Visibility of the Button to false, when the expander is expanded. I tried using a trigger like the following:
<Expander.Style>
<Style TargetType="Expander">
<Style.Triggers>
<DataTrigger Binding="{Binding IsExpanded, RelativeSource={RelativeSource Self}}" Value="False">
<Setter TargetProperty="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
, but the compiler doesn't accept it, because it can't find MyButton (I'm guessing because it's inside a header). Any ideas on how I can get this to work?

You have to move the DataTrigger in a ControlTemplate like this:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander Name="Expander" IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Some Text" />
<Button Name="MyButton"
Visibility="Collapsed"
Content="Add All" />
</StackPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsExpanded, ElementName=Expander}" Value="False">
<Setter TargetName="MyButton" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Notes
I explicitly set the name for the Expander, because the construction RelativeSource={RelativeSource Self} points to control himself, and there in GroupItem is no property IsExpanded.
In Setter does not have a TargetProperty property, but there are TargetName.

Related

ListView Style Trigger affecting one control and not another

I have a ListView control with a list of system messages in it, and a corresponding "action" button that will help my user resolve the message:
If the message is an error message, I want the Foreground of the text and the action button (the [more...] part) to turn red. As you can see, it's not doing that.
I'm using a Style.Trigger bound to the message data's Severity to set the Foreground. This works fine for the TextBlock in the DataTemplate, but fails to affect the Button in that Template. My XAML looks like this:
<ListView x:Name="ImageCenterStatus" Background="{DynamicResource SC.ControlBrush}" Margin="10,22,10,0" FontSize="12" Grid.Column="1" ClipToBounds="True" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock TextWrapping="WrapWithOverflow" cal:Message.Attach="[Event MouseUp] = [Action DoStatusAction($dataContext)]" Text="{Binding Path=DisplayedMessage}"/>
<Button x:Name="ActionButton" Content="{Binding Path=DisplayedActionLabel}" FontSize="12" cal:Message.Attach="DoStatusAction($dataContext)" Style="{DynamicResource SC.ActionButtonHack}"></Button>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Severity}" Value="Warning">
<Setter Property="Foreground" Value="#FFCE7A16" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Severity}" Value="Error">
<Setter Property="Foreground" Value="#FFD13636" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
The Button has its own Style, SC.ActionButtonHack, but I am very careful not to override the Foreground anywhere that style:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Why does the TextBlock in the DataTemplate respond to the Foreground change in the Trigger, but not the Button?
What do I need to do to get the button to respect the Foreground change?
Bind TextElement.Foreground of ContentPresenter to ListViewItem's foreground to get it work:
<Style x:Key="SC.ActionButtonHack" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ContentPresenter x:Name="contentPresenter"
TextElement.Foreground="{Binding Foreground,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=ListViewItem}}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OR
Either bind it on Button itself:
<Button x:Name="ActionButton"
Foreground="{Binding Foreground, RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=ListViewItem}}"
Content="{Binding Path=Name}" FontSize="12"
Style="{DynamicResource SC.ActionButtonHack}"/>

Change TabItem.Header, when selected, from image to text

I have bumped into a problem to which I am seeking the solution.
I have a tabControl with styles added to it.
I want to display only images on each TabItem.Header and when that tab is selected then hide the image from header and display text (the other, inactive headers will be showing images).
Can anybody provide any help?
<Window.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Name="Borderer" Background="#FF777777" Height="70" Width="90">
<ContentPresenter x:Name="ContentTabItem" ContentSource="Header" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="#FF000000" />
<Setter TargetName="Borderer" Property="Background" Value="WhiteSmoke" />
<Setter Property="Header" >
<Setter.Value>
<Grid>
<ContentPresenter ContentSource="Tag" />
</Grid>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl Height="189" HorizontalAlignment="Left" Margin="94,123,0,0" Name="tabControl1" VerticalAlignment="Top" Width="500">
<TabItem Name="tabItem1" IsSelected="True" >
<TabItem.Tag>
<TextBlock Text="blab lab la" />
</TabItem.Tag>
<TabItem.Header>
<Image Source="images/img1.png" Width="35" Height="35" />
</TabItem.Header>
<TabItem.Content>
<Grid>
<TextBlock Name="aaa" />
</Grid>
</TabItem.Content>
</TabItem>
<TabItem >
<TabItem.Tag>
<TextBlock Text="la lab blab" />
</TabItem.Tag>
<TabItem.Header>
<Image Source="images/img2.png" Width="35" Height="35" />
</TabItem.Header>
<TabItem.Content>
<Grid>
<TextBlock Name="bbb" />
</Grid>
</TabItem.Content>
</TabItem>
</TabControl>
</Grid>
You can actually do this without replacing the whole ControlTemplate on the TabItem. You can just alter the TabItem's HeaderTemplate when the TabItem is selected, making your Style a lot shorter:
<DataTemplate x:Key="SelectedHeaderTemplate" >
<ContentControl Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem},
Path=Tag}" />
</DataTemplate>
<Style TargetType="{x:Type TabItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="HeaderTemplate" Value="{StaticResource SelectedHeaderTemplate}" />
</Trigger>
</Style.Triggers>
</Style>

Custom validation of textbox in WPF

So I'm trying to do something along this example: http://www.codeproject.com/KB/WPF/wpfvalidation.aspx
My Textbox currently looks like this:
<TextBox Height="23" HorizontalAlignment="Left" Margin="118,60,0,0" Name="CreateUserCPRTextbox" VerticalAlignment="Top" Width="120" >
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="LostFocus">
<Binding.ValidationRules>
<validators:TextRangeValidator
MinimumLength="10"
MaximumLength="10"
ErrorMessage="ID has to be 10 letters" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
I've directly copied my TextRangeValidator from the example on that website. Nothing happens when I lose focus on the textbox. No matter what I type in it. Any Ideas? :)
Have you set the Validation.ErrorTemplate? It is defined as below in the Application.Resources in the example.You may have missed that
<Application.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
Margin="5"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Green" BorderThickness="3">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
EDIT
Your default value is not triggering the validation routine.To force it to validate for default value you have to set
<validators:TextRangeValidator ValidatesOnTargetUpdated="True"
MinimumLength="10"
MaximumLength="10"
ErrorMessage="ID has to be 10 letters" />
I think you need to set ValidatesOnDataErrors=True in binding for Text to make it work
WPF TextBox Validation
<Style x:Key="TextBoxInError" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel>
<Grid>
<Polygon Points="20,10,20,0 0,0"
Stroke="Black"
StrokeThickness="1"
Fill="Red"
HorizontalAlignment="Right"
VerticalAlignment="Top"
ToolTip="{Binding ElementName=adorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
<AdornedElementPlaceholder x:Name="adorner"/>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</Grid>
<Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0"
Width="150" Height="20" CornerRadius="5"
ToolTip="{Binding ElementName=customAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text= "{Binding ElementName=customAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}" VerticalAlignment="center" HorizontalAlignment="Left"
FontWeight="Bold" Foreground="white" Width="250" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>

What's wrong with this ContentTemplate?

I'm getting an error for this content template within a style: "Must specify both Property and Value for Setter." Aren't I doing that?
<Style x:Key="LinkButton" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label x:Name="ContentRoot">
<StackPanel Orientation="Horizontal">
<Viewbox Width="24" Height="24" VerticalAlignment="Center">
<Image x:Name="ButtonImage" Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=Tag}" />
</Viewbox>
<TextBlock VerticalAlignment="Center" x:Name="ButtonText" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=Content}"></TextBlock>
</StackPanel>
</Label>
<DataTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonText" Property="TextBlock.TextDecorations" Value="Underline"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
And here is a button that will be using this style:
<Button Name="HelpButton" Style="{StaticResource LinkButton}" Height="30" Content="Help" Tag="Help.png"/>
Thanks!
I have loaded this up and have no such problems. The only problem I see is that your button will never have the style applied. This is because if you want the style applied you need to remove the x:Key from the style. Otherwise if you want the style applied only to the HelpButton, the definition should look like this:
<Button Name="HelpButton" Style="{StaticResource LinkButton}" Height="30" Content="Help" Tag="Help.png"/>
But I cannot see the error you are seeing.

Changing (Enable/Disable) GroupStyle in ListView for different category items

How can I switch between GroupStyles for a ListView based on some conditions at run time? For instance I need to use Default for items that that have GroupStyle Header name null, and if it is not null then use the custom GroupStyle theme? I tried GroupStyleSelector, and it doesn't work, because it works for multi level grouping, and in my case I have only one level grouping.
If yes, then how?
Custom GroupStyle:
<Style x:Key="grouping"
TargetType="{x:Type GroupStyle}">
<Setter Property="ContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin"
Value="0,0,0,5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False"
BorderBrush="#FFA4B97F"
BorderThickness="0,0,0,1">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold"
Text="{Binding Name}"
Margin="0"
Width="250" />
<TextBlock FontWeight="Bold"
Text="{Binding Path=Items[0].StartTime, StringFormat=T}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
Thanks a lot.
Sincerely,
Vlad.
Okay,
I found solution for it. Basically I needed to build DataTrigger and check for category in it, and if it matches, use different GroupStyle. Here is the example:
<ControlTemplate TargetType="{x:Type GroupItem}"
x:Key="defaultGroup">
<ItemsPresenter />
</ControlTemplate>
<ListView.GroupStyle>
<GroupStyle >
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin"
Value="0,0,0,5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False"
BorderBrush="Black"
BorderThickness="3"
Padding="5,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold"
Margin="0"
Width="250">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} ({1} jobs)">
<Binding Path="Name" />
<Binding Path="ItemCount" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock FontWeight="Bold"
Text="{Binding Path=Items[0].Category, StringFormat=T}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Items[0].Category}"
Value="ABC">
<Setter Property="Template"
Value="{StaticResource defaultGroup}" />
</DataTrigger>
</Style.Triggers>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>

Resources