Why does my adorner not re-render when the element it's applied to changes?

后端 未结 2 1051
梦谈多话
梦谈多话 2021-02-01 06:32

In a UI I\'m building, I want to adorn a panel whenever one of the controls in the panel has the focus. So I handle the IsKeyboardFocusWithinChanged event, and add

相关标签:
2条回答
  • 2021-02-01 07:16

    WPF has a built-in mechanism to cause all Adorners to be remeasured, rearranged, and rerendered whenever the corresponding AdornedElement changes size, position, or transform. This mechanism requires you to follow certain rules when coding your adorner, not all of which are documented as clearly as they ought to be.

    I will first answer your title question of why your adorner doesn't consistenty re-render, then explain the best way to fix it.

    Why the adorner doesn't re-render

    Whenever an AdornerLayer receives a LayoutChanged notification it scans each of its Adorners to see if the AdornedElement has changed in size, position or transform. If so, it sets flags to force the Adorner to measure, arrange, and render again -- roughly equivalent to InvalidateMeasure(); InvaliateArrange(); InvalidateVisual();.

    What normally happens in this situation is that the control is first measured, then arranged, then rendered. In fact, WPF tries to make this the most common case because it is the most efficient sequence. However there are many situations where a control can end up being rearranged and/or rerendered before it is remeasured. This is a legitimate order of events in WPF (to allow flexible layout techniques), but it is not common so it is often not tested.

    A correctly implemented Adorner or other UIElement will be careful to call InvalidateVisual() any time the rendering may be affected unless only AffectsRender dependency properties were changed.

    In your case, your adorner's size clearly affect rendering. The size properties are not AffectsRender dependency properties, so it is necessary to manualy call InvalidateVisual() when they change. If you don't, WPF may never know to re-render your adorner.

    What is happening in your situation is probably this:

    • Layout completes and the LayoutChanged event fires
    • AdornerLayer discovers the size change on your AdornedElement
    • AdornerLayer schedules your adorner for re-measure, re-layout, and re-render
    • Something causes Arrange() to be called which causes the re-layout and re-render to happen before the re-measure. This causes WPF to think the adorner no longer needs a re-layout or re-render.
    • The layout engine detects that the adorner needs measuring and calls Measure
    • The adorner's MeasureOverride recomputes the desired size but does nothing to tell WPF the adorner needs to re-render
    • The layout engine decides there is nothing more to be done and so the adorner never re-renders

    What you can do to fix it

    The solution is, of course, to fix the bug in the Adorner by calling InvalidateVisual() whenever the control is re-measured, like this:

    protected override Size MeasureOverride(Size constraint)
    {
      var result = base.MeasureOverride(constraint);
      // ... add custom measure code here if desired ...
      InvalidateVisual();
      return result;
    }
    

    Doing this will cause your Adorner to consistently obey all the rules of WPF, so it will work as expected in all situations. This is also the most efficient solution, since InvalidateVisual() will do nothing at all except in those cases where it is really needed.

    0 讨论(0)
  • 2021-02-01 07:26

    You need to invoke the dispatcher on the panel. Add a handler to the TextBox SizeChanged event:

        private void myTextBox_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            panel.Dispatcher.Invoke((Action)(() => 
            {
                if (panel.IsKeyboardFocusWithin)
                {
                    // remove and add adorner to reset
                    myAdornerLayer.Remove(myAdorner);
                    myAdornerLayer.Add(myAdorner);
                }
            }), DispatcherPriority.Render, null);
        }
    

    This basically comes from this post: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx

    0 讨论(0)
提交回复
热议问题