Skip to content

Commit f5d01cd

Browse files
lukewire129rkttu
authored andcommitted
add new project 'wpf-linqui-fba' template
1 parent 4089873 commit f5d01cd

File tree

5 files changed

+392
-0
lines changed

5 files changed

+392
-0
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"mcpServers": {
3+
"nuget": {
4+
"command": "dnx",
5+
"args": [
6+
"NuGet.Mcp.Server",
7+
"--prerelease",
8+
"--yes"
9+
]
10+
},
11+
"microsoft_learn": {
12+
"url": "https://learn.microsoft.com/api/mcp"
13+
}
14+
}
15+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "http://json.schemastore.org/template",
3+
"author": "lukwire",
4+
"classifications": [ "Common", "WPF", "FBA" ],
5+
"identity": "wpf-linqui-fba",
6+
"name": "WPF Application (File-based App)",
7+
"shortName": "wpf-linqui-fba",
8+
"sourceName": "Program",
9+
"preferNameDirectory": true,
10+
"tags": {
11+
"language": "C#",
12+
"type": "project"
13+
},
14+
"symbols": {
15+
"Framework": {
16+
"type": "parameter",
17+
"description": "The target framework for the project (e.g., net10.0-windows).",
18+
"datatype": "string",
19+
"defaultValue": "net10.0-windows",
20+
"replaces": "NET_TFM_WINDOWS"
21+
}
22+
}
23+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"servers": {
3+
"nuget": {
4+
"type": "stdio",
5+
"command": "dnx",
6+
"args": [
7+
"NuGet.Mcp.Server",
8+
"--prerelease",
9+
"--yes"
10+
]
11+
},
12+
"microsoft_learn": {
13+
"type": "http",
14+
"url": "https://learn.microsoft.com/api/mcp"
15+
}
16+
}
17+
}

