Back to list
christian289

rendering-with-drawingvisual

by christian289

ClaudeCode와 함께하는 .NET 개발 튜토리얼

1🍴 0📅 Jan 25, 2026

SKILL.md


name: rendering-with-drawingvisual description: Implements lightweight rendering using WPF DrawingVisual with ContainerVisual, VisualCollection, and DrawingContext. Use when rendering large-scale graphics, charts, game graphics, or custom visuals.

WPF DrawingVisual Patterns

DrawingVisual is a lightweight visual element faster than Shape, suitable for large-scale rendering.

1. Visual Hierarchy

Visual (abstract)
├── UIElement
│   └── FrameworkElement
│       └── Shape (heavyweight, event support)
│
├── DrawingVisual       ← Lightweight, no events, direct rendering
├── ContainerVisual     ← Groups multiple Visuals
└── HostVisual          ← Cross-thread Visual

2. DrawingVisual vs Shape

AspectDrawingVisualShape
OverheadLowHigh
LayoutNon-participatingParticipating
EventsManual implementationBuilt-in support
Data bindingNot availableAvailable
Use caseLarge elements, performance criticalInteractive UI

3. Basic DrawingVisual Host

3.1 FrameworkElement Host

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

3.2 Creating DrawingVisual

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

4. ContainerVisual (Grouping)

4.1 Using ContainerVisual

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

4.2 Hierarchical Visual Structure

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

5. Hit Testing

5.1 Basic Hit Testing

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

5.2 Geometry-based Hit Testing

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

6. Advanced Rendering

For advanced patterns, see references/advanced-rendering.md:

  • Large-scale Rendering (Scatter Plot): 10,000+ point rendering example
  • RenderTargetBitmap: Off-screen rendering and PNG export
  • Performance Optimization Tips: Freeze, StreamGeometry, DrawingGroup

7. References

Score

Total Score

65/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

+10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

1ヶ月以内に更新

+10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

+5

Reviews

💬

Reviews coming soon