From f6b97e01b83dfa6b09000bbb598d72c62b56a052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8E=E6=99=BA=E7=BA=AF?= <于智纯@LODESTAR> Date: Sat, 6 Sep 2025 22:55:02 +0800 Subject: [PATCH] =?UTF-8?q?=E9=AA=8C=E8=AF=81=E8=A3=85=E9=A5=B0=E5=99=A8?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- RazorEngineTest/AdornerHelper.cs | 216 ++++++++++++++++++++++++++++ RazorEngineTest/MainWindow.xaml | 16 ++- RazorEngineTest/MyControl.cs | 137 ++++++++++++++++++ RazorEngineTest/PositionAdorner.cs | 68 +++++++++ RazorEngineTest/Themes/Generic.xaml | 26 ++++ 5 files changed, 462 insertions(+), 1 deletion(-) create mode 100644 RazorEngineTest/AdornerHelper.cs create mode 100644 RazorEngineTest/MyControl.cs create mode 100644 RazorEngineTest/PositionAdorner.cs create mode 100644 RazorEngineTest/Themes/Generic.xaml diff --git a/RazorEngineTest/AdornerHelper.cs b/RazorEngineTest/AdornerHelper.cs new file mode 100644 index 0000000..6f60fe5 --- /dev/null +++ b/RazorEngineTest/AdornerHelper.cs @@ -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 +{ + /// + /// 为一个控件的模板“根”元素外围包装一个装饰层 + /// + /// 要进行包装的控件 + /// 是否包装成功 + 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; + } + /// + /// 为一个FrameworkElemelt对象外围包裹一个包装器 + /// + /// 要包装的对象 + /// 是否包装成功 + 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; + } + + /// + /// 在可视化树中查找指定类型的祖先元素,支持指定层级 + /// + /// 要查找的祖先元素类型 + /// 起始子元素 + /// 要查找的祖先层级(1=父级,2=祖父级等) + /// 找到的祖先元素,若未找到则返回null + public static T? FindAncestor(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; + } + /// + /// 查找祖辈的装饰层,并根据尺寸偏差决定是否要返回(若祖辈装饰器尺寸过大可能不适用) + /// + /// 要查找的对象 + /// 绝对偏差,像素偏差;必须大于等于0 + /// 相对偏差,百分比偏差;必须大于等于1 + /// 是否两者都需要满足?「null」表示不需要考虑祖辈装饰器的尺寸与自身尺寸的差异 + /// 苦找不到则返回null + 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()); + 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; + } + /// + /// 尝试所有可能的方法去获取一个可视元素的装饰器 + /// + /// 要获取装饰器的可视元素 + /// 如果找不到则返回空 + 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; + } +} diff --git a/RazorEngineTest/MainWindow.xaml b/RazorEngineTest/MainWindow.xaml index 9bb139d..da82ba2 100644 --- a/RazorEngineTest/MainWindow.xaml +++ b/RazorEngineTest/MainWindow.xaml @@ -7,6 +7,20 @@ mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> - + + + + + diff --git a/RazorEngineTest/MyControl.cs b/RazorEngineTest/MyControl.cs new file mode 100644 index 0000000..43bcd1c --- /dev/null +++ b/RazorEngineTest/MyControl.cs @@ -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 +{ + /// + /// 按照步骤 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 文件中使用控件。 + /// + /// + /// + /// + 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 = ""; + } + } +} diff --git a/RazorEngineTest/PositionAdorner.cs b/RazorEngineTest/PositionAdorner.cs new file mode 100644 index 0000000..c025cdd --- /dev/null +++ b/RazorEngineTest/PositionAdorner.cs @@ -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; + } + } + } +} diff --git a/RazorEngineTest/Themes/Generic.xaml b/RazorEngineTest/Themes/Generic.xaml new file mode 100644 index 0000000..3ca715f --- /dev/null +++ b/RazorEngineTest/Themes/Generic.xaml @@ -0,0 +1,26 @@ + + + + + + +