验证装饰器功能成功

This commit is contained in:
于智纯
2025-09-06 22:55:02 +08:00
parent d4ff4063e4
commit f6b97e01b8
5 changed files with 462 additions and 1 deletions

View File

@@ -0,0 +1,216 @@
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.Documents;
using System.Windows.Media;
public static class AdornerHelper
{
/// <summary>
/// 为一个控件的模板“根”元素外围包装一个装饰层
/// </summary>
/// <param name="control">要进行包装的控件</param>
/// <returns>是否包装成功</returns>
public static bool WrapDecorator_To_TemplateRoot(this Control control)
{
// 如果控件为空或控件模板为空则退出
if (control == null || control.Template == null) return false;
// 如果这个控件本身有装饰层则不需要再进行包装处理
if (AdornerLayer.GetAdornerLayer(control) != null) return true;
// 应用模板(如果尚未应用)
if (control.Template != null && control.IsInitialized) control.ApplyTemplate();
// 获取模板的根视觉元素
DependencyObject child = VisualTreeHelper.GetChild(control, 0);
FrameworkElement? templateRoot = child as FrameworkElement;
if (templateRoot == null) return false;
// 获取模板根元素的父元素
DependencyObject parent = VisualTreeHelper.GetParent(templateRoot);
if (parent == null) return false;
// 1. 创建新的 AdornerDecorator
AdornerDecorator decorator = new AdornerDecorator();
// 2. 从原父元素中移除原根元素
if (parent is Panel panel)
{
int childIndex = panel.Children.IndexOf(templateRoot);
panel.Children.RemoveAt(childIndex);
// 3. 将原根元素添加到 AdornerDecorator 的 Child 中
decorator.Child = templateRoot;
// 4. 将 AdornerDecorator 添加回原父元素的相同位置
panel.Children.Insert(childIndex, decorator);
return true;
}
else if (parent is Decorator decoratorParent)
{
// 如果父元素是 Decorator如 Border
decoratorParent.Child = null;
decorator.Child = templateRoot;
decoratorParent.Child = decorator;
return true;
}
else if (parent is ContentControl contentControl)
{
// 如果父元素是 ContentControl
contentControl.Content = null;
decorator.Child = templateRoot;
contentControl.Content = decorator;
return true;
}
// 其他类型的父元素可能需要特殊处理
return false;
}
/// <summary>
/// 为一个FrameworkElemelt对象外围包裹一个包装器
/// </summary>
/// <param name="element">要包装的对象</param>
/// <returns>是否包装成功</returns>
public static bool WrapDecorator_To_UIElement(this UIElement element)
{
if (element == null) return false;
// 如果这个控件本身有装饰层则不需要再进行包装处理
if (AdornerLayer.GetAdornerLayer(element) != null) return true;
// 获取模板根元素的父元素
DependencyObject parent = VisualTreeHelper.GetParent(element);
if (parent == null) return false;
// 1. 创建新的 AdornerDecorator
AdornerDecorator decorator = new AdornerDecorator();
// 2. 从原父元素中移除原根元素
if (parent is Panel panel)
{
int childIndex = panel.Children.IndexOf(element);
panel.Children.RemoveAt(childIndex);
// 3. 将原根元素添加到 AdornerDecorator 的 Child 中
decorator.Child = element;
// 4. 将 AdornerDecorator 添加回原父元素的相同位置
panel.Children.Insert(childIndex, decorator);
return true;
}
else if (parent is Decorator decoratorParent)
{
// 如果父元素是 Decorator如 Border
decoratorParent.Child = null;
decorator.Child = element;
decoratorParent.Child = decorator;
return true;
}
else if (parent is ContentControl contentControl)
{
// 如果父元素是 ContentControl
contentControl.Content = null;
decorator.Child = element;
contentControl.Content = decorator;
return true;
}
// 其他类型的父元素可能需要特殊处理
return false;
}
/// <summary>
/// 在可视化树中查找指定类型的祖先元素,支持指定层级
/// </summary>
/// <typeparam name="T">要查找的祖先元素类型</typeparam>
/// <param name="object">起始子元素</param>
/// <param name="level">要查找的祖先层级(1=父级2=祖父级等)</param>
/// <returns>找到的祖先元素若未找到则返回null</returns>
public static T? FindAncestor<T>(this DependencyObject @object, int level = 1) where T : DependencyObject
{
if (@object == null || level < 1) return null;
DependencyObject parent = @object;
int currentLevel = 0;
while (parent != null && currentLevel < level)
{
parent = VisualTreeHelper.GetParent(parent);
if (parent is T foundParent)
{
// 每次找到一个约定类型的袓辈节点计数需要加1
currentLevel++;
if (currentLevel == level)
return foundParent;
}
}
return null;
}
/// <summary>
/// 查找祖辈的装饰层,并根据尺寸偏差决定是否要返回(若祖辈装饰器尺寸过大可能不适用)
/// </summary>
/// <param name="visual">要查找的对象</param>
/// <param name="absoluteDeviation">绝对偏差像素偏差必须大于等于0</param>
/// <param name="relativeDeviation">相对偏差百分比偏差必须大于等于1</param>
/// <param name="isAllSizeDiffToBeMeet">是否两者都需要满足「null」表示不需要考虑祖辈装饰器的尺寸与自身尺寸的差异</param>
/// <returns>苦找不到则返回null</returns>
public static AdornerLayer? GetAncestorAdornerLayer(this Visual visual, double absoluteDeviation = 0, double relativeDeviation = 1, bool? isAllSizeDiffToBeMeet = null)
{
absoluteDeviation = absoluteDeviation < 0 ? 0 : absoluteDeviation; // 限制绝对偏差不能小于0
relativeDeviation = relativeDeviation < 1 ? 1 : relativeDeviation; // 限制相对偏差不能小于1
var adornerLayer = AdornerLayer.GetAdornerLayer(visual);
if (adornerLayer == null) adornerLayer = AdornerLayer.GetAdornerLayer(visual.FindAncestor<AdornerDecorator>());
if (adornerLayer != null)
{
if (isAllSizeDiffToBeMeet == null) return adornerLayer;
else
{
if (visual is FrameworkElement element)
{
bool ad = (adornerLayer.ActualHeight - element.ActualHeight) <= absoluteDeviation & (adornerLayer.ActualWidth - element.ActualWidth) <= absoluteDeviation;
bool rd = (adornerLayer.ActualHeight <= element.ActualHeight * relativeDeviation & adornerLayer.ActualWidth <= element.ActualWidth * relativeDeviation);
if (isAllSizeDiffToBeMeet == true)
{
if (ad & rd) return adornerLayer;
}
else
{
if (ad | rd) return adornerLayer;
}
}
}
}
return null;
}
/// <summary>
/// 尝试所有可能的方法去获取一个可视元素的装饰器
/// </summary>
/// <param name="visual">要获取装饰器的可视元素</param>
/// <returns>如果找不到则返回空</returns>
public static AdornerLayer? TryGetAdornerLayerExhaustively(this Visual visual)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(visual);
if (adornerLayer != null) return adornerLayer;
// 获取祖辈装饰器要求尺寸偏差不能超过4个像素四个边均可留有2个像素的边
adornerLayer = visual.GetAncestorAdornerLayer(4, 1, false);
if (adornerLayer != null) return adornerLayer;
if (visual is Control control)
{
control.WrapDecorator_To_TemplateRoot();
adornerLayer = AdornerLayer.GetAdornerLayer(control);
if (adornerLayer != null) return adornerLayer;
}
else if (visual is UIElement element)
{
element.WrapDecorator_To_UIElement();
adornerLayer = AdornerLayer.GetAdornerLayer(element);
if (adornerLayer != null) return adornerLayer;
}
else return null;
return null;
}
}

