WPF, how to style TabItem basing on TabControl's property? - wpf

I want to achieve similar effect, which can be seen in Notepad++: dividing TabContol into two TabControls. Obviously both will have selected tab on their own, but still only one of them will be active.
For those who doesn't know Notepad++, this is how it looks like:
For that I'll need to introduce "Active" property on TabControl (not Focused, because when one of TabControls loses focus, its selected tab still remains active). However, I have no idea how to craft trigger on TabItem's ControlTemplate, which will allow me to distinguish selected and selected+active tab.
This is how my current TabItem template look:
<Style x:Key="BaseRootTabItem" TargetType="TabItem">
<Style.Setters>
<Setter Property="Background" Value="{StaticResource NormalTabBackgroundBrush}" />
<Setter Property="TextBlock.Foreground" Value="{StaticResource NormalTabForegroundBrush}" />
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.Setters>
<Setter Property="Background" Value="{StaticResource HoverTabBackgroundBrush}" />
<Setter Property="TextBlock.Foreground" Value="{StaticResource HoverTabForegroundBrush}" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="DocumentTabItem" TargetType="TabItem" BasedOn="{StaticResource BaseRootTabItem}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border x:Name="TabBorder" BorderThickness="1,1,1,0" BorderBrush="Transparent"
Background="{TemplateBinding Background}" TextBlock.Foreground="{TemplateBinding Foreground}">
<ContentPresenter x:Name="ContentSite" HorizontalAlignment="Center" VerticalAlignment="Center" ContentSource="Header" Margin="6,2" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.Setters>
<Setter Property="Background" Value="{StaticResource SelectedDocumentTabBackgroundBrush}" />
<Setter Property="Foreground" Value="{StaticResource SelectedDocumentTabForegroundBrush}" />
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
I need something like:
<Trigger Property="(Owning TabControlEx's Active property)" Value="True">
<Trigger.Setters>
...
</Trigger.Setters>
</Trigger>
Or maybe there's some other solution?

Since Active property doesn't belong to TabItem, Trigger won't work. Use DataTrigger with binding to parent:
<DataTrigger Binding="{Binding Active, RelativeSource={RelativeSource AncestorType=TabControlEx}}"
Value="True">
</DataTrigger>

Related

Is there a way to change Foreground of a TextBox ScrollViewer in xaml whithout change the local property?

Is there a way to change the Foreground of a custom TextBox in xaml with triggers or visualstates without change the local main Foreground property?
Here is the xaml style of a generic custom TextBox with randomly chosen colors:
<Style TargetType="{x:Type local:CustomTextBox}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Border x:Name="PART_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1">
<ScrollViewer x:Name="PART_ContentHost"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
Focusable="False"/>
</Border>
<ControlTemplate.Triggers>
<!-- Can be IsMouseOver, IsFocused, etc... -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PART_Border"
Property="Background" Value="Green"/>
<Setter TargetName="PART_Border"
Property="BorderBrush" Value="DarkGreen"/>
<!-- The only method I know that works is this one
that changes the local property -->
<Setter Property="Foreground" Value="Yellow"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I've tried to use these lines in trigger but everyone fails (does nothing):
<Setter TargetName="PART_ContentHost" Property="Foreground" Value="Yellow"/>
<Setter TargetName="PART_ContentHost" Property="TextBlock.Foreground" Value="Yellow"/>
<Setter TargetName="PART_ContentHost" Property="TextElement.Foreground" Value="Yellow"/>
<Setter TargetName="PART_Border" Property="TextBlock.Foreground" Value="Yellow"/>
<Setter TargetName="PART_Border" Property="TextElement.Foreground" Value="Yellow"/>
In Buttons the Foreground color can be changed by changing TextBlock.Foreground of the parent element (e.g. TextBlock.Foreground of PART_Border), but this not works with TextBoxes.
Changing the local property as this line does...
<Setter Property="Foreground" Value="Yellow"/>
...has the problem that if I change later the main Foreground property (from Black to Gray for example), the trigger cannot change it anymore to Yellow, for example with this:
<local:CustomTextBox ... Foreground="Gray"/>
VisualStates cannot interact even with the main Foreground property.
So is there another way I don't know to accomplish this in xaml or is it a limitation of wpf?
What if you completely override the ControlTemplate?
<TextBox Text="Hiho" Foreground="Red">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<TextBox Name="TheContent" Text="{TemplateBinding Text}"/>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="TheContent" Property="Foreground" Value="Yellow"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
</TextBox>
You could animate the property using a Storyboard. This will take effect over local values:
<Window ...>
<Window.Resources>
<Style TargetType="{x:Type local:CustomTextBox}">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Border x:Name="PART_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="1">
<ScrollViewer x:Name="PART_ContentHost"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
Focusable="False"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="PART_Border" Property="Background" Value="Green"/>
<Setter TargetName="PART_Border" Property="BorderBrush" Value="DarkGreen"/>
<Trigger.EnterActions>
<BeginStoryboard x:Name="sb">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Brushes.Yellow}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<RemoveStoryboard BeginStoryboardName="sb" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<local:CustomTextBox Margin="10" Text="sample text" Foreground="Red" />
</StackPanel>
</Window>

