winui-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSearch samples before writing XAML
编写XAML前先搜索示例
This skill ships alongside this (≈100 WinUI Gallery controls, every Windows Community Toolkit scenario, 90+ Reactor declarative-C# controls, curated platform-integration patterns; each result returns full code — XAML + C#, or C#-only for Reactor — plus pitfall notes). Front-load lookups, then code — don't interleave.
winui-search.exeSKILL.mdpowershell
.\winui-search.exe search "<feature 1>" "<feature 2>" ... # batch one focused query per feature (BM25 likes focused phrasing)
.\winui-search.exe get <id 1> <id 2> ... # batch up to 3 IDs — full XAML + C# + pitfall notes
.\winui-search.exe list # browse all patterns (heavy — prefer search)
.\winui-search.exe update # force cache refreshSearch covers controls and platform integration (file pickers, Share, JumpList, drag-drop, app lifecycle, dialogs) — front-load all lookups before writing XAML; don't interleave search with coding.
此技能随附和本文件(包含约100个WinUI Gallery控件、所有Windows Community Toolkit场景、90+个Reactor声明式C#控件、精选平台集成模式;每个结果返回完整代码——XAML + C#,或Reactor仅用C#实现的代码,外加陷阱提示)。先集中查询,再编写代码——不要交替进行。
winui-search.exeSKILL.mdpowershell
.\winui-search.exe search "<功能1>" "<功能2>" ... # 针对每个功能批量发起一个聚焦查询(BM25算法偏好聚焦表述)
.\winui-search.exe get <ID1> <ID2> ... # 批量获取最多3个ID对应的内容——完整XAML + C#代码 + 陷阱提示
.\winui-search.exe list # 浏览所有模式(内容较多——优先使用搜索)
.\winui-search.exe update # 强制刷新缓存搜索覆盖控件及平台集成(文件选择器、分享、跳转列表、拖放、应用生命周期、对话框)——编写XAML前先完成所有查询;不要交替进行搜索和编码。
App-shape anchors
应用形态锚点
Pick the closest shipping app silhouette before laying out a page:
| App type | Anchor controls | Reference apps |
|---|---|---|
| Settings / config tool | | Windows Settings, Slack |
| Document / session editor | | Windows Terminal, VS Code, Notepad |
| Hierarchical browser | | File Explorer, Outlook |
| Developer tool / dashboard | | Dev Home, GitHub Desktop |
| Single-purpose utility | Mode switcher + compact grid | Calculator, Snipping Tool |
| Media / canvas / hero | | Photos, Spotify, Clipchamp |
在布局页面之前,选择最接近已发布应用的形态:
| 应用类型 | 锚点控件 | 参考应用 |
|---|---|---|
| 设置/配置工具 | | Windows 设置、Slack |
| 文档/会话编辑器 | | Windows 终端、VS Code、记事本 |
| 层级浏览器 | | 文件资源管理器、Outlook |
| 开发者工具/仪表盘 | | Dev Home、GitHub Desktop |
| 单一用途工具 | 模式切换器 + 紧凑网格 | 计算器、截图工具 |
| 媒体/画布/核心展示 | 带核心展示区域的 | 照片、Spotify、Clipchamp |
Reach-for-this control map
首选控件映射表
Before writing XAML, map the requirement to a platform control. These mappings exist to short-circuit cross-framework instincts (WPF , web , HTML ):
DataGrid<select><input type=date>- Navigation: 2–7 sections → ; document/session tabs →
NavigationView; breadcrumb trail →TabView; 2–3 modes →BreadcrumbBar.SelectorBar - Data display: Vertical list → ; tiles/grid →
ListVieworGridView+ItemsRepeater; hierarchy →UniformGridLayout; tabular →TreeViewwith aListView-basedGridand a headerItemTemplateabove (WinUI has noGrid; don't default toDataGrid— its columns can't useCommunityToolkit.WinUI.Controls.DataGrid); master-detail →x:Bind+ detailListView.Grid - Input: Text → ; number →
TextBox; search →NumberBox; date →AutoSuggestBox; boolean →CalendarDatePicker; pick one from 2–3 →ToggleSwitch; pick one from 4+ →RadioButtons.ComboBox - Feedback: Blocking decision → ; contextual action →
ContentDialog/Flyout; onboarding / hint →MenuFlyout; inline status / async progress →TeachingTip; system notification →InfoBar.AppNotification
If the mapping above doesn't fit, search before improvising.
winui-search.exe编写XAML前,将需求映射到平台控件。这些映射用于避免跨框架思维定式(如WPF的、网页的、HTML的):
DataGrid<select><input type=date>- 导航:2–7个板块 → ;文档/会话标签 →
NavigationView;面包屑导航 →TabView;2–3种模式 →BreadcrumbBar。SelectorBar - 数据展示:垂直列表 → ;磁贴/网格 →
ListView或GridView+ItemsRepeater;层级结构 →UniformGridLayout;表格形式 → 带基于TreeView的Grid的ItemTemplate,上方搭配表头ListView(WinUI无Grid;不要默认使用DataGrid——其列无法使用CommunityToolkit.WinUI.Controls.DataGrid);主详情布局 →x:Bind+ 详情ListView。Grid - 输入:文本 → ;数字 →
TextBox;搜索 →NumberBox;日期 →AutoSuggestBox;布尔值 →CalendarDatePicker;从2–3个选项选一个 →ToggleSwitch;从4+个选项选一个 →RadioButtons。ComboBox - 反馈:需阻断操作的决策 → ;上下文操作 →
ContentDialog/Flyout;引导/提示 →MenuFlyout;内联状态/异步进度 →TeachingTip;系统通知 →InfoBar。AppNotification
如果上述映射不适用,在自行设计前先搜索。
winui-search.exeWindow sizing (WinUI 3 specifics)
窗口尺寸设置(WinUI 3 特有)
WinUI 3 has no. Without an explicit size, Windows defaults the main window to ~1024×768 — oversized for most utilities. Size it inSizeToContent's constructor.MainWindow
Rubric. Width = widest row + 48 padding, rounded up to nearest 20. Height = 32 (titlebar) + Σ(row heights) + Σ(spacing) + 48 padding, rounded up to 20. Round up — clipped content is a worse failure than a slightly-wide window. Sanity ranges (derive yours from the rubric):
- Single-purpose utility → ~440–560 wide
- Form / single-page tool → ~600–800 wide, ~640–800 tall
- Multi-pane (nav + content) → ~1100–1300 wide, ~720–840 tall
- Document / canvas / media editor → 1280+ wide
AppWindow.ResizeXamlRoot.RasterizationScaleAppWindow.Move[DllImport] GetDpiForWindowcsharp
using Microsoft.UI;
using Microsoft.UI.Windowing;
using System.Runtime.InteropServices;
using Windows.Graphics;
public sealed partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(IntPtr hWnd);
public MainWindow()
{
InitializeComponent();
var hwnd = Win32Interop.GetWindowFromWindowId(AppWindow.Id);
var scale = GetDpiForWindow(hwnd) / 96.0;
// widthDip / heightDip come from the rubric above — derive, don't copy.
AppWindow.Resize(new SizeInt32((int)(widthDip * scale), (int)(heightDip * scale)));
}
}Don't size the window by setting / on the root — that clips content, not the window.
WidthHeightGridWinUI 3 无属性。 若未设置明确尺寸,Windows会将主窗口默认设为约1024×768——对大多数工具来说过大。请在SizeToContent的构造函数中设置尺寸。MainWindow
规则:宽度 = 最宽行 + 48边距,向上取整至最近的20的倍数。高度 = 32(标题栏) + 所有行高度之和 + 所有间距之和 + 48边距,向上取整至最近的20的倍数。向上取整——内容被裁剪比窗口略宽更糟糕。合理范围(根据规则推导):
- 单一用途工具 → 约440–560宽
- 表单/单页工具 → 约600–800宽,约640–800高
- 多窗格(导航+内容) → 约1100–1300宽,约720–840高
- 文档/画布/媒体编辑器 → 1280+宽
AppWindow.ResizeXamlRoot.RasterizationScaleAppWindow.Move[DllImport] GetDpiForWindowcsharp
using Microsoft.UI;
using Microsoft.UI.Windowing;
using System.Runtime.InteropServices;
using Windows.Graphics;
public sealed partial class MainWindow : Window
{
[DllImport("user32.dll")]
private static extern uint GetDpiForWindow(IntPtr hWnd);
public MainWindow()
{
InitializeComponent();
var hwnd = Win32Interop.GetWindowFromWindowId(AppWindow.Id);
var scale = GetDpiForWindow(hwnd) / 96.0;
// widthDip / heightDip 来自上述规则——自行推导,不要直接复制。
AppWindow.Resize(new SizeInt32((int)(widthDip * scale), (int)(heightDip * scale)));
}
}不要通过设置根的/来调整窗口尺寸——这会裁剪内容,而非调整窗口大小。
GridWidthHeightXAML landmines (the things you'll otherwise ship broken)
XAML陷阱(否则你会发布有问题的代码)
x:Bind
defaults to OneTime
x:BindOneTimex:Bind
默认模式为OneTime
x:BindOneTimexml
<!-- ❌ silently never updates -->
<TextBlock Text="{x:Bind Vm.Status}" />
<!-- ✅ -->
<TextBlock Text="{x:Bind Vm.Status, Mode=OneWay}" />xml
<!-- ❌ 会静默不更新 -->
<TextBlock Text="{x:Bind Vm.Status}" />
<!-- ✅ 正确写法 -->
<TextBlock Text="{x:Bind Vm.Status, Mode=OneWay}" />TextBox
two-way needs UpdateSourceTrigger=PropertyChanged
TextBoxUpdateSourceTrigger=PropertyChangedTextBox
双向绑定需设置UpdateSourceTrigger=PropertyChanged
TextBoxUpdateSourceTrigger=PropertyChangedxml
<TextBox Text="{x:Bind Vm.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />Default trigger resolves to specifically for (most other properties default to ). The VM is not updated per keystroke, and UIA keyboard-simulation tests (WinAppDriver , etc.) that assert immediately after typing will see stale VM state until focus moves.
LostFocusTextBox.TextPropertyChangedSendKeysxml
<TextBox Text="{x:Bind Vm.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />TextBox.TextLostFocusPropertyChangedSendKeysAttached properties from C# use static setters, not initializers
C#中使用附加属性需调用静态设置器,而非初始化器
csharp
using Microsoft.UI.Xaml.Automation;
// ❌ WRONG — does not compile. CS0117: 'Button' does not contain a definition for 'AutomationProperties'.
// AutomationProperties is a static class of attached-property accessors, not an instance member.
var btn = new Button { AutomationProperties = { AutomationId = "BtnSave" } };
// ✅ CORRECT
var btn = new Button { Content = "Save" };
AutomationProperties.SetAutomationId(btn, "BtnSave");
AutomationProperties.SetName(btn, "Save button");
Grid.SetRow(btn, 1);
ToolTipService.SetToolTip(btn, "Save the current document");csharp
using Microsoft.UI.Xaml.Automation;
// ❌ 错误——无法编译。CS0117: 'Button' 不包含 'AutomationProperties' 的定义。
// AutomationProperties是附加属性访问器的静态类,而非实例成员。
var btn = new Button { AutomationProperties = { AutomationId = "BtnSave" } };
// ✅ 正确写法
var btn = new Button { Content = "Save" };
AutomationProperties.SetAutomationId(btn, "BtnSave");
AutomationProperties.SetName(btn, "Save button");
Grid.SetRow(btn, 1);
ToolTipService.SetToolTip(btn, "Save the current document");Converter={x:Null}
crashes x:Bind
at runtime
Converter={x:Null}x:BindConverter={x:Null}
会导致x:Bind
运行时崩溃
Converter={x:Null}x:Bind{x:Bind}Converter{StaticResource}Converter={x:Null}LookupConverter("")Resource Dictionary Key can only be String-typedNullReferenceException{x:Bind}Converter{StaticResource}Converter={x:Null}LookupConverter("")Resource Dictionary Key can only be String-typedNullReferenceExceptionPrefer x:Bind
static functions over IValueConverter
x:BindIValueConverter优先使用x:Bind
静态函数而非IValueConverter
x:BindIValueConvertercsharp
// MainPage.xaml.cs
public static Visibility BoolToVisibility(bool v) => v ? Visibility.Visible : Visibility.Collapsed;
public static Visibility InvertBoolToVisibility(bool v) => v ? Visibility.Collapsed : Visibility.Visible;
public static bool Not(bool v) => !v;xml
<TextBlock Visibility="{x:Bind local:MainPage.BoolToVisibility(Vm.IsLoading), Mode=OneWay}" />
<Button IsEnabled="{x:Bind local:MainPage.Not(Vm.IsLoading), Mode=OneWay}" />csharp
// MainPage.xaml.cs
public static Visibility BoolToVisibility(bool v) => v ? Visibility.Visible : Visibility.Collapsed;
public static Visibility InvertBoolToVisibility(bool v) => v ? Visibility.Collapsed : Visibility.Visible;
public static bool Not(bool v) => !v;xml
<TextBlock Visibility="{x:Bind local:MainPage.BoolToVisibility(Vm.IsLoading), Mode=OneWay}" />
<Button IsEnabled="{x:Bind local:MainPage.Not(Vm.IsLoading), Mode=OneWay}" />Acrylic and ThemeShadow
rendering rules
ThemeShadowAcrylic和ThemeShadow
渲染规则
ThemeShadow- defaults to
BackgroundSizingon bothInnerBorderEdgeandBorder, which correctly clips acrylic to the inner stroke. The hazard is the opposite of intuition: don't change it toControlon a bordered acrylic surface — that's what makes the material bleed past the stroke.OuterBorderEdge - casts a shadow from the caster's
ThemeShadowZ. Microsoft's recommended elevations areTranslationfor tooltips,16for popup/flyout UI,32for dialogs — pick by surface type. For non-popup casters, add the surfaces it should land on to128; otherwise the shadow has nothing to fall on and looks clipped.ThemeShadow.Receivers
- 在
BackgroundSizing和Border上默认值为Control,可正确将亚克力效果裁剪至内边框。误区与直觉相反:不要在带边框的亚克力表面将其改为InnerBorderEdge——这会导致材质溢出边框。OuterBorderEdge - 从投射器的
ThemeShadowZ轴投射阴影。微软推荐的高度值为:工具提示16、弹出/浮出UI32、对话框128——根据表面类型选择。对于非弹出式投射器,需将阴影应落在的表面添加至Translation;否则阴影没有投射目标,看起来会被裁剪。ThemeShadow.Receivers
Theming rules (short version)
主题设置规则(精简版)
- at usage sites (updates on theme switch).
{ThemeResource ...}inside{StaticResource}for theme-local definitions;ThemeDictionaries/SystemAccentColorare the exceptions and staySystemColor*.{ThemeResource} - Custom theme dictionaries cover ,
Light, andDarkexplicitly — neverHighContrast.Default - Name resources by purpose (,
CardBackgroundBrush), not hue.DangerTextBrush - Light/Dark working ≠ High Contrast working. Test in a Contrast theme separately.
- Never set unless your app already supplies system-aware brushes throughout.
HighContrastAdjustment="None"
- 使用站点处使用(主题切换时自动更新)。
{ThemeResource ...}内的主题本地定义使用ThemeDictionaries;{StaticResource}/SystemAccentColor是例外,仍使用SystemColor*。{ThemeResource} - 自定义主题字典需明确覆盖、
Light以及Dark——不要使用HighContrast。Default - 按用途命名资源(如、
CardBackgroundBrush),而非色调。DangerTextBrush - 亮色/暗色模式正常工作≠高对比度模式正常工作。需单独在对比度主题中测试。
- 除非应用全程使用系统感知画刷,否则不要设置。
HighContrastAdjustment="None"
Anti-patterns
反模式
| ❌ Don't | ✅ Do instead |
|---|---|
Reflexively build every app as | Pick the closest row in the silhouette table; hero / document / utility shapes are equally valid |
| Treat brand colour or tinted backdrop as off-pattern | Overriding |
| Tiny content island on an oversized window | Either size the window to the content (see Window sizing) or let content fill the available space |
| Custom pill / segmented tab switcher built by hand | |
| Equal-width 50/50 column split where one pane is structural | Stable size for the structural pane, flexible for content — only if a structural pane is part of the silhouette at all |
Hard-coded color literals ( | |
| The collection control already scrolls — give it a constrained height |
Custom | Built-in control + lightweight style overrides |
| Placeholder text used as the only field label | Always provide a visible label |
| Required commands hidden at small widths with no route | Overflow menu, secondary surface, or a responsive promotion rule |
Modal | |
| Destructive action (Delete / Discard / Reset) fired without confirmation | |
Custom list control when | Use the platform collection + virtualisation |
Build custom UI only when all are true: no platform/Gallery/Toolkit control fits; you'll implement keyboard, focus, UI Automation, theme resources, High Contrast, and responsive behaviour; you have specs for default/hover/pressed/disabled/selected/focused/error states; you've tested with keyboard and a contrast theme.
| ❌ 不要做 | ✅ 建议做法 |
|---|---|
习惯性地将所有应用都构建为左侧 | 选择形态表中最接近的布局;核心展示/文档/工具形态同样合理 |
| 将品牌色或着色背景视为不符合模式 | 重写 |
| 超大窗口中放置极小内容区域 | 要么根据内容调整窗口尺寸(见窗口尺寸设置),要么让内容填充可用空间 |
| 手动构建自定义胶囊/分段标签切换器 | 使用 |
| 等宽50/50分栏,其中一个窗格为结构性布局 | 结构性窗格使用固定尺寸,内容窗格使用自适应尺寸——前提是结构性窗格属于所选形态的一部分 |
使用硬编码颜色字面量(如 | 使用按语义命名的 |
在 | 集合控件本身已支持滚动——为其设置固定高度即可 |
为标准控件自定义 | 使用内置控件 + 轻量级样式重写 |
| 仅使用占位文本作为字段标签 | 始终提供可见标签 |
| 在小尺寸下隐藏必要命令且无替代入口 | 使用溢出菜单、次级界面或响应式升级规则 |
对非阻塞提示使用模态 | 使用 |
| 执行破坏性操作(删除/丢弃/重置)前不进行确认 | 使用带明确主操作和 |
当 | 使用平台集合控件 + 虚拟化功能 |
仅当以下所有条件满足时才构建自定义UI:无平台/Gallery/Toolkit控件适用;你将实现键盘、焦点、UI自动化、主题资源、高对比度和响应式行为;你有默认/悬停/按下/禁用/选中/聚焦/错误状态的规范;你已通过键盘和对比度主题测试。
References (load on demand)
参考资料(按需加载)
| File | Load when… |
|---|---|
| Looking up a brush key by purpose, picking between |
| Authoring theme dictionaries, custom brushes/styles/templates, or High Contrast support. |
| Reviewing responsive behaviour, breakpoints, or empty/loading/error coverage on a data-driven page. |
| 文件 | 加载时机… |
|---|---|
| 根据用途查找画刷键、选择 |
| 编写主题字典、自定义画刷/样式/模板或实现高对比度支持时 |
| 评审数据驱动页面的响应式行为、断点或空状态/加载状态/错误状态覆盖情况时 |