Reputation: 14548
I have a complex WPF control that draws a lot of primitives in its OnRender (it's sort of like a map). When a small portion of it changes, I'd only like to re-issue render commands for the affected elements, instead of running the entire OnRender over. While I'm fine with my OnRender function's performance on a resize or whatever, it's not fast enough for mouse hover-based highlighting of primitives.
Currently the only way I know how to force a screen update is to call InvalidateVisual(). No way to send in a dirty rect region to invalidate.
Is the lowest granularity of WPF screen composition the UI element? Will I need to do my renders of primitives into an intermediate target and then have that use InvalidateVisual() to update to the screen?
Upvotes: 8
Views: 2962
Reputation: 2466
You shouldn't be using InvalidateVisual()
unless the size of your control changes, as it causes a fairly expensive re-layout of your UI.
WPF is a retained drawing system. That means OnRender()
might better be called AccumulateDrawingObjects()
. It's actually accumulating a tree of live drawing objects, which only needs to happen once per layout. It then uses these objects to draw your UI whenever it needs to. To change how a portion of your UI looks without re-layout, some objects (like DrawingGroup, RenderTargetBitmap, and WriteableBitmap) can be updated after OnRender()
, anytime you like.
To update a portion of your UI later, wrap those commands in a DrawingGroup
and put that object into the DrawingContext
. Then you can Open()
and update it whenever you want, and WPF will automatically repaint that portion of the UI.
This is what it looks like:
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();
}
Upvotes: 1
Reputation: 138960
When you want to write WPF custom/composite controls, you should try to avoid overriding OnRender as possible especially if you plan to invalidate portions of it. It's much easier to use AddVisualChild + override VisualChildrenCount + override GetVisualChild + override Measure & Arrange like this (pseudo code with 2 children):
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
}
With this kind of code, you can invalidate just one portion, with something like subControlX.InvalidateXXX();
Upvotes: 3
Reputation: 4963
WPF doesn't work quite like that, so you can't invalidate regions. However, there are some optimizations that can be made. There is a Measure, Arrange, and then Render pass. If a control moves but what actually renders doesn't change then you can tell WPF to do only the arrange pass. You can trigger these invalidations off of dependency property value changes with FrameworkPropertyMetadata and FrameworkPropertyMetadataOptions (http://msdn.microsoft.com/en-us/library/system.windows.frameworkpropertymetadataoptions.aspx).
Upvotes: 0