代码之家  ›  专栏  ›  技术社区  ›  Robert Rossney

为什么我的装饰器在应用到更改的元素时不重新渲染?

  •  15
  • Robert Rossney  · 技术社区  · 15 年前

    在我正在构建的UI中,每当面板中的某个控件具有焦点时,我都希望装饰面板。所以我处理 IsKeyboardFocusWithinChanged 事件,并在元素获得焦点时向其添加修饰器,在元素失去焦点时移除修饰器。这似乎行得通。

    我遇到的问题是,如果修饰元素的边界发生更改,则不会重新渲染修饰器。例如,在这个简单的例子中:

    <WrapPanel Orientation="Horizontal"
               IsKeyboardFocusChanged="Panel_IsKeyboardFocusChanged">
       <Label>Caption</Label>
       <TextBox>Data</TextBox>
    </WrapPanel>
    

    装饰师正确地装饰了 WrapPanel TextBox 接收焦点,但当我键入文本时, 文本框 在装饰器边缘下方展开。当然,只要我执行任何强制装饰器渲染的操作,例如从应用程序中退出alt-tab或将焦点放在另一个面板上,它就会自动更正。但是,当修饰元素的边界发生变化时,如何才能让它重新呈现呢?

    2 回复  |  直到 13 年前
        1
  •  57
  •   Ray Burns    15 年前

    WPF有一个内置的机制来 Adorners 每当相应的 AdornedElement 更改大小、位置或转换。这种机制要求您在对装饰器进行编码时遵循某些规则,而不是所有这些规则都像应该的那样清晰地记录下来。

    我先回答你的题目问题 为什么? 你的装饰不一致重新渲染,然后解释修复它的最佳方法。

    为什么装饰不重新渲染

    每当Adornerlayer收到LayoutChanged通知时,它会扫描每个Adornerlayer,以查看 装饰装饰 大小、位置或转换已更改。如果是这样,它将设置标志以强制 Adorner 再次测量、排列和渲染——大致相当于 InvalidateMeasure(); InvaliateArrange(); InvalidateVisual(); .

    在这种情况下,通常会发生的情况是,首先对控件进行测量,然后进行排列,然后进行呈现。事实上,WPF试图使这成为最常见的情况,因为它是最有效的序列。但是,在许多情况下,控件可能会在重新测量之前被重新排列和/或重新定位。这是WPF中事件的合法顺序(允许灵活的布局技术),但它并不常见,因此通常不进行测试。

    a正确实施 装饰者 或其他 UIElement 会小心打电话的 InvalidateVisual() 任何时候渲染都可能受到影响 除非只有 AffectsRender 依赖项属性已更改。

    在您的情况下,装饰器的大小明显影响渲染。大小属性不是 渲染渲染 依赖属性,因此需要手动调用 使Visual()失效 当他们改变的时候。如果你不知道,WPF可能永远不会知道重新渲染你的装饰。

    你的情况可能是这样的:

    • 布局完成,并且 LayoutChanged 事件激发
    • AdornerLayer 发现您的 装饰装饰
    • 装饰层 安排装饰器重新测量、重新布局和重新渲染
    • 一些原因 Arrange() 在重新测量之前调用,这会导致重新布局和重新呈现。这使WPF认为装饰器不再需要重新布局或重新渲染。
    • 布局引擎检测到装饰器需要测量和调用 Measure
    • 装饰者的 MeasureOverride 重新计算所需大小 但是没有什么可以告诉WPF装饰器需要重新渲染
    • 布局引擎决定不再执行任何操作,因此装饰器永远不会重新渲染。

    你能做些什么来修复它

    当然,解决方法是在 装饰者 通过呼叫 使Visual()失效 每当重新测量控制时,如下所示:

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

    这样做将使您的装饰一贯遵守WPF的所有规则,因此它将在所有情况下按预期工作。这也是最有效的解决方案,因为 使Visual()失效 除了那些真正需要的情况,什么都不做。

        2
  •  1
  •   Scott J    15 年前

    您需要在面板上调用调度器。向文本框sizeChanged事件添加处理程序:

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

    这基本上来自于这篇文章: http://geekswithblogs.net/NewThingsILearned/archive/2008/08/25/refresh--update-wpf-controls.aspx

    推荐文章