I have a ControlTemplate which uses the same color in multiple elements. On certain triggers (e.g. OnMouseOver) I'd like to change that color. As far as I can see I have to define a setter for every element to change its color. Is there a way to reference a shared resource in the template that all contained elements can access, and which can be changed by a trigger, so I don't have to address each and every element?
Here's an (made up) example:
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Fill="red" Grid.Column="0"/>
<Ellipse Fill="red" Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
When the control is disabled, I want the ellipses to be grey, without setting both of them explicitly, e.g. I don't want to write
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="_ellipse1" Property="Fill" Value="Grey"/>
<Setter TargetName="_ellipse2" Property="Fill" Value="Grey"/>
</Trigger>
but set the color of both ellipses with just one setter.
Place the trigger on a style for the ellipses (ellipsi?) instead of the button. IsEnabled will propagate down if you set IsEnabled = false on the Button.
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Style TargetType="{x:Type Ellipse}">
<Setter Property="Fill" Value="Red" />
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Fill" Value="Gray" />
</Trigger>
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0"/>
<Ellipse Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
I think the best way to do this is with a value converter. Then you can avoid a messy trigger altogether. Here is your example, but with a converter added.
<Window.Resources>
<local:EnabledToColorConverter x:Key="enabledToColorConverter"/>
<ControlTemplate x:Key="myTemplate" TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Ellipse Name="_ellipse1" Fill="{TemplateBinding IsEnabled, Converter={StaticResource enabledToColorConverter}}" Grid.Column="0"/>
<Ellipse Name="_ellipse2" Fill="{TemplateBinding IsEnabled, Converter={StaticResource enabledToColorConverter}}" Grid.Column="1"/>
<ContentPresenter Grid.ColumnSpan="2" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Template="{StaticResource myTemplate}">Enabled Button</Button>
<Button Template="{StaticResource myTemplate}" IsEnabled="False">Disabled Button</Button>
</StackPanel>
And here is the converter:
public class EnabledToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isEnabled = (bool)value;
return isEnabled ?
Brushes.Red :
Brushes.Gray;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Related
Ok so i have this properties in my View Model:
public double Progress
{
get { return _progress; }
set
{
_progress= value;
OnPropertyChanged();
}
}
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
And as you can see this implement INotifyPropertyChanged.
This is my Progress-Bar:
<ProgressBar Name="progressBarColumn"
Minimum="0"
Maximum="100"
Value="{Binding Progress, UpdateSourceTrigger=PropertyChanged}"
Width="{Binding Path=Width, ElementName=ProgressCell}"
Style="{StaticResource CustomProgressBar2}"/>
And my Style:
<Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="Blue"
BorderBrush="Blue"
BorderThickness="1"/>
<Border Name="PART_Indicator"
CornerRadius="0"
Background="Gray"
BorderBrush="Gray"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So this works fine, my Progress-Bar color is Blue and fill with Gray color.
Now i want to add two more things and dont know when:
1. When my Progress-Bar reach the Value of 100 i want it Background become Green.
When my Property called IsChecked is False i want that my Background become Red.
So until here i have this IMultiValueConverter:
public class ProgressToPropgressBarBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Do my stuff
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem is that i dont know where to add my converter and how.
EDIT:
<Style x:Key="CustomProgressBar2" TargetType="ProgressBar">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar" >
<Grid x:Name="Root">
<Border Name="PART_Track"
CornerRadius="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Background}"
BorderThickness="1"/>
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{TemplateBinding Foreground}"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="1"
HorizontalAlignment="Left" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Value="100" Binding="{Binding Path=Value, RelativeSource={RelativeSource AncestorType=ProgressBar}}">
<Setter Property="Foreground" Value="DarkCyan"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked}" Value="true">
<Setter Property="Background" Value="{DynamicResource ProgressBackgroundColor}" />
<Setter Property="Foreground" Value="{DynamicResource ProgressBarFillColor}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsChecked}" Value="false">
<Setter Property="Background" Value="#55B3B3B6" />
</DataTrigger>
</Style.Triggers>
</Style>
You logic is simple enough you can avoid a converter and simply use triggers in your style:
<Style.Triggers>
<DataTrigger Binding=“{Binding Progress}” Value=“100”>
<Setter Property=“Background” Value=“Green” />
</DataTrigger>
<DataTrigger Binding=“{Binding IsChecked}” Value=“True”>
<Setter Property=“Background” Value=“Red” />
</DataTrigger>
</Style.Triggers>
Then you need to update your control template to properly respect the control’s Background property. You can use TemplateBinding for this:
<Border Name="PART_Track"
CornerRadius="0"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding Background}"
BorderThickness="1"/>
<Border Name="PART_Indicator"
CornerRadius="0"
Background="{TemplateBinding Foreground}"
BorderBrush="{TemplateBinding Foreground}"
BorderThickness="1"
HorizontalAlignment="Left" />
And if you want the same Blue and Gray default colors, add them to the style:
<Setter Property=“Background” Value=“Blue”/>
<Setter Property=“Foreground” Value=“Gray”/>
I need help making a button looking like this:
It should have those rounded corners, no matter what the size of the button.
What I have so far:
A style for the Button in the App.xaml
<!-- Standard Button Colors-->
<SolidColorBrush x:Key="StandardButtonBackground" Color="#1C536F" />
<SolidColorBrush x:Key="StandardButtonForeground" Color="#FEFEFE" />
<!-- Standard Button Template-->
<Style x:Key="StandardButton" TargetType="Button">
<Setter Property="Background" Value="{StaticResource StandardButtonBackground}" />
<Setter Property="Foreground" Value="{StaticResource StandardButtonForeground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="55" Background="{StaticResource StandardButtonBackground}">
<ContentPresenter Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Used in the view:
<Button Style="{StaticResource StandardButton}" Content="Test" FontSize="20"/>
It looks like this:
But the corners are in pixel-size, so when the size of the button changes, the corners do not accordingly.
And next thing is this colored line which should show a status. How can I add such a line?
You can use a converter to get the radius and a second border for the highlight.
MainWindow.xaml
<Window
...
>
<Window.Resources>
<!-- Converter -->
<local:HeightToRadiusConverter x:Key="HeightToRadiusConverter"/>
<!-- Standard Button Colors-->
<SolidColorBrush x:Key="StandardButtonBackground" Color="#1C536F" />
<SolidColorBrush x:Key="StandardButtonForeground" Color="#FEFEFE" />
<SolidColorBrush x:Key="StandardButtonHighlight" Color="GreenYellow" />
<!-- Standard Button Template-->
<Style x:Key="StandardButton" TargetType="Button">
<Setter Property="Background" Value="{StaticResource StandardButtonBackground}" />
<Setter Property="Foreground" Value="{StaticResource StandardButtonForeground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="{Binding ActualHeight, RelativeSource={RelativeSource Self}, Converter={StaticResource HeightToRadiusConverter}}"
Background="{TemplateBinding Background}">
<Border Margin="5" BorderThickness="2" BorderBrush="{StaticResource StandardButtonHighlight}"
CornerRadius="{Binding ActualHeight, RelativeSource={RelativeSource Self}, Converter={StaticResource HeightToRadiusConverter}}">
<ContentPresenter Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True"/>
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Style="{StaticResource StandardButton}" Content="Test" FontSize="20"/>
</Grid>
</Window>
Converter
public class HeightToRadiusConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double height = (double)value;
return height / 2;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new NotImplementedException();
}
}
Resizing the window will resize the button accordingly.
I have a DataGridColumnHeader Style that has a TextBox situated above the DataGridColumn. In the TextBox Style, there is a DataTrigger that sets its visibility based on the Text in the DataGridColumn. Rather than checking if its value equals "Pos", I would like to use a converter to see if its value begins with the string "Pos". But when I add the converter, the value parameter in the converter turns out not being a string. Instead it is the MainWindow object that the datagrid belongs to. Why would it take on a different meaning when I add a converter?
<Style TargetType="{x:Type DataGridColumnHeader}" x:Key="DataGridColumnHeaderStyle"
xmlns:theme="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<Style.Resources>
<SolidColorBrush x:Key="borderBackground">#E4E5ED</SolidColorBrush>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border Background="{StaticResource borderBackground}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Margin="1" Grid.Column="0" Grid.Row="0" IsReadOnly="True"
Text="{Binding Path=DataContext.TotalPos, StringFormat=N0, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type DataGrid}}}">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="Pos">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<theme:DataGridHeaderBorder Grid.Column="0" Grid.Row="1" SortDirection="{TemplateBinding SortDirection}"
IsHovered="{TemplateBinding IsMouseOver}"
IsPressed="{TemplateBinding IsPressed}"
IsClickable="{TemplateBinding CanUserSort}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding ="{TemplateBinding Padding}"
SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
SeparatorBrush="{TemplateBinding SeparatorBrush}">
<TextBlock Grid.Column="0" Grid.Row="1" Text="{TemplateBinding Content}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
TextWrapping="Wrap"></TextBlock>
</theme:DataGridHeaderBorder>
<Thumb Grid.Column="0" Grid.Row="1" Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" Style="{StaticResource ColumnHeaderGripperStyle}"/>
<Thumb Grid.Column="0" Grid.Row="1" Name="PART_RightHeaderGripper" HorizontalAlignment="Right" Style="{StaticResource ColumnHeaderGripperStyle}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With converter:
<src:StartsWith x:Key="startsWith"/>
...
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource startsWith}, ConverterParameter=Pos}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
[ValueConversion(typeof(string), typeof(bool))]
public class StartsWith : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((string)value).StartsWith((string)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("Not Implemented.");
}
}
It doesn't know what to bind to...add Binding RelativeSource={RelativeSource Self}, Path=Text for the converter.
I bind with this, thanks
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridColumnHeader, AncestorLevel=1}, Path=Column.Header, Converter={StaticResource startsWith}, ConverterParameter=Pos}" Value="True">
I just created an Expander style with a checkbox in the header:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Background" Value="{StaticResource ExpanderBackgroundBrush}"/>
<Setter Property="HeaderTemplate" Value="{StaticResource MyHeaderExpander}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" x:Name="ContentRow"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" x:Name="Border" Background="{TemplateBinding Background}" BorderThickness="1"
BorderBrush="{StaticResource ExpanderBorderBrush}" CornerRadius="2,2,0,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="18"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2"/>
<ToggleButton Grid.Column="2" x:Name="ToggleBt" Template="{DynamicResource MyExpanderToggleButton}" Background="{StaticResource ExpanderBackgroundBrush}"
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
OverridesDefaultStyle="True"/>
<ContentPresenter Grid.Column="1" Margin="0,0,0,0" RecognizesAccessKey="True" ContentSource="Header"/>
</Grid>
</Border>
<Border Visibility="Collapsed" Grid.Row="1" x:Name="ExpandSite" Background="{StaticResource ContentBackgroundBrush}"
BorderBrush="{StaticResource ExpanderBorderBrush}" BorderThickness="1,0,1,1" CornerRadius="0,0,2,2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Focusable="false"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter Property="Visibility" Value="Visible" TargetName="ExpandSite"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource ExpanderBackgroundBrush}" TargetName="Border"/>
<Setter Property="BorderBrush" Value="{StaticResource ExpanderBackgroundBrush}" TargetName="Border"/>
<Setter Property="Visibility" Value="Hidden" TargetName="ToggleBt" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I create a new property (IsChecked) for this expander in order to bind to a boolean value? e. g:
<Expander IsChecked="{Binding MyBoolValue}" />
Also on the template:
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2" IsChecked="{TemplateBinding ????}" />
EDIT:
I found a similar question but I want to know if there's another way to do it without using control inheritance.
I created an Attached Property like this:
public sealed class AP : DependencyObject
{
public static bool GetIsChecked(DependencyObject obj)
{
return (bool)obj.GetValue(IsCheckedProperty);
}
public static void SetIsChecked(DependencyObject obj, bool value)
{
obj.SetValue(IsCheckedProperty, value);
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool), typeof(AP), new UIPropertyMetadata(false));
}
Then all I had to do was:
<Expander my:AP.IsChecked="{Binding Path=TestBool}" />
And on the template:
<CheckBox VerticalAlignment="Center" Margin="4,0,0,2" IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
I wonder what to do if the style was defined in a different assembly tough...
I'm trying to customize the TreeView control. When a user selects an item in the TreeView, I need the ActualWidth of the SelectedItem to be stored in the item's Tag:
<Style x:Key="{x:Type TreeViewItem}" TargetType="{x:Type TreeViewItem}">
<!-- ... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Rectangle x:Name="rect" />
<ContentPresenter x:Name="PART_Header" ContentSource="Header" Margin="5" />
<ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="1" />
<!-- ... -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Tag" Value="{Binding ElementName=rect, Path=ActualWidth}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Later, I listen to the SelectedItemChanged event of the TreeView:
private void views_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem item = (TreeViewItem)e.NewValue;
double i = (double)item.Tag;
}
Now the problem is that item.Tag is always null. Is this a problem with my binding? Or should things be done in a different way?
Try that :
<Setter Property="Tag" Value="{Binding Path=ActualWidth, RelativeSource={x:Static RelativeSource.Self}}" />