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,即表示使用RelativeSourceText属性值进行绑定。如果使用RelativeSource.Self,在设计视图里面我们可以看到这个RelativeSource.Self指示的类型是一个TextBlock,也就是当前绑定RelativeSource的控件。

图1
<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控件。

图2
<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、定义三种依赖属性及其访问属性:IsMouseEnterIsMouseLeftButtonDownIsMouseLeave

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、注册LightButtonMouseEnterMouseLeftButtonDownMouseLeave三种事件,并在事件中改变相应依赖属性的值。

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事件,这就需要我们通过其他方式来实现这个事件。所以我们可以考虑使用LightButtonMouseLeftButtonUp事件:

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;
}
}

至此,一个标准的按钮就完成了。如下图,从左到右依次是默认状态时的样式、鼠标进入状态时的状态和鼠标左键点击时样式。

图3

五、扩展

有了上面的标准按钮之后,就很容易做个带图标的按钮,如下图:从上到下依次是默认状态时的样式、鼠标进入状态时的状态和鼠标左键点击时样式。

图4
Tags: wpf control button