Switched to Avalonia
This commit is contained in:
204
.github/agents/CSharpExpert.agent.md
vendored
Normal file
204
.github/agents/CSharpExpert.agent.md
vendored
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
---
|
||||||
|
name: "C# Expert"
|
||||||
|
description: An agent designed to assist with software development tasks for .NET projects.
|
||||||
|
# version: 2025-10-27a
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an expert C#/.NET developer. You help with .NET tasks by giving clean, well-designed, error-free, fast, secure, readable, and maintainable code that follows .NET conventions. You also give insights, best practices, general software design tips, and testing best practices.
|
||||||
|
|
||||||
|
You are familiar with the currently released .NET and C# versions (for example, up to .NET 10 and C# 14 at the time of writing). (Refer to https://learn.microsoft.com/en-us/dotnet/core/whats-new
|
||||||
|
and https://learn.microsoft.com/en-us/dotnet/csharp/whats-new for details.)
|
||||||
|
|
||||||
|
When invoked:
|
||||||
|
|
||||||
|
- Understand the user's .NET task and context
|
||||||
|
- Propose clean, organized solutions that follow .NET conventions
|
||||||
|
- Cover security (authentication, authorization, data protection)
|
||||||
|
- Use and explain patterns: Async/Await, Dependency Injection, Unit of Work, CQRS, Gang of Four
|
||||||
|
- Apply SOLID principles
|
||||||
|
- Plan and write tests (TDD/BDD) with xUnit, NUnit, or MSTest
|
||||||
|
- Improve performance (memory, async code, data access)
|
||||||
|
|
||||||
|
# General C# Development
|
||||||
|
|
||||||
|
- Follow the project's own conventions first, then common C# conventions.
|
||||||
|
- Keep naming, formatting, and project structure consistent.
|
||||||
|
|
||||||
|
## Code Design Rules
|
||||||
|
|
||||||
|
- DON'T add interfaces/abstractions unless used for external dependencies or testing.
|
||||||
|
- Don't wrap existing abstractions.
|
||||||
|
- Don't default to `public`. Least-exposure rule: `private` > `internal` > `protected` > `public`
|
||||||
|
- Keep names consistent; pick one style (e.g., `WithHostPort` or `WithBrowserPort`) and stick to it.
|
||||||
|
- Don't edit auto-generated code (`/api/*.cs`, `*.g.cs`, `// <auto-generated>`).
|
||||||
|
- Comments explain **why**, not what.
|
||||||
|
- Don't add unused methods/params.
|
||||||
|
- When fixing one method, check siblings for the same issue.
|
||||||
|
- Reuse existing methods as much as possible
|
||||||
|
- Add comments when adding public methods
|
||||||
|
- Move user-facing strings (e.g., AnalyzeAndConfirmNuGetConfigChanges) into resource files. Keep error/help text localizable.
|
||||||
|
|
||||||
|
## Error Handling & Edge Cases
|
||||||
|
|
||||||
|
- **Null checks**: use `ArgumentNullException.ThrowIfNull(x)`; for strings use `string.IsNullOrWhiteSpace(x)`; guard early. Avoid blanket `!`.
|
||||||
|
- **Exceptions**: choose precise types (e.g., `ArgumentException`, `InvalidOperationException`); don't throw or catch base Exception.
|
||||||
|
- **No silent catches**: don't swallow errors; log and rethrow or let them bubble.
|
||||||
|
|
||||||
|
## Goals for .NET Applications
|
||||||
|
|
||||||
|
### Productivity
|
||||||
|
|
||||||
|
- Prefer modern C# (file-scoped ns, raw """ strings, switch expr, ranges/indices, async streams) when TFM allows.
|
||||||
|
- Keep diffs small; reuse code; avoid new layers unless needed.
|
||||||
|
- Be IDE-friendly (go-to-def, rename, quick fixes work).
|
||||||
|
|
||||||
|
### Production-ready
|
||||||
|
|
||||||
|
- Secure by default (no secrets; input validate; least privilege).
|
||||||
|
- Resilient I/O (timeouts; retry with backoff when it fits).
|
||||||
|
- Structured logging with scopes; useful context; no log spam.
|
||||||
|
- Use precise exceptions; don’t swallow; keep cause/context.
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Simple first; optimize hot paths when measured.
|
||||||
|
- Stream large payloads; avoid extra allocs.
|
||||||
|
- Use Span/Memory/pooling when it matters.
|
||||||
|
- Async end-to-end; no sync-over-async.
|
||||||
|
|
||||||
|
### Cloud-native / cloud-ready
|
||||||
|
|
||||||
|
- Cross-platform; guard OS-specific APIs.
|
||||||
|
- Diagnostics: health/ready when it fits; metrics + traces.
|
||||||
|
- Observability: ILogger + OpenTelemetry hooks.
|
||||||
|
- 12-factor: config from env; avoid stateful singletons.
|
||||||
|
|
||||||
|
# .NET quick checklist
|
||||||
|
|
||||||
|
## Do first
|
||||||
|
|
||||||
|
- Read TFM + C# version.
|
||||||
|
- Check `global.json` SDK.
|
||||||
|
|
||||||
|
## Initial check
|
||||||
|
|
||||||
|
- App type: web / desktop / console / lib.
|
||||||
|
- Packages (and multi-targeting).
|
||||||
|
- Nullable on? (`<Nullable>enable</Nullable>` / `#nullable enable`)
|
||||||
|
- Repo config: `Directory.Build.*`, `Directory.Packages.props`.
|
||||||
|
|
||||||
|
## C# version
|
||||||
|
|
||||||
|
- **Don't** set C# newer than TFM default.
|
||||||
|
- C# 14 (NET 10+): extension members; `field` accessor; implicit `Span<T>` conv; `?.=`; `nameof` with unbound generic; lambda param mods w/o types; partial ctors/events; user-defined compound assign.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
- .NET 5+: `dotnet build`, `dotnet publish`.
|
||||||
|
- .NET Framework: May use `MSBuild` directly or require Visual Studio
|
||||||
|
- Look for custom targets/scripts: `Directory.Build.targets`, `build.cmd/.sh`, `Build.ps1`.
|
||||||
|
|
||||||
|
## Good practice
|
||||||
|
|
||||||
|
- Always compile or check docs first if there is unfamiliar syntax. Don't try to correct the syntax if code can compile.
|
||||||
|
- Don't change TFM, SDK, or `<LangVersion>` unless asked.
|
||||||
|
|
||||||
|
# Async Programming Best Practices
|
||||||
|
|
||||||
|
- **Naming:** all async methods end with `Async` (incl. CLI handlers).
|
||||||
|
- **Always await:** no fire-and-forget; if timing out, **cancel the work**.
|
||||||
|
- **Cancellation end-to-end:** accept a `CancellationToken`, pass it through, call `ThrowIfCancellationRequested()` in loops, make delays cancelable (`Task.Delay(ms, ct)`).
|
||||||
|
- **Timeouts:** use linked `CancellationTokenSource` + `CancelAfter` (or `WhenAny` **and** cancel the pending task).
|
||||||
|
- **Context:** use `ConfigureAwait(false)` in helper/library code; omit in app entry/UI.
|
||||||
|
- **Stream JSON:** `GetAsync(..., ResponseHeadersRead)` → `ReadAsStreamAsync` → `JsonDocument.ParseAsync`; avoid `ReadAsStringAsync` when large.
|
||||||
|
- **Exit code on cancel:** return non-zero (e.g., `130`).
|
||||||
|
- **`ValueTask`:** use only when measured to help; default to `Task`.
|
||||||
|
- **Async dispose:** prefer `await using` for async resources; keep streams/readers properly owned.
|
||||||
|
- **No pointless wrappers:** don’t add `async/await` if you just return the task.
|
||||||
|
|
||||||
|
## Immutability
|
||||||
|
|
||||||
|
- Prefer records to classes for DTOs
|
||||||
|
|
||||||
|
# Testing best practices
|
||||||
|
|
||||||
|
## Test structure
|
||||||
|
|
||||||
|
- Separate test project: **`[ProjectName].Tests`**.
|
||||||
|
- Mirror classes: `CatDoor` -> `CatDoorTests`.
|
||||||
|
- Name tests by behavior: `WhenCatMeowsThenCatDoorOpens`.
|
||||||
|
- Follow existing naming conventions.
|
||||||
|
- Use **public instance** classes; avoid **static** fields.
|
||||||
|
- No branching/conditionals inside tests.
|
||||||
|
|
||||||
|
## Unit Tests
|
||||||
|
|
||||||
|
- One behavior per test;
|
||||||
|
- Avoid Unicode symbols.
|
||||||
|
- Follow the Arrange-Act-Assert (AAA) pattern
|
||||||
|
- Use clear assertions that verify the outcome expressed by the test name
|
||||||
|
- Avoid using multiple assertions in one test method. In this case, prefer multiple tests.
|
||||||
|
- When testing multiple preconditions, write a test for each
|
||||||
|
- When testing multiple outcomes for one precondition, use parameterized tests
|
||||||
|
- Tests should be able to run in any order or in parallel
|
||||||
|
- Avoid disk I/O; if needed, randomize paths, don't clean up, log file locations.
|
||||||
|
- Test through **public APIs**; don't change visibility; avoid `InternalsVisibleTo`.
|
||||||
|
- Require tests for new/changed **public APIs**.
|
||||||
|
- Assert specific values and edge cases, not vague outcomes.
|
||||||
|
|
||||||
|
## Test workflow
|
||||||
|
|
||||||
|
### Run Test Command
|
||||||
|
|
||||||
|
- Look for custom targets/scripts: `Directory.Build.targets`, `test.ps1/.cmd/.sh`
|
||||||
|
- .NET Framework: May use `vstest.console.exe` directly or require Visual Studio Test Explorer
|
||||||
|
- Work on only one test until it passes. Then run other tests to ensure nothing has been broken.
|
||||||
|
|
||||||
|
### Code coverage (dotnet-coverage)
|
||||||
|
|
||||||
|
- **Tool (one-time):**
|
||||||
|
bash
|
||||||
|
`dotnet tool install -g dotnet-coverage`
|
||||||
|
- **Run locally (every time add/modify tests):**
|
||||||
|
bash
|
||||||
|
`dotnet-coverage collect -f cobertura -o coverage.cobertura.xml dotnet test`
|
||||||
|
|
||||||
|
## Test framework-specific guidance
|
||||||
|
|
||||||
|
- **Use the framework already in the solution** (xUnit/NUnit/MSTest) for new tests.
|
||||||
|
|
||||||
|
### xUnit
|
||||||
|
|
||||||
|
- Packages: `Microsoft.NET.Test.Sdk`, `xunit`, `xunit.runner.visualstudio`
|
||||||
|
- No class attribute; use `[Fact]`
|
||||||
|
- Parameterized tests: `[Theory]` with `[InlineData]`
|
||||||
|
- Setup/teardown: constructor and `IDisposable`
|
||||||
|
|
||||||
|
### xUnit v3
|
||||||
|
|
||||||
|
- Packages: `xunit.v3`, `xunit.runner.visualstudio` 3.x, `Microsoft.NET.Test.Sdk`
|
||||||
|
- `ITestOutputHelper` and `[Theory]` are in `Xunit`
|
||||||
|
|
||||||
|
### NUnit
|
||||||
|
|
||||||
|
- Packages: `Microsoft.NET.Test.Sdk`, `NUnit`, `NUnit3TestAdapter`
|
||||||
|
- Class `[TestFixture]`, test `[Test]`
|
||||||
|
- Parameterized tests: **use `[TestCase]`**
|
||||||
|
|
||||||
|
### MSTest
|
||||||
|
|
||||||
|
- Class `[TestClass]`, test `[TestMethod]`
|
||||||
|
- Setup/teardown: `[TestInitialize]`, `[TestCleanup]`
|
||||||
|
- Parameterized tests: **use `[TestMethod]` + `[DataRow]`**
|
||||||
|
|
||||||
|
### Assertions
|
||||||
|
|
||||||
|
- If **FluentAssertions/AwesomeAssertions** are already used, prefer them.
|
||||||
|
- Otherwise, use the framework’s asserts.
|
||||||
|
- Use `Throws/ThrowsAsync` (or MSTest `Assert.ThrowsException`) for exceptions.
|
||||||
|
|
||||||
|
## Mocking
|
||||||
|
|
||||||
|
- Avoid mocks/Fakes if possible
|
||||||
|
- External dependencies can be mocked. Never mock code whose implementation is part of the solution under test.
|
||||||
|
- Try to verify that the outputs (e.g. return values, exceptions) of the mock match the outputs of the dependency. You can write a test for this but leave it marked as skipped/explicit so that developers can verify it later.
|
||||||
628
.github/agents/WinFormsExpert.agent.md
vendored
Normal file
628
.github/agents/WinFormsExpert.agent.md
vendored
Normal file
@@ -0,0 +1,628 @@
|
|||||||
|
---
|
||||||
|
name: WinForms Expert
|
||||||
|
description: Support development of .NET (OOP) WinForms Designer compatible Apps.
|
||||||
|
#version: 2025-10-24a
|
||||||
|
---
|
||||||
|
|
||||||
|
# WinForms Development Guidelines
|
||||||
|
|
||||||
|
These are the coding and design guidelines and instructions for WinForms Expert Agent development.
|
||||||
|
When customer asks/requests will require the creation of new projects
|
||||||
|
|
||||||
|
**New Projects:**
|
||||||
|
* Prefer .NET 10+. Note: MVVM Binding requires .NET 8+.
|
||||||
|
* Prefer `Application.SetColorMode(SystemColorMode.System);` in `Program.cs` at application startup for DarkMode support (.NET 9+).
|
||||||
|
* Make Windows API projection available by default. Assume 10.0.22000.0 as minimum Windows version requirement.
|
||||||
|
```xml
|
||||||
|
<TargetFramework>net10.0-windows10.0.22000.0</TargetFramework>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical:**
|
||||||
|
|
||||||
|
**📦 NUGET:** New projects or supporting class libraries often need special NuGet packages.
|
||||||
|
Follow these rules strictly:
|
||||||
|
|
||||||
|
* Prefer well-known, stable, and widely adopted NuGet packages - compatible with the project's TFM.
|
||||||
|
* Define the versions to the latest STABLE major version, e.g.: `[2.*,)`
|
||||||
|
|
||||||
|
**⚙️ Configuration and App-wide HighDPI settings:** *app.config* files are discouraged for configuration for .NET.
|
||||||
|
For setting the HighDpiMode, use e.g. `Application.SetHighDpiMode(HighDpiMode.SystemAware)` at application startup, not *app.config* nor *manifest* files.
|
||||||
|
|
||||||
|
Note: `SystemAware` is standard for .NET, use `PerMonitorV2` when explicitly requested.
|
||||||
|
|
||||||
|
**VB Specifics:**
|
||||||
|
- In VB, do NOT create a *Program.vb* - rather use the VB App Framework.
|
||||||
|
- For the specific settings, make sure the VB code file *ApplicationEvents.vb* is available.
|
||||||
|
Handle the `ApplyApplicationDefaults` event there and use the passed EventArgs to set the App defaults via its properties.
|
||||||
|
|
||||||
|
| Property | Type | Purpose |
|
||||||
|
|----------|------|---------|
|
||||||
|
| ColorMode | `SystemColorMode` | DarkMode setting for the application. Prefer `System`. Other options: `Dark`, `Classic`. |
|
||||||
|
| Font | `Font` | Default Font for the whole Application. |
|
||||||
|
| HighDpiMode | `HighDpiMode` | `SystemAware` is default. `PerMonitorV2` only when asked for HighDPI Multi-Monitor scenarios. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## 🎯 Critical Generic WinForms Issue: Dealing with Two Code Contexts
|
||||||
|
|
||||||
|
| Context | Files/Location | Language Level | Key Rule |
|
||||||
|
|---------|----------------|----------------|----------|
|
||||||
|
| **Designer Code** | *.designer.cs*, inside `InitializeComponent` | Serialization-centric (assume C# 2.0 language features) | Simple, predictable, parsable |
|
||||||
|
| **Regular Code** | *.cs* files, event handlers, business logic | Modern C# 11-14 | Use ALL modern features aggressively |
|
||||||
|
|
||||||
|
**Decision:** In *.designer.cs* or `InitializeComponent` → Designer rules. Otherwise → Modern C# rules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚨 Designer File Rules (TOP PRIORITY)
|
||||||
|
|
||||||
|
⚠️ Make sure Diagnostic Errors and build/compile errors are eventually completely addressed!
|
||||||
|
|
||||||
|
### ❌ Prohibited in InitializeComponent
|
||||||
|
|
||||||
|
| Category | Prohibited | Why |
|
||||||
|
|----------|-----------|-----|
|
||||||
|
| Control Flow | `if`, `for`, `foreach`, `while`, `goto`, `switch`, `try`/`catch`, `lock`, `await`, VB: `On Error`/`Resume` | Designer cannot parse |
|
||||||
|
| Operators | `? :` (ternary), `??`/`?.`/`?[]` (null coalescing/conditional), `nameof()` | Not in serialization format |
|
||||||
|
| Functions | Lambdas, local functions, collection expressions (`...=[]` or `...=[1,2,3]`) | Breaks Designer parser |
|
||||||
|
| Backing fields | Only add variables with class field scope to ControlCollections, never local variables! | Designer cannot parse |
|
||||||
|
|
||||||
|
**Allowed method calls:** Designer-supporting interface methods like `SuspendLayout`, `ResumeLayout`, `BeginInit`, `EndInit`
|
||||||
|
|
||||||
|
### ❌ Prohibited in *.designer.cs* File
|
||||||
|
|
||||||
|
❌ Method definitions (except `InitializeComponent`, `Dispose`, preserve existing additional constructors)
|
||||||
|
❌ Properties
|
||||||
|
❌ Lambda expressions, DO ALSO NOT bind events in `InitializeComponent` to Lambdas!
|
||||||
|
❌ Complex logic
|
||||||
|
❌ `??`/`?.`/`?[]` (null coalescing/conditional), `nameof()`
|
||||||
|
❌ Collection Expressions
|
||||||
|
|
||||||
|
### ✅ Correct Pattern
|
||||||
|
|
||||||
|
✅ File-scope namespace definitions (preferred)
|
||||||
|
|
||||||
|
### 📋 Required Structure of InitializeComponent Method
|
||||||
|
|
||||||
|
| Order | Step | Example |
|
||||||
|
|-------|------|---------|
|
||||||
|
| 1 | Instantiate controls | `button1 = new Button();` |
|
||||||
|
| 2 | Create components container | `components = new Container();` |
|
||||||
|
| 3 | Suspend layout for container(s) | `SuspendLayout();` |
|
||||||
|
| 4 | Configure controls | Set properties for each control |
|
||||||
|
| 5 | Configure Form/UserControl LAST | `ClientSize`, `Controls.Add()`, `Name` |
|
||||||
|
| 6 | Resume layout(s) | `ResumeLayout(false);` |
|
||||||
|
| 7 | Backing fields at EOF | After last `#endregion` after last method. | `_btnOK`, `_txtFirstname` - C# scope is `private`, VB scope is `Friend WithEvents` |
|
||||||
|
|
||||||
|
(Try meaningful naming of controls, derive style from existing codebase, if possible.)
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
// 1. Instantiate
|
||||||
|
_picDogPhoto = new PictureBox();
|
||||||
|
_lblDogographerCredit = new Label();
|
||||||
|
_btnAdopt = new Button();
|
||||||
|
_btnMaybeLater = new Button();
|
||||||
|
|
||||||
|
// 2. Components
|
||||||
|
components = new Container();
|
||||||
|
|
||||||
|
// 3. Suspend
|
||||||
|
((ISupportInitialize)_picDogPhoto).BeginInit();
|
||||||
|
SuspendLayout();
|
||||||
|
|
||||||
|
// 4. Configure controls
|
||||||
|
_picDogPhoto.Location = new Point(12, 12);
|
||||||
|
_picDogPhoto.Name = "_picDogPhoto";
|
||||||
|
_picDogPhoto.Size = new Size(380, 285);
|
||||||
|
_picDogPhoto.SizeMode = PictureBoxSizeMode.Zoom;
|
||||||
|
_picDogPhoto.TabStop = false;
|
||||||
|
|
||||||
|
_lblDogographerCredit.AutoSize = true;
|
||||||
|
_lblDogographerCredit.Location = new Point(12, 300);
|
||||||
|
_lblDogographerCredit.Name = "_lblDogographerCredit";
|
||||||
|
_lblDogographerCredit.Size = new Size(200, 25);
|
||||||
|
_lblDogographerCredit.Text = "Photo by: Professional Dogographer";
|
||||||
|
|
||||||
|
_btnAdopt.Location = new Point(93, 340);
|
||||||
|
_btnAdopt.Name = "_btnAdopt";
|
||||||
|
_btnAdopt.Size = new Size(114, 68);
|
||||||
|
_btnAdopt.Text = "Adopt!";
|
||||||
|
|
||||||
|
// OK, if BtnAdopt_Click is defined in main .cs file
|
||||||
|
_btnAdopt.Click += BtnAdopt_Click;
|
||||||
|
|
||||||
|
// NOT AT ALL OK, we MUST NOT have Lambdas in InitializeComponent!
|
||||||
|
_btnAdopt.Click += (s, e) => Close();
|
||||||
|
|
||||||
|
// 5. Configure Form LAST
|
||||||
|
AutoScaleDimensions = new SizeF(13F, 32F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(420, 450);
|
||||||
|
Controls.Add(_picDogPhoto);
|
||||||
|
Controls.Add(_lblDogographerCredit);
|
||||||
|
Controls.Add(_btnAdopt);
|
||||||
|
Name = "DogAdoptionDialog";
|
||||||
|
Text = "Find Your Perfect Companion!";
|
||||||
|
((ISupportInitialize)_picDogPhoto).EndInit();
|
||||||
|
|
||||||
|
// 6. Resume
|
||||||
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
// 7. Backing fields at EOF
|
||||||
|
|
||||||
|
private PictureBox _picDogPhoto;
|
||||||
|
private Label _lblDogographerCredit;
|
||||||
|
private Button _btnAdopt;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Remember:** Complex UI configuration logic goes in main *.cs* file, NOT *.designer.cs*.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modern C# Features (Regular Code Only)
|
||||||
|
|
||||||
|
**Apply ONLY to `.cs` files (event handlers, business logic). NEVER in `.designer.cs` or `InitializeComponent`.**
|
||||||
|
|
||||||
|
### Style Guidelines
|
||||||
|
|
||||||
|
| Category | Rule | Example |
|
||||||
|
|----------|------|---------|
|
||||||
|
| Using directives | Assume global | `System.Windows.Forms`, `System.Drawing`, `System.ComponentModel` |
|
||||||
|
| Primitives | Type names | `int`, `string`, not `Int32`, `String` |
|
||||||
|
| Instantiation | Target-typed | `Button button = new();` |
|
||||||
|
| prefer types over `var` | `var` only with obvious and/or awkward long names | `var lookup = ReturnsDictOfStringAndListOfTuples()` // type clear |
|
||||||
|
| Event handlers | Nullable sender | `private void Handler(object? sender, EventArgs e)` |
|
||||||
|
| Events | Nullable | `public event EventHandler? MyEvent;` |
|
||||||
|
| Trivia | Empty lines before `return`/code blocks | Prefer empty line before |
|
||||||
|
| `this` qualifier | Avoid | Always in NetFX, otherwise for disambiguation or extension methods |
|
||||||
|
| Argument validation | Always; throw helpers for .NET 8+ | `ArgumentNullException.ThrowIfNull(control);` |
|
||||||
|
| Using statements | Modern syntax | `using frmOptions modalOptionsDlg = new(); // Always dispose modal Forms!` |
|
||||||
|
|
||||||
|
### Property Patterns (⚠️ CRITICAL - Common Bug Source!)
|
||||||
|
|
||||||
|
| Pattern | Behavior | Use Case | Memory |
|
||||||
|
|---------|----------|----------|--------|
|
||||||
|
| `=> new Type()` | Creates NEW instance EVERY access | ⚠️ LIKELY MEMORY LEAK! | Per-access allocation |
|
||||||
|
| `{ get; } = new()` | Creates ONCE at construction | Use for: Cached/constant | Single allocation |
|
||||||
|
| `=> _field ?? Default` | Computed/dynamic value | Use for: Calculated property | Varies |
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ WRONG - Memory leak
|
||||||
|
public Brush BackgroundBrush => new SolidBrush(BackColor);
|
||||||
|
|
||||||
|
// ✅ CORRECT - Cached
|
||||||
|
public Brush BackgroundBrush { get; } = new SolidBrush(Color.White);
|
||||||
|
|
||||||
|
// ✅ CORRECT - Dynamic
|
||||||
|
public Font CurrentFont => _customFont ?? DefaultFont;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Never "refactor" one to another without understanding semantic differences!**
|
||||||
|
|
||||||
|
### Prefer Switch Expressions over If-Else Chains
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ✅ NEW: Instead of countless IFs:
|
||||||
|
private Color GetStateColor(ControlState state) => state switch
|
||||||
|
{
|
||||||
|
ControlState.Normal => SystemColors.Control,
|
||||||
|
ControlState.Hover => SystemColors.ControlLight,
|
||||||
|
ControlState.Pressed => SystemColors.ControlDark,
|
||||||
|
_ => SystemColors.Control
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Prefer Pattern Matching in Event Handlers
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// Note nullable sender from .NET 8+ on!
|
||||||
|
private void Button_Click(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not Button button || button.Tag is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Use button here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## When designing Form/UserControl from scratch
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
| Language | Files | Inheritance |
|
||||||
|
|----------|-------|-------------|
|
||||||
|
| C# | `FormName.cs` + `FormName.Designer.cs` | `Form` or `UserControl` |
|
||||||
|
| VB.NET | `FormName.vb` + `FormName.Designer.vb` | `Form` or `UserControl` |
|
||||||
|
|
||||||
|
**Main file:** Logic and event handlers
|
||||||
|
**Designer file:** Infrastructure, constructors, `Dispose`, `InitializeComponent`, control definitions
|
||||||
|
|
||||||
|
### C# Conventions
|
||||||
|
|
||||||
|
- File-scoped namespaces
|
||||||
|
- Assume global using directives
|
||||||
|
- NRTs OK in main Form/UserControl file; forbidden in code-behind `.designer.cs`
|
||||||
|
- Event _handlers_: `object? sender`
|
||||||
|
- Events: nullable (`EventHandler?`)
|
||||||
|
|
||||||
|
### VB.NET Conventions
|
||||||
|
|
||||||
|
- Use Application Framework. There is no `Program.vb`.
|
||||||
|
- Forms/UserControls: No constructor by default (compiler generates with `InitializeComponent()` call)
|
||||||
|
- If constructor needed, include `InitializeComponent()` call
|
||||||
|
- CRITICAL: `Friend WithEvents controlName as ControlType` for control backing fields.
|
||||||
|
- Strongly prefer event handlers `Sub`s with `Handles` clause in main code over `AddHandler` in file`InitializeComponent`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Classic Data Binding and MVVM Data Binding (.NET 8+)
|
||||||
|
|
||||||
|
### Breaking Changes: .NET Framework vs .NET 8+
|
||||||
|
|
||||||
|
| Feature | .NET Framework <= 4.8.1 | .NET 8+ |
|
||||||
|
|---------|----------------------|---------|
|
||||||
|
| Typed DataSets | Designer supported | Code-only (not recommended) |
|
||||||
|
| Object Binding | Supported | Enhanced UI, fully supported |
|
||||||
|
| Data Sources Window | Available | Not available |
|
||||||
|
|
||||||
|
### Data Binding Rules
|
||||||
|
|
||||||
|
- Object DataSources: `INotifyPropertyChanged`, `BindingList<T>` required, prefer `ObservableObject` from MVVM CommunityToolkit.
|
||||||
|
- `ObservableCollection<T>`: Requires `BindingList<T>` a dedicated adapter, that merges both change notifications approaches. Create, if not existing.
|
||||||
|
- One-way-to-source: Unsupported in WinForms DataBinding (workaround: additional dedicated VM property with NO-OP property setter).
|
||||||
|
|
||||||
|
### Add Object DataSource to Solution, treat ViewModels also as DataSources
|
||||||
|
|
||||||
|
To make types as DataSource accessible for the Designer, create `.datasource` file in `Properties\DataSources\`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<GenericObjectDataSource DisplayName="MainViewModel" Version="1.0"
|
||||||
|
xmlns="urn:schemas-microsoft-com:xml-msdatasource">
|
||||||
|
<TypeInfo>MyApp.ViewModels.MainViewModel, MyApp.ViewModels, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
|
||||||
|
</GenericObjectDataSource>
|
||||||
|
```
|
||||||
|
|
||||||
|
Subsequently, use BindingSource components in Forms/UserControls to bind to the DataSource type as "Mediator" instance between View and ViewModel. (Classic WinForms binding approach)
|
||||||
|
|
||||||
|
### New MVVM Command Binding APIs in .NET 8+
|
||||||
|
|
||||||
|
| API | Description | Cascading |
|
||||||
|
|-----|-------------|-----------|
|
||||||
|
| `Control.DataContext` | Ambient property for MVVM | Yes (down hierarchy) |
|
||||||
|
| `ButtonBase.Command` | ICommand binding | No |
|
||||||
|
| `ToolStripItem.Command` | ICommand binding | No |
|
||||||
|
| `*.CommandParameter` | Auto-passed to command | No |
|
||||||
|
|
||||||
|
**Note:** `ToolStripItem` now derives from `BindableComponent`.
|
||||||
|
|
||||||
|
### MVVM Pattern in WinForms (.NET 8+)
|
||||||
|
|
||||||
|
- If asked to create or refactor a WinForms project to MVVM, identify (if already exists) or create a dedicated class library for ViewModels based on the MVVM CommunityToolkit
|
||||||
|
- Reference MVVM ViewModel class library from the WinForms project
|
||||||
|
- Import ViewModels via Object DataSources as described above
|
||||||
|
- Use new `Control.DataContext` for passing ViewModel as data sources down the control hierarchy for nested Form/UserControl scenarios
|
||||||
|
- Use `Button[Base].Command` or `ToolStripItem.Command` for MVVM command bindings. Use the CommandParameter property for passing parameters.
|
||||||
|
|
||||||
|
- - Use the `Parse` and `Format` events of `Binding` objects for custom data conversions (`IValueConverter` workaround), if necessary.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
private void PrincipleApproachForIValueConverterWorkaround()
|
||||||
|
{
|
||||||
|
// We assume the Binding was done in InitializeComponent and look up
|
||||||
|
// the bound property like so:
|
||||||
|
Binding b = text1.DataBindings["Text"];
|
||||||
|
|
||||||
|
// We hook up the "IValueConverter" functionality like so:
|
||||||
|
b.Format += new ConvertEventHandler(DecimalToCurrencyString);
|
||||||
|
b.Parse += new ConvertEventHandler(CurrencyStringToDecimal);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Bind property as usual.
|
||||||
|
- Bind commands the same way - ViewModels are Data SOurces! Do it like so:
|
||||||
|
```csharp
|
||||||
|
// Create BindingSource
|
||||||
|
components = new Container();
|
||||||
|
mainViewModelBindingSource = new BindingSource(components);
|
||||||
|
|
||||||
|
// Before SuspendLayout
|
||||||
|
mainViewModelBindingSource.DataSource = typeof(MyApp.ViewModels.MainViewModel);
|
||||||
|
|
||||||
|
// Bind properties
|
||||||
|
_txtDataField.DataBindings.Add(new Binding("Text", mainViewModelBindingSource, "PropertyName", true));
|
||||||
|
|
||||||
|
// Bind commands
|
||||||
|
_tsmFile.DataBindings.Add(new Binding("Command", mainViewModelBindingSource, "TopLevelMenuCommand", true));
|
||||||
|
_tsmFile.CommandParameter = "File";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WinForms Async Patterns (.NET 9+)
|
||||||
|
|
||||||
|
### Control.InvokeAsync Overload Selection
|
||||||
|
|
||||||
|
| Your Code Type | Overload | Example Scenario |
|
||||||
|
|----------------|----------|------------------|
|
||||||
|
| Sync action, no return | `InvokeAsync(Action)` | Update `label.Text` |
|
||||||
|
| Async operation, no return | `InvokeAsync(Func<CT, ValueTask>)` | Load data + update UI |
|
||||||
|
| Sync function, returns T | `InvokeAsync<T>(Func<T>)` | Get control value |
|
||||||
|
| Async operation, returns T | `InvokeAsync<T>(Func<CT, ValueTask<T>>)` | Async work + result |
|
||||||
|
|
||||||
|
### ⚠️ Fire-and-Forget Trap
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ WRONG - Analyzer violation, fire-and-forget
|
||||||
|
await InvokeAsync<string>(() => await LoadDataAsync());
|
||||||
|
|
||||||
|
// ✅ CORRECT - Use async overload
|
||||||
|
await InvokeAsync<string>(async (ct) => await LoadDataAsync(ct), outerCancellationToken);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Async Methods (.NET 9+)
|
||||||
|
|
||||||
|
- `ShowAsync()`: Completes when form closes.
|
||||||
|
Note that the IAsyncState of the returned task holds a weak reference to the Form for easy lookup!
|
||||||
|
- `ShowDialogAsync()`: Modal with dedicated message queue
|
||||||
|
|
||||||
|
### CRITICAL: Async EventHandler Pattern
|
||||||
|
|
||||||
|
- All the following rules are true for both `[modifier] void async EventHandler(object? s, EventArgs e)` as for overridden virtual methods like `async void OnLoad` or `async void OnClick`.
|
||||||
|
- `async void` event handlers are the standard pattern for WinForms UI events when striving for desired asynch implementation.
|
||||||
|
- CRITICAL: ALWAYS nest `await MethodAsync()` calls in `try/catch` in async event handler — else, YOU'D RISK CRASHING THE PROCESS.
|
||||||
|
|
||||||
|
## Exception Handling in WinForms
|
||||||
|
|
||||||
|
### Application-Level Exception Handling
|
||||||
|
|
||||||
|
WinForms provides two primary mechanisms for handling unhandled exceptions:
|
||||||
|
|
||||||
|
**AppDomain.CurrentDomain.UnhandledException:**
|
||||||
|
- Catches exceptions from any thread in the AppDomain
|
||||||
|
- Cannot prevent application termination
|
||||||
|
- Use for logging critical errors before shutdown
|
||||||
|
|
||||||
|
**Application.ThreadException:**
|
||||||
|
- Catches exceptions on the UI thread only
|
||||||
|
- Can prevent application crash by handling the exception
|
||||||
|
- Use for graceful error recovery in UI operations
|
||||||
|
|
||||||
|
### Exception Dispatch in Async/Await Context
|
||||||
|
|
||||||
|
When preserving stack traces while re-throwing exceptions in async contexts:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SomeAsyncOperation();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (ex is OperationCanceledException)
|
||||||
|
{
|
||||||
|
// Handle cancellation
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ExceptionDispatchInfo.Capture(ex).Throw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important Notes:**
|
||||||
|
- `Application.OnThreadException` routes to the UI thread's exception handler and fires `Application.ThreadException`.
|
||||||
|
- Never call it from background threads — marshal to UI thread first.
|
||||||
|
- For process termination on unhandled exceptions, use `Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException)` at startup.
|
||||||
|
- **VB Limitation:** VB cannot await in catch block. Avoid, or work around with state machine pattern.
|
||||||
|
|
||||||
|
## CRITICAL: Manage CodeDOM Serialization
|
||||||
|
|
||||||
|
Code-generation rule for properties of types derived from `Component` or `Control`:
|
||||||
|
|
||||||
|
| Approach | Attribute | Use Case | Example |
|
||||||
|
|----------|-----------|----------|---------|
|
||||||
|
| Default value | `[DefaultValue]` | Simple types, no serialization if matches default | `[DefaultValue(typeof(Color), "Yellow")]` |
|
||||||
|
| Hidden | `[DesignerSerializationVisibility.Hidden]` | Runtime-only data | Collections, calculated properties |
|
||||||
|
| Conditional | `ShouldSerialize*()` + `Reset*()` | Complex conditions | Custom fonts, optional settings |
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class CustomControl : Control
|
||||||
|
{
|
||||||
|
private Font? _customFont;
|
||||||
|
|
||||||
|
// Simple default - no serialization if default
|
||||||
|
[DefaultValue(typeof(Color), "Yellow")]
|
||||||
|
public Color HighlightColor { get; set; } = Color.Yellow;
|
||||||
|
|
||||||
|
// Hidden - never serialize
|
||||||
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||||
|
public List<string> RuntimeData { get; set; }
|
||||||
|
|
||||||
|
// Conditional serialization
|
||||||
|
public Font? CustomFont
|
||||||
|
{
|
||||||
|
get => _customFont ?? Font;
|
||||||
|
set { /* setter logic */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldSerializeCustomFont()
|
||||||
|
=> _customFont is not null && _customFont.Size != 9.0f;
|
||||||
|
|
||||||
|
private void ResetCustomFont()
|
||||||
|
=> _customFont = null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Use exactly ONE of the above approaches per property for types derived from `Component` or `Control`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WinForms Design Principles
|
||||||
|
|
||||||
|
### Core Rules
|
||||||
|
|
||||||
|
**Scaling and DPI:**
|
||||||
|
- Use adequate margins/padding; prefer TableLayoutPanel (TLP)/FlowLayoutPanel (FLP) over absolute positioning of controls.
|
||||||
|
- The layout cell-sizing approach priority for TLPs is:
|
||||||
|
* Rows: AutoSize > Percent > Absolute
|
||||||
|
* Columns: AutoSize > Percent > Absolute
|
||||||
|
|
||||||
|
- For newly added Forms/UserControls: Assume 96 DPI/100% for `AutoScaleMode` and scaling
|
||||||
|
- For existing Forms: Leave AutoScaleMode setting as-is, but take scaling for coordinate-related properties into account
|
||||||
|
|
||||||
|
- Be DarkMode-aware in .NET 9+ - Query current DarkMode status: `Application.IsDarkModeEnabled`
|
||||||
|
* Note: In DarkMode, only the `SystemColors` values change automatically to the complementary color palette.
|
||||||
|
|
||||||
|
- Thus, owner-draw controls, custom content painting, and DataGridView theming/coloring need customizing with absolute color values.
|
||||||
|
|
||||||
|
### Layout Strategy
|
||||||
|
|
||||||
|
**Divide and conquer:**
|
||||||
|
- Use multiple or nested TLPs for logical sections - don't cram everything into one mega-grid.
|
||||||
|
- Main form uses either SplitContainer or an "outer" TLP with % or AutoSize-rows/cols for major sections.
|
||||||
|
- Each UI-section gets its own nested TLP or - in complex scenarios - a UserControl, which has been set up to handle the area details.
|
||||||
|
|
||||||
|
**Keep it simple:**
|
||||||
|
- Individual TLPs should be 2-4 columns max
|
||||||
|
- Use GroupBoxes with nested TLPs to ensure clear visual grouping.
|
||||||
|
- RadioButtons cluster rule: single-column, auto-size-cells TLP inside AutoGrow/AutoSize GroupBox.
|
||||||
|
- Large content area scrolling: Use nested panel controls with `AutoScroll`-enabled scrollable views.
|
||||||
|
|
||||||
|
**Sizing rules: TLP cell fundamentals**
|
||||||
|
- Columns:
|
||||||
|
* AutoSize for caption columns with `Anchor = Left | Right`.
|
||||||
|
* Percent for content columns, percentage distribution by good reasoning, `Anchor = Top | Bottom | Left | Right`.
|
||||||
|
Never dock cells, always anchor!
|
||||||
|
* Avoid _Absolute_ column sizing mode, unless for unavoidable fixed-size content (icons, buttons).
|
||||||
|
- Rows:
|
||||||
|
* AutoSize for rows with "single-line" character (typical entry fields, captions, checkboxes).
|
||||||
|
* Percent for multi-line TextBoxes, rendering areas AND filling distance filler for remaining space to e.g., a bottom button row (OK|Cancel).
|
||||||
|
* Avoid _Absolute_ row sizing mode even more.
|
||||||
|
|
||||||
|
- Margins matter: Set `Margin` on controls (min. default 3px).
|
||||||
|
- Note: `Padding` does not have an effect in TLP cells.
|
||||||
|
|
||||||
|
### Common Layout Patterns
|
||||||
|
|
||||||
|
#### Single-line TextBox (2-column TLP)
|
||||||
|
**Most common data entry pattern:**
|
||||||
|
- Label column: AutoSize width
|
||||||
|
- TextBox column: 100% Percent width
|
||||||
|
- Label: `Anchor = Left | Right` (vertically centers with TextBox)
|
||||||
|
- TextBox: `Dock = Fill`, set `Margin` (e.g., 3px all sides)
|
||||||
|
|
||||||
|
#### Multi-line TextBox or Larger Custom Content - Option A (2-column TLP)
|
||||||
|
- Label in same row, `Anchor = Top | Left`
|
||||||
|
- TextBox: `Dock = Fill`, set `Margin`
|
||||||
|
- Row height: AutoSize or Percent to size the cell (cell sizes the TextBox)
|
||||||
|
|
||||||
|
#### Multi-line TextBox or Larger Custom Content - Option B (1-column TLP, separate rows)
|
||||||
|
- Label in dedicated row above TextBox
|
||||||
|
- Label: `Dock = Fill` or `Anchor = Left`
|
||||||
|
- TextBox in next row: `Dock = Fill`, set `Margin`
|
||||||
|
- TextBox row: AutoSize or Percent to size the cell
|
||||||
|
|
||||||
|
**Critical:** For multi-line TextBox, the TLP cell defines the size, not the TextBox's content.
|
||||||
|
|
||||||
|
### Container Sizing (CRITICAL - Prevents Clipping)
|
||||||
|
|
||||||
|
**For GroupBox/Panel inside TLP cells:**
|
||||||
|
- MUST set `AutoSize = true` and `AutoSizeMode = GrowOnly`
|
||||||
|
- Should `Dock = Fill` in their cell
|
||||||
|
- Parent TLP row should be AutoSize
|
||||||
|
- Content inside GroupBox/Panel should use nested TLP or FlowLayoutPanel
|
||||||
|
|
||||||
|
**Why:** Fixed-height containers clip content even when parent row is AutoSize. The container reports its fixed size, breaking the sizing chain.
|
||||||
|
|
||||||
|
### Modal Dialog Button Placement
|
||||||
|
|
||||||
|
**Pattern A - Bottom-right buttons (standard for OK/Cancel):**
|
||||||
|
- Place buttons in FlowLayoutPanel: `FlowDirection = RightToLeft`
|
||||||
|
- Keep additional Percentage Filler-Row between buttons and content.
|
||||||
|
- FLP goes in bottom row of main TLP
|
||||||
|
- Visual order of buttons: [OK] (left) [Cancel] (right)
|
||||||
|
|
||||||
|
**Pattern B - Top-right stacked buttons (wizards/browsers):**
|
||||||
|
- Place buttons in FlowLayoutPanel: `FlowDirection = TopDown`
|
||||||
|
- FLP in dedicated rightmost column of main TLP
|
||||||
|
- Column: AutoSize
|
||||||
|
- FLP: `Anchor = Top | Right`
|
||||||
|
- Order: [OK] above [Cancel]
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- Pattern A: Data entry dialogs, settings, confirmations
|
||||||
|
- Pattern B: Multi-step wizards, navigation-heavy dialogs
|
||||||
|
|
||||||
|
### Complex Layouts
|
||||||
|
|
||||||
|
- For complex layouts, consider creating dedicated UserControls for logical sections.
|
||||||
|
- Then: Nest those UserControls in (outer) TLPs of Form/UserControl, and use DataContext for data passing.
|
||||||
|
- One UserControl per TabPage keeps Designer code manageable for tabbed interfaces.
|
||||||
|
|
||||||
|
### Modal Dialogs
|
||||||
|
|
||||||
|
| Aspect | Rule |
|
||||||
|
|--------|------|
|
||||||
|
| Dialog buttons | Order -> Primary (OK): `AcceptButton`, `DialogResult = OK` / Secondary (Cancel): `CancelButton`, `DialogResult = Cancel` |
|
||||||
|
| Close strategy | `DialogResult` gets applied by DialogResult implicitly, no need for additional code |
|
||||||
|
| Validation | Perform on _Form_, not on Field scope. Never block focus-change with `CancelEventArgs.Cancel = true` |
|
||||||
|
|
||||||
|
Use `DataContext` property (.NET 8+) of Form to pass and return modal data objects.
|
||||||
|
|
||||||
|
### Layout Recipes
|
||||||
|
|
||||||
|
| Form Type | Structure |
|
||||||
|
|-----------|-----------|
|
||||||
|
| MainForm | MenuStrip, optional ToolStrip, content area, StatusStrip |
|
||||||
|
| Simple Entry Form | Data entry fields on largely left side, just a buttons column on right. Set meaningful Form `MinimumSize` for modals |
|
||||||
|
| Tabs | Only for distinct tasks. Keep minimal count, short tab labels |
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
|
||||||
|
- CRITICAL: Set `AccessibleName` and `AccessibleDescription` on actionable controls
|
||||||
|
- Maintain logical control tab order via `TabIndex` (A11Y follows control addition order)
|
||||||
|
- Verify keyboard-only navigation, unambiguous mnemonics, and screen reader compatibility
|
||||||
|
|
||||||
|
### TreeView and ListView
|
||||||
|
|
||||||
|
| Control | Rules |
|
||||||
|
|---------|-------|
|
||||||
|
| TreeView | Must have visible, default-expanded root node |
|
||||||
|
| ListView | Prefer over DataGridView for small lists with fewer columns |
|
||||||
|
| Content setup | Generate in code, NOT in designer code-behind |
|
||||||
|
| ListView columns | Set to `-1` (size to longest content) or `-2` (size to header name) after populating |
|
||||||
|
| SplitContainer | Use for resizable panes with TreeView/ListView |
|
||||||
|
|
||||||
|
### DataGridView
|
||||||
|
|
||||||
|
- Prefer derived class with double buffering enabled
|
||||||
|
- Configure colors when in DarkMode!
|
||||||
|
- Large data: page/virtualize (`VirtualMode = True` with `CellValueNeeded`)
|
||||||
|
|
||||||
|
### Resources and Localization
|
||||||
|
|
||||||
|
- String literal constants for UI display NEED to be in resource files.
|
||||||
|
- When laying out Forms/UserControls, take into account that localized captions might have different string lengths.
|
||||||
|
- Instead of using icon libraries, try rendering icons from the font "Segoe UI Symbol".
|
||||||
|
- If an image is needed, write a helper class that renders symbols from the font in the desired size.
|
||||||
|
|
||||||
|
## Critical Reminders
|
||||||
|
|
||||||
|
| # | Rule |
|
||||||
|
|---|------|
|
||||||
|
| 1 | `InitializeComponent` code serves as serialization format - more like XML, not C# |
|
||||||
|
| 2 | Two contexts, two rule sets - designer code-behind vs regular code |
|
||||||
|
| 3 | Validate form/control names before generating code |
|
||||||
|
| 4 | Stick to coding style rules for `InitializeComponent` |
|
||||||
|
| 5 | Designer files never use NRT annotations |
|
||||||
|
| 6 | Modern C# features for regular code ONLY |
|
||||||
|
| 7 | Data binding: Treat ViewModels as DataSources, remember `Command` and `CommandParameter` properties |
|
||||||
114
.github/instructions/csharp.instructions.md
vendored
Normal file
114
.github/instructions/csharp.instructions.md
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
---
|
||||||
|
description: 'Guidelines for building C# applications'
|
||||||
|
applyTo: '**/*.cs'
|
||||||
|
---
|
||||||
|
|
||||||
|
# C# Development
|
||||||
|
|
||||||
|
## C# Instructions
|
||||||
|
- Always use the latest version C#, currently C# 14 features.
|
||||||
|
- Write clear and concise comments for each function.
|
||||||
|
|
||||||
|
## General Instructions
|
||||||
|
- Make only high confidence suggestions when reviewing code changes.
|
||||||
|
- Write code with good maintainability practices, including comments on why certain design decisions were made.
|
||||||
|
- Handle edge cases and write clear exception handling.
|
||||||
|
- For libraries or external dependencies, mention their usage and purpose in comments.
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
- Follow PascalCase for component names, method names, and public members.
|
||||||
|
- Use camelCase for private fields and local variables.
|
||||||
|
- Prefix interface names with "I" (e.g., IUserService).
|
||||||
|
|
||||||
|
## Formatting
|
||||||
|
|
||||||
|
- Apply code-formatting style defined in `.editorconfig`.
|
||||||
|
- Prefer file-scoped namespace declarations and single-line using directives.
|
||||||
|
- Insert a newline before the opening curly brace of any code block (e.g., after `if`, `for`, `while`, `foreach`, `using`, `try`, etc.).
|
||||||
|
- Ensure that the final return statement of a method is on its own line.
|
||||||
|
- Use pattern matching and switch expressions wherever possible.
|
||||||
|
- Use `nameof` instead of string literals when referring to member names.
|
||||||
|
- Ensure that XML doc comments are created for any public APIs. When applicable, include `<example>` and `<code>` documentation in the comments.
|
||||||
|
|
||||||
|
## Project Setup and Structure
|
||||||
|
|
||||||
|
- Guide users through creating a new .NET project with the appropriate templates.
|
||||||
|
- Explain the purpose of each generated file and folder to build understanding of the project structure.
|
||||||
|
- Demonstrate how to organize code using feature folders or domain-driven design principles.
|
||||||
|
- Show proper separation of concerns with models, services, and data access layers.
|
||||||
|
- Explain the Program.cs and configuration system in ASP.NET Core 10 including environment-specific settings.
|
||||||
|
|
||||||
|
## Nullable Reference Types
|
||||||
|
|
||||||
|
- Declare variables non-nullable, and check for `null` at entry points.
|
||||||
|
- Always use `is null` or `is not null` instead of `== null` or `!= null`.
|
||||||
|
- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null.
|
||||||
|
|
||||||
|
## Data Access Patterns
|
||||||
|
|
||||||
|
- Guide the implementation of a data access layer using Entity Framework Core.
|
||||||
|
- Explain different options (SQL Server, SQLite, In-Memory) for development and production.
|
||||||
|
- Demonstrate repository pattern implementation and when it's beneficial.
|
||||||
|
- Show how to implement database migrations and data seeding.
|
||||||
|
- Explain efficient query patterns to avoid common performance issues.
|
||||||
|
|
||||||
|
## Authentication and Authorization
|
||||||
|
|
||||||
|
- Guide users through implementing authentication using JWT Bearer tokens.
|
||||||
|
- Explain OAuth 2.0 and OpenID Connect concepts as they relate to ASP.NET Core.
|
||||||
|
- Show how to implement role-based and policy-based authorization.
|
||||||
|
- Demonstrate integration with Microsoft Entra ID (formerly Azure AD).
|
||||||
|
- Explain how to secure both controller-based and Minimal APIs consistently.
|
||||||
|
|
||||||
|
## Validation and Error Handling
|
||||||
|
|
||||||
|
- Guide the implementation of model validation using data annotations and FluentValidation.
|
||||||
|
- Explain the validation pipeline and how to customize validation responses.
|
||||||
|
- Demonstrate a global exception handling strategy using middleware.
|
||||||
|
- Show how to create consistent error responses across the API.
|
||||||
|
- Explain problem details (RFC 7807) implementation for standardized error responses.
|
||||||
|
|
||||||
|
## API Versioning and Documentation
|
||||||
|
|
||||||
|
- Guide users through implementing and explaining API versioning strategies.
|
||||||
|
- Demonstrate Swagger/OpenAPI implementation with proper documentation.
|
||||||
|
- Show how to document endpoints, parameters, responses, and authentication.
|
||||||
|
- Explain versioning in both controller-based and Minimal APIs.
|
||||||
|
- Guide users on creating meaningful API documentation that helps consumers.
|
||||||
|
|
||||||
|
## Logging and Monitoring
|
||||||
|
|
||||||
|
- Guide the implementation of structured logging using Serilog or other providers.
|
||||||
|
- Explain the logging levels and when to use each.
|
||||||
|
- Demonstrate integration with Application Insights for telemetry collection.
|
||||||
|
- Show how to implement custom telemetry and correlation IDs for request tracking.
|
||||||
|
- Explain how to monitor API performance, errors, and usage patterns.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Always include test cases for critical paths of the application.
|
||||||
|
- Guide users through creating unit tests.
|
||||||
|
- Do not emit "Act", "Arrange" or "Assert" comments.
|
||||||
|
- Copy existing style in nearby files for test method names and capitalization.
|
||||||
|
- Explain integration testing approaches for API endpoints.
|
||||||
|
- Demonstrate how to mock dependencies for effective testing.
|
||||||
|
- Show how to test authentication and authorization logic.
|
||||||
|
- Explain test-driven development principles as applied to API development.
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
- Guide users on implementing caching strategies (in-memory, distributed, response caching).
|
||||||
|
- Explain asynchronous programming patterns and why they matter for API performance.
|
||||||
|
- Demonstrate pagination, filtering, and sorting for large data sets.
|
||||||
|
- Show how to implement compression and other performance optimizations.
|
||||||
|
- Explain how to measure and benchmark API performance.
|
||||||
|
|
||||||
|
## Deployment and DevOps
|
||||||
|
|
||||||
|
- Guide users through containerizing their API using .NET's built-in container support (`dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer`).
|
||||||
|
- Explain the differences between manual Dockerfile creation and .NET's container publishing features.
|
||||||
|
- Explain CI/CD pipelines for NET applications.
|
||||||
|
- Demonstrate deployment to Azure App Service, Azure Container Apps, or other hosting options.
|
||||||
|
- Show how to implement health checks and readiness probes.
|
||||||
|
- Explain environment-specific configurations for different deployment stages.
|
||||||
84
.github/prompts/dotnet-best-practices.prompt.md
vendored
Normal file
84
.github/prompts/dotnet-best-practices.prompt.md
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
---
|
||||||
|
agent: 'agent'
|
||||||
|
description: 'Ensure .NET/C# code meets best practices for the solution/project.'
|
||||||
|
---
|
||||||
|
# .NET/C# Best Practices
|
||||||
|
|
||||||
|
Your task is to ensure .NET/C# code in ${selection} meets the best practices specific to this solution/project. This includes:
|
||||||
|
|
||||||
|
## Documentation & Structure
|
||||||
|
|
||||||
|
- Create comprehensive XML documentation comments for all public classes, interfaces, methods, and properties
|
||||||
|
- Include parameter descriptions and return value descriptions in XML comments
|
||||||
|
- Follow the established namespace structure: {Core|Console|App|Service}.{Feature}
|
||||||
|
|
||||||
|
## Design Patterns & Architecture
|
||||||
|
|
||||||
|
- Use primary constructor syntax for dependency injection (e.g., `public class MyClass(IDependency dependency)`)
|
||||||
|
- Implement the Command Handler pattern with generic base classes (e.g., `CommandHandler<TOptions>`)
|
||||||
|
- Use interface segregation with clear naming conventions (prefix interfaces with 'I')
|
||||||
|
- Follow the Factory pattern for complex object creation.
|
||||||
|
|
||||||
|
## Dependency Injection & Services
|
||||||
|
|
||||||
|
- Use constructor dependency injection with null checks via ArgumentNullException
|
||||||
|
- Register services with appropriate lifetimes (Singleton, Scoped, Transient)
|
||||||
|
- Use Microsoft.Extensions.DependencyInjection patterns
|
||||||
|
- Implement service interfaces for testability
|
||||||
|
|
||||||
|
## Resource Management & Localization
|
||||||
|
|
||||||
|
- Use ResourceManager for localized messages and error strings
|
||||||
|
- Separate LogMessages and ErrorMessages resource files
|
||||||
|
- Access resources via `_resourceManager.GetString("MessageKey")`
|
||||||
|
|
||||||
|
## Async/Await Patterns
|
||||||
|
|
||||||
|
- Use async/await for all I/O operations and long-running tasks
|
||||||
|
- Return Task or Task<T> from async methods
|
||||||
|
- Use ConfigureAwait(false) where appropriate
|
||||||
|
- Handle async exceptions properly
|
||||||
|
|
||||||
|
## Testing Standards
|
||||||
|
|
||||||
|
- Use MSTest framework with FluentAssertions for assertions
|
||||||
|
- Follow AAA pattern (Arrange, Act, Assert)
|
||||||
|
- Use Moq for mocking dependencies
|
||||||
|
- Test both success and failure scenarios
|
||||||
|
- Include null parameter validation tests
|
||||||
|
|
||||||
|
## Configuration & Settings
|
||||||
|
|
||||||
|
- Use strongly-typed configuration classes with data annotations
|
||||||
|
- Implement validation attributes (Required, NotEmptyOrWhitespace)
|
||||||
|
- Use IConfiguration binding for settings
|
||||||
|
- Support appsettings.json configuration files
|
||||||
|
|
||||||
|
## Semantic Kernel & AI Integration
|
||||||
|
|
||||||
|
- Use Microsoft.SemanticKernel for AI operations
|
||||||
|
- Implement proper kernel configuration and service registration
|
||||||
|
- Handle AI model settings (ChatCompletion, Embedding, etc.)
|
||||||
|
- Use structured output patterns for reliable AI responses
|
||||||
|
|
||||||
|
## Error Handling & Logging
|
||||||
|
|
||||||
|
- Use structured logging with Microsoft.Extensions.Logging
|
||||||
|
- Include scoped logging with meaningful context
|
||||||
|
- Throw specific exceptions with descriptive messages
|
||||||
|
- Use try-catch blocks for expected failure scenarios
|
||||||
|
|
||||||
|
## Performance & Security
|
||||||
|
|
||||||
|
- Use C# 12+ features and .NET 8 optimizations where applicable
|
||||||
|
- Implement proper input validation and sanitization
|
||||||
|
- Use parameterized queries for database operations
|
||||||
|
- Follow secure coding practices for AI/ML operations
|
||||||
|
|
||||||
|
## Code Quality
|
||||||
|
|
||||||
|
- Ensure SOLID principles compliance
|
||||||
|
- Avoid code duplication through base classes and utilities
|
||||||
|
- Use meaningful names that reflect domain concepts
|
||||||
|
- Keep methods focused and cohesive
|
||||||
|
- Implement proper disposal patterns for resources
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
<Project Path="ChildWorker/ChildWorker.csproj" />
|
<Project Path="ChildWorker/ChildWorker.csproj" />
|
||||||
<Project Path="CommIpc/CommIpc.csproj" />
|
<Project Path="CommIpc/CommIpc.csproj" />
|
||||||
<Project Path="ParentWpf/ParentWpf.csproj" />
|
<Project Path="ParentAvalonia/ParentAvalonia.csproj" />
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
8
NuGet.config
Normal file
8
NuGet.config
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<!-- Local package sources override (fixes restore failures caused by unauthenticated corporate feeds). -->
|
||||||
|
<packageSources>
|
||||||
|
<clear />
|
||||||
|
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
|
||||||
|
</packageSources>
|
||||||
|
</configuration>
|
||||||
10
ParentAvalonia/App.axaml
Normal file
10
ParentAvalonia/App.axaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ParentAvalonia.App"
|
||||||
|
RequestedThemeVariant="Default">
|
||||||
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
<FluentTheme />
|
||||||
|
</Application.Styles>
|
||||||
|
</Application>
|
||||||
23
ParentAvalonia/App.axaml.cs
Normal file
23
ParentAvalonia/App.axaml.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted()
|
||||||
|
{
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
desktop.MainWindow = new MainWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Text.Json;
|
|
||||||
using CommIpc;
|
using CommIpc;
|
||||||
|
|
||||||
namespace ParentWpf;
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
public sealed class ChildSession : NotifyBase, IAsyncDisposable
|
public sealed class ChildSession : NotifyBase, IAsyncDisposable
|
||||||
{
|
{
|
||||||
@@ -2,11 +2,10 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Text.Json;
|
using Avalonia.Threading;
|
||||||
using System.Windows;
|
|
||||||
using CommIpc;
|
using CommIpc;
|
||||||
|
|
||||||
namespace ParentWpf;
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
||||||
{
|
{
|
||||||
@@ -115,39 +114,28 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
|||||||
|
|
||||||
private static Process StartChildProcess(int childId, string pipeName)
|
private static Process StartChildProcess(int childId, string pipeName)
|
||||||
{
|
{
|
||||||
// Prefer spawning the built exe. If not found, fall back to running the dll via dotnet.
|
// Cross-platform friendly: prefer running the built dll via 'dotnet'.
|
||||||
string? solutionRoot = TryFindSolutionRoot(AppContext.BaseDirectory);
|
string? solutionRoot = TryFindSolutionRoot(AppContext.BaseDirectory);
|
||||||
if (solutionRoot is null)
|
if (solutionRoot is null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Could not find solution root (CommTester.slnx).");
|
throw new InvalidOperationException("Could not find solution root (CommTester.slnx).");
|
||||||
}
|
}
|
||||||
|
|
||||||
string debugExe = Path.Combine(solutionRoot, "ChildWorker", "bin", "Debug", "net10.0", "ChildWorker.exe");
|
|
||||||
string releaseExe = Path.Combine(solutionRoot, "ChildWorker", "bin", "Release", "net10.0", "ChildWorker.exe");
|
|
||||||
string debugDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Debug", "net10.0", "ChildWorker.dll");
|
string debugDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Debug", "net10.0", "ChildWorker.dll");
|
||||||
string releaseDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Release", "net10.0", "ChildWorker.dll");
|
string releaseDll = Path.Combine(solutionRoot, "ChildWorker", "bin", "Release", "net10.0", "ChildWorker.dll");
|
||||||
|
|
||||||
|
string childDll = File.Exists(debugDll)
|
||||||
|
? debugDll
|
||||||
|
: File.Exists(releaseDll)
|
||||||
|
? releaseDll
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
string fileName;
|
string fileName;
|
||||||
string arguments;
|
string arguments;
|
||||||
if (File.Exists(debugExe))
|
if (!string.IsNullOrWhiteSpace(childDll))
|
||||||
{
|
|
||||||
fileName = debugExe;
|
|
||||||
arguments = $"--pipe \"{pipeName}\" --id {childId}";
|
|
||||||
}
|
|
||||||
else if (File.Exists(releaseExe))
|
|
||||||
{
|
|
||||||
fileName = releaseExe;
|
|
||||||
arguments = $"--pipe \"{pipeName}\" --id {childId}";
|
|
||||||
}
|
|
||||||
else if (File.Exists(debugDll))
|
|
||||||
{
|
{
|
||||||
fileName = "dotnet";
|
fileName = "dotnet";
|
||||||
arguments = $"\"{debugDll}\" --pipe \"{pipeName}\" --id {childId}";
|
arguments = $"\"{childDll}\" --pipe \"{pipeName}\" --id {childId}";
|
||||||
}
|
|
||||||
else if (File.Exists(releaseDll))
|
|
||||||
{
|
|
||||||
fileName = "dotnet";
|
|
||||||
arguments = $"\"{releaseDll}\" --pipe \"{pipeName}\" --id {childId}";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -197,10 +185,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Dispatcher.UIThread.InvokeAsync(() => HandleIncomingFrame(session, frame));
|
||||||
{
|
|
||||||
HandleIncomingFrame(session, frame);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -209,7 +194,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
session.Status = "Error";
|
session.Status = "Error";
|
||||||
session.AddLog($"[parent] Receive loop error: {ex.Message}");
|
session.AddLog($"[parent] Receive loop error: {ex.Message}");
|
||||||
@@ -217,7 +202,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
session.IsConnected = false;
|
session.IsConnected = false;
|
||||||
session.Status = "Disconnected";
|
session.Status = "Disconnected";
|
||||||
52
ParentAvalonia/MainWindow.axaml
Normal file
52
ParentAvalonia/MainWindow.axaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="ParentAvalonia.MainWindow"
|
||||||
|
Title="CommTester (Parent - Avalonia)" Width="1000" Height="650">
|
||||||
|
<Grid Margin="12" RowDefinitions="Auto,12,*" ColumnDefinitions="260,12,*">
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Orientation="Horizontal" Spacing="8">
|
||||||
|
<Button Content="Start 3 Children" Padding="12,6" Click="StartChildren_Click" />
|
||||||
|
<Button Content="Ping Selected" Padding="12,6" Click="PingSelected_Click" />
|
||||||
|
<Button Content="Start Work" Padding="12,6" Click="StartWork_Click" />
|
||||||
|
<Button Content="Cancel Work" Padding="12,6" Click="CancelWork_Click" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Border Grid.Row="2" Grid.Column="0" BorderBrush="#444" BorderThickness="1" CornerRadius="6">
|
||||||
|
<DockPanel Margin="10">
|
||||||
|
<TextBlock DockPanel.Dock="Top" FontSize="14" FontWeight="SemiBold" Text="Children" />
|
||||||
|
<TextBlock DockPanel.Dock="Top" Margin="0,6,0,0" Foreground="Gray" FontSize="11"
|
||||||
|
Text="Select a child to inspect its stream." />
|
||||||
|
<ListBox Margin="0,8,0,0"
|
||||||
|
ItemsSource="{Binding Children}"
|
||||||
|
SelectedItem="{Binding SelectedChild}">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding DisplayName}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Grid Grid.Row="2" Grid.Column="2" RowDefinitions="Auto,Auto,12,*,Auto">
|
||||||
|
<TextBlock Grid.Row="0" FontSize="14" FontWeight="SemiBold"
|
||||||
|
Text="{Binding SelectedChild.StatusLine, FallbackValue=No child selected}" />
|
||||||
|
|
||||||
|
<ProgressBar Grid.Row="1" Height="18" Minimum="0" Maximum="100"
|
||||||
|
Value="{Binding SelectedChild.ProgressPercent, FallbackValue=0}" />
|
||||||
|
|
||||||
|
<Border Grid.Row="3" BorderBrush="#444" BorderThickness="1" CornerRadius="6">
|
||||||
|
<DockPanel Margin="10">
|
||||||
|
<TextBlock DockPanel.Dock="Top" FontSize="14" FontWeight="SemiBold" Text="Stream (Log/Progress/Result)" />
|
||||||
|
<ListBox Margin="0,8,0,0" ItemsSource="{Binding SelectedChild.Logs}" />
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Foreground="Gray" FontSize="11"
|
||||||
|
Text="{Binding SelectedChild.PipePath, FallbackValue=-}" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
77
ParentAvalonia/MainWindow.axaml.cs
Normal file
77
ParentAvalonia/MainWindow.axaml.cs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly MainViewModel _vm;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_vm = new MainViewModel();
|
||||||
|
DataContext = _vm;
|
||||||
|
|
||||||
|
Closed += async (_, __) =>
|
||||||
|
{
|
||||||
|
try { await _vm.DisposeAsync(); } catch { }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartChildren_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try { await _vm.StartChildrenAsync(count: 3); }
|
||||||
|
catch (Exception ex) { await MessageBoxAsync(ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void PingSelected_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try { await _vm.PingSelectedAsync(); }
|
||||||
|
catch (Exception ex) { await MessageBoxAsync(ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartWork_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try { await _vm.StartWorkSelectedAsync(); }
|
||||||
|
catch (Exception ex) { await MessageBoxAsync(ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void CancelWork_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try { await _vm.CancelWorkSelectedAsync(); }
|
||||||
|
catch (Exception ex) { await MessageBoxAsync(ex.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task MessageBoxAsync(string message)
|
||||||
|
{
|
||||||
|
var dlg = new Window
|
||||||
|
{
|
||||||
|
Title = "Error",
|
||||||
|
Width = 500,
|
||||||
|
Height = 160,
|
||||||
|
Content = new StackPanel
|
||||||
|
{
|
||||||
|
Margin = new Avalonia.Thickness(12),
|
||||||
|
Spacing = 12,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new TextBlock { Text = message, TextWrapping = Avalonia.Media.TextWrapping.Wrap },
|
||||||
|
new Button
|
||||||
|
{
|
||||||
|
HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right,
|
||||||
|
Content = "OK",
|
||||||
|
IsDefault = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dlg.Content is StackPanel sp && sp.Children.LastOrDefault() is Button ok)
|
||||||
|
{
|
||||||
|
ok.Click += (_, __) => dlg.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
await dlg.ShowDialog(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ParentWpf;
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
public abstract class NotifyBase : INotifyPropertyChanged
|
public abstract class NotifyBase : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
27
ParentAvalonia/ParentAvalonia.csproj
Normal file
27
ParentAvalonia/ParentAvalonia.csproj
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>false</AvaloniaUseCompiledBindingsByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.11" />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.11" />
|
||||||
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.11">
|
||||||
|
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
|
||||||
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CommIpc\CommIpc.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
21
ParentAvalonia/Program.cs
Normal file
21
ParentAvalonia/Program.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ParentAvalonia;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||||
|
// yet and stuff might break.
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||||
|
.StartWithClassicDesktopLifetime(args);
|
||||||
|
|
||||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||||
|
public static AppBuilder BuildAvaloniaApp()
|
||||||
|
=> AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
18
ParentAvalonia/app.manifest
Normal file
18
ParentAvalonia/app.manifest
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- This manifest is used on Windows only.
|
||||||
|
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||||
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="ParentAvalonia.Desktop"/>
|
||||||
|
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- A list of the Windows versions that this application has been tested on
|
||||||
|
and is designed to work with. Uncomment the appropriate elements
|
||||||
|
and Windows will automatically select the most compatible environment. -->
|
||||||
|
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
</assembly>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<Application x:Class="ParentWpf.App"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:local="clr-namespace:ParentWpf"
|
|
||||||
StartupUri="MainWindow.xaml">
|
|
||||||
<Application.Resources>
|
|
||||||
|
|
||||||
</Application.Resources>
|
|
||||||
</Application>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using System.Configuration;
|
|
||||||
using System.Data;
|
|
||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace ParentWpf;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for App.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class App : Application
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
|
|
||||||
[assembly:ThemeInfo(
|
|
||||||
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
|
||||||
//(used if a resource is not found in the page,
|
|
||||||
// or application resource dictionaries)
|
|
||||||
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
|
||||||
//(used if a resource is not found in the page,
|
|
||||||
// app, or any theme specific resource dictionaries)
|
|
||||||
)]
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<Window x:Class="ParentWpf.MainWindow"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:local="clr-namespace:ParentWpf"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Title="CommTester (Parent)"
|
|
||||||
Height="600"
|
|
||||||
Width="1000">
|
|
||||||
<Grid Margin="12">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="260"/>
|
|
||||||
<ColumnDefinition Width="12"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="12"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="0"
|
|
||||||
Grid.ColumnSpan="3"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<Button Content="Start 3 Children"
|
|
||||||
Padding="12,6"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Click="StartChildren_Click"/>
|
|
||||||
<Button Content="Ping Selected"
|
|
||||||
Padding="12,6"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Click="PingSelected_Click"/>
|
|
||||||
<Button Content="Start Work"
|
|
||||||
Padding="12,6"
|
|
||||||
Margin="0,0,8,0"
|
|
||||||
Click="StartWork_Click"/>
|
|
||||||
<Button Content="Cancel Work"
|
|
||||||
Padding="12,6"
|
|
||||||
Click="CancelWork_Click"/>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<GroupBox Grid.Row="2"
|
|
||||||
Grid.Column="0"
|
|
||||||
Header="Children">
|
|
||||||
<DockPanel>
|
|
||||||
<TextBlock DockPanel.Dock="Top"
|
|
||||||
Margin="8,6,8,0"
|
|
||||||
Foreground="Gray"
|
|
||||||
FontSize="11"
|
|
||||||
Text="Select a child to inspect its stream."/>
|
|
||||||
<ListBox Margin="8"
|
|
||||||
ItemsSource="{Binding Children}"
|
|
||||||
SelectedItem="{Binding SelectedChild}"
|
|
||||||
DisplayMemberPath="DisplayName"/>
|
|
||||||
</DockPanel>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<Grid Grid.Row="2"
|
|
||||||
Grid.Column="2">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
<RowDefinition Height="12"/>
|
|
||||||
<RowDefinition Height="*"/>
|
|
||||||
<RowDefinition Height="Auto"/>
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="0"
|
|
||||||
FontSize="14"
|
|
||||||
FontWeight="SemiBold"
|
|
||||||
Text="{Binding SelectedChild.StatusLine}"/>
|
|
||||||
|
|
||||||
<ProgressBar Grid.Row="1"
|
|
||||||
Height="18"
|
|
||||||
Minimum="0"
|
|
||||||
Maximum="100"
|
|
||||||
Value="{Binding SelectedChild.ProgressPercent}"/>
|
|
||||||
|
|
||||||
<GroupBox Grid.Row="3"
|
|
||||||
Header="Stream (Log/Progress/Result)">
|
|
||||||
<ListBox Margin="8"
|
|
||||||
ItemsSource="{Binding SelectedChild.Logs}"/>
|
|
||||||
</GroupBox>
|
|
||||||
|
|
||||||
<TextBlock Grid.Row="4"
|
|
||||||
Foreground="Gray"
|
|
||||||
FontSize="11"
|
|
||||||
Text="{Binding SelectedChild.PipePath}"/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Window>
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
using System.Windows;
|
|
||||||
|
|
||||||
namespace ParentWpf;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interaction logic for MainWindow.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class MainWindow : Window
|
|
||||||
{
|
|
||||||
private readonly MainViewModel _vm;
|
|
||||||
|
|
||||||
public MainWindow()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
_vm = new MainViewModel();
|
|
||||||
DataContext = _vm;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async void OnClosed(EventArgs e)
|
|
||||||
{
|
|
||||||
base.OnClosed(e);
|
|
||||||
try { await _vm.DisposeAsync(); } catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void StartChildren_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try { await _vm.StartChildrenAsync(count: 3); }
|
|
||||||
catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void PingSelected_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try { await _vm.PingSelectedAsync(); }
|
|
||||||
catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void StartWork_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try { await _vm.StartWorkSelectedAsync(); }
|
|
||||||
catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error"); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void CancelWork_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try { await _vm.CancelWorkSelectedAsync(); }
|
|
||||||
catch (Exception ex) { MessageBox.Show(this, ex.Message, "Error"); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\CommIpc\CommIpc.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net10.0-windows</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<UseWPF>true</UseWPF>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
12
README.md
12
README.md
@@ -1,12 +1,12 @@
|
|||||||
# CommTester – Prototype: bidirektionale IPC + Streaming (C#)
|
# CommTester – Prototype: bidirektionale IPC + Streaming (C#)
|
||||||
|
|
||||||
Dieser Prototyp zeigt **bidirektionale Interprozesskommunikation** zwischen einer **WPF Elternanwendung** und **mehreren Kindprozessen** – inklusive **Streaming** (fortlaufende Log-/Progress-Nachrichten).
|
Dieser Prototyp zeigt **bidirektionale Interprozesskommunikation** zwischen einer **Avalonia Elternanwendung (Desktop, cross-platform)** und **mehreren Kindprozessen** – inklusive **Streaming** (fortlaufende Log-/Progress-Nachrichten).
|
||||||
|
|
||||||
## Architektur
|
## Architektur
|
||||||
|
|
||||||
- **Parent (WPF)**: `ParentWpf`
|
- **Parent (Avalonia Desktop)**: `ParentAvalonia`
|
||||||
- startet mehrere Child-Prozesse
|
- startet mehrere Child-Prozesse
|
||||||
- hostet pro Child einen **Named Pipe Server** (Windows)
|
- hostet pro Child einen **Named Pipe Server** (Windows/Linux/macOS via .NET)
|
||||||
- sendet Commands (Ping, StartWork, CancelWork)
|
- sendet Commands (Ping, StartWork, CancelWork)
|
||||||
- empfängt Streaming-Events (Log, Progress, Result)
|
- empfängt Streaming-Events (Log, Progress, Result)
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ Dieser Prototyp zeigt **bidirektionale Interprozesskommunikation** zwischen eine
|
|||||||
|
|
||||||
2. Parent starten:
|
2. Parent starten:
|
||||||
|
|
||||||
- Starte `ParentWpf` (Debug oder Run).
|
- Starte `ParentAvalonia` (Debug oder Run).
|
||||||
|
|
||||||
3. In der UI:
|
3. In der UI:
|
||||||
|
|
||||||
@@ -50,3 +50,7 @@ Damit kann ein Parent mehrere Childs parallel bedienen.
|
|||||||
- Reconnect-Strategien / Heartbeats
|
- Reconnect-Strategien / Heartbeats
|
||||||
- Backpressure (Channel für Outbound Frames)
|
- Backpressure (Channel für Outbound Frames)
|
||||||
- Auth/ACL (bei Bedarf), Logging, Telemetrie
|
- Auth/ACL (bei Bedarf), Logging, Telemetrie
|
||||||
|
|
||||||
|
## Hinweis zu NuGet-Feeds
|
||||||
|
|
||||||
|
Im Repo liegt eine `NuGet.config`, die die Paketquelle explizit auf **nuget.org** setzt. Das verhindert Restore-Fehler, falls im globalen Setup eine private (authentifizierte) Quelle konfiguriert ist.
|
||||||
|
|||||||
Reference in New Issue
Block a user