我在上一篇博文中介绍了如何使用自定义装饰器实现SAP的焦点样式。
上次的思路是遍历窗体中的控件,如果是指定类型则使用自定义的装饰器。但是如果要求部分控件使用自定义装饰器,或者用户控件中的控件也要实现,上一种方法是无法完成的。
看了MSDN的中“AdornerDecorator“类能为 “可视化树”中的子元素提供 AdornerLayer。
用法:
<AdornerDecorator>
Child
</AdornerDecorator>
那么是否能自定义一个AdornerDecorator来实现AdornerLayer呢?答案是肯定的。但是就必须写成:
<MyAdornerDecorator>
Child
</MyAdornerDecorator>
这样有个弊端,如果Child(控件)是复杂控件,则不能很好的实现AdornerLayer的装饰器。
如果改成附加属性则会使代码最小化。Good idea!我们就按这个思路来实现。
思路:
1.定义一个自定义AdornerDecorator,用于为控件创建一个AdornerLayer;
2.在AdornerLayer上定义自定义的装饰器样式
代码比较简单,见注释说明。
实现步骤:
1.创建自定义AdornerDecorator类,AdornerDecoratorHelper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Controls;
namespace AdornerDecoratorDemo {
public class AdornerDecoratorHelper {
public static DependencyProperty GetAdornerLayer(DependencyObject obj) {
return (DependencyProperty)obj.GetValue(AdornerLayerProperty);
}
public static void SetAdornerLayer(DependencyObject obj, DependencyProperty value) {
obj.SetValue(AdornerLayerProperty, value);
}
/// <summary>
/// 注册附加属性
/// 用于设置控件的AdornerLayer
/// </summary>
public static readonly DependencyProperty AdornerLayerProperty =
DependencyProperty.RegisterAttached("AdornerLayer", typeof(DependencyObject), typeof(AdornerDecoratorHelper), new UIPropertyMetadata(null, (o,n) => {
if (o is Control && n.NewValue is Visual) {
//获得注册附加属性的控件
Control c = o as Control;
c.Loaded += (s1, e1) => {
AdornerLayer layer = AdornerLayer.GetAdornerLayer((Visual)n.NewValue);
//为控件添加获得焦点和失去焦点事件
if (layer != null) {
c.GotFocus += (s, e) => {
//添加自定义装饰器
layer.Add(new SimpleAdorner(c));
};
c.LostFocus += (s, e) => {
//取消控件的装饰器
Adorner[] ads = layer.GetAdorners(c);
if (ads != null) {
for (int i = ads.Length - 1; i >= 0; i--) {
layer.Remove(ads[i]);
}
}
};
}
};
}
}));
}
}
2.创建自定义装饰器,用于在控件的四个角上显示红线
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
namespace AdornerDecoratorDemo {
/// <summary>
/// 自定义装饰器
/// </summary>
public class SimpleAdorner : Adorner {
public SimpleAdorner(UIElement adornedElement) : base(adornedElement) { }
/// <summary>
/// 绘制自定义装饰器的呈现样子
/// </summary>
/// <param name="drawingContext"></param>
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext) {
Rect adornedElementRect = new Rect(this.AdornedElement.RenderSize);
Pen renderPen = new Pen(new SolidColorBrush(Colors.Red), 1.0);
// Draw a circle at each corner.
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X + 5, adornedElementRect.TopLeft.Y - 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y - 3), new Point(adornedElementRect.TopLeft.X - 3, adornedElementRect.TopLeft.Y + 5));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X - 5, adornedElementRect.TopRight.Y - 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y - 3), new Point(adornedElementRect.TopRight.X + 3, adornedElementRect.TopRight.Y + 5));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X + 5, adornedElementRect.BottomLeft.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y + 3), new Point(adornedElementRect.BottomLeft.X - 3, adornedElementRect.BottomLeft.Y - 5));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X - 5, adornedElementRect.BottomRight.Y + 3));
drawingContext.DrawLine(renderPen, new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y + 3), new Point(adornedElementRect.BottomRight.X + 3, adornedElementRect.BottomRight.Y - 5));
}
}
}
3.窗体实现
<Window x:Class="AdornerDecoratorDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:AdornerDecoratorDemo"
Title="用装饰器实现 模仿SAP焦点样式" Height="225" Width="442">
<Grid Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Text="Name:" />
<TextBox Grid.Row="0" Grid.Column="1" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}" />
<TextBlock Grid.Row="1" Text="Gender:" />
<ComboBox Grid.Row="1" Grid.Column="1" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}">
<ComboBoxItem Content="Male" />
<ComboBoxItem Content="Female" />
</ComboBox>
<TextBlock Grid.Row="2" Text="Marriage:" />
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal">
<RadioButton Content="Married" GroupName="Marry" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}"/>
<RadioButton Content="Single" GroupName="Marry" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}"/>
</StackPanel>
<TextBlock Grid.Row="3" Text="Interest:" />
<StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
<CheckBox Content="Badminton" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}"/>
<CheckBox Content="angle" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}"/>
</StackPanel>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal">
<Button Content="Ok" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}" />
<Button Content="Reset" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}" />
<Button Content="Cancel" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}" />
</StackPanel>
</Grid>
</Window>
注意:<TextBox Grid.Row="0" Grid.Column="1" m:AdornerDecoratorHelper.AdornerLayer="{Binding ElementName=grid}" />
AdornerDecoratorHelper.AdornerLayer是我定义的附加属性,附加属性中指定了一个grid对象,目的是用grid的默认AdornerLayer,在器上面添加自定义装饰器(控件周围的红线)。
4.辅助的样式,为了美观,在App中定义一些默认样式
<Application x:Class="AdornerDecoratorDemo.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<Style x:Key="ControlBaseStyle" TargetType="{x:Type Control}">
<Setter Property="Margin" Value="5" />
<Setter Property="Width" Value="180" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style TargetType="{x:Type TextBlock}" >
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="10,0" />
</Style>
<Style TargetType="{x:Type TextBox}" BasedOn="{StaticResource ControlBaseStyle}" />
<Style TargetType="{x:Type RadioButton}" BasedOn="{StaticResource ControlBaseStyle}">
<Setter Property="Width" Value="Auto" />
</Style>
<Style TargetType="{x:Type CheckBox}" BasedOn="{StaticResource ControlBaseStyle}">
<Setter Property="Width" Value="Auto" />
</Style>
<Style TargetType="{x:Type ComboBox}" BasedOn="{StaticResource ControlBaseStyle}" />
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ControlBaseStyle}">
<Setter Property="Width" Value="75" />
</Style>
</Application.Resources>
</Application>
看看效果吧: