WPF 控件移动但它的装饰器 - 不是:"/
Posted
技术标签:
【中文标题】WPF 控件移动但它的装饰器 - 不是:"/【英文标题】:WPF Control moves but its Adorner - Not :"/ 【发布时间】:2010-11-17 14:34:57 【问题描述】:我在 WPF 行元素上创建了一个装饰器,因为需要添加一些文本。
现在,当这条线被移动时,装饰器不会自动“跟随”这条线。事实上,它并没有刷新它的ef:
这里黑色的曲线是控制图,红色的“120 m”是装饰图。
一些代码
void SegmentLine_Loaded(object sender, RoutedEventArgs e)
AdornerLayer aLayer = AdornerLayer.GetAdornerLayer(this);
if (aLayer != null)
aLayer.Add(new TextAdorner(this));
class TextAdorner : Adorner
public TextAdorner(UIElement adornedElement)
: base(adornedElement)
protected override void OnRender(DrawingContext drawingContext)
SegmentLine segment = (this.AdornedElement as SegmentLine);
if (segment != null)
Rect segmentBounds = new Rect(segment.DesiredSize);
var midPoint = new Point(
(segment.X1 + segment.X2) / 2.0,
(segment.Y1 + segment.Y2) / 2.0);
var lineFont = // get line font as Font
FormattedText ft = new FormattedText(
string.Format("0 m", segment.Distance),
Thread.CurrentThread.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface(lineFont.FontFamily.ToString()),
ligneFont.Size, Brushes.Red);
drawingContext.DrawText(ft, midPoint);
【问题讨论】:
【参考方案1】:为什么没有调用 MeasureOverride 等
您的装饰器的MeasureOverride
、ArrangeOverride
和OnRender
不会被调用,因为您的 SegmentLine 控件永远不会改变大小或位置:
MeasureOverride
,因此它始终具有布局引擎分配的默认大小。
由于您的 SegmentLine 没有实现 ArrangeOverride
或操作任何变换,因此它的位置始终正好是容器的左上角。
装饰器的MeasureOverride
、ArrangeOverride
和OnRender
仅在这些条件下由 WPF 调用:
AdornedElement
改变大小或位置(这是最常见的情况),或者
Adorner 的属性之一 chagnes 并且该属性标记为 AffectsMeasure
、AffectsArrange
或 AffectsRender
,或
您可以致电装饰器上的InvalidateMeasure()
、InvalidateArrange()
或InvalidateVisuaul()
。
因为您的 SegmentLine 永远不会改变大小或位置,所以案例 1 不适用。由于您在 Adorner 上没有任何此类属性并且不要调用 InvalidateMeasure()
、InvalidateArrange()
或 InvalidateVisual()
,因此其他情况也不适用。
装饰器重新测量的精确规则
以下是装饰元素更改触发对Adorner.MeasureOverride
的调用时的精确规则:
装饰元素必须通过使其Measure
或Arrange
无效来响应某些事件来强制布局通过。这可以通过使用 AffectsMeasure
或 AffectsArrange
更改 DependencyProperty 或通过直接调用 InvalidateMeasure()
、InvalidateArrange()
或 InvalidateVisual()
来自动触发。
装饰元素的Measure
和Arrange
方法不能在失效和布局通道之间直接从用户代码调用。换句话说,您必须等待布局管理器完成这项工作。
装饰元素必须对其RenderSize
或Transform
进行重大更改。
AdornerLayer
和装饰元素之间的所有转换的组合必须是仿射的。只要您不使用 3D,通常就是这种情况。
您的 SegmentLine 只是在新的地方画线,而不是更新自己的尺寸,因此省略了我上面的第 3 条要求。
推荐
通常我会建议您的装饰器将 AffectsRender DependencyProperties 绑定到 SegmentLine 的属性,因此每当 X1、Y1 等在 SegmentLine 中发生更改时,它们也会在 Adorner 中更新,从而导致 Adorner 重新渲染。这提供了一个非常干净的接口,因为装饰器可以用于任何具有 X1、Y1 等属性的控件,但它的效率低于紧密耦合它们。
在您的情况下,装饰器显然与您的 SegmentLine 紧密绑定,因此我认为从 SegmentLine 的 OnRender()
调用装饰器上的 InvalidateVisual()
也同样有意义,如下所示:
public class SegmentLine : Shape
TextAdorner adorner;
...
protected override void OnRender(DrawingContext drawingContext)
base.OnRender(drawingContext);
if(adorner==null)
var layer = AdornerLayer.GetAdornerLayer(this); if(layer==null) return;
adorner = new TextAdorner(this);
... set other adorner properties and events ...
layer.Add(adorner);
adorner.InvalidateVisual();
请注意,这不涉及将 SegmentLine 从可视化树中删除然后稍后再次添加的情况。您的原始代码也没有处理这个问题,所以我避免了处理这种情况的复杂性。如果您需要它来工作,请改为这样做:
public class SegmentLine : Shape
AdornerLayer lastLayer;
TextAdorner adorner;
...
protected override void OnRender(DrawingContext drawingContext)
base.OnRender(drawingContext);
var layer = AdornerLayer.GetAdornerLayer(this);
if(layer!=lastLayer)
if(adorner==null)
adorner = new TextAdorner(this);
... set other adorner properties and events ...
if(lastLayer!=null) lastLayer.Remove(adorner);
if(layer!=null) layer.Add(adorner);
lastLayer = layer;
adorner.InvalidateVisual();
【讨论】:
你看到我的回答了吗?我应该在哪里调用 Adorner.MeasureOverride?我应该在我的自定义装饰器中覆盖这个方法吗?谢谢。 在这里查看课程详情:***.com/questions/4214435/… 感谢您发布课程详情。我已经更新了我的答案。不,您不应该调用或实施 Adorner.MeasureOverride。请参阅此更新答案的最后一部分,了解您应该做什么。 另一个注意事项:我注意到您使用了 Loaded 事件。您是否知道此事件仅在连接 PresentationSource 时触发?使用 Loaded 时,您的 Adorner 在某些用例中会丢失,例如打印和成像。最好将 WPF 控件设计为忽略 Loaded,而是在其第一个 MeasureOverride(或 ArrangeOverride 或 OnRender)中执行此类任务。这样,无论 PresentationSource 是否连接,您的控件都将可靠地工作。所有内置的 WPF 控件都是这样做的。 感谢您的 cmets。现在,一个问题是为什么装饰器不拦截事件并将其重定向到segmentLine,我应该手动附加它们,就像你提到的“......设置其他装饰器属性和事件......”【参考方案2】:线是如何移动的?装饰器的 MeasureOverride 或 ArrangeOverride 是否在移动后被调用? OnRender 只有在视觉对象无效(例如 invalidatevisual)时才会被调用,所以我猜测渲染没有被无效。
【讨论】:
移动后不会调用任何内容。如果我每次移动线条时都要使画布无效,我会离开装饰器方法,因为我回到了 GDI+ 图形,我真的希望 WPF 不会强迫我每次移动时都刷新屏幕。 @serhio:请放心,WPF 无需完全重绘即可轻松移动装饰器。您的代码中的一个错误阻止了 OnRender 调用的发生。我添加了一个答案,解释了调用 OnRender 所需的内容。 我在这里添加了一些课程细节:***.com/questions/4214435/…【参考方案3】:您可能想使用segmentBounds
来定义midPoint
?否则它在那里做什么?看起来您正在定义 midPoint
相对于未重新渲染段。
【讨论】:
问题是当我移动行时,代码甚至没有进入 OnRender() 装饰器方法。【参考方案4】:白痴修复,但它有效
AdornerLayer aLayer;
void SegmentLine_Loaded(object sender, RoutedEventArgs e)
aLayer = AdornerLayer.GetAdornerLayer(this);
if (aLayer != null)
aLayer.Add(new TextAdorner(this));
protected override void OnRender(DrawingContext drawingContext)
base.OnRender(drawingContext);
if (aLayer != null)
aLayer.Update();
现在,问题是当我点击装饰器时,控件本身没有收到点击...
【讨论】:
它可以工作,但是如果你在一个图层上有数千行并且你只移动了一个,它会很慢。 查看我的回答,了解问题所在的完整解释和更有效的解决方法。以上是关于WPF 控件移动但它的装饰器 - 不是:"/的主要内容,如果未能解决你的问题,请参考以下文章