From wpf-dev-pack
Implements WPF DrawingContext canvas for 10-50x faster rendering of thousands of shapes versus Shapes, bypassing layout and visual tree overhead.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
A pattern for achieving 10-50x performance improvement over Shape objects when rendering large numbers of shapes in WPF using DrawingContext.
Generates design tokens/docs from CSS/Tailwind/styled-components codebases, audits visual consistency across 10 dimensions, detects AI slop in UI.
Records polished WebM UI demo videos of web apps using Playwright with cursor overlay, natural pacing, and three-phase scripting. Activates for demo, walkthrough, screen recording, or tutorial requests.
Delivers idiomatic Kotlin patterns for null safety, immutability, sealed classes, coroutines, Flows, extensions, DSL builders, and Gradle DSL. Use when writing, reviewing, refactoring, or designing Kotlin code.
A pattern for achieving 10-50x performance improvement over Shape objects when rendering large numbers of shapes in WPF using DrawingContext.
| Item | Shape (Polygon, Rectangle, etc.) | DrawingContext |
|---|---|---|
| Inheritance | Canvas | FrameworkElement |
| Visual count | One per shape (n) | 1 |
| Layout calculation | O(n) Measure/Arrange | O(1) |
| Memory usage | Very high (WPF object overhead) | Very low (data only) |
| Performance | Baseline | 10-50x faster |
| Suitable for | Few interactive shapes (tens to hundreds) | Large static shapes (thousands to tens of thousands) |
namespace MyApp.Controls;
using System.Windows;
using System.Windows.Media;
public sealed class HighPerformanceCanvas : FrameworkElement
{
// 1. Struct for storing shape data (lightweight)
private readonly record struct ShapeData(
Point Position,
double Width,
double Height,
Brush Fill);
// 2. Only rendering data stored in memory
private readonly List<ShapeData> _shapes = [];
// 3. Optimized Pen (Freeze applied)
private readonly Pen _pen = new(Brushes.Black, 1);
public HighPerformanceCanvas()
{
// Freeze Pen for performance optimization
_pen.Freeze();
}
// 4. Shape addition method
public void AddShape(Point position, double width, double height, Color color)
{
var brush = new SolidColorBrush(color);
brush.Freeze(); // Freeze for performance optimization
_shapes.Add(new ShapeData(position, width, height, brush));
}
// 5. Trigger rendering (call once after data addition is complete)
public void Render()
{
InvalidateVisual();
}
// 6. Actual rendering - direct drawing in OnRender
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
foreach (var shape in _shapes)
{
dc.DrawRectangle(
shape.Fill,
_pen,
new Rect(shape.Position, new Size(shape.Width, shape.Height)));
}
}
// 7. Clear shapes
public void Clear()
{
_shapes.Clear();
InvalidateVisual();
}
}
Advanced: See ADVANCED.md for complex shapes (StreamGeometry triangles/polygons), async rendering with performance measurement, MVVM integration (delegate pattern), and Shape approach comparison.
// ✅ Pen optimization
private readonly Pen _pen = new(Brushes.Black, 1);
public MyControl()
{
_pen.Freeze(); // WPF can optimize internally
}
// ✅ Brush optimization
var brush = new SolidColorBrush(Color.FromRgb(255, 0, 0));
brush.Freeze(); // Can be shared in memory
// ✅ Geometry optimization
var geometry = new StreamGeometry();
// ... configure geometry ...
geometry.Freeze(); // Rendering pipeline optimization
// ✅ Value type (stack allocation) → Memory efficient
private readonly record struct ShapeData(
Point Position,
Size Size,
Brush Fill);
// Auto-generated Equals, GetHashCode
// Immutable semantics enforced
// ✅ StreamGeometry - Lightweight, write-only
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(startPoint, true, true);
ctx.LineTo(point2, true, false);
}
// ❌ PathGeometry - Relatively heavyweight
var geometry = new PathGeometry();
var figure = new PathFigure { StartPoint = startPoint };
figure.Segments.Add(new LineSegment(point2, true));
// ❌ Bad example: Calling InvalidateVisual() inside loop
for (int i = 0; i < count; i++)
{
_items.Add(data);
if (i % 10 == 0)
{
InvalidateVisual(); // OnRender iterates entire _items!
}
}
// Result: 10 + 20 + ... + n = O(n²)
// ✅ Good example: Render only once after data collection
for (int i = 0; i < count; i++)
{
_items.Add(data);
}
// Render only once at the end
InvalidateVisual();
Performance Difference: