Skip to content

Rendering Engine

abbaye edited this page Mar 7, 2026 · 1 revision

Rendering Engine

The HexEditor uses a DrawingContext-based rendering pipeline that draws directly onto a Visual — no intermediate UI elements, no layout passes per byte.


Why Custom Rendering?

Approach 50 000 bytes Notes
Legacy (V1) — ItemsControl + TextBlock per byte ~3 000 ms WPF layout for every element
Modern (V2+) — DrawingContext on DrawingVisual ~30 ms Single pass, one visual

Result: 99% faster. The rendering path is the critical hot loop — it runs on every scroll tick, resize, and selection change.


Architecture

flowchart TD
    VM["HexEditorViewModel\nScrollPosition · Selection · BytePerLine"]
    BP["ByteProvider\nvirtual byte stream"]
    VP["HexViewport\nDrawingVisual host"]
    DC["DrawingContext\ndc.DrawText() · dc.DrawRectangle()"]
    HL["HighlightService\ncolor overlays"]
    CBG["CustomBackgroundService\nuser-defined color blocks"]
    Sel["SelectionService\nselection rectangle"]

    VM --> VP
    BP --> VP
    VP --> DC
    HL --> DC
    CBG --> DC
    Sel --> DC
Loading

HexViewport — Rendering Loop

HexViewport extends FrameworkElement and overrides OnRender. Only the visible region is drawn on each frame:

Visible lines = Math.Floor(ViewportHeight / LineHeight)
First byte    = ScrollPosition × BytePerLine
Last byte     = FirstByte + (VisibleLines × BytePerLine)

On scroll, only the changed delta is redrawn (partial invalidation).

Columns rendered per line

Each line draws three columns left to right:

[Offset col]  [Hex col]                        [ASCII col]
00000000      41 42 43 20 48 65 6C 6C 6F 00    ABC Hello.

Each column is a single DrawingContext pass with dc.DrawText() calls using pre-formatted FormattedText objects.


DrawingContext Usage

protected override void OnRender(DrawingContext dc)
{
    // Background
    dc.DrawRectangle(BackgroundBrush, null, new Rect(RenderSize));

    for (int line = firstLine; line < lastLine; line++)
    {
        double y = (line - firstLine) * LineHeight;

        // Selection highlight
        if (lineIsSelected)
            dc.DrawRectangle(SelectionBrush, null, new Rect(hexColX, y, hexColWidth, LineHeight));

        // Custom background blocks
        foreach (var block in customBackgrounds.GetForLine(line))
            dc.DrawRectangle(block.Brush, null, BlockRect(block, y));

        // Hex bytes
        dc.DrawText(formattedHexLine, new Point(hexColX, y));

        // ASCII
        dc.DrawText(formattedAsciiLine, new Point(asciiColX, y));

        // Offset
        dc.DrawText(formattedOffset, new Point(offsetColX, y));
    }

    // Caret
    dc.DrawRectangle(CaretBrush, null, CaretRect);
}

FormattedText Caching

FormattedText objects are reused across frames for unchanged lines:

Cache key = (lineNumber, firstByteOffset, selectionState, highlightState)

When a byte changes (edit, undo, selection move), only the affected lines are invalidated and their FormattedText rebuilt.


HiLo Byte Display (ByteGroupingService)

When BytePerGroup = 2 (16-bit display) or 4 (32-bit):

  • Bytes are grouped and the group rendered as a single FormattedText
  • ByteOrderService handles Hi-Lo / Lo-Hi byte order within the group

Scroll Synchronization

flowchart LR
    User["User scrolls\nScrollBar"]
    SB["VerticalScroll.Value"]
    VM["HexEditorViewModel\n.ScrollPosition"]
    VP["HexViewport\nInvalidateVisual()"]

    User --> SB
    SB -->|Scroll event| VM
    VM -->|PropertyChanged| VP
Loading

Keyboard navigation bypasses the scrollbar event. In that case, VerticalScroll.Value is also set programmatically to keep the scrollbar thumb in sync (the Scroll event fires only on user interaction).


Character Encoding Rendering

The ASCII column uses EncodingService to convert bytes to display characters:

  • Default: Encoding.ASCII — non-printable → .
  • TBL mode: TblService maps bytes to ROM character table glyphs
  • Unicode mode: Encoding.UTF8 with multi-byte grouping

The encoding is applied per-byte during the FormattedText build phase.


Performance Tips

Scenario Recommendation
Very large files (> 1 GB) Default — ByteProvider reads lazily, viewport only renders visible lines
Many custom background blocks CustomBackgroundService uses interval tree — O(log n) lookup per line
High-DPI displays HexViewport reads VisualTreeHelper.GetDpi() and adjusts pixel snapping
Slow themes Brushes are resolved once at startup and cached — no FindResource per frame

See Also

Navigation

Getting Started

IDE Documentation

HexEditor Control

Advanced

Development


v0.6.4.75 Highlights

  • whfmt.FileFormatCatalog v1.0.0 NuGet (cross-platform net8.0)
  • 690+ .whfmt definitions (schema v2.3)
  • Structure Editor — block DataGrid, drag-drop, validation, SmartComplete
  • WhfmtBrowser/Catalog panels — browse all embedded formats
  • AI Assistant (5 providers, 25 MCP tools)
  • Tab Groups, Document Structure, Lazy Plugin Loading
  • Window Menu + Win32 Fullscreen (F11)
  • Git Integration UI (changes, history, blame)
  • Shared Undo Engine (HexEditor ↔ CodeEditor)
  • Bracket pair colorization, sticky scroll, peek definition
  • Format detection hardening (thread-safe, crash guard)

Links

Clone this wiki locally