今天说服了领导用WPF开发前端,原因就是开发相对来说比较方便,写小项目就不用前后端分离什么的了。反正就是有个机会写WPF了,真开心。我已经写了一年的Uniapp了
就是想写一个简单的变色控件。

如果想知道附加属性,就得先了解依赖属性。详细的可以看我这篇文章
知道了依赖属性之后,我解释一下附加属性是什么意思。附加属性就是为了方便在原有的控件基础上面进行细微的修改。我们先保证编译通过
附加属性的快捷键是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"/>

依赖属性有两种使用方法
样式定义
<Style x:Key="UserSelection"
TargetType="TextBlock">
"wpfEx:TextBlockExtension.Test"
Value="10">
"FontSize"
Value="10" />
"wpfEx:TextBlockExtension.Test"
Value="20">
"FontSize"
Value="20" />
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>
<TextBlock Text="用户">
TextBlock>
<TextBlock Text="用户" FontSize="30">
TextBlock>

附加属性和依赖属性差不多,就是声明麻烦一点。因为Get,Set是需要额外写的。
控件模板一般用于按钮,我们只要会按钮的控件模板就可以了。
HandyControl的Button有IconButton的样式源码。看一下还是挺有收获的。

参考样式代码
<Style x:Key="ButtonDashedBaseStyle" BasedOn="{StaticResource ButtonBaseStyle}" TargetType="Button">
"Background" Value="Transparent"/>
"Template" >
"Button">
<hc:DashedBorder BorderDashArray="3,2" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Background="Transparent" CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}">
"Horizontal" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}">
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}"/>
x:Name="ContentPresenterMain" RecognizesAccessKey="True" VerticalAlignment="Center" Margin="6,0,0,0" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
hc:DashedBorder>
"Content" Value="{x:Null}">
"Visibility" Value="Collapsed" TargetName="ContentPresenterMain"/>
"hc:IconElement.Geometry" Value="{x:Null}">
"Visibility" Value="Collapsed" TargetName="PathMain"/>
"Margin" Value="0" TargetName="ContentPresenterMain"/>
Style>

我们想写一个控件模板,如果不是很熟练,我们就先把控件模板的原型写出来,这样更利于理解。
<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>
<TextBlock Text="TIM登录"
HorizontalAlignment="Center" />
DockPanel>

<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
"Template" >
"RadioButton" >
"Top"
Width="50"
Height="50"
CornerRadius="25"
Background="DeepSkyBlue">
"{wpfEx:MaterialGeometry Kind=BellRing}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="25"
Height="25"
Fill="White" />
x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
Style>

然后里面能绑定的就绑定。也是照着HandyControl改的。注意这里的Banding用的是TemplateBinding

修改好的效果
<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
"Template" >
"RadioButton" >
"Top"
Width="{TemplateBinding hc:IconElement.Width}"
Height="{TemplateBinding hc:IconElement.Height}"
CornerRadius="25"
Background="{TemplateBinding Foreground}">
"{TemplateBinding hc:IconElement.Geometry}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="25"
Height="25"
Fill="{TemplateBinding Background}" />
x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
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">
<Style x:Key="UserSelection"
TargetType="RadioButton"
BasedOn="{StaticResource {x:Type RadioButton}}">
"Foreground"
Value="Gray" />
"Template" >
"RadioButton">
"Top"
Width="70"
Height="70"
CornerRadius="35"
x:Name="Background">
"{TemplateBinding hc:IconElement.Geometry}"
x:Name="Icon"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
SnapsToDevicePixels="True"
Stretch="Uniform"
Width="35"
Height="35"
Fill="Gray" />
x:Name="ContentPresenterMain"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="6,0,0,0"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
"IsMouseOver"
Value="True">
"Background"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" />
"Icon"
Property="Fill"
Value="White" />
"IsFocused"
Value="True">
"Background"
Property="Background"
Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}" />
"Icon"
Property="Fill"
Value="White" />
Style>
<Style TargetType="RadioButton"
x:Key="UserSelectioin_Admin"
BasedOn="{StaticResource UserSelection}">
"HorizontalAlignment"
Value="Right" />
"Margin"
Value="0 0 10 0" />
"Background"
Value="DeepSkyBlue" />
"hc:IconElement.Geometry"
Value="{wpfEx:MaterialGeometry Kind=AccountLock}" />
"Content"
Value="管理员登录" />
Style>
<Style TargetType="RadioButton"
x:Key="UserSelectioin_User"
BasedOn="{StaticResource UserSelection}">
"HorizontalAlignment"
Value="Left" />
"Margin"
Value="10 0 0 0" />
"Background"
Value="Green" />
"hc:IconElement.Geometry"
Value="{wpfEx:MaterialGeometry Kind=Account}" />
"Content"
Value="用户" />
Style>
ResourceDictionary>
<RadioButton Style="{StaticResource UserSelectioin_Admin}" />
<RadioButton Style="{StaticResource UserSelectioin_User}" />


HandyControl的源码看了真的是打开眼界,但是WPF的Xaml有一个无法在内部简单计算的问题。比如我想Witdh=Height = CornerRadius*2。我可能就要写个触发器了。我后面回去测试一下有没有方法可以在Xaml里面简单计算的。