WPF控制移动但其装配者 - 不是:"/

ser*_*hio 3 wpf adorner wpf-controls

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

现在,当移动此行时,装饰器不会自动"跟随"该行.事实上,它没有刷新itsef:

替代文字 替代文字
这里黑色曲线是控制图,红色"120米"是装饰图.

一些代码

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

        }
    }
Run Code Online (Sandbox Code Playgroud)

Ray*_*rns 7

为什么没有调用MeasureOverride等

你的装饰器的MeasureOverride,ArrangeOverrideOnRender不被调用,因为你的SegmentLine控制是永远不会改变大小或位置:

  • 由于您的SegmentLine未实现MeasureOverride,因此它始终具有布局引擎指定的默认大小.
  • 由于您的SegmentLine不实现ArrangeOverride或操纵任何变换,因此其位置始终恰好位于容器的左上角.

装饰器的MeasureOverride,ArrangeOverride并且OnRender仅通过WPF在这些条件下称为:

  1. AdornedElement变化大小或位置(这是最常见的情况),或
  2. 其中一个的装饰器的属性chagnes和属性标记AffectsMeasure,AffectsArrangeAffectsRender,或
  3. 你打电话InvalidateMeasure(),InvalidateArrange()InvalidateVisuaul()在装饰上.

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

Adorner重新测量的精确规则

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

  1. 装饰元素必须通过使其无效MeasureArrange响应某些事件来强制布局传递.这可以自动改变对DependencyProperty与触发AffectsMeasure或者AffectsArrange,或者通过直接调用InvalidateMeasure(),InvalidateArrange()InvalidateVisual().

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

  3. 装饰元素必须对其RenderSize或它进行非平凡的更改Transform.

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

您的SegmentLine只是在新的地方绘制线而不是更新自己的尺寸,从而省略了我上面的要求#3.

建议

通常我会建议您的装饰者将AffectsRender DependencyProperties绑定到SegmentLine的属性,因此在SegmentLine中任何时候X1,Y1等都会发生变化,他们也会在Adorner中更新,这会导致Adorner重新渲染.这提供了一个非常干净的界面,因为装饰器可用于任何具有属性X1,Y1等的控件,但它比紧耦合它们的效率低.

在您的情况下,装饰器显然与您的SegmentLine紧密相关,因此我认为InvalidateVisual()从SegmentLine 调用装饰器同样有意义OnRender(),如下所示:

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();
  } 
}
Run Code Online (Sandbox Code Playgroud)

请注意,这不涉及从可视树中删除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();
  } 
}
Run Code Online (Sandbox Code Playgroud)