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 等

您的装饰器的MeasureOverrideArrangeOverrideOnRender 不会被调用,因为您的 SegmentLine 控件永远不会改变大小或位置:

由于您的 SegmentLine 未实现 MeasureOverride,因此它始终具有布局引擎分配的默认大小。 由于您的 SegmentLine 没有实现 ArrangeOverride 或操作任何变换,因此它的位置始终正好是容器的左上角。

装饰器的MeasureOverrideArrangeOverrideOnRender 仅在这些条件下由 WPF 调用:

    AdornedElement 改变大小或位置(这是最常见的情况),或者 Adorner 的属性之一 chagnes 并且该属性标记为 AffectsMeasureAffectsArrangeAffectsRender,或 您可以致电装饰器上的InvalidateMeasure()InvalidateArrange()InvalidateVisuaul()

因为您的 SegmentLine 永远不会改变大小或位置,所以案例 1 不适用。由于您在 Adorner 上没有任何此类属性并且不要调用 InvalidateMeasure()InvalidateArrange()InvalidateVisual(),因此其他情况也不适用。

装饰器重新测量的精确规则

以下是装饰元素更改触发对Adorner.MeasureOverride 的调用时的精确规则:

    装饰元素必须通过使其MeasureArrange 无效来响应某些事件来强制布局通过。这可以通过使用 AffectsMeasureAffectsArrange 更改 DependencyProperty 或通过直接调用 InvalidateMeasure()InvalidateArrange()InvalidateVisual() 来自动触发。

    装饰元素的MeasureArrange 方法不能在失效和布局通道之间直接从用户代码调用。换句话说,您必须等待布局管理器完成这项工作。

    装饰元素必须对其RenderSizeTransform 进行重大更改。

    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 控件移动但它的装饰器 - 不是:"/的主要内容,如果未能解决你的问题,请参考以下文章

WPF - 拖放 - 装饰器在控件外消失

如何在 WPF 中为装饰器设置 Z 顺序索引

我啥时候应该使用装饰器?

Adorner 装饰器

WPF IrfanView 风格的图像裁剪控件

没有事件传递给 WPF 装饰层