Switched to Avalonia

This commit is contained in:
Holger Börchers
2026-01-30 15:55:58 +01:00
parent 894fbbfa5a
commit 7182061a5f
23 changed files with 1291 additions and 224 deletions

204
.github/agents/CSharpExpert.agent.md vendored Normal file
View 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; dont 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:** dont 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 frameworks 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
View 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 |

View 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.

View 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

View File

@@ -1,5 +1,5 @@
<Solution>
<Project Path="ChildWorker/ChildWorker.csproj" />
<Project Path="CommIpc/CommIpc.csproj" />
<Project Path="ParentWpf/ParentWpf.csproj" />
<Project Path="ParentAvalonia/ParentAvalonia.csproj" />
</Solution>

8
NuGet.config Normal file
View 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
View 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>

View 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();
}
}

View File

@@ -1,10 +1,9 @@
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO.Pipes;
using System.Text.Json;
using CommIpc;
namespace ParentWpf;
namespace ParentAvalonia;
public sealed class ChildSession : NotifyBase, IAsyncDisposable
{

View File

@@ -2,11 +2,10 @@ using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Text.Json;
using System.Windows;
using Avalonia.Threading;
using CommIpc;
namespace ParentWpf;
namespace ParentAvalonia;
public sealed class MainViewModel : NotifyBase, IAsyncDisposable
{
@@ -115,39 +114,28 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
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);
if (solutionRoot is null)
{
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 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 arguments;
if (File.Exists(debugExe))
{
fileName = debugExe;
arguments = $"--pipe \"{pipeName}\" --id {childId}";
}
else if (File.Exists(releaseExe))
{
fileName = releaseExe;
arguments = $"--pipe \"{pipeName}\" --id {childId}";
}
else if (File.Exists(debugDll))
if (!string.IsNullOrWhiteSpace(childDll))
{
fileName = "dotnet";
arguments = $"\"{debugDll}\" --pipe \"{pipeName}\" --id {childId}";
}
else if (File.Exists(releaseDll))
{
fileName = "dotnet";
arguments = $"\"{releaseDll}\" --pipe \"{pipeName}\" --id {childId}";
arguments = $"\"{childDll}\" --pipe \"{pipeName}\" --id {childId}";
}
else
{
@@ -197,10 +185,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
break;
}
await Application.Current.Dispatcher.InvokeAsync(() =>
{
HandleIncomingFrame(session, frame);
});
await Dispatcher.UIThread.InvokeAsync(() => HandleIncomingFrame(session, frame));
}
}
catch (OperationCanceledException)
@@ -209,7 +194,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
}
catch (Exception ex)
{
await Application.Current.Dispatcher.InvokeAsync(() =>
await Dispatcher.UIThread.InvokeAsync(() =>
{
session.Status = "Error";
session.AddLog($"[parent] Receive loop error: {ex.Message}");
@@ -217,7 +202,7 @@ public sealed class MainViewModel : NotifyBase, IAsyncDisposable
}
finally
{
await Application.Current.Dispatcher.InvokeAsync(() =>
await Dispatcher.UIThread.InvokeAsync(() =>
{
session.IsConnected = false;
session.Status = "Disconnected";

View 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>

View 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);
}
}

View File

@@ -1,7 +1,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ParentWpf;
namespace ParentAvalonia;
public abstract class NotifyBase : INotifyPropertyChanged
{

View 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
View 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();
}

View 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>

View File

@@ -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>

View File

@@ -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
{
}

View File

@@ -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)
)]

View File

@@ -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>

View File

@@ -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"); }
}
}

View File

@@ -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>

View File

@@ -1,12 +1,12 @@
# 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
- **Parent (WPF)**: `ParentWpf`
- **Parent (Avalonia Desktop)**: `ParentAvalonia`
- 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)
- empfängt Streaming-Events (Log, Progress, Result)
@@ -27,7 +27,7 @@ Dieser Prototyp zeigt **bidirektionale Interprozesskommunikation** zwischen eine
2. Parent starten:
- Starte `ParentWpf` (Debug oder Run).
- Starte `ParentAvalonia` (Debug oder Run).
3. In der UI:
@@ -50,3 +50,7 @@ Damit kann ein Parent mehrere Childs parallel bedienen.
- Reconnect-Strategien / Heartbeats
- Backpressure (Channel für Outbound Frames)
- 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.