Datatrigger Change ImageBrush source

Currently am working on a reversi game and this is the ControlTemplate for a Stone on the board , Am using a DataTrigger to see who the Owner is for the Stone to set the button to the appropriate Image , But when I use TargetName on the setter (To the imagebrush wich is imga). I get an Error "Cannot find the Trigger target 'imga'. (The target must appear before any Setters, Triggers, or Conditions that use it.) "
but since I declared this brush before my setters this seems really strange to me. this code is in the app.xaml resources.
Thanks in advance
this is the relevant part of the button
<Style x:Key="0" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Height" Value="48"/>
<Setter Property="Width" Value="48" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Ellipse>
<Ellipse.Fill x:Uid="filler">
<ImageBrush x:Name="imga" ImageSource="afbeeldingen/vuur.jpg"/>
</Ellipse.Fill>
</Ellipse>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E59400" />
</Trigger>
<DataTrigger Binding="{Binding Owner.Value.ArrayIndex}" Value="0">
<DataTrigger.Setters>
<Setter TargetName="imga" Property="ImageSource" Value="afbeeldingen/vuur.jpg" />
</DataTrigger.Setters>
</DataTrigger>
imga is not a part of your Template, it is a resource, so you cannot change its properties from a trigger. What you have to do is change the Fill property from you Ellipse instead. You would have Something like this :
<ControlTemplate TargetType="{x:Type Button}">
<Ellipse x:Name="myEllipse">
<Ellipse.Fill x:Uid="filler">
<ImageBrush x:Name="imga" ImageSource="afbeeldingen/vuur.jpg"/>
</Ellipse.Fill>
</Ellipse>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#E59400" />
</Trigger>
<DataTrigger Binding="{Binding Owner.Value.ArrayIndex}" Value="0">
<DataTrigger.Setters>
<Setter TargetName="myEllipse" Property="Fill">
<Setter.Value>
<ImageBrush x:Name="imga" ImageSource="afbeeldingen/vuur.jpg"/>
</Setter.Value>
</Setter>
</DataTrigger.Setters>
</DataTrigger>

Binding TextBlock color to Button IsEnabled from Style

I am trying to define a Style that could be used where Button holds a TextBlock as its Content and when Button has IsEnabled=False I want to set TextBlock's Foregroung color.
<Button Style="{StaticResource TransparentButtonStyle}"
IsEnabled="{Binding IsAllowed}">
<TextBlock Text="Click Me"
Style="{StaticResource HyperLinkStyle}">
</TextBlock>
</Button>
<Style x:Key="TransparentButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="Transparent">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="HyperLinkStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="Blue" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Foreground" Value="LightBlue" />
<Setter Property="TextDecorations" Value="Underline" />
</Trigger>
</Style.Triggers>
</Style>
Can I modify the TextBlock's Style to grab the parent Button's IsEnabled value somehow to be able to set the Foreground color ?
you can just add <Trigger Property="IsEnabled" Value="False"> to your HyperLinkStyle, but I recommend you to create LinkButtonStyle instead, so you final markup fill be much cleaner:
<Button Content="Link text" Style="{StaticResource LinkButtonStyle}" />
this just makes your views much cleaner...
here's my LinkButton template:
<Style x:Key="LinkButtonStyle" TargetType="Button">
<Setter Property="Foreground" Value="{StaticResource LinkButtonText}" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<TextBlock x:Name="ContentPresenter" Background="{TemplateBinding Background}"
Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ContentPresenter" Property="Foreground" Value="{DynamicResource LinkButtonDisabled}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsEnabled" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="ContentPresenter" Property="TextDecorations" Value="Underline"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If you want to get to Button you can use DataTrigger with RalativeSource binding
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Setters -->
</Trigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Button}}, Path=IsEnabled}" Value="False">
<!-- Setters -->
</DataTrigger>
</Style.Triggers>
but if you want to make your Style independent of what is the parent then normal Trigger on IsEnabled property should work just as good
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- Setters -->
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<!-- Setters -->
</Trigger>
</Style.Triggers>
normal Trigger should work because IsEnabled value is influenced by UIElement.IsEnabledCore which
Gets a value that becomes the return value of IsEnabled in derived classes
...
The default implementation of this property caches the value and also calculates whether the parent element of this element is enabled. (If the parent is not enabled, the child element cannot be effectively enabled in practical user interface (UI).)
so basically if parent Button is disabled child TextBlock will be disabled as well

