1.附加属性
附加属性是可用于多个控件但在另一个类中定义的属性。在WPF中,附加属性常用于控件布局。
每个控件都有各自的固有属性,当在容器中放置控件时,根据容器的类型控件会获得额外的特征(例如,如果在网格中放置一个文本框,需要选择文本框放在网格控件中的哪个单元格中)。使用附加属性设置这些附加的细节。
附加属性始终使用包含两个部分的命名形式:定义类型.属性名。这种包含两个部分的命名语法使XAML解析器能够分开普通属性和附加属性。
附加属性根本不是真正的属性。它们实际上被转换为方法调用。XAML解析器采用以下形式调用静态方法:DefiningType.SetPropertyName()。
namespace WpfDll
{
using System.Windows;
using System.Windows.Media;
/// <summary>
/// 将旋转角度变为附加属性设置
/// </summary>
public class RotationManager : DependencyObject
{
public static readonly DependencyProperty AngleProperty =
DependencyProperty.RegisterAttached("Angle", typeof(double),
typeof(RotationManager), new PropertyMetadata(0.0, OnAngleChanged));
public static double GetAngle(DependencyObject obj)
{
return (double)obj.GetValue(AngleProperty);
}
public static void SetAngle(DependencyObject obj, double value)
{
obj.SetValue(AngleProperty, value);
}
private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var element = obj as UIElement;
if (element != null)
{
element.RenderTransformOrigin = new Point(0.5, 0.5);
element.RenderTransform = new RotateTransform((double)e.NewValue);
}
}
}
}
<Window x:Class="WpfApp1.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
xmlns:dll="clr-namespace:WpfDll;assembly=WpfDll"
mc:Ignorable="d"
Title="Window2" Height="450" Width="800">
<StackPanel>
<Button x:Name="btn1" Content="666" dll:RotationManager.Angle="90" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
</Window>
用代码实现,就是直接调用静态方法就可以了:
RotationManager.SetAngle(this.btn1, 10);
2.依赖属性
只能为依赖对象(继承自DependencyObject的类)添加依赖项属性。
创建属性封装器时,应当只包含对SetValue和GetValue方法的调用,不应当添加任何验证属性值的额外代码、引发事件的代码等,这是因为WPF中的其他功能可能会忽略属性封装器,并直接调用SetValue和GetValue方法。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace WpfCtr
{
/// <summary>
/// AngleImg.xaml 的交互逻辑
/// </summary>
public partial class AngleImg : UserControl
{
public static readonly DependencyProperty ImgSourceProperty =
DependencyProperty.Register(nameof(ImgSource), typeof(BitmapSource),
typeof(AngleImg), new PropertyMetadata(null));
public static readonly DependencyProperty CornerProperty =
DependencyProperty.Register(nameof(Corner), typeof(CornerRadius), typeof(AngleImg),
new PropertyMetadata(new CornerRadius(0), OnCornerChanged));
static void OnCornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AngleImg angleImg = d as AngleImg;
angleImg.Border1.CornerRadius = (CornerRadius)e.NewValue;
}
public BitmapSource ImgSource
{
set { SetValue(ImgSourceProperty, value); }
get { return (BitmapSource)GetValue(ImgSourceProperty); }
}
public CornerRadius Corner
{
set { SetValue(CornerProperty, value); }
get { return (CornerRadius)GetValue(CornerProperty); }
}
public AngleImg()
{
InitializeComponent();
}
}
}
<UserControl Name="uctr" x:Class="WpfCtr.AngleImg"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="50" Height="50" d:DesignHeight="50" d:DesignWidth="50" mc:Ignorable="d">
<Border x:Name="Border1">
<Border.Background>
<ImageBrush ImageSource="{Binding ElementName=uctr, Path=ImgSource}" />
</Border.Background>
</Border>
</UserControl>
属性验证
在定义任何类型的属性中,都需要面对错误设置属性的可能性。
WPF提供了两种方法来阻止非法值:
- ValidateValueCallback:该回调函数可接受或拒绝新值。通常,该回调函数用于捕获违反属性约束的明显错误。可作为DependencyProperty.Register()方法的一个参数提供该回调函数。
- CoerceValueCallback:该回调函数可将新值修改为更能接受的值。该回调函数通常用于处理为相同对象设置的依赖属性值相互冲突的问题。这些值本身可能是合法的,但当同时应用时它们不相容的。为了使用这个回调函数,当创建FrameworkPropertyMetadata对象时,作为构造函数的一个参数提供该回调函数。
using System; using System.Windows; using System.Windows.Controls; namespace WpfCtr { /// <summary> /// AngleImg.xaml 的交互逻辑 /// </summary> public partial class AngleImg : UserControl { public static readonly DependencyProperty CornerProperty = DependencyProperty.Register(nameof(Corner), typeof(CornerRadius), typeof(AngleImg), new PropertyMetadata(new CornerRadius(0), OnCornerChanged, CornerCoerceValueCallback), CornerValidateValueCallback); static void OnCornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { AngleImg angleImg = d as AngleImg; angleImg.Border1.CornerRadius = (CornerRadius)e.NewValue; } static bool CornerValidateValueCallback(object value) { return true; } static object CornerCoerceValueCallback(DependencyObject d, object baseValue) { AngleImg angleImg = d as AngleImg; CornerRadius cornerRadius = (CornerRadius)baseValue; double minWidth = angleImg.Border1.ActualWidth / 2; double minHeight = angleImg.Border1.ActualHeight / 2; double maxValue = Math.Min(minHeight, minWidth); if (cornerRadius.TopLeft > maxValue) { return new CornerRadius(maxValue); } return baseValue; } public CornerRadius Corner { set { SetValue(CornerProperty, value); } get { return (CornerRadius)GetValue(CornerProperty); } } public AngleImg() { InitializeComponent(); } } }
当属性值被修改时,先触发ValidateValueCallback,再触发CoerceValueCallback,最后才触发PropertyChangedCallback 。PropertyChangedCallback只有属性值有变化才会触发,如果多次设置同样的值,并不会触发该回调函数。
3.路由事件
namespace WpfApp2
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
/// <summary>
/// AngleImg.xaml 的交互逻辑
/// </summary>
public partial class AngleImg : UserControl
{
public static readonly DependencyProperty CornerProperty;
public static readonly DependencyProperty ImgSourceProperty;
public static readonly RoutedEvent ClickEvent;
public static readonly RoutedEvent CornerChangedEvent;
static AngleImg()
{
CornerProperty = DependencyProperty.Register(nameof(Corner), typeof(CornerRadius), typeof(AngleImg), new PropertyMetadata(new CornerRadius(0), OnCornerChanged));
ImgSourceProperty = DependencyProperty.Register(nameof(ImgSource), typeof(BitmapSource), typeof(AngleImg), new PropertyMetadata(null));
ClickEvent = EventManager.RegisterRoutedEvent(nameof(Click), RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AngleImg));
CornerChangedEvent = EventManager.RegisterRoutedEvent(nameof(CornerChanged), RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<CornerRadius>), typeof(AngleImg));
}
static void OnCornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
AngleImg angleImg = d as AngleImg;
angleImg.OnCornerChanged((CornerRadius)e.OldValue, (CornerRadius)e.NewValue);
}
public CornerRadius Corner
{
set { SetValue(CornerProperty, value); }
get { return (CornerRadius)GetValue(CornerProperty); }
}
public BitmapSource ImgSource
{
set { SetValue(ImgSourceProperty, value); }
get { return (BitmapSource)GetValue(ImgSourceProperty); }
}
public event RoutedEventHandler Click
{
add { base.AddHandler(ClickEvent, value); }
remove { base.RemoveHandler(ClickEvent, value); }
}
public event RoutedPropertyChangedEventHandler<CornerRadius> CornerChanged
{
add { base.AddHandler(CornerChangedEvent, value); }
remove { base.RemoveHandler(CornerChangedEvent, value); }
}
public AngleImg()
{
InitializeComponent();
}
protected virtual void OnClick()
{
base.RaiseEvent(new RoutedEventArgs(ClickEvent));
}
protected virtual void OnCornerChanged(CornerRadius oldRadius, CornerRadius newRadius)
{
base.RaiseEvent(new RoutedPropertyChangedEventArgs<CornerRadius>(oldRadius, newRadius, CornerChangedEvent));
}
private void border_MouseDown(object sender, MouseButtonEventArgs e)
{
OnClick();
}
}
}
<UserControl x:Class="WpfApp2.AngleImg"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Border MouseDown="border_MouseDown" >
<Border.Resources>
<RelativeSource x:Key="This" Mode="FindAncestor" AncestorType="UserControl"/>
<ImageBrush x:Key="img" ImageSource="{Binding Path=ImgSource,RelativeSource={StaticResource This}}" Stretch="UniformToFill"/>
</Border.Resources>
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="{Binding Path=Corner,RelativeSource={StaticResource This}}"/>
<Setter Property="Background" Value="{StaticResource img}"/>
</Style>
</Border.Style>
</Border>
</UserControl>
在定义的对象中通过RaiseEvent触发事件。
4.命令
WPF命令模型的核心是System.Windows.Input.ICommand接口,该接口定义了命令的工作原理。当创建自己的命令时,不会直接实现ICommand接口;而是使用System.Windows.Input.RoutedCommand类,该类自动实现了ICommand接口。RoutedCommand类是WPF中唯一实现了ICommand接口的类。
在程序中处理的大部分命令不是RoutedCommand对象,而是RoutedUICommand类的实例,RoutedUICommand类继承自RoutedCommand类(实际上,WPF提供的所有预先构建好的命令都是RoutedUICommand对象)。
namespace WpfDll
{
using System.Windows.Input;
public class DataCommands
{
public static RoutedUICommand Requery { get; private set; }
static DataCommands()
{
InputGestureCollection inputs = new InputGestureCollection();
inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
Requery = new RoutedUICommand("Requery", "Requert", typeof(DataCommands), inputs);
}
}
}
<Window x:Class="WpfApp2.MainWindow" Title="MainWindow"
xmlns:local="clr-namespace:WpfApp2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dll="clr-namespace:WpfDll;assembly=WpfDll"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Width="800" Height="450"
mc:Ignorable="d">
<Grid>
<StackPanel>
<Button x:Name="btn1" Content="Button" Width="100" Click="Button_Click">
<Button.CommandBindings>
<CommandBinding Command="dll:DataCommands.Requery" Executed="CommandBinding_Executed" />
</Button.CommandBindings>
</Button>
<Button Content="Button1" />
</StackPanel>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
MessageBox.Show("666");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("777");
}
}
通过代码添加命令:
CommandBinding commandBinding = new CommandBinding(DataCommands.Requery);
commandBinding.Executed += CommandBinding_Executed;
this.btn1.CommandBindings.Add(commandBinding);
通过代码触发控件的命令:
DataCommands.Requery.Execute(null, this.btn1);