-
Notifications
You must be signed in to change notification settings - Fork 143
Editor Registry
EditorRegistry (in WpfHexEditor.Editor.Core) is the central catalog of document editors. It resolves which editor opens a given file — with format-aware preferred-editor selection and fallback logic.
flowchart TD
App["MainWindow.Editors.cs"]
Registry["EditorRegistry\nIEditorRegistry"]
Catalog["EmbeddedFormatCatalog\n.whfmt definitions\npreferredEditor key"]
Factory["IEditorFactory\nDescriptor · CanOpen() · Create()"]
Editor["IDocumentEditor\n(UserControl / FrameworkElement)"]
Persist["IEditorPersistable\n(optional)\nbookmarks · scroll · encoding"]
Diag["IDiagnosticSource\n(optional)\nerrors → Error Panel"]
UndoEng["UndoEngine (Editor.Core.Undo)\ncoalescing · transactions · save-point"]
App --> Registry
Registry --> Catalog
Registry --> Factory
Factory --> Editor
Editor -.->|implements| Persist
Editor -.->|implements| Diag
Editor --> UndoEng
Every editor implements IDocumentEditor. The host (docking system, main window) drives all editors through this interface uniformly:
public interface IDocumentEditor
{
// State
bool IsDirty { get; }
bool CanUndo { get; }
bool CanRedo { get; }
bool IsReadOnly { get; set; }
string Title { get; }
bool IsBusy { get; }
// Undo engine stats (v0.6.0 — default interface members)
int UndoCount => 0;
int RedoCount => 0;
// Bindable commands (bind to MenuItem.Command, toolbar buttons…)
ICommand? UndoCommand { get; }
ICommand? RedoCommand { get; }
ICommand? SaveCommand { get; }
ICommand? CopyCommand { get; }
ICommand? CutCommand { get; }
ICommand? PasteCommand { get; }
ICommand? DeleteCommand { get; }
ICommand? SelectAllCommand { get; }
// Methods
void Undo(); void Redo();
void Save(); Task SaveAsync(CancellationToken ct = default);
Task SaveAsAsync(string filePath, CancellationToken ct = default);
void Copy(); void Cut(); void Paste();
void Delete(); void SelectAll(); void Close();
void CancelOperation();
// Events
event EventHandler? ModifiedChanged;
event EventHandler? CanUndoChanged;
event EventHandler? CanRedoChanged;
event EventHandler<string>? TitleChanged;
event EventHandler<string>? StatusMessage;
event EventHandler<string>? OutputMessage;
event EventHandler? SelectionChanged;
event EventHandler<DocumentOperationEventArgs>? OperationStarted;
event EventHandler<DocumentOperationEventArgs>? OperationProgress;
event EventHandler<DocumentOperationCompletedEventArgs>? OperationCompleted;
}public interface IEditorFactory
{
IEditorDescriptor Descriptor { get; } // id, display name, supported extensions
bool CanOpen(string filePath); // extension / magic-byte check
IDocumentEditor Create(); // returns a new blank editor instance
}The returned IDocumentEditor must also be a FrameworkElement — the host casts after Create().
EditorRegistry.FindFactory(filePath, preferredEditorId?) uses a three-step resolution:
flowchart TD
Call["FindFactory(filePath, preferredId?)"]
Step1{"preferredId\nprovided?"}
Step2{"EmbeddedFormatCatalog\nhas preferredEditor?"}
Step3{"isTextFormat\n== true?"}
Fallback["First factory\nwhere CanOpen() == true"]
Hex["HexEditor (default)"]
Call --> Step1
Step1 -->|Yes| Preferred["Return factory\nwith matching Id"]
Step1 -->|No| Step2
Step2 -->|Yes| WhfmtEditor["Return factory\nnamed in .whfmt"]
Step2 -->|No| Step3
Step3 -->|Yes| TextEditor["Return TextEditor factory"]
Step3 -->|No| Fallback
Fallback -->|None found| Hex
In a .whfmt format definition:
{
"id": "fmt-zip",
"name": "ZIP Archive",
"detection": { "isTextFormat": false },
"preferredEditor": "structure-editor"
}Formats with preferredEditor set: ZIP, RAR, PNG, JPEG, BMP, PDF, PE_EXE, ELF, XAML.
public sealed class MyEditorFactory : IEditorFactory
{
public IEditorDescriptor Descriptor { get; } = new EditorDescriptor(
id: "my-editor",
name: "My Editor",
extensions: new[] { ".myext" });
public bool CanOpen(string filePath) =>
Path.GetExtension(filePath).Equals(".myext", StringComparison.OrdinalIgnoreCase);
public IDocumentEditor Create() => new MyEditorControl();
}// In App.xaml.cs or MainWindow constructor
editorRegistry.Register(new MyEditorFactory());{
"id": "fmt-myext",
"preferredEditor": "my-editor"
}Implement to let the project system save/restore bookmarks, scroll position, and pending edits.
public class MyEditorControl : UserControl, IDocumentEditor, IEditorPersistable
{
public EditorConfigDto GetEditorConfig() => new() { /* … */ };
public void ApplyEditorConfig(EditorConfigDto c) { /* … */ }
public byte[]? GetUnsavedModifications() => null;
public void ApplyUnsavedModifications(byte[] d) { /* … */ }
// …bookmarks, changeset…
}EditorConfigDto.Extra — extensible dictionary for editor-specific settings (e.g. xd.layout for XAML Designer layout mode).
Implement to push errors/warnings directly to the Error Panel:
public class MyEditorControl : UserControl, IDocumentEditor, IDiagnosticSource
{
public event EventHandler<DiagnosticEventArgs>? DiagnosticRaised;
private void ReportError(string message) =>
DiagnosticRaised?.Invoke(this, new DiagnosticEventArgs(
DiagnosticSeverity.Error, message, offset: 0));
}Implement on a factory to perform background validation before the editor opens:
public sealed class MyEditorFactory : IEditorFactory, IFileValidator
{
public async Task<ValidationResult> ValidateAsync(string filePath, CancellationToken ct)
{
var info = new FileInfo(filePath);
if (info.Length == 0)
return ValidationResult.Error("File is empty.");
return ValidationResult.Ok();
}
}// Find best factory for a file
IEditorFactory? factory = editorRegistry.FindFactory(@"C:\data\image.png");
// Find with explicit preference
IEditorFactory? factory = editorRegistry.FindFactory(@"C:\data\image.png", "hex-editor");
// List all registered factories
IReadOnlyList<IEditorFactory> all = editorRegistry.GetAll();| Factory Id | Editor | Progress | Handles |
|---|---|---|---|
hex-editor |
HexEditor |
~75% | Binary / unknown formats |
code-editor |
CodeEditor |
~90% | Source code (55+ languages), XAML, config files |
xaml-designer |
XamlDesigner |
~70% |
.xaml — visual designer mode |
text-editor |
TextEditor |
~50% | Text files, syntax highlighting |
tbl-editor |
TblEditor |
~60% |
.tbl ROM character tables |
json-editor |
JsonEditor |
~65% |
.json with live validation |
structure-editor |
StructureEditor |
~30% | ZIP, archives, structured binary |
image-viewer |
ImageViewer |
~30% | PNG, JPEG, BMP, GIF… |
entropy-viewer |
EntropyViewer |
~25% | Entropy graph for binary analysis |
diff-viewer |
DiffViewer |
~20% | Binary diff |
All editors based on WpfHexEditor.Editor.Core share the UndoEngine:
// Access from any IDocumentEditor that wraps UndoEngine
if (editor is ICodeEditorHost ceh)
{
int undoCount = ceh.UndoEngine.UndoCount;
int redoCount = ceh.UndoEngine.RedoCount;
bool isDirty = !ceh.UndoEngine.IsAtSavePoint;
}The IDocumentEditor.UndoCount and RedoCount default interface members expose undo stack depth to the toolbar (dynamic "Undo (N)" header).
- Architecture — editor registry in context
-
Project System —
IEditorPersistableand.whchgfiles - ContentId Routing — how tab ContentIds map to factories
- Plugin System — registering editors from a plugin
✨ Wpf HexEditor user control, by Derek Tremblay (derektremblay666@gmail.com) coded for your fun! 😊🤟
- API Reference
- Performance
- Services
- Core Components
- ByteProvider
- Rendering Engine
- Search Architecture
- 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)