如何让Adorner上的Rectangle随绑定Image窗口缩放同步适配?
解决WPF Adorner中Rectangle随Image同步缩放的问题
嘿,这个问题我刚入门WPF的时候也踩过坑!你现在的问题核心很明确:你用绝对像素值定义了Adorner里的Rectangle大小和位置,但Image在窗口resize时是通过Stretch="Uniform"自适应缩放的,Adorner根本不知道Image的缩放比例,自然没法跟着同步调整。你想到的「归一化坐标」完全是正确的思路,咱们直接把这个思路落地成代码:
核心思路
放弃用绝对像素存储ROI区域,改成存储相对于原始图片尺寸的比例值。每次Adorner渲染时,根据当前Image的实际显示尺寸,把比例值转换成对应的绝对Rect,这样不管Image怎么缩放,ROI都会精准覆盖图片上的同一区域。
修改后的完整代码
1. 重构RoiAdorner类
class RoiAdorner : Adorner { // 存储相对比例:相对于原始图片宽高的百分比 private readonly double _relativeX; private readonly double _relativeY; private readonly double _relativeWidth; private readonly double _relativeHeight; public RoiAdorner(UIElement adornedElement) : base(adornedElement) { var image = adornedElement as Image; if (image?.Source == null) return; // 获取原始图片的真实尺寸(不是显示尺寸) double originalWidth = 0; double originalHeight = 0; if (image.Source is BitmapImage bitmap) { originalWidth = bitmap.PixelWidth; originalHeight = bitmap.PixelHeight; } // 兼容其他图片源类型(比如BitmapFrame) else if (image.Source is BitmapFrame frame) { originalWidth = frame.PixelWidth; originalHeight = frame.PixelHeight; } // 这里替换成你最初想要的ROI绝对区域(相对于原始图片) double roiX = 0; double roiY = 0; double roiWidth = 100; double roiHeight = 30; // 计算相对比例 _relativeX = roiX / originalWidth; _relativeY = roiY / originalHeight; _relativeWidth = roiWidth / originalWidth; _relativeHeight = roiHeight / originalHeight; IsHitTestVisible = false; // 监听Image尺寸变化,自动触发重绘 adornedElement.SizeChanged += (_, __) => InvalidateVisual(); } protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); var pen = new Pen(Brushes.Green, 5); // 获取Image当前的实际显示尺寸 var currentRenderSize = AdornedElement.RenderSize; // 把相对比例转换成当前的绝对Rect var currentRoiRect = new Rect( _relativeX * currentRenderSize.Width, _relativeY * currentRenderSize.Height, _relativeWidth * currentRenderSize.Width, _relativeHeight * currentRenderSize.Height ); drawingContext.DrawRectangle(null, pen, currentRoiRect); } }
2. 调整MainWindow代码(确保图片加载完成再创建Adorner)
如果你的图片是异步加载的(比如本地文件或网络图片),直接在构造函数里创建Adorner可能拿不到原始图片尺寸,所以我们把Adorner的创建放到Image的Loaded事件里:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); // 监听Image加载完成事件 Btn.Loaded += OnImageLoaded; } private void OnImageLoaded(object sender, RoutedEventArgs e) { var adornerLayer = AdornerLayer.GetAdornerLayer(Btn); // 避免重复添加 if (adornerLayer.GetAdorners(Btn)?.Length == 0) { adornerLayer.Add(new RoiAdorner(Btn)); } } }
3. XAML代码无需修改
你的原有XAML代码保持不变即可:
<Window x:Class="TestingAdorners.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TestingAdorners" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid ClipToBounds="True"> <AdornerDecorator> <Image Name="Btn" Source="nyan.png" Stretch="Uniform"/> </AdornerDecorator> </Grid> </Window>
关键细节说明
- 为什么要监听
SizeChanged?因为窗口resize时,Image的RenderSize会变化,我们需要触发InvalidateVisual()让Adorner重新渲染,计算新的Rect。 - 原始图片尺寸的获取:要区分
PixelWidth/PixelHeight(图片本身的真实尺寸)和RenderSize(控件显示的尺寸),我们的比例是基于真实尺寸计算的,这样才能保证ROI始终对应图片上的同一区域。 - 如果需要支持用户拖拽调整ROI,只需要在调整后重新计算相对比例即可,逻辑是一样的。
这样修改后,不管你怎么缩放窗口,那个绿色矩形都会牢牢贴合在图片的目标区域上啦!
内容的提问来源于stack exchange,提问作者Conchors
相关产品推荐
相关产品推荐