View File

@@ -7,6 +7,20 @@
mc:Ignorable="d" mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"> Title="MainWindow" Height="450" Width="800">
<Grid> <Grid>
<Border BorderBrush="Green" BorderThickness="2" VerticalAlignment="Top" HorizontalAlignment="Left">
<local:MyControl Width="200" Height="40"/>
</Border>
<Path Width="24" Height="24" Fill="Orange" Data="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-8.512 8.272l5.532 3.243-.686 1.162-5.533-3.243.687-1.162zm-1.456 3.113l6.185 1.739-.332 1.23-6.204-1.667.351-1.302zm-.672 2.813l6.498.65-.117 1.28-6.504-.586.123-1.344zm-.193 2.469h6.667v1.333h-6.667v-1.333zm8.833 3.333h-11v-7h1v6h9v-6h1v7zm-.852-8.704l-3.56-5.219 1.115-.76 3.559 5.219-1.114.76zm1.356-.841l-1.08-6.224 1.328-.231 1.082 6.224-1.33.231z"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Top" Background="Transparent" BorderThickness="0" Foreground="Green">
<Image Width="64" Height="64" HorizontalAlignment="Left">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="{Binding RelativeSource={RelativeSource AncestorType={x:Type Button}},Path=Foreground}" Geometry="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-8.512 8.272l5.532 3.243-.686 1.162-5.533-3.243.687-1.162zm-1.456 3.113l6.185 1.739-.332 1.23-6.204-1.667.351-1.302zm-.672 2.813l6.498.65-.117 1.28-6.504-.586.123-1.344zm-.193 2.469h6.667v1.333h-6.667v-1.333zm8.833 3.333h-11v-7h1v6h9v-6h1v7zm-.852-8.704l-3.56-5.219 1.115-.76 3.559 5.219-1.114.76zm1.356-.841l-1.08-6.224 1.328-.231 1.082 6.224-1.33.231z"/>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>
</Grid> </Grid>
</Window> </Window>

