Complete system architecture for the WpfHexEditor IDE — MVVM, services, plugin editors, project system, and docking.
- Introduction
- Design Principles
- System Architecture
- IDE Application Architecture
- Layered Architecture
- Core Components
- Rendering Engine
- Data Flow
- Key Innovations
- Performance Characteristics
- See Also
WpfHexEditor is a full binary analysis IDE built on top of the HexEditor WPF UserControl. The architecture is split into independent, reusable projects with clear dependency boundaries.
- ✅ Comprehensive ByteProvider API (186+ methods)
- ⚡ 99% faster rendering via custom DrawingContext (vs legacy ItemsControl)
- 🏗️ Clean MVVM Architecture — 16 specialized services
- 🔄 Comprehensive Undo/Redo with granular control
- 🖥️ VS-style docking — float, dock, auto-hide, colored tabs, 18 themes
- 📁 Project system —
.whsln/.whproj, virtual & physical folders, item links; VS.sln/.csprojopen + MSBuild build with incremental dirty tracking - 🔌 Plugin editors —
IDocumentEditorcontract (Hex, CodeEditor, TBL, Text, ImageViewer, DiffViewer, XAML Designer, Markdown, Script, Structure, Entropy, Disassembly, Assembly Explorer, Diff…) - 🗂️ IDE panels — ParsedFields, DataInspector, SolutionExplorer, Properties, ErrorPanel, PluginMonitoring, SearchPanel, FindReferencesPanel, UnitTestingPanel, DiagnosticToolsPanel
- 🔍 400+ File Format Auto-Detection with binary templates and signature matching
- 📊 Data Inspector with 40+ data type interpretations
- 🔌 Plugin System — SDK contracts,
WpfPluginHost, watchdog, sandbox (out-of-process IPC), plugin signing - 💻 Terminal Panel —
TerminalMode,HxScriptEngine, 31+ commands, 6 export formats - 🖼️ ImageViewer — zoom/pan, transform pipeline, crop with handles,
IImageTransform - 🐛 Debugger UI — debug menu + toolbar pod, execution line highlight, breakpoint info popup, gutter hover ghost, non-executable line validation, debug status bar
- 📦 Workspace System —
.whidewsZIP+JSON format; save/restore layout, solution, open files, theme - ⚡ Command Palette —
Ctrl+Shift+P; 9 modes; frequency boost; content grep;Ctrl+Pquick file open - 🔬 LSP Engine — full JSON-RPC client; 10 providers; F# (
fsautocomplete) + VB.NET (OmniSharp) servers - 📋 Sticky Scroll — scope headers pinned at top with line numbers; allocation-free rendering
- 🔗 Find All References —
Shift+F12; solution-wide reference counting; dockable panel;F8navigation - 🗂️ Sample.Docking — standalone WPF docking sample (2 themes, layout persistence)
Each layer has a single, well-defined responsibility:
- View - UI rendering and user interaction
- ViewModel - Business logic and state management
- Services - Specialized functionality (search, clipboard, etc.)
- ByteProvider - Data access coordination
- Core - Low-level byte operations
Users see a virtual representation with all edits applied, while the original file remains unchanged until Save:
Original File: [41 42 43 44 45]
Insert FF at position 2
Virtual View: [41 42 FF 43 44 45] ← User sees this
Physical File: [41 42 43 44 45] ← File unchanged
All changes are tracked in three separate collections:
- Modifications - Byte value changes (file length unchanged)
- Insertions - Added bytes (increases file length)
- Deletions - Removed bytes (decreases file length)
Bidirectional conversion between virtual and physical positions:
- Virtual Position - What the user sees (includes insertions)
- Physical Position - Actual byte offset in file
- Custom DrawingContext rendering (99% faster than ItemsControl)
- Lazy loading with caching
- Only render visible bytes
- Frozen brushes and cached FormattedText
The codebase is split into independent projects with clear dependency boundaries:
graph LR
subgraph "IDE Application"
App["WpfHexEditor.App<br/>(IDE, MainWindow,<br/>DockHost)"]
ProjectSystem["WpfHexEditor.ProjectSystem<br/>(Solution/Project,<br/>File templates)"]
end
subgraph "Core & Data"
Core["WpfHexEditor.Core<br/>(ByteProvider, Services,<br/>Data Layer)"]
EditorCore["WpfHexEditor.Editor.Core<br/>(IDocumentEditor,<br/>IEditorFactory, Interfaces)"]
end
subgraph "HexEditor Control"
HexEditor["WpfHexEditor.HexEditor<br/>(HexEditor Control,<br/>ViewModel, HexViewport)"]
end
subgraph "Editors (Plugin)"
CodeEditor["WpfHexEditor.Editor.CodeEditor"]
TblEditor["WpfHexEditor.Editor.TblEditor"]
TextEditor["WpfHexEditor.Editor.TextEditor"]
ImageViewer["WpfHexEditor.Editor.ImageViewer"]
DiffViewer["WpfHexEditor.Editor.DiffViewer"]
end
subgraph "Plugin System"
SDK["WpfHexEditor.SDK<br/>(IWpfHexEditorPlugin,<br/>11 service contracts)"]
PluginHost["WpfHexEditor.PluginHost<br/>(Discovery, Load/Unload,<br/>Watchdog, PluginManagerControl)"]
Terminal["WpfHexEditor.Terminal<br/>(TerminalPanel, TerminalMode,<br/>TerminalExportService)"]
CoreTerm["WpfHexEditor.Core.Terminal<br/>(31+ commands, HxScriptEngine,<br/>CommandHistory)"]
end
subgraph "Panels"
PanelsIDE["WpfHexEditor.Panels.IDE<br/>(SolutionExplorer, Properties,<br/>ErrorPanel, PluginMonitoring)"]
PanelsBinary["WpfHexEditor.Panels.BinaryAnalysis<br/>(ParsedFields, DataInspector,<br/>StructureOverlay, FileStatistics,<br/>PatternAnalysis, EnrichedFormatInfo)"]
PanelsFileOps["WpfHexEditor.Panels.FileOps<br/>(FileDiff,<br/>FileComparison)"]
end
subgraph "Standalone Controls"
BinaryAnalysis["WpfHexEditor.BinaryAnalysis"]
ColorPicker["WpfHexEditor.ColorPicker"]
HexBox["WpfHexEditor.HexBox"]
DockingCore["WpfHexEditor.Docking.Core"]
DockingWpf["WpfHexEditor.Docking.Wpf"]
end
App --> HexEditor
App --> PanelsIDE
App --> PanelsBinary
App --> PanelsFileOps
App --> ProjectSystem
App --> DockingWpf
App --> SDK
App --> CodeEditor
App --> TblEditor
App --> TextEditor
App --> ImageViewer
App --> DiffViewer
SDK --> PluginHost
PluginHost --> Terminal --> CoreTerm
HexEditor --> Core
HexEditor --> EditorCore
HexEditor --> ColorPicker
HexEditor --> HexBox
PanelsIDE --> EditorCore
PanelsBinary --> Core
PanelsBinary --> HexEditor
PanelsFileOps --> Core
PanelsFileOps --> HexEditor
CodeEditor --> Core
CodeEditor --> EditorCore
TblEditor --> Core
TblEditor --> EditorCore
TextEditor --> Core
TextEditor --> EditorCore
BinaryAnalysis --> Core
DockingWpf --> DockingCore
ProjectSystem --> EditorCore
style Core fill:#ffccbc,stroke:#d84315,stroke-width:3px
style HexEditor fill:#fff9c4,stroke:#f57c00,stroke-width:3px
style EditorCore fill:#e0f2f1,stroke:#00796b,stroke-width:2px
style App fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
graph TB
subgraph "🎨 Presentation Layer (WpfHexEditor.HexEditor)"
HexEditor["HexEditor<br/>(UserControl)"]
HexViewport["HexViewport<br/>(Custom Rendering)"]
end
subgraph "🧠 Business Logic (WpfHexEditor.HexEditor)"
ViewModel["HexEditorViewModel<br/>(MVVM Pattern)"]
end
subgraph "🔧 Service Layer (WpfHexEditor.Core)"
SearchService["SearchService<br/>(Find/Replace)"]
ClipboardService["ClipboardService<br/>(Copy/Paste)"]
BookmarkService["BookmarkService<br/>(Navigation)"]
HighlightService["HighlightService<br/>(Visual Markers)"]
SelectionService["SelectionService<br/>(Text Selection)"]
FormatDetectionService["FormatDetectionService<br/>(400+ Formats)"]
Others["+ 9 More Services"]
end
subgraph "📊 Analysis (WpfHexEditor.BinaryAnalysis)"
DataInspectorService["DataInspectorService<br/>(Type Interpretation)"]
end
subgraph "💾 Data Access (WpfHexEditor.Core)"
ByteProvider["ByteProvider<br/>(Coordinator & API)"]
end
subgraph "⚙️ Core Processing (WpfHexEditor.Core)"
ByteReader["ByteReader<br/>(Virtual View)"]
EditsManager["EditsManager<br/>(Track Changes)"]
PositionMapper["PositionMapper<br/>(Virtual↔Physical)"]
UndoRedoManager["UndoRedoManager<br/>(History)"]
FileProvider["FileProvider<br/>(File I/O)"]
end
subgraph "💿 Storage Layer"
FileSystem["File System<br/>(Disk/Memory/Stream)"]
end
HexEditor --> HexViewport
HexEditor --> ViewModel
ViewModel --> SearchService
ViewModel --> ClipboardService
ViewModel --> BookmarkService
ViewModel --> HighlightService
ViewModel --> SelectionService
ViewModel --> FormatDetectionService
ViewModel --> DataInspectorService
ViewModel --> Others
ViewModel --> ByteProvider
SearchService --> ByteProvider
ClipboardService --> ByteProvider
FormatDetectionService --> ByteProvider
DataInspectorService --> ByteProvider
ByteProvider --> ByteReader
ByteProvider --> EditsManager
ByteProvider --> PositionMapper
ByteProvider --> UndoRedoManager
ByteProvider --> FileProvider
ByteReader --> EditsManager
ByteReader --> PositionMapper
ByteReader --> FileProvider
EditsManager --> PositionMapper
FileProvider --> FileSystem
style HexEditor fill:#fff9c4,stroke:#f57c00,stroke-width:3px
style ViewModel fill:#e1f5ff,stroke:#0277bd,stroke-width:3px
style ByteProvider fill:#ffccbc,stroke:#d84315,stroke-width:3px
style ByteReader fill:#c8e6c9,stroke:#388e3c,stroke-width:2px
style FileSystem fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
MainWindow orchestrates the entire IDE. Key responsibilities:
| Concern | Implementation |
|---|---|
| Docking | DockHost (WpfHexEditor.Docking.Wpf) — all panels and editors docked here |
| Active document | ActiveDocumentEditor : IDocumentEditor? INPC — drives Edit menu |
| Active hex editor | ActiveHexEditor : HexEditorControl? INPC — drives status bar DataContext |
| Content factory | Dictionary<string, UIElement> _contentCache keyed by ContentId |
| Project system | SolutionManager.Instance singleton |
| Panel singletons | _parsedFieldsPanel, _errorPanel, _solutionExplorer, _propertiesPanel |
Every editor implements IDocumentEditor from WpfHexEditor.Editor.Core:
IDocumentEditor
├── Title, IsDirty, IsReadOnly
├── UndoCommand, RedoCommand, CopyCommand, CutCommand, PasteCommand
├── SaveAsync(), CloseAsync()
├── StatusMessage event
├── ModifiedChanged, TitleChanged events
└── (optional) IDiagnosticSource — ErrorPanel integration
IEditorPersistable — state save/restore
IPropertyProviderSource — Properties panel (F4)
ISearchTarget — Find/Replace integration
IChangesetEditor — Changeset replay (DiffViewer)
Registration at startup:
EditorRegistry.Instance.Register(new TblEditorFactory());
EditorRegistry.Instance.Register(new CodeEditorFactory());
EditorRegistry.Instance.Register(new ImageViewerFactory());
// etc.Format-aware editor selection — EditorRegistry.FindFactory(filePath, preferredId?) consults EmbeddedFormatCatalog for PreferredEditor first, then falls back to the first registered factory that matches the extension.
Every docked item has a deterministic ContentId:
| ContentId | Content |
|---|---|
doc-welcome |
WelcomePanel |
doc-proj-{itemId} |
Project item (default editor) |
doc-proj-{factoryId}-{itemId} |
Project item (specific editor) |
doc-file-{path} |
Standalone file |
panel-terminal |
TerminalPanel |
plugin-manager |
PluginManagerControl |
panel-properties |
PropertiesPanel |
panel-solution-explorer |
SolutionExplorerPanel |
WpfHexEditor.SDK contains only public contracts — no UI dependencies:
IWpfHexEditorPlugin— plugin entry pointIIDEHostContext— gateway to all IDE services from a plugin- 11 service contracts:
IHexEditorService,ITerminalService,ISolutionExplorerService,IOutputService,IErrorPanelService,IParsedFieldService,IFocusContextService,IThemeService,IPermissionService,IMarketplaceService,ICodeEditorService
WpfHexEditor.PluginHost is the runtime:
WpfPluginHost— discovery, load/unload,AssemblyLoadContext(collectible)PluginWatchdog— wraps async plugin calls with timeoutPluginCrashHandler,PluginEventBus,PluginSchedulerPluginManagerControl— WPF management UI (list, enable/disable, details)PluginMonitoringPanel— real-time CPU%/memory charting per plugin
WpfHexEditor.PluginSandbox — out-of-process isolation via IPC named pipes for untrusted plugins.
MainWindow.PluginSystem.cs owns InitializePluginSystemAsync() — bootstraps host, wires IDEHostContext services, defers DataContext to panels via Dispatcher.InvokeAsync.
SolutionManager (singleton)
└── ISolution (.whsln)
└── IProject (.whproj)
├── IProjectItem (file)
├── IVirtualFolder (logical grouping)
└── IVirtualFolder → PhysicalRelativePath (mirrors disk dir)
IEditorPersistable (per file):
GetBookmarks() / ApplyBookmarks()
GetConfig() / ApplyConfig() ← scroll, caret, encoding, edit mode
Format versioning: MigrationPipeline (current v=2) migrates .whsln/.whproj in-memory on load. UpgradeFormatAsync() writes all files atomically with .v{N}.bak backups.
graph LR
subgraph "Layer 1: UI"
UI["HexEditor<br/>HexViewport"]
end
subgraph "Layer 2: Presentation"
VM["HexEditorViewModel<br/>State Management<br/>INotifyPropertyChanged"]
end
subgraph "Layer 3: Services"
Services["16 Services<br/>Search, Clipboard,<br/>FormatDetection, DataInspector,<br/>Bookmark, etc."]
end
subgraph "Layer 4: Data Access"
Provider["ByteProvider<br/>Coordinator<br/>Public API"]
end
subgraph "Layer 5: Core"
Core["ByteReader<br/>EditsManager<br/>PositionMapper<br/>UndoRedoManager<br/>FileProvider"]
end
subgraph "Layer 6: Storage"
Storage["File System<br/>Memory<br/>Stream"]
end
UI --> VM
VM --> Services
VM --> Provider
Services --> Provider
Provider --> Core
Core --> Storage
style UI fill:#fff9c4
style VM fill:#e1f5ff
style Services fill:#c8e6c9
style Provider fill:#ffccbc
style Core fill:#f8bbd0
style Storage fill:#f3e5f5
| Layer | Project(s) | Components | Responsibility |
|---|---|---|---|
| 0. IDE Shell | WpfHexEditor.App |
MainWindow, DockHost, SolutionManager | Application host, docking, project system |
| 1. UI | WpfHexEditor.HexEditor |
HexEditor, HexViewport | Hex control — user interaction, rendering |
| 1. UI | WpfHexEditor.Panels.BinaryAnalysis |
ParsedFieldsPanel, DataInspector, StructureOverlay | Binary analysis panels |
| 1. UI | WpfHexEditor.Panels.IDE |
SolutionExplorer, Properties, ErrorPanel | IDE panels |
| 1. UI | WpfHexEditor.Panels.FileOps |
FileDiff, FileComparison | File operation panels |
| 1. UI | WpfHexEditor.Editor.* |
TblEditor, CodeEditor, TextEditor, ImageViewer, DiffViewer | Plugin editors |
| 1. UI | WpfHexEditor.Terminal |
TerminalPanel | Integrated terminal with script engine |
| 1. UI | WpfHexEditor.PluginHost |
PluginManagerControl, PluginMonitoringPanel | Plugin management UI |
| 2. Presentation | WpfHexEditor.HexEditor |
HexEditorViewModel | MVVM — business logic, state management |
| 3. Services | WpfHexEditor.Core |
16 specialized services | Search, format detection, clipboard, etc. |
| 3. Services | WpfHexEditor.BinaryAnalysis |
DataInspectorService | Multi-type interpretation |
| 4. Data Access | WpfHexEditor.Core |
ByteProvider | API coordination, caching |
| 5. Core | WpfHexEditor.Core |
ByteReader, EditsManager, PositionMapper, UndoRedoManager | Low-level byte operations |
| 6. Storage | WpfHexEditor.Core |
FileProvider, FileSystem | File I/O, persistence |
Location: HexEditor.xaml.cs
Purpose: Public API surface with partial classes organized by functionality.
Structure:
graph TD
Root["📦 HexEditor.cs<br/>(Main Control)"]
Core["⚙️ Core/<br/>File, Stream, Byte, Edit<br/>Batch, Diagnostics, Async"]
Features["✨ Features/<br/>Bookmarks, Highlights<br/>FileComparison, TBL"]
Search["🔍 Search/<br/>Find, Replace, Count"]
UI["🎨 UI/<br/>Events, Clipboard<br/>Zoom, UIHelpers"]
Compat["🔄 Compatibility/<br/>Legacy V1 API wrappers"]
Root --> Core
Root --> Features
Root --> Search
Root --> UI
Root --> Compat
style Root fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style Core fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Features fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style Search fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
style UI fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style Compat fill:#e0f2f1,stroke:#00796b,stroke-width:2px
Key Methods:
// File Operations
public void Open(string fileName)
public void Save()
public void Close()
// Byte Operations
public byte GetByte(long position)
public void ModifyByte(byte value, long position)
public void InsertByte(byte value, long position)
public void DeleteBytes(long position, long count)
// Search Operations
public long FindFirst(byte[] pattern, long startPosition = 0)
public List<long> FindAll(byte[] pattern)
public int CountOccurrences(byte[] pattern)
// Edit Operations
public void Undo()
public void Redo()
public void ClearModifications()
public void ClearInsertions()
public void ClearDeletions()Location: HexEditorViewModel.cs
Purpose: MVVM pattern business logic layer, coordinates services and ByteProvider.
Responsibilities:
- Manages ByteProvider lifecycle
- Coordinates 15 specialized services
- Handles virtual position calculations for UI
- Implements INotifyPropertyChanged for data binding
- Manages selection and cursor state
- Generates HexLines for rendering
Key Properties:
public ByteProvider Provider { get; }
public SelectionService SelectionService { get; }
public SearchService SearchService { get; }
public ClipboardService ClipboardService { get; }
// ... 12 more services
public long Position { get; set; }
public long SelectionStart { get; set; }
public long SelectionLength { get; set; }
public bool IsModified { get; }Location: ByteProvider.cs
Purpose: Coordinates core components and provides 186-method public API.
Architecture:
public class ByteProvider
{
private readonly ByteReader _reader; // Virtual view reader
private readonly EditsManager _edits; // Track all changes
private readonly PositionMapper _mapper; // Virtual↔Physical
private readonly UndoRedoManager _undo; // History stack
private readonly FileProvider _file; // File I/O
// 186 public methods for complete API
public byte ReadByte(long virtualPosition) => _reader.ReadByte(virtualPosition);
public void ModifyByte(long position, byte value) { /* ... */ }
public void InsertByte(long position, byte value) { /* ... */ }
public void DeleteBytes(long position, long count) { /* ... */ }
// ... 182 more methods
}Key Features:
- ✅ 100% API Compatibility with ByteProvider specification
- 🔄 Edit Tracking - Modifications, insertions, deletions
- 🎯 Virtual View - Users see final result before save
- 🔙 Undo/Redo - Unlimited history with granular control
- 💾 Smart Save - Fast path for modifications-only edits
Location: ByteReader.cs
Purpose: Reads bytes from the virtual view (showing all edits applied).
Algorithm:
public byte ReadByte(long virtualPosition)
{
// 1. Check if position is an inserted byte
if (_edits.IsInsertion(virtualPosition, out byte insertedValue))
return insertedValue;
// 2. Convert virtual → physical position
long physicalPos = _mapper.VirtualToPhysical(virtualPosition);
// 3. Check if deleted
if (_edits.IsDeleted(physicalPos))
throw new InvalidOperationException("Position deleted");
// 4. Check if modified
if (_edits.IsModified(physicalPos, out byte modifiedValue))
return modifiedValue;
// 5. Return original byte from file
return _file.ReadByte(physicalPos);
}Location: EditsManager.cs
Purpose: Tracks all modifications, insertions, and deletions.
Data Structures:
public class EditsManager
{
// Modifications: physical position → new byte value
private Dictionary<long, byte> _modifications;
// Insertions: virtual position → list of inserted bytes (LIFO)
private Dictionary<long, List<byte>> _insertions;
// Deletions: physical position → deleted byte count
private Dictionary<long, long> _deletions;
// Statistics
public int ModificationCount => _modifications.Count;
public int InsertionCount => _insertions.Values.Sum(list => list.Count);
public int DeletionCount => (int)_deletions.Values.Sum();
}LIFO Insertion Semantics:
Insert 'A' at position 5: [... A ...]
Insert 'B' at position 5: [... B A ...] // B pushed before A (LIFO)
Insert 'C' at position 5: [... C B A ...] // C pushed before B
Location: PositionMapper.cs
Purpose: Bidirectional mapping between virtual and physical positions.
Mapping Logic:
public class PositionMapper
{
// Segments track ranges where insertions/deletions occurred
private List<MapSegment> _segments;
public long VirtualToPhysical(long virtualPos)
{
// Find segment containing virtualPos
// Subtract insertion offsets
// Add deletion offsets
// Return physical position
}
public long PhysicalToVirtual(long physicalPos)
{
// Find segment containing physicalPos
// Add insertion offsets
// Subtract deletion offsets
// Return virtual position
}
}Example:
Original file: 10 bytes (positions 0-9)
Insert 3 bytes at position 5
Virtual positions: 0 1 2 3 4 5 6 7 | 8 9 10 11 12
↑ Insertions
Physical positions: 0 1 2 3 4 | 5 6 7 8 9
VirtualToPhysical(8) = 5 // Skip 3 inserted bytes
PhysicalToVirtual(5) = 8 // Account for 3 insertions
Location: HexViewport.cs
Purpose: High-performance custom rendering using DrawingContext.
Performance Optimization:
protected override void OnRender(DrawingContext dc)
{
// Cache visible lines only
var visibleLines = GetVisibleLines();
foreach (var line in visibleLines)
{
// Draw offset column
dc.DrawText(offsetText, offsetPosition);
// Draw hex bytes with highlights
foreach (var byteInfo in line.Bytes)
{
if (byteInfo.IsSelected)
dc.DrawRectangle(selectionBrush, null, rect);
dc.DrawText(hexText, hexPosition);
}
// Draw ASCII column
dc.DrawText(asciiText, asciiPosition);
}
}V1 vs V2 Rendering:
| Approach | V1 (ItemsControl) | V2 (DrawingContext) | Speedup |
|---|---|---|---|
| Initial load (1000 lines) | ~450ms | ~5ms | 99% faster |
| Scroll update | ~120ms | ~2ms | 98% faster |
| Selection change | ~80ms | ~1ms | 99% faster |
| Memory (10MB file) | ~950MB | ~85MB | 91% less |
Location: HexViewport.cs — 3200+ lines
HexViewport is a FrameworkElement that bypasses WPF's template/binding/virtualization overhead entirely. It draws hex content directly via DrawingContext (WPF's retained-mode drawing API), achieving 99% faster rendering than the V1 ItemsControl approach.
graph TB
subgraph "HexViewport (FrameworkElement)"
direction TB
subgraph "Data Layer"
HexLines["HexLine[]<br/>(from ViewModel)"]
ByteData["ByteData<br/>VirtualPos, Value, Action,<br/>HexRect, AsciiRect"]
end
subgraph "Rendering Pipeline (OnRender)"
direction TB
BG["1. DrawRectangle<br/>(background)"]
Pop["2. PopulateByteRects<br/>(geometry pre-pass)"]
CBG["3. DrawCustomBackgroundBlocks<br/>(format structure overlay)"]
TBL["4. DrawTblBackgrounds<br/>(TBL character types)"]
HL["5. DrawHighlights<br/>(selection, search,<br/>auto-highlight, hover)"]
Lines["6. DrawLineContent × N<br/>(offset + hex + ASCII)"]
OV["7. UpdateOverlays<br/>(cursor blink + hover preview)"]
end
subgraph "Cache System"
CW["CellWidth Cache<br/>(byteCount, fontSize,<br/>fontFamily, visualType)"]
OW["OffsetWidth Cache<br/>(fontSize, fontFamily,<br/>visualType)"]
HT["HexText Cache<br/>Dictionary<(text,alt),<br/>FormattedText>"]
AT["AsciiText Cache<br/>Dictionary<string,<br/>FormattedText>"]
end
subgraph "Visual Layers"
Main["Main DrawingGroup<br/>(OnRender — full repaint)"]
CursorOV["Cursor Overlay<br/>(DrawingVisual #0)<br/>blink timer, caret/cell"]
HoverOV["Hover Overlay<br/>(DrawingVisual #1)<br/>mouse preview"]
end
end
HexLines --> Pop
ByteData --> Pop
BG --> Pop --> CBG --> TBL --> HL --> Lines --> OV
Pop --> CW
Lines --> HT
Lines --> AT
Lines --> OW
Lines --> Main
OV --> CursorOV
OV --> HoverOV
style BG fill:#f3e5f5,stroke:#7b1fa2
style Pop fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style HL fill:#e8f5e9,stroke:#388e3c
style Lines fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style OV fill:#fce4ec,stroke:#c2185b
style CW fill:#fff9c4,stroke:#f57c00
style HT fill:#fff9c4,stroke:#f57c00
style AT fill:#fff9c4,stroke:#f57c00
style OW fill:#fff9c4,stroke:#f57c00
style Main fill:#e0f2f1,stroke:#00796b,stroke-width:2px
style CursorOV fill:#fce4ec,stroke:#c2185b,stroke-width:2px
style HoverOV fill:#fce4ec,stroke:#c2185b,stroke-width:2px
The OnRender method executes 7 sequential passes on each frame:
| Pass | Method | Purpose |
|---|---|---|
| 1 | DrawRectangle |
Fill background |
| 2 | PopulateByteRects() |
Pre-compute HexRect / AsciiRect on each ByteData (no drawing) |
| 3 | DrawCustomBackgroundBlocks() |
Render format structure overlay (parsed fields colored regions) |
| 4 | DrawTblBackgrounds() |
Render TBL character type backgrounds (EndBlock, EndLine, Japonais) |
| 5 | DrawHighlights() |
Render selection, search results, auto-highlight, hover highlight |
| 6 | DrawLineContent() × N |
For each visible line: DrawOffset → DrawHexByte × B → DrawAsciiByte × B |
| 7 | UpdateCursorOverlay() + UpdateHoverOverlay() |
Refresh the two independent DrawingVisual layers |
Each line is rendered left-to-right as four columns:
┌──────────┬──────────────────────────────────────────────┬───┬────────────────┐
│ Offset │ Hex Bytes │ │ │ ASCII Bytes │
│ 00000000 │ 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 0D 0A │ │ │ Hello World!.. │
│ 0000000E │ 54 68 69 73 20 69 73 20 61 20 74 65 73 74 │ │ │ This is a test │
└──────────┴──────────────────────────────────────────────┴───┴────────────────┘
↑ byte grouping (configurable: 4/8/16) ↑ separator bar
- Offset — Line address (hex), fixed font size 13, togglable via
ShowOffset - Hex Bytes — Each byte drawn individually via
DrawHexByte, colored byByteAction(modified=red, inserted=green, deleted=strikethrough) - Separator — Vertical line between hex and ASCII columns
- ASCII — Character representation via
DrawAsciiByte, with TBL multi-byte greedy matching (8→2→1 bytes)
Four dictionaries avoid redundant FormattedText creation (the most expensive WPF text operation):
| Cache | Key | Value | Invalidation |
|---|---|---|---|
_cellWidthCache |
(byteCount, fontSize, fontFamily, visualType) |
double (pixel width) |
Font/DPI change |
_offsetWidthCache |
(fontSize, fontFamily, visualType) |
double (pixel width) |
Font/DPI change |
_hexTextCache |
(text, isAlternate) |
FormattedText |
Font, DPI, or VisualType change |
_asciiTextCache |
string (display char) |
FormattedText |
DPI change |
All brushes are frozen (Brush.Freeze()) to enable cross-thread access and reduce GC pressure.
A [Flags] enum tracks what changed to enable future partial-render optimization:
[Flags]
internal enum DirtyReason
{
None = 0,
LinesChanged = 1, // Scroll, data reload → ALL lines dirty
SelectionChanged = 2, // Selection range changed → affected lines
CursorMoved = 4, // Cursor position changed → old + new line
ByteModified = 8, // Data changed → affected line
EditingChanged = 16, // Editing state changed → affected line
HighlightsChanged= 32, // Auto-highlight or search results → all lines
FullInvalidate = 64, // Font, DPI, colors, TBL, layout → ALL lines
}Note: Because
FrameworkElement.OnRenderreplaces the entire DrawingGroup on each call, partial line skipping is not possible today. The dirty tracking is retained for diagnostics (LastRenderReason) and as groundwork for a future DrawingVisual-per-line architecture.
HexViewport declares VisualChildrenCount = 2 — two independent DrawingVisual layers that repaint without triggering a full OnRender:
graph LR
subgraph "HexViewport Visual Tree"
Main["Main Content<br/>(OnRender DrawingGroup)<br/>background, highlights,<br/>offset, hex, ASCII"]
Cursor["DrawingVisual #0<br/>Cursor Overlay<br/>blink timer (500ms)<br/>caret line or cell fill"]
Hover["DrawingVisual #1<br/>Hover Overlay<br/>mouse position preview<br/>hex + ASCII highlight"]
end
Main -.-> Cursor
Main -.-> Hover
style Main fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style Cursor fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Hover fill:#fce4ec,stroke:#c2185b,stroke-width:2px
| Layer | Visual | Update Trigger | Cost |
|---|---|---|---|
| Main content | OnRender DrawingGroup |
Scroll, selection, data change | Full repaint (~2-5ms) |
| Cursor overlay | DrawingVisual #0 |
500ms blink timer | ~0.1ms (single rect) |
| Hover overlay | DrawingVisual #1 |
MouseMove |
~0.1ms (single rect) |
This separation means cursor blinking and mouse hover never trigger a full re-render — they only repaint their own lightweight DrawingVisual.
classDiagram
class HexLine {
+long LineNumber
+VirtualPosition StartPosition
+List~ByteData~ Bytes
+string OffsetLabel
}
class ByteData {
+VirtualPosition VirtualPos
+PhysicalPosition? PhysicalPos
+byte Value
+byte[] Values
+ByteSizeType ByteSize
+ByteOrderType ByteOrder
+ByteAction Action
+bool IsSelected
+bool IsHighlighted
+bool IsCursor
+Rect? HexRect
+Rect? AsciiRect
}
HexLine "1" --> "*" ByteData : Bytes
class DirtyReason {
<<enumeration>>
None
LinesChanged
SelectionChanged
CursorMoved
ByteModified
EditingChanged
HighlightsChanged
FullInvalidate
}
HexLineis produced by HexEditorViewModel and contains a row ofByteDataByteData.HexRect/AsciiRectare populated byPopulateByteRects()(pass 2) and consumed byDrawHighlights()(pass 5) for precise hit-testing and highlight drawingByteActiondrives per-byte colorization:Nothing(default),Modified(red),Added(green),Deleted(strikethrough)
Location: FormatDetectionService.cs
Purpose: Automatic detection of 400+ file formats using binary signatures and templates.
Key Features:
- 🔍 Signature Matching - Identifies file type from magic bytes
- 📋 Binary Templates - Parses structured file formats (PNG, ZIP, ELF, PE, etc.)
- 🎯 Field Parsing - Extracts fields with proper types (uint8, uint16, string, etc.)
- ✅ Checksum Validation - Automatic validation with visual error display
- 📊 Structure Overlay - Visual representation of file structure in hex view
Example Detection:
// PNG format detection
var formatInfo = formatDetectionService.DetectFormat(byteProvider);
// Returns: { FormatName: "PNG Image", Extension: "png", Confidence: 100% }
// Parse PNG header fields
var fields = formatDetectionService.ParseFields(byteProvider);
// Returns: [
// { Name: "signature", Offset: 0, Length: 8, Type: "bytes", Value: [137, 80, 78, 71...] },
// { Name: "width", Offset: 16, Length: 4, Type: "uint32", Value: 1920 },
// { Name: "height", Offset: 20, Length: 4, Type: "uint32", Value: 1080 }
// ]Supported Format Categories:
- 🖼️ Images: PNG, JPEG, GIF, BMP, TIFF, WebP, ICO
- 📦 Archives: ZIP, RAR, 7Z, TAR, GZ
- 🎵 Audio: MP3, WAV, FLAC, OGG, AAC
- 🎬 Video: MP4, AVI, MKV, WebM, MOV
- 📄 Documents: PDF, DOCX, XLSX, PPTX
- 💾 Executables: EXE, DLL, ELF, Mach-O
- 🎮 Game ROMs: NES, SNES, GB, GBA, N64
- And 350+ more formats...
Location: DataInspectorService.cs
Purpose: Interprets selected bytes as multiple data types simultaneously.
Supported Interpretations (15+ types):
// Select 8 bytes at position 100
var interpretations = dataInspectorService.Interpret(byteProvider, position: 100, length: 8);
// Returns:
// Int8: 127
// UInt8: 255
// Int16 (LE): 32767
// Int16 (BE): -32768
// Int32 (LE): 2147483647
// Int32 (BE): -2147483648
// Int64 (LE): 9223372036854775807
// Float (LE): 3.14159
// Double (LE): 2.718281828
// ASCII: "Hello!\n"
// UTF-8: "世界"
// UTF-16: "🚀"
// Hex: "48 65 6C 6C 6F 21 0A 00"
// Binary: "01001000 01100101 01101100..."
// Timestamp: "2026-02-22 14:30:00 UTC"UI Integration:
- 📊 Data Inspector Panel - Real-time display as cursor moves
- 🎯 Selection-based - Updates automatically on selection change
- 🔄 Endianness Toggle - Switch between Little/Big Endian
- 📋 Copy Values - Right-click to copy specific interpretation
Location: ParsedFieldsPanel.xaml
Purpose: Displays parsed file structure in a hierarchical tree view.
Features:
- 🌳 Tree View - Hierarchical field structure
- 🎨 Color-Coded Types - Visual distinction for different data types
- 📍 Click to Navigate - Jump to field location in hex view
- 🔍 Field Details - Name, offset, length, type, value
- 📊 Structure Overlay - Highlights field boundaries in hex view
Example Display (PNG file):
📄 PNG Image (1920x1080)
├─ 📦 PNG Signature (0x00, 8 bytes)
│ └─ Value: 89 50 4E 47 0D 0A 1A 0A
├─ 📦 IHDR Chunk (0x08, 25 bytes)
│ ├─ Length: 13 (uint32)
│ ├─ Type: "IHDR" (string)
│ ├─ Width: 1920 (uint32)
│ ├─ Height: 1080 (uint32)
│ ├─ Bit Depth: 8 (uint8)
│ ├─ Color Type: 2 (uint8)
│ └─ CRC: 0x1A2B3C4D (uint32) ✅
├─ 📦 IDAT Chunk (0x21, 65536 bytes)
│ └─ Compressed Data: [...]
└─ 📦 IEND Chunk (0x10021, 12 bytes)
└─ CRC: 0xAE426082 (uint32) ✅
Location: WpfHexEditor.Terminal/ + WpfHexEditor.Core.Terminal/
Purpose: VS Code-style integrated terminal with custom HxScript engine and plugin API.
TerminalMode state machine:
| Mode | Behaviour |
|---|---|
Interactive |
Default — accepts user commands, shows prompt |
Script |
Running .hxscript — prompt hidden, commands echoed |
ReadOnly |
Output only — input disabled (e.g. build output sink) |
TerminalPanelViewModel implements both ITerminalContext and ITerminalOutput (saves an adapter class; VM is single source of truth).
Export formats (6, zero NuGet dependencies):
| Format | Extension | Notes |
|---|---|---|
| Plain Text | .txt |
Default |
| HTML | .html |
CSS <span> coloring |
| RTF/Word | .rtf |
Color table \cf |
| ANSI | .ansi |
Escape codes |
| Markdown | .md |
Table |
| SpreadsheetML | .xml |
XmlWriter, Excel/LibreOffice |
Key patterns:
ToggleButtonwithIsChecked=TwoWaymust NOT also haveCommand=(causes double-toggle)BringIntoView()must be deferred viaDispatcher.InvokeAsync(DispatcherPriority.Background)to prevent scroll overshootPanelIconButtonStyle/PanelDropdownButtonStyleinheritFontFamily="Segoe MDL2 Assets"— addFontFamily="Segoe UI"explicitly on textTextBlocks
Location: FileDiffService.cs
Purpose: Side-by-side comparison of two binary files with highlighting.
Features:
- ⚖️ Dual View - Two hex editors side-by-side
- 🎨 Diff Highlighting - Added (green), removed (red), modified (yellow)
- 🔄 Synchronized Scrolling - Both views scroll together
- 📊 Statistics - Total differences, % similarity
- 📍 Navigation - Jump to next/previous difference
Use Cases:
- 📝 Comparing file versions
- 🔧 Analyzing patch differences
- 🐛 Debugging binary protocol changes
- 🎮 ROM hacking comparison
Location: WpfHexEditor.Editor.CodeEditor/
Purpose: Multi-language code editor (formerly JsonEditor). Standalone project implementing IDocumentEditor, IEditorPersistable, IDiagnosticSource, and ISearchTarget.
Note: The project was renamed from
WpfHexEditor.Editor.JsonEditortoWpfHexEditor.Editor.CodeEditorto reflect its expanded scope beyond JSON.
CodeEditor Features (61 Dependency Properties):
- 🎨 Syntax Highlighting - 13+ token types (keys, values, keywords, etc.)
- 💡 SmartComplete - Context-aware autocomplete with 9 contexts
- ✅ Real-time Validation - 4-layer validation with squiggly lines
- 🔤 Line Numbers - Configurable gutter
- 🎯 Bracket Matching - Visual highlighting of matching {}, [], ()
- ↩️ Auto-Indent - Smart indentation on Enter
- 📋 Clipboard - Copy/Paste/Cut with formatting
- ⏮️ Undo/Redo - Unlimited history
- 🔍 Find/Replace - Full
Replace()andReplaceAll()implementation - 📄 Code Folding - Collapse/expand blocks
- 💾 IEditorPersistable - Scroll position, caret, encoding, edit mode restored on reopen
SmartComplete Contexts:
- Root level (formatName, signature, fields, etc.)
- Field properties (name, type, offset, length, etc.)
- Type suggestions (uint8, uint16, string, etc.)
- Conditional expressions (calc:, var:)
- Loop constructs (count, body)
- Action definitions (onLoad, onParse)
- Validation rules (checksum, bounds)
- Display hints (color, format, label)
- Custom properties (metadata, tags)
Validation Layers:
- JSON Syntax - Parse errors, unclosed brackets
- Schema - Missing required fields (formatName, signature)
- Format Rules - Invalid field types, offset conflicts
- Semantic - Undefined variable references, circular dependencies
Location: WpfHexEditor.Editor.ImageViewer/Controls/
Purpose: Image viewer implementing IDocumentEditor (read-only). Supports zoom/pan, transform pipeline, and non-destructive crop.
Features:
- 🔍 Zoom / Pan — mouse wheel + drag, pinch-to-zoom
- 🔄 Transform Pipeline —
IImageTransformchain (rotate, flip, resize, color adjust) - ✂️ Crop with handles — 4 veil rectangles + 8 resize handles,
_cropRectstate model - 💾 Multi-format save — PNG, JPEG, BMP, TIFF via
BitmapEncoder(no NuGet) - 📂 Concurrent access —
FileShare.ReadWriteallows HexEditor to hold same file open simultaneously
sequenceDiagram
actor User
participant HE as HexEditor
participant VM as ViewModel
participant BP as ByteProvider
participant FP as FileProvider
participant FS as FileSystem
User->>HE: Open("data.bin")
HE->>VM: Create ViewModel
VM->>BP: new ByteProvider()
BP->>FP: OpenFile("data.bin")
FP->>FS: Open FileStream
FS-->>FP: Stream handle
FP-->>BP: File opened
BP->>FP: Read first 64KB (cache)
FP->>FS: Read bytes
FS-->>FP: Byte data
FP-->>BP: Cached bytes
BP-->>VM: Provider ready
VM-->>HE: ViewModel ready
HE->>VM: GetVisibleLines()
VM->>BP: ReadBytes(0, 1000)
BP-->>VM: Byte data
VM-->>HE: HexLine[]
HE->>User: Display hex view
sequenceDiagram
actor User
participant HE as HexEditor
participant VM as ViewModel
participant BP as ByteProvider
participant EM as EditsManager
participant PM as PositionMapper
participant UR as UndoRedoManager
User->>HE: Type 'FF' at position 100
HE->>VM: InsertByte(100, 0xFF)
VM->>BP: InsertByte(100, 0xFF)
BP->>EM: AddInsertion(100, 0xFF)
EM->>PM: RecalculateSegments()
PM-->>EM: Segments updated
EM-->>BP: Insertion added
BP->>UR: PushEdit(InsertEdit)
UR-->>BP: Edit saved
BP-->>VM: Length increased
VM->>VM: RefreshVisibleLines()
VM-->>HE: Lines updated
HE->>User: Display updated view
Problem: How to show edits without modifying the original file?
Solution: Maintain separate edit collections and compute virtual view on-demand.
// User sees: [41 42 FF 43 44 45] (6 bytes)
// File has: [41 42 43 44 45] (5 bytes)
// Insertion: Position 2 → 0xFF
// When user reads position 2:
ByteReader.ReadByte(2)
→ EditsManager.IsInsertion(2) // Returns true, value=0xFF
→ Return 0xFF // No file access needed!Problem: Multiple insertions at same position - what order?
Solution: Last-In-First-Out (stack behavior).
InsertByte(5, 'A'); // [... A ...]
InsertByte(5, 'B'); // [... B A ...] ← B inserted before A
InsertByte(5, 'C'); // [... C B A ...] ← C inserted before B
// Cursor stays after last insertion for natural typing flowProblem: Save performance for large files with many edits.
Solution: Two paths - fast path for modifications only, full rebuild for structural changes.
public void Save()
{
if (HasInsertionsOrDeletions)
{
// Full rebuild: iterate virtual positions
RebuildFile(); // ~100ms for 10MB file
}
else
{
// Fast path: only write modified bytes
ApplyModifications(); // ~1ms for 10MB file (100x faster!)
}
}Problem: User wants to undo only certain types of edits.
Solution: Three granular clear methods.
// Clear only value changes, keep structure
hexEditor.ClearModifications();
// Remove all inserted bytes
hexEditor.ClearInsertions();
// Restore all deleted bytes
hexEditor.ClearDeletions();
// Clear everything
hexEditor.ClearAllChanges();Problem: Thousands of small edits cause UI lag.
Solution: Batch mode defers UI updates until complete.
hexEditor.BeginBatch();
try
{
for (int i = 0; i < 10000; i++)
hexEditor.ModifyByte((byte)(i % 256), i);
}
finally
{
hexEditor.EndBatch(); // Update UI once
}
// Result: 3x faster than individual operations| Operation | V1 | V2 | Improvement |
|---|---|---|---|
| Initial load (1000 lines) | 450ms | 5ms | 99% faster |
| Scroll update | 120ms | 2ms | 98% faster |
| Selection change | 80ms | 1ms | 99% faster |
| Byte modification | 200ms | 3ms | 98% faster |
| Insert byte | 250ms | 4ms | 98% faster |
| File Size | V1 Memory | V2 Memory | Improvement |
|---|---|---|---|
| 1 MB | 180 MB | 25 MB | 86% less |
| 10 MB | 950 MB | 85 MB | 91% less |
| 100 MB | OOM | 320 MB | Handles GB+ files |
| Operation | Time Complexity | Space Complexity |
|---|---|---|
| ReadByte | O(log n) | O(1) |
| ModifyByte | O(1) | O(1) |
| InsertByte | O(log n) | O(1) |
| DeleteBytes | O(log n) | O(1) |
| FindFirst | O(n) | O(1) |
| FindAll | O(n) | O(k) where k = matches |
| CountOccurrences | O(n) | O(1) |
| Undo/Redo | O(1) | O(h) where h = history |
| Save (modifications only) | O(m) | O(1) |
| Save (with insertions) | O(n) | O(n) |
n = file size, m = modification count, k = match count, h = history depth
- ByteProvider System - Data access coordination
- Position Mapping - Virtual↔Physical conversion
- Edit Tracking - Modification management
- Undo/Redo System - History management
- Rendering System - Custom DrawingContext
- Service Layer - 16 specialized services
- Format Detection System - 400+ file format auto-detection
- Data Inspector - Multi-type interpretation
- Parsed Fields Panel - Structure visualization
- File Diff System - Binary comparison
- Format Script Editor - JsonEditor with SmartComplete
- File Operations - Open, Close, Save sequences
- Edit Operations - Modify, Insert, Delete sequences
- Search Operations - Find, Replace sequences
- Save Operations - Smart save algorithm
- Format Detection Flow - Signature matching and field parsing
- API Reference - Complete method documentation
- PartialClasses - HexEditor code organization (Core, Features, Search, UI)
- Core Services - Service layer (22 services)
- Performance Guide - Optimization tips
- Main README - Project overview
Last Updated: 2026-03-07 Status: 🚧 Active Development
- ✅ Multi-project architecture: Core, HexEditor, Panels., Editors., Docking, BinaryAnalysis, ProjectSystem, App
- ✅ 100% ByteProvider API (186 methods)
- ✅ 400+ file format auto-detection + format-aware preferred editor selection
- ✅ Custom Docking system (VS-style, no third-party dependency)
- ✅ Plugin editor system (IDocumentEditor): HexEditor, CodeEditor, TblEditor, TextEditor, ImageViewer, DiffViewer
- ✅ IDE project system (.whsln / .whproj), format versioning v2
- ✅ Data Inspector, Parsed Fields, SolutionExplorer, Properties, ErrorPanel, PluginMonitoring
- ✅ Plugin System: SDK (11 contracts), PluginHost (watchdog, crash handler, event bus), PluginSandbox (IPC)
- ✅ Terminal Panel: TerminalMode, HxScriptEngine (31+ commands), 6 export formats
- ✅ ImageViewer: transform pipeline, IImageTransform, crop with 8 handles
- ✅ Sample.Docking: standalone WPF docking sample (2 themes, layout persistence)
- 🔧 Stub editors: AudioViewer, DisassemblyViewer, EntropyViewer, TileEditor