开发者

WPF Control moves but its Adorner - Not :"/

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:

WPF Control moves but its Adorner - Not :"/

WPF Control moves but its Adorner - Not :"/

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

        }
    }


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


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.


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.


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...

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