WPF 控件移动,但其装饰器 - 不是:“/

发布于 2024-10-03 11:07:09 字数 1575 浏览 7 评论 0原文

我在 WPF 行元素上创建了一个装饰器,因为需要添加一些文本。

现在,当移动该线时,装饰器不会自动“跟随”该线。事实上,它不会刷新它自己:

alt text 替代文字
这里黑色曲线是控制图,红色“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);
            }

        }
    }

I created an adorner on a WPF line element, because there was neet to add some text.

Now, when this line is moved, the adorner does not "follow" the line automatically. In fact, it does not refresh itsef:

alt text alt text
here black curves is the Control drawing, and the red "120 m" is the adorner one.

Some code

    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);
            }

        }
    }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

囚你心 2024-10-10 11:07:09

为什么 MeasureOverride 等没有被调用

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

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

Adorner 的 MeasureOverrideArrangeOverrideOnRender 仅在以下条件下由 WPF 调用:

  1. AdornedElement 更改大小或位置(这是最常见的情况),或者
  2. Adorner 的属性之一发生变化,并且该属性被标记为 AffectsMeasureAffectsArrangeAffectsRender,或者
  3. 您在装饰器上调用 InvalidateMeasure()InvalidateArrange()InvalidateVisuaul()

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

Adorner 重新测量的精确规则

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

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

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

  3. 装饰元素必须对其 RenderSize 或其 Transform 进行重要更改。

  4. AdornerLayer 和装饰元素之间的所有变换的组合必须是仿射的。只要您不使用 3D,通常就会出现这种情况。

您的 SegmentLine 只是在新位置绘制线条,而不是更新其自己的尺寸,从而忽略了我上面的要求 #3。

建议

通常,我会建议您的装饰器将 AffectsRender DependencyProperties 绑定到 SegmentLine 的属性,因此只要 X1、Y1 等在 SegmentLine 中发生更改,它们也会在装饰器中更新,从而导致装饰器重新渲染。这提供了一个非常干净的界面,因为装饰器可以在具有属性 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();
  } 
}

Why MeasureOverride, etc aren't being called

Your adorner's MeasureOverride, ArrangeOverride, and OnRender aren't being called because your SegmentLine control is never changing size or position:

  • Since your SegmentLine doesn't implement MeasureOverride, it always has the default size assigned by the layout engine.
  • Since your SegmentLine doesn't implement ArrangeOverride or manipulate any transforms, its position is always exactly the upper-left corner of the container.

The Adorner's MeasureOverride, ArrangeOverride and OnRender are only called by WPF under these conditions:

  1. The AdornedElement changes size or position (this the most common case), or
  2. One of the Adorner's properties chagnes and that property is marked AffectsMeasure, AffectsArrange, or AffectsRender, or
  3. You call InvalidateMeasure(), InvalidateArrange(), or InvalidateVisuaul() on the adorner.

Because your SegmentLine never changes size or position, case 1 doesn't apply. Since you don't have any such properties on the Adorner and don't call InvalidateMeasure(), InvalidateArrange() or InvalidateVisual(), the other cases don't apply either.

Precise rules for Adorner re-measure

Here are the precise rules for when an adorned element change triggers a call to Adorner.MeasureOverride:

  1. The adorned element must force a layout pass by invalidating its Measure or Arrange in response to some event. This could be triggered automatically by a change to a DependencyProperty with AffectsMeasure or AffectsArrange, or by a direct call to InvalidateMeasure(), InvalidateArrange() or InvalidateVisual().

  2. The adorned element's Measure and Arrange methods must not be called directly from user code between the invalidation and the layout pass. In other words, you must wait for the layout manager to do the job.

  3. The adorned element must make a non-trivial change to either its RenderSize or its Transform.

  4. The combination of all transforms between the AdornerLayer and the adorned element must be affine. This will generally be the case as long as you are not using 3D.

Your SegmentLine is just drawing the line in a new place rather than updating its own dimensions, thereby omitting my requirement #3 above.

Recommendation

Normally I would recommend your adorner have AffectsRender DependencyProperties bound to the SegmentLine's properties, so any time X1, Y1, etc change in the SegmentLine they are also updated in the Adorner which causes the Adorner to re-render. This provides a very clean interface, since the adorner can be used on any control that has properties X1, Y1, etc, but it is less efficient than tightly coupling them.

In your case the adorner is clearly tightly bound to your SegmentLine, so I think it makes just as much sense to call InvalidateVisual() on the adorner from the SegmentLine's OnRender(), like this:

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();
  } 
}

Note that this doesn't deal with the situation where the SegmentLine is removed from the visual tree and then added again later. Your original code doesn't deal with this either, so I avoided the complexity of dealing with that case. If you need that to work, do this instead:

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();
  } 
}
七婞 2024-10-10 11:07:09

线路如何移动?移动后是否会调用装饰器的 MeasureOverride 或 ArrangeOverride?仅当视觉效果无效(例如invalidatevisual)时才会调用OnRender,因此我猜测渲染并未无效。

How is the line being moved? Does the MeasureOverride or ArrangeOverride of the adorner get invoked after the move? OnRender will only get invoked if the visual is invalidated (e.g. invalidatevisual) so I'm guessing that the render isn't being invalidated.

染年凉城似染瑾 2024-10-10 11:07:09

也许您想使用segmentBounds来定义midPoint?否则它在那里做什么?看起来您正在定义相对于未重新渲染的段的 midPoint

May be you wanted to use segmentBounds to define midPoint? Otherwise what is it doing there? Looks like you are defining midPoint relative to not rerendered segment.

小傻瓜 2024-10-10 11:07:09

白痴修复,但它有效

    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();
        }
    }

现在,问题是当我单击装饰器时,控件本身没有收到点击...

idiot fix, but it works

    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();
        }
    }

Now, the problem is that when I click on a the adorner the control itself does not recieve the hit...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文