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:
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:
- The
AdornedElement
changes size or position (this the most common case), or - One of the Adorner's properties chagnes and that property is marked
AffectsMeasure
,AffectsArrange
, orAffectsRender
, or - You call
InvalidateMeasure()
,InvalidateArrange()
, orInvalidateVisuaul()
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
:
The adorned element must force a layout pass by invalidating its
Measure
orArrange
in response to some event. This could be triggered automatically by a change to a DependencyProperty withAffectsMeasure
orAffectsArrange
, or by a direct call toInvalidateMeasure()
,InvalidateArrange()
orInvalidateVisual()
.The adorned element's
Measure
andArrange
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.The adorned element must make a non-trivial change to either its
RenderSize
or itsTransform
.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...
精彩评论