WPF Styles / Template Inheritance

I am currently trying to learn WPF and explore the possibility of using a standard .Net control using a style. Using C # as my preferred language, although all the code below is WPF markup.

I am setting up gmail today with a new theme (see image below) and, thus, setting myself a task, can be done in WPF.

New GMail buttons

What I managed to achieve was to create a middle Spam button using a style with a control pattern and triggers.

The right and left buttons are very similar, but have only 2 differences. They have an angular radius of 1 and an edge of 15 on the left or right side, and the middle button is at 0.

Questions!

Q1. Instead of copying the whole style and changing only these 2 attributes, this can be done using some type of inheritance. Where the right and left buttons are based on the existing style, but this makes these 2 visual changes. I already tried the BasedOn property when creating a new style, but could not edit the necessary attributes.

Q2. Are styles the correct ways to solve this problem in WPF. In WinForms, you would like to create a custom control that has a visible property associated with the enumeration, i.e. You click on the button and style options, possibly Left, Middle, Right.

Q3. The most difficult question to the last. Is it possible to do this, therefore, if my style is applied to the button. Then, when you set your background color to blue. Then the button supports gradients, but instead of them they are not quite white, now they are a shade of blue. that is, the background linear gradient brush is based, rather than overwriting the background color that was applied to the button. Or they should have separate styles. I personally do not see any code without code that can be achieved, which makes gradient brushes from one brush in WPF markup.

i.e. as below the blue button and the gray / normal button

Google buttons 2

MyStyle

 <Style x:Key="GoogleMiddleButton" TargetType="{x:Type Button}"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,1" EndPoint="0,0"> <GradientStop Color="#F1F1F1" Offset="0"/> <GradientStop Color="#F5F5F5" Offset="1"/> </LinearGradientBrush> </Setter.Value> </Setter> <Setter Property="Foreground" Value="#666666"/> <Setter Property="FontFamily" Value="Arial"/> <Setter Property="FontSize" Value="13"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="dropShadowBorder" BorderThickness="0,0,0,1" CornerRadius="1" > <Border.BorderBrush> <SolidColorBrush Color="#00000000"/> </Border.BorderBrush> <Border Name="border" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" CornerRadius="0" Background="{TemplateBinding Background}"> <Border.BorderBrush> <SolidColorBrush Color="#D8D8D8"/> </Border.BorderBrush> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" TargetName="border"> <Setter.Value> <SolidColorBrush Color="#939393"/> </Setter.Value> </Setter> <Setter Property="BorderBrush" TargetName="dropShadowBorder"> <Setter.Value> <SolidColorBrush Color="#EBEBEB"/> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="#333333"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,1" EndPoint="0,0"> <GradientStop Color="#F1F1F1" Offset="1"/> <GradientStop Color="#F5F5F5" Offset="0"/> </LinearGradientBrush> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> 

ps If you notice beginner errors in WPF above, feel free to point them out to me.

+6
source share
3 answers

I have done this in the past by defining the attached property of ExtendedProperties.CornerRadius . Then I can install it in my style:

 <Style TargetType="Button"> <Setter Property="local:ExtendedProperties.CornerRadius" Value="0"/> ... 

And use it inside the template:

 <Border CornerRadius="{Binding Path=(local:ExtendedProperties.CornerRadius), RelativeSource={RelativeSource TemplatedParent}"> 

I could then override it locally in the same way as override any other property:

 <Button Content="Archive" local:ExtendedProperties.CornerRadius="5,0,0,5"/> <Button Content="Span"/> <Button Content="Delete" local:ExtendedProperties.CornerRadius="0,5,5,0"/> 

In my case, this gives me (obviously my topic is dark):

enter image description here

And, just changing a couple of attached properties to the theme, I created this effect:

enter image description here

The advantage of this approach is that there is no need to subclass Button . You can also use the same attached property to define angular radii for other controls (e.g. TextBox ). Of course, you are not limited to a radius in a radius. You can define attached properties for all views specific to your theme that are not found in the basic controls.

