在上一篇中,完成了一个自定义样式的按钮,下面在这个 基础上再继续编写更多样式~。
本篇实现三种按钮,带图片的按钮,文本按钮(类似超链接),和等待按钮。
效果如下图:
git地址:https://github.com/gxygit/WpfTemplate
有兴趣的小伙伴可以下载查看哦
图片按钮样式如下 :
<Style x:Key="ImageButton" TargetType="{x:Type Button}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Padding" Value="10 2"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Margin" Value="10"/> <Setter Property="FontSize" Value="{StaticResource FontSizeRegular}"/> <Setter Property="Template" > <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="back" Background="{TemplateBinding Background}" BorderBrush="Black" BorderThickness="0.8" CornerRadius="3" Padding="{TemplateBinding Padding}"> <StackPanel Orientation="Horizontal" Height="{TemplateBinding Height}" HorizontalAlignment="Center" VerticalAlignment="Center"> <Image Source="/Images/head.jpg" Height="{Binding ActualHeight,ElementName=text}" Margin="0 0 5 0" Stretch="UniformToFill" /> <TextBlock Text="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" Foreground="#000000" VerticalAlignment="Center" x:Name="text" /> </StackPanel> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard > <ColorAnimation To="#57A64A" Duration="0:0:0.3" Storyboard.TargetName="back" Storyboard.TargetProperty="Background.Color"/> <ColorAnimation To="#FFFFFF" Duration="0:0:0.3" Storyboard.TargetName="text" Storyboard.TargetProperty="Foreground.Color"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard > <ColorAnimation To="#0057A64A" Duration="0:0:0.3" Storyboard.TargetName="back" Storyboard.TargetProperty="Background.Color"/> <ColorAnimation To="#000000" Duration="0:0:0.3" Storyboard.TargetName="text" Storyboard.TargetProperty="Foreground.Color"/> </Storyboard> </BeginStoryboard> </EventTrigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter></Style>
文本按钮样式如下 :
<Style x:Key="TextButton" TargetType="{x:Type Button}" > <Setter Property="Background" Value="Transparent"/> <Setter Property="Foreground" Value="Black"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="FontSize" Value="{StaticResource FontSizeRegular}"/> <Setter Property="Padding" Value="10 2"/> <Setter Property="Margin" Value="0 10"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="border" CornerRadius="10" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> <TextBlock Text="{TemplateBinding Content}" Focusable="False" FontFamily="{TemplateBinding FontFamily}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Foreground" Value="Blue"/> </Trigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Foreground" Value="Gray"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter></Style>
样式都统一写在了/Styles/Button.xaml里面,并在App.xaml中合并引用。
最后,等待图标,我使用了FontAwesome字体,下载链接为:
http://fontawesome.dashgame.com/
点击下载即可。下载后,找到fonts/fontawesome-webfont.ttf ,双击打开后即可看到
,字体名字为FontAwesome。把它复制到项目/fonts/ 目录。
添加/Styles/Fonts.xaml。在其中添加字体的引用。代码如下
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfTemplate" xmlns:system="clr-namespace:System;assembly=mscorlib"> <FontFamily x:Key="WebDings" >pack://application;,,,/Fonts/#WEBDINGS</FontFamily> <FontFamily x:Key="FontAwesome" >pack://application;,,,/Fonts/#FontAwesome</FontFamily> <system:Double x:Key="FontSizeSmaller">10</system:Double> <system:Double x:Key="FontSizeSmall">12</system:Double> <system:Double x:Key="FontSizeRegular">14</system:Double> <system:Double x:Key="FontSizeLarge">16</system:Double> <system:Double x:Key="FontSizeXLarge">24</system:Double> <system:Double x:Key="FontSizeXXLarge">30</system:Double>
<system:String x:Key="FontAwesomeWaitIcon"></system:String></ResourceDictionary>
FontAwesomeWaitIcon 既是旋转图标的代码。代码可在fontswesome官网首页查看。
使用开发者工具查看网页原代码
发现\f110 ,在xaml中引用就是 
顺便写了几个字体大小的定义
准备工作完成了,下面就是等待按钮的样式了
<Style x:Key="WaitingButton" TargetType="{x:Type Button}"> <Setter Property="Background" Value="Transparent"/> <Setter Property="Padding" Value="10 2"/> <Setter Property="BorderThickness" Value="0"/> <Setter Property="Margin" Value="10"/> <Setter Property="FontSize" Value="{StaticResource FontSizeRegular}"/> <!--<Setter Property="local:IsBusyProperty.Value" Value="False"/>--> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ButtonBase}"> <Border Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Background="{TemplateBinding Background}" BorderBrush="Black" BorderThickness="0.8" CornerRadius="3" Padding="{TemplateBinding Padding}" x:Name="border" > <Grid> <TextBlock Text="{TemplateBinding Content}" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}" Visibility="{TemplateBinding local:IsBusyProperty.Value,Converter={local:BooleanToVisiblityConverter}}" FontFamily="{TemplateBinding FontFamily}" x:Name="text" /> <TextBlock Style="{StaticResource SpinningText}" Visibility="{TemplateBinding local:IsBusyProperty.Value,Converter={local:BooleanToVisiblityConverter},ConverterParameter=True}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" /> </Grid> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard > <ColorAnimation To="#57A64A" Duration="0:0:0.3" Storyboard.TargetName="border" Storyboard.TargetProperty="Background.Color"/> <ColorAnimation To="#FFFFFF" Duration="0:0:0.3" Storyboard.TargetName="text" Storyboard.TargetProperty="Foreground.Color"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard > <ColorAnimation To="#0057A64A" Duration="0:0:0.3" Storyboard.TargetName="border" Storyboard.TargetProperty="Background.Color"/> <ColorAnimation To="#000000" Duration="0:0:0.3" Storyboard.TargetName="text" Storyboard.TargetProperty="Foreground.Color"/> </Storyboard> </BeginStoryboard> </EventTrigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" TargetName="border" Value="LightGray"/> <Setter Property="Foreground" TargetName="text" Value="White"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter></Style>
样式中引用了一个textblock的样式 SpinningText ,样式代码写在/Styles/Texts.xaml中。代码如下
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfTemplate" xmlns:system="clr-namespace:System;assembly=mscorlib"> <Style TargetType="{x:Type TextBlock}" x:Key="TextBlockBaseStyle"> <Setter Property="FontFamily" Value="{StaticResource FontAwesome}"/> <Setter Property="FontSize" Value="{StaticResource FontSizeRegular}"/></Style>
<Style TargetType="{x:Type TextBlock}" x:Key="SpinningText" BasedOn="{StaticResource TextBlockBaseStyle}"> <Setter Property="FontFamily" Value="{StaticResource FontAwesome}"/> <Setter Property="Text" Value="{StaticResource FontAwesomeWaitIcon}"/> <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/> <Setter Property="RenderTransform" > <Setter.Value> <RotateTransform/> </Setter.Value> </Setter>
<Style.Resources> <Storyboard x:Key="Spin"> <DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever"/> </Storyboard> </Style.Resources> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={x:Static RelativeSource.Self},Path=IsVisible}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard Name="SpinStoryboard" Storyboard="{StaticResource Spin}"/> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <RemoveStoryboard BeginStoryboardName="SpinStoryboard"/> </DataTrigger.ExitActions> </DataTrigger> </Style.Triggers></Style></ResourceDictionary>
在 WaitingButton 样式中还用到了 一个附加属性 IsBusyProperty。
为了以后定义附加属性的方便,把它封闭一下,
using System;using System.Windows;
namespace WpfTemplate{ /// <summary> /// a base attached property to replace the vanilla wpf attached property /// </summary> /// <typeparam name="Parent">the parent class to be the attached property</typeparam> /// <typeparam name="Property">the type of this attached property</typeparam> public abstract class BaseAttachedProperty<Parent, Property> where Parent : new() { #region public properties
public static Parent Instance { get; private set; } = new Parent();
#endregion
#region attached property definitions
public static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value", typeof(Property), typeof(BaseAttachedProperty<Parent, Property>), new UIPropertyMetadata( default(Property), new PropertyChangedCallback(OnValuePropertyChanged), new CoerceValueCallback(OnValuePropertyUpdated) ));
/// <summary> /// the callback event when the <see cref="ValueProperty"/> is changed /// 值改变了才会调用 /// </summary> /// <param name="d">the ui element that had it's property changed</param> /// <param name="e">the arguments for the event</param> private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e){ //call the parent function (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueChanged(d, e); //call event listeners (Instance as BaseAttachedProperty<Parent, Property>)?.ValueChanged(d, e); } /// <summary> /// the callback event when the <see cref="ValueProperty"/> is changed /// 每次设置值 即使值和本次设置之前的相同也会调用 /// </summary> /// <param name="d">the ui element that had it's property changed</param> /// <param name="e">the arguments for the event</param> private static object OnValuePropertyUpdated(DependencyObject d, object value){ //call the parent function (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueUpdated(d, value); //call event listeners (Instance as BaseAttachedProperty<Parent, Property>)?.ValueUpdated(d, value);
return value; } /// <summary> /// get the attached property /// </summary> /// <param name="d">the element to get the property from</param> /// <returns></returns> public static Property GetValue(DependencyObject d) => (Property)d.GetValue(ValueProperty);
/// <summary> /// set the attached property /// </summary> /// <param name="d">the element to set the property</param> /// <param name="value">the value to set the property</param> public static void SetValue(DependencyObject d, Property value) => d.SetValue(ValueProperty, value); #endregion
#region public events /// <summary> /// 当值改变时会引发事件 /// </summary> public event Action<DependencyObject, DependencyPropertyChangedEventArgs> ValueChanged = (sender, e) => { }; /// <summary> /// 当值改变时会引发事件,即使新值和旧值相同 /// </summary> public event Action<DependencyObject, object> ValueUpdated = (sender, value) => { }; #endregion
#region event methods /// <summary> /// 值改变事件 /// </summary> /// <param name="sender">the ui element</param> /// <param name="e">the argument of this event</param> public virtual void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { } /// <summary> /// 值更新事件,(同改变事件,并且再次值相同时) /// </summary> /// <param name="sender">the ui element</param> /// <param name="e">the argument of this event</param> public virtual void OnValueUpdated(DependencyObject sender, object value) { } #endregion }}
定义好之后 ,再定义附加属性只需要像下面这样添加上IsBusyProperty:
namespace WpfTemplate{ public class IsBusyProperty : BaseAttachedProperty<IsBusyProperty, bool> { }}
其次,还用到了一个ValueConverter.同样,先封闭一下喽,
using System;using System.Globalization;using System.Windows.Data;using System.Windows.Markup;
namespace WpfTemplate{ /// <summary> /// a base value converter that allows direct xaml usage /// </summary> /// <typeparam name="T">the type of this value converter</typeparam> public abstract class BaseValueConverter<T> : MarkupExtension, IValueConverter where T:class, new() { /// <summary> /// a single static instance of this value converter /// </summary> private static T Converter = null;
#region markup extension methods
/// <summary> /// provides a static instance of the value converter /// </summary> /// <param name="serviceProvider">the service provider</param> /// <returns></returns> public override object ProvideValue(IServiceProvider serviceProvider) { return Converter ?? (Converter = new T()); } #endregion
#region value converter methods
/// <summary> /// the method to convert one type to another /// </summary> /// <param name="value"></param> /// <param name="targetType"></param> /// <param name="parameter"></param> /// <param name="culture"></param> /// <returns></returns> public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); /// <summary> /// converter a value back to it's source type /// </summary> /// <param name="value"></param> /// <param name="targetType"></param> /// <param name="parameter"></param> /// <param name="culture"></param> /// <returns></returns> public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); #endregion }}
,然后,像下面这样添加一个bool->Visibilty的转换器
using System;using System.Globalization;using System.Windows;
namespace WpfTemplate{ /// <summary> /// a converter that takes in a boolean and return a <see cref="Visibility"/> /// </summary> public class BooleanToVisiblityConverter : BaseValueConverter<BooleanToVisiblityConverter> { public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if(parameter==null) return (bool)value ? Visibility.Hidden : Visibility.Visible; else return (bool)value ? Visibility.Visible : Visibility.Hidden; }
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }}
在MainWindowViewModel中添加一个BusyCommand, 用于改变按钮的IsBusyProperty。
using System.Windows;using System.Windows.Input;
namespace WpfTemplate{ /// <summary> /// /// </summary> public class MainWindowViewModel:BaseViewModel { private Window mWindow;//window 对象
public bool ButtonIsBusy { get; set; } public ICommand MinimizeCommand { get; set; } public ICommand MaximizeCommand { get; set; } public ICommand CloseCommand { get; set; } public ICommand BusyCommand { get; set; }
#region constructor public MainWindowViewModel(Window window) { mWindow = window; MinimizeCommand = new RelayCommand(() => mWindow.WindowState = WindowState.Minimized); MaximizeCommand = new RelayCommand(() => mWindow.WindowState ^= WindowState.Maximized); CloseCommand = new RelayCommand(() => mWindow.Close()); BusyCommand = new RelayCommand(() => ButtonIsBusy = !ButtonIsBusy); } #endregion }}
。这样,MainWindow.xaml里面就可以添加按钮了
<Button Content="测试BusyProperty" Style="{StaticResource WaitingButton}" Command="{Binding BusyCommand}" local:IsBusyProperty.Value="{Binding ButtonIsBusy}"/>
按钮command绑定到BusyCommand,同时设置IsBusyProperty.Value绑定到ButtonIsBusy属性上。
好了,大功造成啦~