WPF 自定义控件之自定义按钮
上一节说到了 自定义窗口,那么这一节就来说说自定义按钮。自定义窗口我们继承的基类是Window
,那么自定义按钮我们就有 2 种方式,一种是继承自Button
类,另一种就是继承自Control
类。这里我选的是第二种,直接继承自Control
类。
一、模板样式
第一步依旧是定义按钮的模板样式:
<Style x:Key="LightButtonStyle" xmlns:SuziUI="clr-namespace:SuziUI.WPF.Controls" TargetType="SuziUI:LightButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Name="LightButton" Style="{StaticResource ButtonBorderStyle}">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding RelativeSource={x:Static RelativeSource.TemplatedParent},Path=Text}"/>
Border>
ControlTemplate>
Setter.Value>
Setter>
Style>
上述样式中,由于我们定义的按钮不是系统命名空间下的Button
类,所以对于自定义控件类,我们需要先添加命名空间的引用,即 xmlns:SuziUI = "clr-namespace:SuziUI.WPF.Controls",然后在TargetType
中通过“命名空间:类名”的方式指定该样式作用的控件类型。
另外,在绑定按钮文本的时候使用了RelativeSource
,并指定Path=Text
,即表示使用RelativeSource
的Text
属性值进行绑定。如果使用RelativeSource.Self
,在设计视图里面我们可以看到这个RelativeSource.Self
指示的类型是一个TextBlock
,也就是当前绑定RelativeSource
的控件。
<ControlTemplate>
<Border Name="LightButton" Style="{StaticResource ButtonBorderStyle}">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding RelativeSource={x:Static RelativeSource.Self} ,Path=Text}"/>
Border>
ControlTemplate>
如果我们使用RelativeSource.TemplateParent
,那么设计视图里面指示的类型就是LightButton
,也就是我们自定义模板的最外层Border
控件。
<ControlTemplate>
<Border Name="LightButton" Style="{StaticResource ButtonBorderStyle}">
<TextBlock TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding RelativeSource={x:Static RelativeSource.TemplatedParent} ,Path=Text}"/>
Border>
ControlTemplate>
二、依赖属性
上面说到了Path=Text
,那么我们的LightButton
类里面就必须得有个名为Text
的依赖属性,以便我们对按钮上显示的文本进行更改。定义依赖属性跟我们定义一般的属性还不一样,定义依赖属性需要2个步骤:
1、注册依赖属性
注册依赖属性的目的是当我们改变Text
的值的时候,UI也会得到通知,进而将新的值显示在UI上。
public readonly static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(String), typeof(LightButton));
2、定义访问属性
public String Text
{
get
{
return (String)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
可以看到,定义访问属性时也并不像我们之前简单属性那样直接赋值,而是调用了基类的GetValue(DependencyProperty dp)
和SetValue(DependencyProperty dp,Object value)
两个方法。
三、动态效果
到这里,一个简单的按钮就可以显示了,也可以显示按钮文本了。但是,它还少了某些动态的效果,例如,鼠标进入时按钮变换效果,鼠标退出时按钮变换效果,鼠标按下左键时按钮变换效果等。要实现这三种效果,需要3个步骤:
1、定义三种依赖属性及其访问属性:IsMouseEnter
、IsMouseLeftButtonDown
、IsMouseLeave
。
public readonly static DependencyProperty IsMouseEnterProperty = DependencyProperty.Register("IsMouseEnter", typeof(Boolean), typeof(LightButton));
public readonly static DependencyProperty IsMouseLeaveProperty = DependencyProperty.Register("IsMouseLeave", typeof(Boolean), typeof(LightButton));
public readonly static DependencyProperty IsMouseLeftButtonDownProperty = DependencyProperty.Register("IsMouseLeftButtonDown", typeof(Boolean), typeof(LightButton));
public Boolean IsMouseEnter
{
get
{
return (Boolean)GetValue(IsMouseEnterProperty);
}
set
{
SetValue(IsMouseEnterProperty, value);
}
}
public Boolean IsMouseLeave
{
get
{
return (Boolean)GetValue(IsMouseLeaveProperty);
}
set
{
SetValue(IsMouseLeaveProperty, value);
}
}
public Boolean IsMouseLeftButtonDown
{
get
{
return (Boolean)GetValue(IsMouseLeftButtonDownProperty);
}
set
{
SetValue(IsMouseLeftButtonDownProperty, value);
}
}
2、定义这三种属性的触发器。
<Style.Triggers>
<Trigger Property="IsMouseEnter" Value="True">
<Setter Property="Background" Value="{StaticResource LightButtonMouseOverBackgroundBrush}">Setter>
<Setter Property="BorderBrush" Value="{StaticResource LightButtonMouseOverBorderBrush}">Setter>
Trigger>
<Trigger Property="IsMouseLeftButtonDown" Value="True">
<Setter Property="Background" Value="{StaticResource LightButtonMouseDownBackgroundBrush}">Setter>
Trigger>
<Trigger Property="IsMouseLeave" Value="True">
<Setter Property="Background" Value="{StaticResource LightButtonBackgroundBrush}">Setter>
<Setter Property="BorderBrush" Value="{StaticResource LightButtonBorderBrush}">Setter>
Trigger>
Style.Triggers>
3、注册LightButton
的MouseEnter
、MouseLeftButtonDown
、MouseLeave
三种事件,并在事件中改变相应依赖属性的值。
private void LightButton_MouseEnter(Object sender, MouseEventArgs e)
{
this.IsMouseEnter = true;
this.IsMouseLeave = false;
//将Setter里面定义的样式设为自定义Border的样式
_innerButton.Background = this.Background;
_innerButton.BorderBrush = this.BorderBrush;
}
其他两种事件同上。
四、Click事件
由于LightButton
是一个Border
,所以它并没有内置的Click
事件,这就需要我们通过其他方式来实现这个事件。所以我们可以考虑使用LightButton
的MouseLeftButtonUp
事件:
private event EventHandler<MouseButtonEventArgs> _clickHandler;
public event EventHandler<MouseButtonEventArgs> Click
{
add
{
_clickHandler += value;
}
remove
{
_clickHandler -= value;
}
}
private void LightButton_MouseLeftButtonUp(Object sender, MouseButtonEventArgs e)
{
this.IsMouseLeftButtonUp = true;
this.IsMouseLeftButtonDown = false;
_innerButton.Background = this.Background;
if (_buttonIsCaptured)
{
if (_clickHandler != null)
{
_clickHandler(sender, e);
}
_buttonIsCaptured = false;
}
}
至此,一个标准的按钮就完成了。如下图,从左到右依次是默认状态时的样式、鼠标进入状态时的状态和鼠标左键点击时样式。
五、扩展
有了上面的标准按钮之后,就很容易做个带图标的按钮,如下图:从上到下依次是默认状态时的样式、鼠标进入状态时的状态和鼠标左键点击时样式。