The disadvantage is that it is an attached property and therefore harder to detect. Documenting your topic will help in this regard.

So, to answer your specific questions:

Q1. Yes, see my answer above. You can either override locally or define new styles that override the property.

Q2. This is a gray area. In my opinion, if this is purely visual (and not behavioral), then styles are the way to go. Of course, if it gets out of hand, you can instead subclass all the built-in controls and add your specific properties. But this makes it difficult to re-work your theme, and your application is more burdensome to develop (because you need to use your own set of controls, not standard ones).

Q3. I would say that this is possible in the code, but not intuitively for use as a managing consumer. I think you better define additional attached properties - for example. ExtendedProperties.HoverBackground , ExtendedProperties.PressedBackground - and using those from your template in exactly the same way. Then the consumers of your control then have more control over the brushes that are used when your control is in different states. I did this in the past, but used more generic property names ( SecondaryBackground , TernaryBackground ), so I can reuse these properties in other contexts. Again, documenting your topic is helpful.

+8
source

Q1: As far as I know

Q2: I would say that styles are the way to go and you can of course make your own class that comes from the button and selects the right corner radius based on if it is left, middle, right.

Q3: Must be done using a custom value converter and your own style.

Turning on. In this case, I might be tempted to place a background gradient and an angular radius on an adjacent stack panel. The buttons will be transparent with text. Then you do not have to deal with the angular radius on the individual buttons.

Edit: Added code and style for Q3 answer above. To OP; I'm not sure if this is exactly what you need, but maybe there is something interesting here.

The way I interpreted you is that you wanted to set the button background to a specific color, but it should display as a linear gradient based on that color. Other posters mentioned opacity masks, and that's not a bad idea at all. It seemed to me that I was showing how to do this using custom value converters.

The idea is that I create a converted user value that converts a solid brush to a linear gradient brush. Then I use this converter to convert the button background color from a solid color brush to a linear gradient brush.

Here is a custom value converter:

 class SolidColorBrushToGradientConverter : IValueConverter { const float DefaultLowColorScale = 0.95F; public object Convert (object value, Type targetType, object parameter, CultureInfo culture) { var solidColorBrush = value as SolidColorBrush; if (!targetType.IsAssignableFrom (typeof (LinearGradientBrush)) || solidColorBrush == null) { return Binding.DoNothing; } var lowColorScale = ParseParameterAsDouble (parameter); var highColor = solidColorBrush.Color; var lowColor = Color.Multiply (highColor, lowColorScale); lowColor.A = highColor.A; return new LinearGradientBrush ( highColor, lowColor, new Point (0, 0), new Point (0, 1) ); } static float ParseParameterAsDouble (object parameter) { if (parameter is float) { return (float)parameter; } else if (parameter is string) { float result; return float.TryParse( (string) parameter, NumberStyles.Float, CultureInfo.InvariantCulture, out result ) ? result : DefaultLowColorScale ; } else { return DefaultLowColorScale; } } public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) { return Binding.DoNothing; } } 

Then I refer to this in the style that I copied from you (basically the same, but changed it a bit), the important part of the line is this:

 Background="{Binding Path=Background,Mode=OneWay,RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource SolidColorBrushToGradientConverter}, ConverterParameter=0.95}" 

This means that we are attached to the parent background template (for example, Button.Background), we use the SolidColorBrushToGradientConverter with the parameter 0.95 (this determines how much darker the β€œlow” color should be compared with the β€œhigh” color).