View File

@@ -0,0 +1,137 @@
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;
namespace RazorEngineTest
{
/// <summary>
/// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
///
/// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
/// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
/// 元素中:
///
/// xmlns:MyNamespace="clr-namespace:RazorEngineTest"
///
///
/// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
/// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
/// 元素中:
///
/// xmlns:MyNamespace="clr-namespace:RazorEngineTest;assembly=RazorEngineTest"
///
/// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用,
/// 并重新生成以避免编译错误:
///
/// 在解决方案资源管理器中右击目标项目,然后依次单击
/// “添加引用”->“项目”->[浏览查找并选择此项目]
///
///
/// 步骤 2)
/// 继续操作并在 XAML 文件中使用控件。
///
/// <MyNamespace:MyControl/>
///
/// </summary>
public class MyControl : Control
{
private PositionAdorner? _currentAdorner = null;
private AdornerLayer? _adornerLayer = null;
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
// 公开一个属性来获取当前拖放位置
public string CurrentDropPosition { get; private set; } = "";
private void UpdateAdorner(string position)
{
RemoveAdorner();
if (_adornerLayer != null)
{
_currentAdorner = new PositionAdorner(this, position);
_adornerLayer.Add(_currentAdorner);
}
}
private void RemoveAdorner()
{
if (_currentAdorner != null && _adornerLayer != null)
{
_adornerLayer.Remove(_currentAdorner);
_currentAdorner = null;
}
}
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
// 初始化装饰器层
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
e.Handled = true;
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
// 获取鼠标相对于当前控件的位置
Point position = e.GetPosition(this);
// 计算控件的大小
double width = ActualWidth;
double height = ActualHeight;
// 定义边缘阈值例如距离边缘10%的区域被认为是边缘)
double edgeThreshold = 0.25;
double horizontalThreshold = width * edgeThreshold;
double verticalThreshold = height * edgeThreshold;
// 判断位置
string positionName;
if (position.X < horizontalThreshold)
{
positionName = "左";
}
else if (position.X > width - horizontalThreshold)
{
positionName = "右";
}
else if (position.Y < verticalThreshold)
{
positionName = "上";
}
else if (position.Y > height - verticalThreshold)
{
positionName = "下";
}
else
{
positionName = "中";
}
// 更新当前位置属性
CurrentDropPosition = positionName;
// 更新装饰器
UpdateAdorner(positionName);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
RemoveAdorner();
CurrentDropPosition = "";
}
}
}

View File

@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace RazorEngineTest
{
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;
public class PositionAdorner : Adorner
{
private readonly string _position;
private static readonly Pen _pen;
static PositionAdorner()
{
// 定义装饰器的画笔
_pen = new Pen(Brushes.Red, 5.0);
_pen.Freeze();
}
public PositionAdorner(UIElement adornedElement, string position)
: base(adornedElement)
{
_position = position;
IsHitTestVisible = false;
}
protected override void OnRender(DrawingContext drawingContext)
{
var adornedElementRect = new Rect(AdornedElement.RenderSize);
// 根据位置信息绘制不同的视觉效果
switch (_position)
{
case "前":
case "上":
drawingContext.DrawLine(_pen,
new Point(0, 0),
new Point(adornedElementRect.Right, 0));
break;
case "后":
case "下":
drawingContext.DrawLine(_pen,
new Point(0, adornedElementRect.Bottom),
new Point(adornedElementRect.Right, adornedElementRect.Bottom));
break;
case "左":
drawingContext.DrawLine(_pen,
new Point(0, 0),
new Point(0, adornedElementRect.Bottom));
break;
case "右":
drawingContext.DrawLine(_pen,
new Point(adornedElementRect.Right, 0),
new Point(adornedElementRect.Right, adornedElementRect.Bottom));
break;
case "中":
drawingContext.DrawRectangle(null, _pen, adornedElementRect);
break;
default:break;
}
}
}
}

View File

@@ -0,0 +1,26 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RazorEngineTest">
<Path x:Key="Icon_Window" Height="24" Width="24" Data="M22 6v16h-20v-16h20zm2-6h-24v24h24v-24zm-12.879 14l-4.707-4.707-1.414 1.414 3.293 3.293-3.293 3.293 1.414 1.414 4.707-4.707zm7.879 3h-7v2h7v-2z"/>
<GeometryDrawing x:Key="icon" Brush="Black" Geometry="M22 6v16h-20v-16h20zm2-6h-24v24h24v-24zm-12.879 14l-4.707-4.707-1.414 1.414 3.293 3.293-3.293 3.293 1.414 1.414 4.707-4.707zm7.879 3h-7v2h7v-2z"/>
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Background" Value="Gray"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyControl}">
<AdornerDecorator>
<Border Background="{TemplateBinding Background}">
<TextBlock Text="装饰器测试" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</AdornerDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>