可以在给定区域上使用InvalidateVisual()而不是整个WPF控件吗?

时间:2010-04-05 02:03:06

标签: wpf user-interface rendering

我有一个复杂的WPF控件,它在OnRender中绘制了很多基元(它有点像地图)。当它的一小部分发生变化时,我只想为受影响的元素重新发出渲染命令,而不是运行整个OnRender。虽然我对OnRender函数在调整大小或其他方面的性能很好,但对于基于鼠标悬停的基元突出显示来说还不够快。

目前,我知道如何强制进行屏幕更新的唯一方法是调用InvalidateVisual()。无法发送脏区域以使其无效。

WPF屏幕组成的最低粒度是UI元素吗?我是否需要将原始渲染渲染到中间目标中,然后使用InvalidateVisual()更新到屏幕?

3 个答案:

答案 0 :(得分:3)

如果要编写WPF自定义/复合控件,应尽量避免覆盖OnRender,尤其是在计划使其部分无效时。使用AddVisualChild +覆盖VisualChildrenCount +覆盖GetVisualChild +覆盖Measure&更容易像这样安排(带2个孩子的伪代码):

private void BuildMyControls()
{
  AddVisualChild(subControl1);
  AddVisualChild(subControl2);
}

protected override int VisualChildrenCount
{
  get
  {
    return 2;
  }
}

protected override Visual GetVisualChild(int index)
{
  if (index == 0) return subControl1;
  if (index == 1) return subControl2;
  return null; // should never be called in fact...
}

protected override Size MeasureCore(Size availableSize)
{
  base.Measure...
  BuildMyControls();
  .. measure them, probably call subControlX.Measure(...);
}

protected override void ArrangeCore(Rect finalRect)
{
  base.ArrangeCore(finalRect);
  ... arrange them, probably call subControlX.Arrange
}

使用这种代码,你可以使用subControlX.InvalidateXXX();

之类的东西使一部分无效。

答案 1 :(得分:0)

WPF不能正常工作,因此您无法使区域无效。但是,可以进行一些优化。有一个Measure,Arrange,然后是Render pass。如果控件移动但实际呈现的内容不会改变,那么您可以告诉WPF仅执行安排传递。您可以使用FrameworkPropertyMetadata和FrameworkPropertyMetadataOptions(http://msdn.microsoft.com/en-us/library/system.windows.frameworkpropertymetadataoptions.aspx)从依赖项属性值更改中触发这些失效。

答案 2 :(得分:0)

除非您的控件尺寸发生变化,否则您不应该使用InvalidateVisual(),因为它会导致相对昂贵的UI重新布局。

WPF是保留的绘图系统。这意味着OnRender()最好称为AccumulateDrawingObjects()。它实际上正在累积一个实时绘图对象树,每个布局只需要发生一次。然后,它会使用这些对象在需要时绘制UI。要在不重新布局的情况下更改UI的一部分外观,可以在OnRender()之后随时更新某些对象(如DrawingGroup,RenderTargetBitmap和WriteableBitmap)。

要稍后更新部分UI,请将这些命令包装在DrawingGroup中,并将对象放入DrawingContext。然后你可以Open()随时更新它,WPF会自动重新绘制UI的那一部分。

这就是它的样子:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}