文章目录
- 相关连接
- 前言
- 需要实现的效果
- 附加属性
- 添加附加属性,以Test修改FontSize为例
- 依赖属性使用
- 触发器使用
- 直接操控
- 结论
- 控件模板,在HandyControl的基础上面进行修改
- 参考HandyControl的源码
- 控件模板原型
- 控件模板
- 控件模板触发器
- 完整样式
- 简单使用
- 结论
相关连接
WPF控件模板(6)
WPF 附加属性
WPF教程:附加属性
前言
今天说服了领导用WPF开发前端,原因就是开发相对来说比较方便,写小项目就不用前后端分离什么的了。反正就是有个机会写WPF了,真开心。我已经写了一年的Uniapp了
需要实现的效果
就是想写一个简单的变色控件。
附加属性
如果想知道附加属性,就得先了解依赖属性。详细的可以看我这篇文章
WPF 用户控件依赖属性赋值
添加附加属性,以Test修改FontSize为例
知道了依赖属性之后,我解释一下附加属性是什么意思。附加属性就是为了方便在原有的控件基础上面进行细微的修改。我们先保证编译通过
附加属性的快捷键是propa
简单给TextBox添加一个附加属性
public partial class TextBlockExtension
{
public static int GetTest(DependencyObject obj)
{
return (int)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, int value)
{
obj.SetValue(TestProperty, value);
}
// Using a DependencyProperty as the backing store for Test. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("Test", typeof(int), typeof(TextBox), new PropertyMetadata(10));
}
这样我们就能编译通过了。
<TextBlock Text="用户" wpfEx:TextBlockExtension.Test="2"/>
依赖属性使用
依赖属性有两种使用方法
触发器使用
样式定义
<!--一个简单的FontSize修改-->
<Style x:Key="UserSelection"
TargetType="TextBlock">
<!--因为Triggers只有等于判断,所以这里简单写了一下-->
<Style.Triggers>
<Trigger Property="wpfEx:TextBlockExtension.Test"
Value="10">
<Setter Property="FontSize"
Value="10" />
</Trigger>
<Trigger Property="wpfEx:TextBlockExtension.Test"
Value="20">
<Setter Property="FontSize"
Value="20" />
</Trigger>
</Style.Triggers>
</Style>
简单使用
<TextBlock Text="用户"
wpfEx:TextBlockExtension.Test="10" Style="{StaticResource UserSelection}">
</TextBlock>
<TextBlock Text="用户"
wpfEx:TextBlockExtension.Test="20"
Style="{StaticResource UserSelection}">
</TextBlock>
直接操控
附加属性修改
//如果想直接操控元素,得在PropertyMetadata进行操控。记得设置初始值
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("Test", typeof(int), typeof(TextBox), new PropertyMetadata(10,(s, e) =>
{
//s是控件本身,
var mdp = s as TextBlock;
//如果控件是该元素的父组件,类似于Grid和DockPanel,就使用Parent来寻找,这里不展开
//var mdpParent = (s as FrameworkElement).Parent as TextBlock;
if (mdp != null && e.NewValue != null)
{
mdp.FontSize = (int)e.NewValue;
}
}));
<!--如果想要预览生效需要重新编译一下-->
<TextBlock Text="用户"
wpfEx:TextBlockExtension.Test="15">
</TextBlock>
<TextBlock Text="用户"
wpfEx:TextBlockExtension.Test="20">
</TextBlock>
<!--因为我们设置了默认值为10,所以这里是10-->
<TextBlock Text="用户">
</TextBlock>
<!--优先级还是依赖属性高,所以这里是30而不是默认值10-->
<TextBlock Text="用户" FontSize="30">
</TextBlock>
结论
附加属性和依赖属性差不多,就是声明麻烦一点。因为Get,Set是需要额外写的。
控件模板,在HandyControl的基础上面进行修改
控件模板一般用于按钮,我们只要会按钮的控件模板就可以了。
WPF控件模板(6)
参考HandyControl的源码
HandyControl 页面
HandyControl的Button有IconButton的样式源码。看一下还是挺有收获的。
参考样式代码
<Style x:Key="ButtonDashedBaseStyle" BasedOn="{StaticResource ButtonBaseStyle}" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<hc:DashedBorder BorderDashArray="3,2" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="Transparent" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}">
<Path x:Name="PathMain" Width="{TemplateBinding hc:IconElement.Width}" Height="{TemplateBinding hc:IconElement.Height}" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" Stretch="Uniform" Data="{TemplateBinding hc:IconElement.Geometry}"/>
<ContentPresenter x:Name="ContentPresenterMain" RecognizesAccessKey="True" VerticalAlignment="Center" Margin="6,0,0,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</hc:DashedBorder>
<ControlTemplate.Triggers>
<Trigger Property="Content" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" TargetName="ContentPresenterMain"/>
</Trigger>
<Trigger Property="hc:IconElement.Geometry" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" TargetName="PathMain"/>
<Setter Property="Margin" Value="0" TargetName="ContentPresenterMain"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
控件模板原型
我们想写一个控件模板,如果不是很熟练,我们就先把控件模板的原型写出来,这样更利于理解。
<DockPanel>
<!--这里仿照HandyControl,使用Gemotery。IconPacks怎么转Gemotery可以看我的文章-->
<Border DockPanel.Dock="Top"
Width="50"
Height="50"
CornerRadius="25"
Background="DeepSkyBlue">
<Path Data="{wpfEx:MaterialGeometry Kind=BellRing}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="25"
Height="25"
Fill="White" />
</Border>
<TextBlock Text="TIM登录"
HorizontalAlignment="Center" />
</DockPanel>
控件模板
<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
<Setter Property="Template">
<Setter.Value>
<!--先按照之前的样式粘贴一下-->
<ControlTemplate TargetType="RadioButton">
<DockPanel>
<Border DockPanel.Dock="Top"
Width="50"
Height="50"
CornerRadius="25"
Background="DeepSkyBlue">
<Path Data="{wpfEx:MaterialGeometry Kind=BellRing}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="25"
Height="25"
Fill="White" />
</Border>
<ContentPresenter x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
然后里面能绑定的就绑定。也是照着HandyControl改的。注意这里的Banding用的是TemplateBinding
修改好的效果
<!--一个简单的FontSize修改-->
<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
<Setter Property="Template">
<Setter.Value>
<!--先按照之前的样式粘贴一下-->
<ControlTemplate TargetType="RadioButton">
<DockPanel>
<Border DockPanel.Dock="Top"
Width="{TemplateBinding hc:IconElement.Width}"
Height="{TemplateBinding hc:IconElement.Height}"
CornerRadius="25"
Background="{TemplateBinding Foreground}">
<Path Data="{TemplateBinding hc:IconElement.Geometry}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="25"
Height="25"
Fill="{TemplateBinding Background}" />
</Border>
<ContentPresenter x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
简单使用
<RadioButton Content="TIM登录"
GroupName="UserSelect"
Style="{StaticResource UserSelection}"
Foreground="DeepSkyBlue"
Background="White"
hc:IconElement.Geometry="{wpfEx:MaterialGeometry Kind=AbTesting}" />
控件模板触发器
完整样式
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:wpfEx="clr-namespace:BluetoothWPF.WpfExtensions"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--一个简单的FontSize修改-->
<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
<Setter Property="Foreground"
Value="Gray" />
<Setter Property="Template">
<Setter.Value>
<!--先按照之前的样式粘贴一下-->
<ControlTemplate TargetType="RadioButton">
<DockPanel>
<Border DockPanel.Dock="Top"
Width="70"
Height="70"
CornerRadius="35"
x:Name="Background">
<Path Data="{TemplateBinding hc:IconElement.Geometry}"
x:Name="Icon"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="35"
Height="35"
Fill="Gray" />
</Border>
<ContentPresenter x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver"
Value="True">
<Setter TargetName="Background"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" />
<Setter TargetName="Icon"
Property="Fill"
Value="White" />
</Trigger>
<Trigger Property="IsFocused"
Value="True">
<Setter TargetName="Background"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" />
<Setter TargetName="Icon"
Property="Fill"
Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="RadioButton"
x:Key="UserSelectioin_Admin"
BasedOn="{StaticResource UserSelection}">
<Setter Property="HorizontalAlignment"
Value="Right" />
<Setter Property="Margin"
Value="0 0 10 0" />
<Setter Property="Background"
Value="DeepSkyBlue" />
<Setter Property="hc:IconElement.Geometry"
Value="{wpfEx:MaterialGeometry Kind=AccountLock}" />
<Setter Property="Content"
Value="管理员登录" />
</Style>
<Style TargetType="RadioButton"
x:Key="UserSelectioin_User"
BasedOn="{StaticResource UserSelection}">
<Setter Property="HorizontalAlignment"
Value="Left" />
<Setter Property="Margin"
Value="10 0 0 0" />
<Setter Property="Background"
Value="Green" />
<Setter Property="hc:IconElement.Geometry"
Value="{wpfEx:MaterialGeometry Kind=Account}" />
<Setter Property="Content"
Value="用户" />
</Style>
</ResourceDictionary>
简单使用
<RadioButton Style="{StaticResource UserSelectioin_Admin}" />
<RadioButton Style="{StaticResource UserSelectioin_User}" />
结论
HandyControl的源码看了真的是打开眼界,但是WPF的Xaml有一个无法在内部简单计算的问题。比如我想Witdh=Height = CornerRadius*2。我可能就要写个触发器了。我后面回去测试一下有没有方法可以在Xaml里面简单计算的。