content/wpf-linqui-fba/AGENTS.md

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# GitHub Copilot Instructions — WPF File-based App (FBA) Template
2+
3+
> Pragmatic, production-ready guidance for building **.NET 10** WPF apps as a **single C# file** with **MVVM (CommunityToolkit.Mvvm)**.
4+
> **Apartment model:** Per field validation, WPF startup **must** perform the two-step STA initialization: **`Unknown → STA`**.
5+
6+
---
7+
8+
## 1) Project Overview
9+
10+
Author a modern WPF desktop app using the **File-based App (FBA)** model: keep build/runtime directives at the very top of one `.cs` file, place top-level bootstrapping first, and define all types (App, Window, ViewModels, etc.) after it.
11+
12+
---
13+
14+
## 2) FBA Directives (Windows-only GUI)
15+
16+
```csharp
17+
// Shebang optional for Windows-only apps
18+
19+
#:sdk Microsoft.NET.Sdk
20+
21+
22+
#:property OutputType=WinExe
23+
#:property TargetFramework=net10.0-windows
24+
#:property UseWPF=True
25+
#:property UseWindowsForms=False
26+
#:property PublishAot=False // WPF is not AOT-compatible
27+
#:property Nullable=enable
28+
```
29+
30+
**Notes**
31+
32+
* Use `net10.0-windows` to enable Windows APIs.
33+
* Keep every directive at the top of the file.
34+
* If you add more packages, **pin exact versions**.
35+
36+
---
37+
38+
## 3) Quick Start (single file, DI + required STA init, XAML-free UI)
39+
40+
```csharp
41+
// wpf.cs
42+
// FBA directives appear above (see section 2)
43+
44+
using System;
45+
using System.Threading;
46+
using System.Windows;
47+
using System.Windows.Controls;
48+
using System.Windows.Data;
49+
using Microsoft.Extensions.DependencyInjection;
50+
using Microsoft.Extensions.Hosting;
51+
using CommunityToolkit.Mvvm.ComponentModel;
52+
using CommunityToolkit.Mvvm.Input;
53+
using LinqUI.WPF;
54+
// --- Top-level bootstrap must precede type declarations ---
55+
56+
// **CRITICAL**: Two-step STA initialization (empirically required)
57+
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
58+
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
59+
60+
// Host for DI/config/logging
61+
using var host = Host.CreateApplicationBuilder(args)
62+
.ConfigureServices(s =>
63+
{
64+
s.AddSingleton<App>();
65+
s.AddSingleton<MainWindow>();
66+
s.AddSingleton<CounterViewModel>();
67+
})
68+
.Build();
69+
70+
// Create WPF Application and wire global behavior
71+
var app = host.Services.GetRequiredService<App>();
72+
app.ShutdownMode(ShutdownMode.OnMainWindowClose)
73+
.OnDispatcherUnhandledException((_, e) =>
74+
{
75+
MessageBox.Show(e.Exception.ToString(), "Unhandled", MessageBoxButton.OK, MessageBoxImage.Error);
76+
e.Handled = true;
77+
});
78+
79+
// Compose main window & VM
80+
var main = host.Services.GetRequiredService<MainWindow>();
81+
main.DataContext = host.Services.GetRequiredService<CounterViewModel>();
82+
83+
// Run (blocks until closed). Call Application.Current.Shutdown() for scripted/agent flows.
84+
app.Run(main);
85+
86+
// ---- Types follow here ----
87+
88+
public sealed class App : Application { }
89+
90+
public sealed class MainWindow : Window
91+
{
92+
public MainWindow()
93+
{
94+
this.Title()"WPF FBA (.NET 10) — MVVM (Toolkit)")
95+
.Size(480, 280)
96+
.WindowStartupLocation(WindowStartupLocation.CenterScreen)
97+
.Content(
98+
new StackPanel()
99+
.VCenter()
100+
.Margin(16)
101+
.Children(
102+
new TextBlock()
103+
.FontSize(16)
104+
.Margin(bottom: 12)
105+
.Bind(TextBlock.TextProperty, new Binding(nameof(CounterViewModel.Greeting))),
106+
new TextBlock()
107+
.FontSize(32)
108+
.Margin(bottom: 12)
109+
.Bind(TextBlock.TextProperty, new Binding(nameof(CounterViewModel.Count))),
110+
new StackPanel()
111+
.Orientation(Orientation.Horizontal)
112+
.Children(
113+
new Button()
114+
.Content("Increment")
115+
.MinWidth(120)
116+
.Margin(right: 8)
117+
.Bind(Button.CommandProperty, new Binding(nameof(CounterViewModel.IncrementCountCommand))),
118+
new Button()
119+
.Content("Decrement")
120+
.MinWidth(120)
121+
.Bind(Button.CommandProperty, new Binding(nameof(CounterViewModel.DecrementCountCommand)))
122+
)
123+
)
124+
)
125+
}
126+
}
127+
128+
// ViewModel with Toolkit generators
129+
public sealed partial class CounterViewModel : ObservableObject
130+
{
131+
[ObservableProperty] private int _count = 0;
132+
public string Greeting => $"Hello, today is {DateTime.Now:yyyy-MM-dd}";
133+
134+
[RelayCommand] private void Increment() => Count++;
135+
[RelayCommand] private void Decrement() => Count--;
136+
}
137+
```
138+
139+
**Run**
140+
141+
```powershell
142+
dotnet run wpf.cs
143+
```
144+
145+
---
146+
147+
## 4) Application Initialization
148+
149+
* **STA Apartment (CRITICAL):**
150+
Execute **both**:
151+
152+
```csharp
153+
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
154+
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
155+
```
156+
157+
This two-step init prevents intermittent COM initialization issues and ensures stable WPF UI thread behavior.
158+
* **App lifecycle:** single `Application` instance; set `ShutdownMode` as needed.
159+
* **Unhandled exceptions:** handle `DispatcherUnhandledException` to fail gracefully.
160+
* **Termination for agents:** call `Application.Current.Shutdown()` when objectives are achieved.
161+
162+
---
163+
164+
## 5) Declarative UI in Code
165+
166+
* Use **object/collection initializers** to compose the visual tree.
167+
* Bind with `SetBinding` and `Binding`; prefer `nameof(ViewModel.Property)` to avoid string typos.
168+
* Keep layout simple (`Grid`, `StackPanel`, `DockPanel`); avoid absolute coordinates.
169+
170+
---
171+
172+
## 6) MVVM with CommunityToolkit.Mvvm
173+
174+
* Base view models on `ObservableObject`.
175+
* Use `[ObservableProperty]` for bindable state and `[RelayCommand]`/`AsyncRelayCommand` for actions.
176+
* Inject dependencies via DI; keep VMs UI-agnostic and testable.
177+
178+
---
179+
180+
## 7) Data Binding & Validation
181+
182+
* Use OneWay bindings by default; switch to TwoWay only where input is required.
183+
* Validation: `INotifyDataErrorInfo` or data annotations; present errors clearly.
184+
* Collections: `ObservableCollection<T>`; use `CollectionViewSource` for filter/sort.
185+
186+
---
187+
188+
## 8) Threading & Async
189+
190+
* UI access must occur on the **Dispatcher** (`Dispatcher.Invoke/BeginInvoke`).
191+
* Offload long work: `await Task.Run(...)` with `CancellationToken`.
192+
* Avoid blanket `ConfigureAwait(false)` in UI code (you typically need the captured context).
193+
194+
---
195+
196+
## 9) Styling, Theming, Resources
197+
198+
* Centralize styles/brushes in `Application.Resources` (build programmatically in FBA as needed).
199+
* Respect system/high-contrast themes; avoid hard-coded colors.
200+
201+
---
202+
203+
## 10) Performance
204+
205+
* Reduce binding churn; compute heavy values lazily.
206+
* Virtualize lists (`VirtualizingStackPanel`) and avoid retaining event handlers unintentionally.
207+
* Monitor allocations/leaks in long-running sessions.
208+
209+
---
210+
211+
## 11) Testing
212+
213+
* **Unit:** ViewModels, commands, and validation (no WPF references).
214+
* **Integration/UI:** automate critical flows (e.g., WinAppDriver/Playwright for Windows).
215+
* **Perf/Leak checks:** open/close windows and watch for survivors/GC pressure.
216+
217+
---
218+
219+
## 12) Deployment
220+
221+
* Package with MSIX/installer; document Windows/.NET requirements.
222+
* Store user settings under user scope; protect secrets (DPAPI/Credential Locker).
223+
* Provide safe upgrade paths and migrations.
224+
225+
---
226+
227+
## 13) File-based Dev Standards
228+
229+
* Default to **single-file FBA**; only convert on explicit request.
230+
* Keep directives (`#:sdk`, `#:property`, `#:package`) at the top.
231+
* If the file grows, add `Directory.Build.props` for shared MSBuild—but the FBA file remains authoritative.
232+
233+
---
234+
235+
## 14) Project Conversion (on request)
236+
237+
* Convert when asked:
238+
239+
```bash
240+
dotnet project convert wpf.cs -o WpfProject
241+
```
242+
* Preserve the original FBA; mirror properties and pinned packages.
243+
244+
---
245+
246+
## 15) Agent Execution Compatibility
247+
248+
* Don’t block with `Console.ReadLine()`/`ReadKey()`.
249+
* Terminate deterministically (`Application.Current.Shutdown()` or close MainWindow) so agents can capture outputs/exit codes.
250+
251+
---
252+
253+
## 16) Review Checklist
254+
255+
* [ ] `TargetFramework=net10.0-windows`, `UseWPF=True`, `PublishAot=False`, `Nullable=enable`
256+
* [ ] **Apartment state set: `Unknown``STA` (two-step)**
257+
* [ ] Top-level bootstrap before types; DI host configured
258+
* [ ] Bindings use `nameof`; TwoWay only where necessary; validation strategy chosen
259+
* [ ] Dispatcher marshaling respected; no blanket `ConfigureAwait(false)` in UI paths
260+
* [ ] Resources/styles centralized; DPI/contrast respected
261+
* [ ] Single-file FBA; exact package versions pinned for any dependencies
262+
263+
---
264+
265+
## 17) MCP Integration (NuGet & Docs)
266+
267+
* **Never guess versions.** Resolve with your **`nuget`** MCP server and pin with `#:package [email protected]`.
268+
* Use **`microsoft_learn`** MCP to confirm current WPF/MVVM guidance, DPI, accessibility, and deployment patterns.