Setting a different color for each tab using the same style

So I have the following style in my Window.Resources:
<Style TargetType="TabItem" x:Key="tiS">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Grid Height="40" Width="186">
<Border Name="tiBorder" Background="Transparent">
<ContentPresenter ContentSource="Header"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextBlock.FontSize="20"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="90"/>
<Setter TargetName="tiBorder" Property="Background" Value="{DynamicResource tiB}"/>
<!--<Setter TargetName="tiBorder" Property="Margin" Value="0,-4,0,-4"/>-->
<Setter TargetName="tiBorder" Property="BorderThickness" Value="0,0,0,1"/>
<Setter TargetName="tiBorder" Property="BorderBrush" Value="{StaticResource tiLineFade}"/>
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="Panel.ZIndex" Value="80"/>
<Setter TargetName="tiBorder" Property="BorderThickness" Value="0,0,0,1"/>
<Setter TargetName="tiBorder" Property="BorderBrush" Value="{StaticResource tiLineFade}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style is used on 5 TabItems, each with a different color given by 'DynamicResource tiB' in the trigger.
In each TabItem I have the following resource placed (Color1 is set in the Window.Resources):
<TabItem.Resources>
<SolidColorBrush x:Key="tiB" Color="{StaticResource Color1}"/>
</TabItem.Resources>
I was wondering, is there an easier way to do this or am I doing it right? This is the first time I'm working with styles in WPF so I want to do it right. (This code is working! Looking for a better (if there is one) solution to my situation.)
Here is the full code: http://pastebin.com/igwxgp6M
I believe this will work
<TabControl ...>
<TabControl.ItemsContainerStyle>
<Style TargetType="TabItem">
//Put triggers here
</Style>
<TabControl.ItemsContainerStyle>
</TabControl>

WPF repeatbutton in scrollbar only triggers IsPressed from the template, not the style

I have a minor issue. We'd like to put as much stylistic items in the styles and outside of the control templates, to make themeing easier. So for the scrollbar's repeatbutton, I can get all of this to work but IsPressed. That only works from the template.
So the template is (basically):
<ControlTemplate x:Key="ScrollBarButtonCT" TargetType="{x:Type RepeatButton}">
<Border
x:Name="borderRepeatButton"
Margin="1"
CornerRadius="2"
Background="{TemplateBinding Background}">
<Path x:Name="pathArrow"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{DynamicResource ThumbBrush}"
Data="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="true">
<Setter TargetName="borderRepeatButton" Property="Background" Value="{DynamicResource ThumbPressedBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
And the style is
<Style x:Key="ScrollBarButtonStyle" TargetType="{x:Type RepeatButton}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Focusable" Value="false"/>
<Setter Property="IsTabStop" Value="false"/>
<Setter Property="Background" Value="{DynamicResource ScrollBarBGBrush}"/> <!-- borderRepeatButton -->
<Setter Property="OpacityMask" Value="{DynamicResource ThumbBrush}"/> <!-- pathArrow-->
<Setter Property="Template" Value="{StaticResource ScrollBarButtonCT}"/>
<Style.Triggers>
<!--<Trigger Property="IsPressed" Value="true"> .... this doesn't work coming from the style
<Setter Property="Background" Value="{DynamicResource ThumbPressedBrush}" />
</Trigger>-->
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource ScrollBarDisabledBGBrush}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="{DynamicResource ThumbHoverBrush}"/>
</Trigger>
</Style.Triggers>
</Style>
I can't get IsPressed to work from the style. Looking in Snoop IsPressed is raised just fine when using the control. What am I doing wrong? Thanks!
No idea why it doesnt work, maybe it needs static resource? u can try this to get all styles in one place.
<Style x:Key="xxxtyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Fill" TargetName="rectangle" Value="#FFD5D5D5"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ps TargetType="typeName" == TargetType="{x:Type typename}"
I know this is old, but it turns out this must be a bug in the template. We could never get it to work, and talking to some people on the inside more or less confirmed it. We just left the value in the template and worked around it by swapping templates when we needed a different RepeatButton style.

Resources