用途:为 Windows 桌面应用提供 C# 14 与 WinUI 3 的代码编写规则,让生成的代码能零警告构建、启动时不出现 CRASH 行,并符合既定功能描述。界面视觉规范请调用 windows-design 技能,可复制的 XAML 参考同仓库的 patterns.md。
线程规则:WinUI 每个窗口只有一个 UI 线程,所有控件以及绑定到控件的对象都活在该线程上。页面、视图模型、绑定到列表的 ObservableCollection 放在 UI 线程(事件处理器和 OnLaunched 的默认上下文);CPU 密集的纯计算用 await Task.Run(...),结果再回 UI 线程设置;文件和数据库操作用 async 方法,少量 SQLite 调用可在 UI 线程,超过几毫秒的放入 Task.Run;来自其他线程的 Win32 回调(热键、钩子、计时器)用 DispatcherQueue.TryEnqueue 切回。事件处理器中 await 会自动回到 UI 线程;Task.Run 内部是线程池线程,绝不可触碰控件。async void 仅用于事件处理器,其余返回 Task。窗口外运行的代码要保留 window.DispatcherQueue。后台线程崩溃会直接杀掉进程且不输出 CRASH 行,因此每个启动后不等待的 Task.Run 体都要捕获并记录异常,改动后把每条后台路径跑一遍。
状态规则:模型写成 partial class X : ObservableObject,属性用 [ObservableProperty] public partial string Title { get; set; }(CommunityToolkit.Mvvm 8.4 与 C# 14);partial 属性不能带初始化器,需在构造函数中初始化。计算属性用 [NotifyPropertyChangedFor(nameof(DisplayTitle))]。列表绑定 ObservableCollection<T>,就地重建(Clear、Add),不要给绑定属性整体赋新集合。优先用编译期检查的 x:Bind 而非 {Binding};其默认模式为 OneTime,会变化的内容要写 Mode=OneWay,输入写 Mode=TwoWay,边输边存的 TextBox 加 UpdateSourceTrigger=PropertyChanged。若绑定路径起点在变更对象之上,改完 Model.Selection 后要调用 Bindings.Update()。JSON 使用源生成:把每个持久化类型加入 AppJson([JsonSerializable(typeof(T))]),通过 AppJson.Default.T 使用;反射式 JSON 在 NativeAOT 和裁剪下会报 IL2026、IL3050。小偏好放 Prefs(与数据库同目录的 JSON);未打包应用没有 ApplicationData.Current.LocalSettings,调用会抛异常。机密放 Windows 凭据保险箱(PasswordVault)或 DPAPI(ProtectedData),绝不写进 JSON。属性赋相同值不会触发变更;构造函数中也要填充列表,不能只靠 OnXChanged。
SQLite:每个应用一个 SqliteConnection,打开一次,启用 WAL 模式,放在 <App>.Core 中。架构变更作为 ItemStore.Migrations 的新步骤,以 PRAGMA user_version 为键;已发布的步骤不可修改。新增列要带 DEFAULT,保证旧文件能加载。每行都有 Guid id,不要用行号或列表索引作标识。APP_STORE 可覆盖路径,且除非指向 %TEMP% 否则应用拒绝测试模式;测试不得触碰 %LOCALAPPDATA%<App><App>.db。撤销:把删除的模型留在内存中再存回,SQLite 跨提交没有自带撤销。多进程访问同一文件(CLI、第二实例)时,改为向运行中的应用发命令,保持单写入者。
命令与按键:每个命令都是带 KeyboardAccelerator 的按钮或菜单项,加速器在窗口内任意焦点处生效。打字时也有含义的键(Delete、Backspace、方向键、Enter、普通字母)要限定作用域:ScopeOwner="{x:Bind List}",并在 Invoked 中设 args.Handled = true。Flyout 和上下文菜单项不在可视树中,拿不到 DataContext,用 Tag="{x:Bind}" 传行数据。
命名冲突:Window、Page、Frame、Layout、Path、Rectangle、Grid、Color、Colors、Thickness、Point、Size、Application 都存在于 WinUI 命名空间,不要用它们命名自己的类型。Path 在文件引入 Microsoft.UI.Xaml.Shapes 后与 System.IO.Path 冲突,需写全限定名;Task 与 System.Threading.Tasks.Task 冲突,待办类命名为 Todo 或 TaskItem。命名空间冲突更严重:C# 先查外层命名空间再查 using,名为 MyApp.Features.Windows 的命名空间会让同级命名空间中的 Windows.Foundation 被解析成 MyApp.Features.Windows.Foundation(CS0234);Clipboard、Colors、Launcher 同样会遮蔽 WinRT 类型。各功能单独编译正常,合并后才报错。命名空间段永远不要用 Windows、Microsoft、System 或所调用类型的名字,改用 Snapping、ClipboardHistory、Palette、AppLauncher。
Win32 互操作:取窗口句柄用 WinRT.Interop.WindowNative.GetWindowHandle(window);WinRT 的 FileOpenPicker、FolderPicker 必须先 WinRT.Interop.InitializeWithWindow.Initialize(picker, hwnd),否则抛异常;DPI 用 GetDpiForWindow,AppWindow 尺寸是物理像素而 XAML 是 epx,模板中的 WindowBehavior 负责换算。全局热键、托盘图标、钩子、其他应用窗口需调用 os-integrations 技能。所有 P/Invoke 用 [LibraryImport] 集中在一个 Win32.cs,开启 AllowUnsafeBlocks;回调(窗口过程、钩子、EnumDisplayMonitors)是 [UnmanagedCallersOnly] 静态方法,以 &Method 传递,回调内必须捕获所有异常,逃逸的异常会终止进程。unsafe 只包住取地址的那一行:标记 unsafe 的类无法使用 await(CS4004)。
代码构建 UI 的三个陷阱:主题画刷——Application.Current.Resources["TextFillColorSecondaryBrush"] 返回应用主题的画刷而非目标元素所在主题,深色面板会出现浅底深字或深底深字,应给代码构建的元素套用 XAML 中基于 {ThemeResource ...} 的 Style,或直接做成 XAML UserControl;窗口显示前测量——在未显示的窗口中测量代码构建内容会得到错误高度(模板尚未应用),应在首次布局后(SizeChanged 中或显示后一个 DispatcherQueue 轮次)按内容定尺寸;初始化器属性顺序——new TextBox { Text = s, AcceptsReturn = true } 只保留首行,因为设 Text 时 AcceptsReturn 仍为 false,应先设 AcceptsReturn 和 TextWrapping。代码设值也会触发事件:设 IsOn 显示状态会触发 ToggleSwitch.Toggled,TextBox.TextChanged 在设置 Text 后触发;页面自填充时用 _loading 标志保护处理器,自测时直接调用搜索或过滤函数而非等待 TextChanged。
源码中的字形:Segoe Fluent Icons 字形是私用区字符,在编辑器或 diff 中显示为空,应写码点:C# 用 char.ConvertFromUtf32(0xE734),XAML 用 ,不要粘贴字符本身。
常见构建与启动错误与修复:XamlParseException 提示 ColumnDefinition.Width 类型不符,说明资源类型用错,行列宽高用 GridLength 资源,Width 和 Spacing 用 x:Double;0xC000027B 退出并带 CRASH 与 XamlParseException,按消息中的属性和类型排查,行号常为 0;0xC000027B 且无 CRASH 行,多为 App 构造函数在处理器建立前抛异常,构造函数只留 InitializeComponent(),初始化移到 OnLaunched;Application.Current.Resources 抛 COMException 0x8000FFFF,因在 App 构造函数中调用,移到 OnLaunched;CS0104 'Path' 引用不明确,写 System.IO.Path;CS0104 'Layout' 不明确,自己的类型与 WinUI 同名,改名(如 SnapLayout);CS4004 无法在 unsafe 上下文 await,去掉类的 unsafe,只把指针行包在 unsafe { };CS0234 命名空间段遮蔽框架名,重命名命名空间;Padding="{StaticResource SpaceSm}" 加载崩溃,Spacing 令牌是 x:Double,Padding、Margin、BorderThickness 需 Thickness,改用 Thickness 令牌(如 CardPadding);DispatcherQueue.TryEnqueue 回调中 0xC000027B 无 CRASH 行,入队回调异常会在 UnhandledException 之前终止进程,用 try 包裹并用 AgentLog.Line 记录;dotnet publish 后立即退出,因未设 EnableMsixTooling=true 丢失 <App>.pri,改用 tools/publish.ps1;多行 TextBox 只显示首行,因先设了 Text,改为先设 AcceptsReturn;仅某面板深底深字或浅色卡片,因代码中查了画刷,见上文;ContentDialog.ShowAsync 抛异常,先设 XamlRoot = this.XamlRoot;x:Bind 报属性未找到,DataTemplate 加 x:DataType 且属性为 public;绑定不更新,x:Bind 默认 OneTime,加 Mode=OneWay;ApplicationData.Current 抛 InvalidOperationException,应用未打包,改用 Prefs 或 AppPaths.Folder 中的文件;CS8618 非空字段警告,在构造函数初始化或改为可空,逐个修完所有警告。先修第一个错误,后续错误常由其引发。
完成标准:应用零警告构建、自测通过、需求验收检查通过。
适用场景:用 AI 生成或审查 WinUI 3 桌面应用的 C# 代码、排查构建与启动问题。限制:面向 Windows 与 WinUI 3,依赖 CommunityToolkit.Mvvm 8.4 等组件;UI 视觉规范与系统集成需配合对应技能;资料未提供收费、价格与平台支持信息。