content/wpf-linqui-fba/Program.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#:sdk Microsoft.NET.Sdk
2+
3+
#:package CommunityToolkit.Mvvm@8.4.0
4+
#:package LinqUI.WPF@1.0.6
5+
6+
#:property OutputType=WinExe
7+
#:property TargetFramework=NET_TFM_WINDOWS
8+
#:property UseWPF=True
9+
#:property UseWindowsForms=False
10+
11+
// WPF cannot use AOT compilation.
12+
#:property PublishAot=False
13+
14+
using System.Windows;
15+
using System.Windows.Controls;
16+
using System.Windows.Data;
17+
using CommunityToolkit.Mvvm.ComponentModel;
18+
using CommunityToolkit.Mvvm.Input;
19+
using LinqUI.WPF;
20+
21+
// https://github.com/dotnet/winforms/issues/5071#issuecomment-908789632
22+
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
23+
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
24+
25+
new Application()
26+
.ShutdownMode(ShutdownMode.OnMainWindowClose)
27+
.OnDispatcherUnhandledException((_, e) =>
28+
{
29+
MessageBox.Show(e.Exception.ToString(), "Unhandled", MessageBoxButton.OK, MessageBoxImage.Error);
30+
e.Handled = true;
31+
})
32+
.Run(new Window()
33+
.WindowStartupLocation(WindowStartupLocation.CenterScreen)
34+
.Title($"Hello, World! - {Thread.CurrentThread.GetApartmentState()}")
35+
.Size(640, 240)
36+
.DataContext(new CounterViewModel())
37+
.Content(
38+
new StackPanel()
39+
.Children(
40+
new TextBlock()
41+
.FontSize(24)
42+
.Margin(8)
43+
.HCenter()
44+
.Bind(TextBlock.TextProperty, new Binding(nameof(CounterViewModel.Count))),
45+
new Button()
46+
.Content("Increment (+1)")
47+
.Margin(8)
48+
.Bind(Button.CommandProperty, new Binding(nameof(CounterViewModel.IncrementCountCommand))),
49+
new Button()
50+
.Content("Decrement (-1)")
51+
.Margin(8)
52+
.Bind(Button.CommandProperty, new Binding(nameof(CounterViewModel.DecrementCountCommand)))
53+
)
54+
)
55+
);
56+
57+
public sealed partial class CounterViewModel : ObservableObject
58+
{
59+
[ObservableProperty]
60+
private int _count = 0;
61+
62+
[RelayCommand]
63+
private void IncrementCount()
64+
=> Count++;
65+
66+
[RelayCommand]
67+
private void DecrementCount()
68+
=> Count--;
69+
}

0 commit comments

Comments
 (0)