-
-
Notifications
You must be signed in to change notification settings - Fork 141
Project System
WpfHexEditor.ProjectSystem manages solutions, projects, and per-file state persistence. Three solution formats are supported via pluggable ISolutionLoader plugins.
flowchart TD
App["WpfHexEditor.App\nMainWindow.ProjectSystem.cs"]
SM["SolutionManager\nopen · close · create · save"]
Loaders["ISolutionLoader plugins\n(sorted by Priority)"]
WH["SolutionLoader.WH (priority 95)\n.whsln / .whproj"]
VS["SolutionLoader.VS (priority 90)\n.sln / .csproj (4 templates)"]
Folder["SolutionLoader.Folder (priority 85)\n.whfolder JSON marker + gitignore"]
Sol["ISolution / SolutionDto\n.whsln JSON file"]
User["SolutionUserDto\n.whsln.user sidecar\n(machine-local state)"]
Items["IProjectItem\nfiles + virtual folders"]
Migrate["MigrationPipeline\nIFormatMigrator chain\n.v{N}.bak backup"]
Persist["IEditorPersistable\nbookmarks · scroll · encoding\nunsaved edits (.whchg)"]
Build["WpfHexEditor.BuildSystem\nIBuildSystem + IBuildAdapter"]
App --> SM
SM --> Loaders
Loaders --> WH
Loaders --> VS
Loaders --> Folder
WH --> Sol
Sol --> User
Sol --> Items
SM --> Migrate
Items --> Persist
SM --> Build
| Loader Plugin | Priority | Handles | Notes |
|---|---|---|---|
SolutionLoader.WH |
95 |
.whsln / .whproj
|
Native format — full feature set |
SolutionLoader.VS |
90 |
.sln / .csproj
|
Read Visual Studio solutions; 4 project templates |
SolutionLoader.Folder |
85 |
.whfolder JSON marker |
Open-folder mode (VS Code–like), gitignore-aware |
SolutionManager iterates loaders in descending priority order, calling CanLoad(path) until one accepts.
public interface ISolutionLoader : IWpfHexEditorPluginV2
{
int Priority { get; }
bool CanLoad(string path);
Task<ISolution> LoadAsync(string path, CancellationToken ct);
Task SaveAsync(ISolution solution, CancellationToken ct);
}| File | Format | Description |
|---|---|---|
*.whsln |
JSON | Solution root — references projects, solution folders |
*.whproj |
JSON | Project — references files, virtual folders, item metadata |
*.whsln.user |
JSON | Machine-local sidecar — tree expand state, recent tabs, layout hints |
*.whchg |
Binary | Companion to a binary file — stores pending in-memory edits |
All formats carry a Version integer field used by MigrationPipeline.
A minimal JSON marker placed at the root of the folder:
{
"version": 1,
"name": "MyProject",
"excludes": ["bin/", "obj/", ".vs/"]
}FolderFileEnumerator walks the directory tree, applying:
-
.gitignorerules (GitIgnoreFilter) - Built-in exclusions (
bin/,obj/,node_modules/,.vs/)
FolderFileWatcher monitors for external changes with a 500 ms debounce and refreshes the Solution Explorer automatically.
ISolution
├── string Name
├── string FilePath
├── int SourceFormatVersion
├── bool IsReadOnlyFormat ← true when file cannot be upgraded
├── IReadOnlyList<IProject> Projects
└── IReadOnlyList<ISolutionFolder> Folders
IProject
├── string Name
├── string ProjectFilePath
└── IReadOnlyList<IProjectItem> Items
IProjectItem
├── string Id ← stable GUID, used as ContentId key
├── string Name
├── string RelativePath ← relative to .whproj dir
├── bool IsVirtual ← virtual (no physical file)
└── Dictionary<string,string> Metadata ← editor prefs, ActiveEditorId…
ISolutionManager sm = serviceProvider.GetRequiredService<ISolutionManager>();
// Open — loader is auto-selected by priority + CanLoad()
ISolution sol = await sm.OpenAsync(@"C:\Projects\MySolution\MySolution.whsln");
ISolution sol = await sm.OpenAsync(@"C:\Projects\MySolution.sln"); // VS format
ISolution sol = await sm.OpenFolderAsync(@"C:\Projects\MyFolder"); // open-folder
// Create a new blank solution (native format)
ISolution sol = await sm.CreateAsync("MySolution", @"C:\Projects\MySolution");
// Add a project
IProject proj = await sm.AddProjectAsync(sol, "MyProject", @"C:\Projects\MyProject");
// Add a file to a project
IProjectItem item = await sm.AddItemAsync(proj, @"C:\Data\firmware.bin");
// Save everything
await sm.SaveAsync(sol);
// Close
await sm.CloseAsync(sol);When a solution or project file has an older Version, MigrationPipeline upgrades it automatically:
flowchart LR
Open["Open .whsln"] --> Check{"Version ==\nCurrentVersion?"}
Check -->|Yes| Load["Load directly"]
Check -->|No| Backup["Create .v{N}.bak"]
Backup --> Run["Run IFormatMigrator chain\n(v1→v2→v3…)"]
Run --> Update["Set Version = CurrentVersion"]
Update --> Save["Save upgraded file"]
Save --> Load
public sealed class V1ToV2Migrator : IFormatMigrator
{
public int FromVersion => 1;
public int ToVersion => 2;
public void Migrate(SolutionDto dto)
{
// e.g. move layout JSON from .whsln to .whsln.user
dto.DeprecatedLayoutJson = null;
}
}Register in MigrationPipeline:
MigrationPipeline.Register(new V1ToV2Migrator());ISolution.IsReadOnlyFormat is true when the file cannot be written (e.g. opened from a read-only location). The IDE will warn the user and block save.
When a solution is opened, MainWindow.Build.cs wires the build system:
flowchart LR
Build["Ctrl+Shift+B"] --> BS["IBuildSystem.BuildAsync()"]
BS --> Adapter["IBuildAdapter.BuildAsync()\n(Build.MSBuild plugin)"]
Adapter --> CLI["dotnet build ..."]
CLI --> Parser["BuildOutputParser\nDiagnosticEntry[]"]
Parser --> EP["IErrorPanelService.PostDiagnostic()"]
Parser --> OP["IOutputService.WriteLine()"]
Build configuration management:
// Get available configurations (Debug/Release/custom)
var configs = buildSystem.GetConfigurations(project);
// Set active configuration
buildSystem.SetActiveConfiguration(project, "Release");
// Trigger build
await buildSystem.BuildAsync(project, activeConfig, progress, ct);Editors that implement IEditorPersistable can round-trip their state through the project system:
public interface IEditorPersistable
{
EditorConfigDto GetEditorConfig();
void ApplyEditorConfig(EditorConfigDto config);
byte[]? GetUnsavedModifications();
void ApplyUnsavedModifications(byte[] data);
ChangesetSnapshot GetChangesetSnapshot();
void ApplyChangeset(ChangesetDto changeset);
void MarkChangesetSaved();
IReadOnlyList<BookmarkDto>? GetBookmarks();
void ApplyBookmarks(IReadOnlyList<BookmarkDto> bookmarks);
}EditorConfigDto.Extra (dictionary) stores editor-specific keys:
-
xd.layout— XAML Designer split layout mode -
ce.foldDepth— Code Editor folding depth -
he.bytesPerLine— HexEditor bytes-per-line preference
What is persisted per file:
| Data | Storage | Notes |
|---|---|---|
| Bytes/line, encoding | IProjectItem.Metadata |
Restored on next open |
| Bookmarks | IProjectItem.Metadata |
Named offsets |
| Scroll position, caret | EditorConfigDto |
Session restore |
| Pending byte edits |
.whchg companion file |
Survives IDE restart |
| Editor-specific prefs | EditorConfigDto.Extra |
Extensible key-value |
Projects support both physical folders (mirroring a directory on disk) and virtual folders (grouping only inside the IDE):
// Add a virtual folder
IProjectFolder folder = await sm.AddVirtualFolderAsync(proj, "Headers");
// Add a file under it
IProjectItem item = await sm.AddItemAsync(folder, @"C:\Data\header.bin");Virtual folders have RelativePath = null in the DTO.
- Architecture — project system in context
-
Editor Registry —
IEditorPersistableand format selection -
ContentId Routing —
doc-proj-{itemId}tab lifecycle -
Plugin System —
ISolutionLoaderandIBuildAdaptercontracts
✨ 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
- WpfHexEditor.Shell (renamed from Docking.Wpf)
- Code Editor ~90% (NavBar, Inline Hints, Quick Info)
- XAML Visual Designer ~70%
- Shared UndoEngine (coalescing + transactions)
- VS
.sln/.csproj+ Open-Folder mode - Build System (IBuildAdapter + MSBuild plugin)
- Assembly Explorer + NuGet Solution Manager
- Synalysis Grammar Support (IGrammarProvider)