Files
Example/RazorEngineTest/AdornerHelper.cs

217 lines
8.8 KiB
C#
Raw Normal View History

2025-09-06 22:55:02 +08:00
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;
}
}