Full style:

 <local:SolidColorBrushToGradientConverter x:Key="SolidColorBrushToGradientConverter" /> <Style x:Key="GoogleMiddleButton" TargetType="{x:Type Button}"> <Setter Property="Background" Value="#F5F5F5" /> <Setter Property="Foreground" Value="#666666"/> <Setter Property="FontFamily" Value="Arial"/> <Setter Property="FontSize" Value="13"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="dropShadowBorder" BorderThickness="0,0,0,1" CornerRadius="1" > <Border.BorderBrush> <SolidColorBrush Color="#00000000"/> </Border.BorderBrush> <Border Name="border" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" CornerRadius="0" Background="{Binding Path=Background,Mode=OneWay,RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource SolidColorBrushToGradientConverter}, ConverterParameter=0.95}" > <Border.BorderBrush> <SolidColorBrush Color="#D8D8D8"/> </Border.BorderBrush> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Border> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" TargetName="border"> <Setter.Value> <SolidColorBrush Color="#939393"/> </Setter.Value> </Setter> <Setter Property="BorderBrush" TargetName="dropShadowBorder"> <Setter.Value> <SolidColorBrush Color="#EBEBEB"/> </Setter.Value> </Setter> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background" Value="#4A8FF7" /> <Setter Property="Foreground" Value="#F5F5F5" /> <Setter Property="BorderBrush" TargetName="border"> <Setter.Value> <SolidColorBrush Color="#5185D8"/> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> 
+2
source

I like this challenge!

Q1: Other comments / answers are correct, templates cannot be changed or inherited. However, there are ways to pass values ​​to your template to change their appearance. A simple (but slightly hacked way) would be to pass the CornerRadius border to the template using the Tag enter code here property. A better way would be a subclass button to add a "location" property.

Q2: Yes, you are on the right track with styles / patterns

Q3: Modify your template to include an OpacityMask that has the desired gradient. Then you can either place the element behind this mask or mask the element for your background color. Full example shown below:

enter image description here

 <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Window.Resources> <Style x:Key="GoogleButton" TargetType="{x:Type Button}"> <Setter Property="Background" Value="White"/> <Setter Property="Foreground" Value="#666666"/> <Setter Property="Tag"> <Setter.Value> <CornerRadius>0</CornerRadius> </Setter.Value> </Setter> <Setter Property="FontFamily" Value="Arial"/> <Setter Property="FontSize" Value="13"/> <Setter Property="FontWeight" Value="Bold"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Border Name="dropShadowBorder" BorderThickness="0,0,0,1" CornerRadius="1" BorderBrush="Transparent" Background="White"> <Grid> <Border Name="backgroundFill" BorderBrush="Red" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding Tag}"> <Border.OpacityMask> <LinearGradientBrush StartPoint="0,1" EndPoint="0,0"> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#00000000" Offset="1"/> </LinearGradientBrush> </Border.OpacityMask> </Border> <Border Name="border" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" CornerRadius="{TemplateBinding Tag}" Background="Transparent"> <Border.BorderBrush> <SolidColorBrush Color="#D8D8D8"/> </Border.BorderBrush> <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="BorderBrush" TargetName="border"> <Setter.Value> <SolidColorBrush Color="#939393"/> </Setter.Value> </Setter> <Setter Property="BorderBrush" TargetName="dropShadowBorder"> <Setter.Value> <SolidColorBrush Color="#EBEBEB"/> </Setter.Value> </Setter> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="#333333"/> </Trigger> <Trigger Property="IsPressed" Value="True"> <Setter Property="Background"> <Setter.Value> <LinearGradientBrush StartPoint="0,1" EndPoint="0,0"> <GradientStop Color="#F1F1F1" Offset="1"/> <GradientStop Color="#F5F5F5" Offset="0"/> </LinearGradientBrush> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> </Window.Resources> <Grid> <StackPanel Orientation="Horizontal" VerticalAlignment="Top"> <Button Style="{StaticResource GoogleButton}" Content="Archive"> <Button.Tag> <CornerRadius>2,0,0,2</CornerRadius> </Button.Tag> </Button> <Button Style="{StaticResource GoogleButton}" Content="Spam" Background="LightBlue"/> <Button Style="{StaticResource GoogleButton}" Content="Delete"> <Button.Tag> <CornerRadius>0,2,2,0</CornerRadius> </Button.Tag> </Button> </StackPanel> </Grid> </Window> 
+1
source

Source: https://habr.com/ru/post/895088/


All Articles