From wpf-dev-pack
Implements lightweight WPF rendering using DrawingVisual, ContainerVisual, VisualCollection, and DrawingContext for large-scale graphics, charts, game graphics, or custom visuals.
npx claudepluginhub christian289/dotnet-with-claudecode --plugin wpf-dev-packThis skill uses the workspace's default tool permissions.
DrawingVisual is a lightweight visual element faster than Shape, suitable for large-scale rendering.
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.
DrawingVisual is a lightweight visual element faster than Shape, suitable for large-scale rendering.
Visual (abstract)
├── UIElement
│ └── FrameworkElement
│ └── Shape (heavyweight, event support)
│
├── DrawingVisual ← Lightweight, no events, direct rendering
├── ContainerVisual ← Groups multiple Visuals
└── HostVisual ← Cross-thread Visual
| Aspect | DrawingVisual | Shape |
|---|---|---|
| Overhead | Low | High |
| Layout | Non-participating | Participating |
| Events | Manual implementation | Built-in support |
| Data binding | Not available | Available |
| Use case | Large elements, performance critical | Interactive UI |
namespace MyApp.Controls;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
/// <summary>
/// Control that hosts DrawingVisual
/// </summary>
public sealed class DrawingVisualHost : FrameworkElement
{
private readonly List<Visual> _visuals = [];
protected override int VisualChildrenCount => _visuals.Count;
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
/// <summary>
/// Add Visual
/// </summary>
public void AddVisual(Visual visual)
{
_visuals.Add(visual);
AddVisualChild(visual);
AddLogicalChild(visual);
}
/// <summary>
/// Remove Visual
/// </summary>
public void RemoveVisual(Visual visual)
{
_visuals.Remove(visual);
RemoveVisualChild(visual);
RemoveLogicalChild(visual);
}
/// <summary>
/// Remove all Visuals
/// </summary>
public void ClearVisuals()
{
foreach (var visual in _visuals)
{
RemoveVisualChild(visual);
RemoveLogicalChild(visual);
}
_visuals.Clear();
}
/// <summary>
/// Find Visual at coordinate (Hit Testing)
/// </summary>
public Visual? GetVisualAt(Point point)
{
var hitResult = VisualTreeHelper.HitTest(this, point);
return hitResult?.VisualHit;
}
}
namespace MyApp.Graphics;
using System.Windows;
using System.Windows.Media;
public static class DrawingVisualFactory
{
/// <summary>
/// Create circular DrawingVisual
/// </summary>
public static DrawingVisual CreateCircle(
Point center,
double radius,
Brush fill,
Pen? stroke = null)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawEllipse(fill, stroke, center, radius, radius);
}
return visual;
}
/// <summary>
/// Create rectangle DrawingVisual
/// </summary>
public static DrawingVisual CreateRectangle(
Rect rect,
Brush fill,
Pen? stroke = null)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(fill, stroke, rect);
}
return visual;
}
/// <summary>
/// Create text DrawingVisual
/// </summary>
public static DrawingVisual CreateText(
string text,
Point origin,
Brush foreground,
double fontSize = 12)
{
var visual = new DrawingVisual();
var formattedText = new FormattedText(
text,
System.Globalization.CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Segoe UI"),
fontSize,
foreground,
VisualTreeHelper.GetDpi(visual).PixelsPerDip);
using (var dc = visual.RenderOpen())
{
dc.DrawText(formattedText, origin);
}
return visual;
}
/// <summary>
/// Create image DrawingVisual
/// </summary>
public static DrawingVisual CreateImage(
ImageSource image,
Rect rect)
{
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(image, rect);
}
return visual;
}
}
namespace MyApp.Graphics;
using System.Windows;
using System.Windows.Media;
public sealed class VisualGroup
{
public ContainerVisual Container { get; } = new();
/// <summary>
/// Add child Visual
/// </summary>
public void Add(Visual visual)
{
Container.Children.Add(visual);
}
/// <summary>
/// Remove child Visual
/// </summary>
public void Remove(Visual visual)
{
Container.Children.Remove(visual);
}
/// <summary>
/// Move entire group
/// </summary>
public void SetOffset(double x, double y)
{
Container.Offset = new Vector(x, y);
}
/// <summary>
/// Transform entire group
/// </summary>
public void SetTransform(Transform transform)
{
Container.Transform = transform;
}
/// <summary>
/// Set opacity for entire group
/// </summary>
public void SetOpacity(double opacity)
{
Container.Opacity = opacity;
}
}
// Hierarchical structure example
//
// ContainerVisual (root)
// ├── ContainerVisual (layer 1 - background)
// │ ├── DrawingVisual (grid)
// │ └── DrawingVisual (background image)
// ├── ContainerVisual (layer 2 - content)
// │ ├── DrawingVisual (node 1)
// │ ├── DrawingVisual (node 2)
// │ └── DrawingVisual (connections)
// └── ContainerVisual (layer 3 - overlay)
// └── DrawingVisual (selection area)
public sealed class LayeredCanvas : FrameworkElement
{
private readonly ContainerVisual _rootVisual = new();
private readonly ContainerVisual _backgroundLayer = new();
private readonly ContainerVisual _contentLayer = new();
private readonly ContainerVisual _overlayLayer = new();
public LayeredCanvas()
{
_rootVisual.Children.Add(_backgroundLayer);
_rootVisual.Children.Add(_contentLayer);
_rootVisual.Children.Add(_overlayLayer);
AddVisualChild(_rootVisual);
}
protected override int VisualChildrenCount => 1;
protected override Visual GetVisualChild(int index) => _rootVisual;
public void AddToBackground(DrawingVisual visual)
{
_backgroundLayer.Children.Add(visual);
}
public void AddToContent(DrawingVisual visual)
{
_contentLayer.Children.Add(visual);
}
public void AddToOverlay(DrawingVisual visual)
{
_overlayLayer.Children.Add(visual);
}
}
namespace MyApp.Controls;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
public sealed class InteractiveDrawingHost : FrameworkElement
{
private readonly List<DrawingVisual> _visuals = [];
private DrawingVisual? _hoveredVisual;
private DrawingVisual? _selectedVisual;
public InteractiveDrawingHost()
{
MouseMove += OnMouseMove;
MouseLeftButtonDown += OnMouseLeftButtonDown;
}
// ... VisualChildrenCount, GetVisualChild implementation omitted ...
private void OnMouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var hitVisual = HitTestVisual(position);
if (hitVisual != _hoveredVisual)
{
// Hover state changed
_hoveredVisual = hitVisual;
Cursor = hitVisual is not null ? Cursors.Hand : Cursors.Arrow;
}
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var position = e.GetPosition(this);
_selectedVisual = HitTestVisual(position);
if (_selectedVisual is not null)
{
// Handle selected Visual
OnVisualSelected(_selectedVisual);
}
}
private DrawingVisual? HitTestVisual(Point point)
{
DrawingVisual? result = null;
VisualTreeHelper.HitTest(
this,
null,
hitResult =>
{
if (hitResult.VisualHit is DrawingVisual visual)
{
result = visual;
return HitTestResultBehavior.Stop;
}
return HitTestResultBehavior.Continue;
},
new PointHitTestParameters(point));
return result;
}
private void OnVisualSelected(DrawingVisual visual)
{
// Raise selection event
}
}
/// <summary>
/// Find all Visuals within specific area
/// </summary>
public List<DrawingVisual> HitTestArea(Rect area)
{
var results = new List<DrawingVisual>();
var geometry = new RectangleGeometry(area);
VisualTreeHelper.HitTest(
this,
null,
hitResult =>
{
if (hitResult.VisualHit is DrawingVisual visual)
{
results.Add(visual);
}
return HitTestResultBehavior.Continue;
},
new GeometryHitTestParameters(geometry));
return results;
}
For advanced patterns, see references/advanced-rendering.md: