WPF中如何为Adorner按单个元素设置局部样式?
Adorner样式在子元素资源中不生效的解决方案
我有一个Adorner,需要放置在被装饰元素的旁侧。该Adorner会根据自定义Position依赖属性的值,显示在元素的左侧或右侧。
我希望通过样式设置Position属性的值,但仅当样式添加到顶级控件的资源中时才生效。若将样式置于任一子元素的资源内,则无法产生效果。是否存在办法如下方示例所示,按元素为Adorner设置样式?
<Window x:Class="StyledAdorner.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:StyledAdorner"> <Window.Resources> <Style TargetType="local:MyAdorner"> <Setter Property="Position" Value="Right" /> </Style> <Style TargetType="Button"> <Setter Property="Content" Value="Adorn me!" /> <Setter Property="Margin" Value="15" /> <EventSetter Event="Click" Handler="AddAdorner" /> </Style> </Window.Resources> <StackPanel> <Button /> <Button> <Button.Resources> <Style TargetType="local:MyAdorner"> <!-- 这个Setter无效果! --> <Setter Property="Position" Value="Left" /> </Style> </Button.Resources> </Button> </StackPanel> </Window>
我能想到的唯一解决方案是扫描被装饰元素的资源以查找Adorner样式,若存在则检查是否有针对Position属性的Setter并使用该值,但这看起来像是一个非常拙劣的权宜之计...
现有代码
- 创建Adorner的
AddAdorner处理程序代码:
private void AddAdorner(object sender, RoutedEventArgs e) { new MyAdorner((UIElement)sender); }
MyAdorner的构造函数:
private Path _indicator = new Path { /* 细节省略 */ }; public MyAdorner(UIElement adornedElement) : base(adornedElement) { AdornerLayer.GetAdornerLayer(AdornedElement).Add(this); AddVisualChild(_indicator); InvalidateMeasure(); InvalidateArrange(); }
问题根源
Adorner的视觉树独立于被装饰元素的视觉树,它的资源查找逻辑是从AdornerLayer所在的顶级元素向上遍历,不会继承被装饰元素的子级资源,因此子元素资源中的Adorner样式无法被自动应用。
可行解决方案
方案1:创建Adorner时主动应用被装饰元素的资源
修改AddAdorner方法,创建Adorner后直接从被装饰元素的资源字典中查找匹配的Style并应用,无需手动扫描Setter:
private void AddAdorner(object sender, RoutedEventArgs e) { var adornedElement = (FrameworkElement)sender; var adorner = new MyAdorner(adornedElement); // 查找被装饰元素资源中针对MyAdorner的样式 if (adornedElement.TryFindResource(typeof(MyAdorner)) is Style adornerStyle) { adorner.Style = adornerStyle; } }
这个方法利用WPF内置的TryFindResource方法自动查找元素的资源(包括继承的资源),比手动扫描Setter简洁可靠。
方案2:使用附加属性直接指定Position
如果仅需要设置Position单个属性,没必要用样式,可以给控件添加一个附加属性来指定对应Adorner的位置:
- 定义附加属性:
public static class AdornerSettings { public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached( "Position", typeof(Position), typeof(AdornerSettings), new PropertyMetadata(Position.Right)); public static void SetPosition(UIElement element, Position value) { element.SetValue(PositionProperty, value); } public static Position GetPosition(UIElement element) { return (Position)element.GetValue(PositionProperty); } }
- 在XAML中给目标Button指定位置:
<Button local:AdornerSettings.Position="Left"> <!-- 按钮内容 --> </Button>
- 创建Adorner时读取附加属性的值:
private void AddAdorner(object sender, RoutedEventArgs e) { var adornedElement = (UIElement)sender; var targetPosition = AdornerSettings.GetPosition(adornedElement); var adorner = new MyAdorner(adornedElement); adorner.Position = targetPosition; }
这个方案最直接,避免了样式资源的查找问题,适合单一属性配置的场景。
方案3:让Adorner绑定被装饰元素的资源键
给MyAdorner添加一个依赖属性,用来关联被装饰元素中的样式资源键,自动加载对应样式:
- 给
MyAdorner添加属性:
public static readonly DependencyProperty AdornerStyleKeyProperty = DependencyProperty.Register( "AdornerStyleKey", typeof(object), typeof(MyAdorner), new PropertyMetadata(null, OnAdornerStyleKeyChanged)); public object AdornerStyleKey { get => GetValue(AdornerStyleKeyProperty); set => SetValue(AdornerStyleKeyProperty, value); } private static void OnAdornerStyleKeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var adorner = (MyAdorner)d; if (adorner.AdornedElement is FrameworkElement fe && e.NewValue != null) { if (fe.TryFindResource(e.NewValue) is Style style) { adorner.Style = style; } } }
- 在XAML中定义带键的样式并指定给Button:
<Button> <Button.Resources> <Style x:Key="LeftPositionAdorner" TargetType="local:MyAdorner"> <Setter Property="Position" Value="Left" /> </Style> </Button.Resources> <Button.Tag> <local:AdornerSettings AdornerStyleKey="LeftPositionAdorner" /> </Button.Tag> </Button>
- 创建Adorner时传递样式键:
private void AddAdorner(object sender, RoutedEventArgs e) { var adornedElement = (FrameworkElement)sender; var adorner = new MyAdorner(adornedElement); if (adornedElement.Tag is AdornerSettings settings) { adorner.AdornerStyleKey = settings.AdornerStyleKey; } }
这个方案适合需要给Adorner配置多个属性的场景,保持了样式的灵活性。
内容的提问来源于stack exchange,提问作者Matumba
相关产品推荐
相关产品推荐

