winui-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Search samples before writing XAML

编写XAML前先搜索示例

This skill ships
winui-search.exe
alongside this
SKILL.md
(≈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.
powershell
.\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 refresh
Search 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.
此技能随附
winui-search.exe
和本
SKILL.md
文件(包含约100个WinUI Gallery控件、所有Windows Community Toolkit场景、90+个Reactor声明式C#控件、精选平台集成模式;每个结果返回完整代码——XAML + C#,或Reactor仅用C#实现的代码,外加陷阱提示)。先集中查询,再编写代码——不要交替进行。
powershell
.\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 typeAnchor controlsReference apps
Settings / config tool
NavigationView
Left +
SettingsCard
/
SettingsExpander
Windows Settings, Slack
Document / session editor
TabView
+ full-bleed content, light chrome
Windows Terminal, VS Code, Notepad
Hierarchical browser
TreeView
+
ListView
+
BreadcrumbBar
File Explorer, Outlook
Developer tool / dashboard
NavigationView
+ card layout
Dev Home, GitHub Desktop
Single-purpose utilityMode switcher + compact gridCalculator, Snipping Tool
Media / canvas / hero
Grid
with hero surface, floating commands, no
NavigationView
Photos, Spotify, Clipchamp
在布局页面之前,选择最接近已发布应用的形态:
应用类型锚点控件参考应用
设置/配置工具
NavigationView
(左侧布局) +
SettingsCard
/
SettingsExpander
Windows 设置、Slack
文档/会话编辑器
TabView
+ 全屏内容、简洁边框
Windows 终端、VS Code、记事本
层级浏览器
TreeView
+
ListView
+
BreadcrumbBar
文件资源管理器、Outlook
开发者工具/仪表盘
NavigationView
+ 卡片布局
Dev Home、GitHub Desktop
单一用途工具模式切换器 + 紧凑网格计算器、截图工具
媒体/画布/核心展示带核心展示区域的
Grid
、浮动命令、
NavigationView
照片、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
DataGrid
, web
<select>
, HTML
<input type=date>
):
  • Navigation: 2–7 sections →
    NavigationView
    ; document/session tabs →
    TabView
    ; breadcrumb trail →
    BreadcrumbBar
    ; 2–3 modes →
    SelectorBar
    .
  • Data display: Vertical list →
    ListView
    ; tiles/grid →
    GridView
    or
    ItemsRepeater
    +
    UniformGridLayout
    ; hierarchy →
    TreeView
    ; tabular →
    ListView
    with a
    Grid
    -based
    ItemTemplate
    and a header
    Grid
    above
    (WinUI has no
    DataGrid
    ; don't default to
    CommunityToolkit.WinUI.Controls.DataGrid
    — its columns can't use
    x:Bind
    ); master-detail →
    ListView
    + detail
    Grid
    .
  • Input: Text →
    TextBox
    ; number →
    NumberBox
    ; search →
    AutoSuggestBox
    ; date →
    CalendarDatePicker
    ; boolean →
    ToggleSwitch
    ; pick one from 2–3 →
    RadioButtons
    ; pick one from 4+ →
    ComboBox
    .
  • Feedback: Blocking decision →
    ContentDialog
    ; contextual action →
    Flyout
    /
    MenuFlyout
    ; onboarding / hint →
    TeachingTip
    ; inline status / async progress →
    InfoBar
    ; system notification →
    AppNotification
    .
If the mapping above doesn't fit, search
winui-search.exe
before improvising.
编写XAML前,将需求映射到平台控件。这些映射用于避免跨框架思维定式(如WPF的
DataGrid
、网页的
<select>
、HTML的
<input type=date>
):
  • 导航:2–7个板块 →
    NavigationView
    ;文档/会话标签 →
    TabView
    ;面包屑导航 →
    BreadcrumbBar
    ;2–3种模式 →
    SelectorBar
  • 数据展示:垂直列表 →
    ListView
    ;磁贴/网格 →
    GridView
    ItemsRepeater
    +
    UniformGridLayout
    ;层级结构 →
    TreeView
    表格形式 → 带基于
    Grid
    ItemTemplate
    ListView
    ,上方搭配表头
    Grid
    (WinUI无
    DataGrid
    ;不要默认使用
    CommunityToolkit.WinUI.Controls.DataGrid
    ——其列无法使用
    x:Bind
    );主详情布局 →
    ListView
    + 详情
    Grid
  • 输入:文本 →
    TextBox
    ;数字 →
    NumberBox
    ;搜索 →
    AutoSuggestBox
    ;日期 →
    CalendarDatePicker
    ;布尔值 →
    ToggleSwitch
    ;从2–3个选项选一个 →
    RadioButtons
    ;从4+个选项选一个 →
    ComboBox
  • 反馈:需阻断操作的决策 →
    ContentDialog
    ;上下文操作 →
    Flyout
    /
    MenuFlyout
    ;引导/提示 →
    TeachingTip
    ;内联状态/异步进度 →
    InfoBar
    ;系统通知 →
    AppNotification
如果上述映射不适用,在自行设计前先搜索
winui-search.exe

Window sizing (WinUI 3 specifics)

窗口尺寸设置(WinUI 3 特有)

WinUI 3 has no
SizeToContent
.
Without an explicit size, Windows defaults the main window to ~1024×768 — oversized for most utilities. Size it in
MainWindow
's constructor.
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.Resize
takes physical pixels, not DIPs — multiply by the monitor's DPI scale.
XamlRoot.RasterizationScale
is null in the constructor and stale after
AppWindow.Move
, so
[DllImport] GetDpiForWindow
is the cleanest path:
csharp
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
Width
/
Height
on the root
Grid
— that clips content, not the window.
WinUI 3 无
SizeToContent
属性。
若未设置明确尺寸,Windows会将主窗口默认设为约1024×768——对大多数工具来说过大。请在
MainWindow
的构造函数中设置尺寸。
规则:宽度 = 最宽行 + 48边距,向上取整至最近的20的倍数。高度 = 32(标题栏) + 所有行高度之和 + 所有间距之和 + 48边距,向上取整至最近的20的倍数。向上取整——内容被裁剪比窗口略宽更糟糕。合理范围(根据规则推导):
  • 单一用途工具 → 约440–560宽
  • 表单/单页工具 → 约600–800宽,约640–800高
  • 多窗格(导航+内容) → 约1100–1300宽,约720–840高
  • 文档/画布/媒体编辑器 → 1280+宽
AppWindow.Resize
接收物理像素,而非DIP(设备独立像素)——需乘以显示器的DPI缩放比例。
XamlRoot.RasterizationScale
在构造函数中为null,且在
AppWindow.Move
后失效,因此
[DllImport] GetDpiForWindow
是最简洁的实现方式:
csharp
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)));
    }
}
不要通过设置根
Grid
Width
/
Height
来调整窗口尺寸——这会裁剪内容,而非调整窗口大小。

XAML landmines (the things you'll otherwise ship broken)

XAML陷阱(否则你会发布有问题的代码)

x:Bind
defaults to
OneTime

x:Bind
默认模式为
OneTime

xml
<!-- ❌ 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

TextBox
双向绑定需设置
UpdateSourceTrigger=PropertyChanged

xml
<TextBox Text="{x:Bind Vm.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Default trigger resolves to
LostFocus
specifically for
TextBox.Text
(most other properties default to
PropertyChanged
). The VM is not updated per keystroke, and UIA keyboard-simulation tests (WinAppDriver
SendKeys
, etc.) that assert immediately after typing will see stale VM state until focus moves.
xml
<TextBox Text="{x:Bind Vm.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
TextBox.Text
的默认触发方式为
LostFocus
(大多数其他属性默认是
PropertyChanged
)。视图模型(VM)不会随按键实时更新,且UIA键盘模拟测试(如WinAppDriver的
SendKeys
)在输入后立即断言时,会看到过时的VM状态,直到焦点转移。

Attached 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:Bind
运行时崩溃

{x:Bind}
requires
Converter
to be a
{StaticResource}
lookup.
Converter={x:Null}
compiles but the generated code calls
LookupConverter("")
, which returns null, then dereferences it — you get
Resource Dictionary Key can only be String-typed
/
NullReferenceException
on first activation of the binding. If you don't want a converter, omit the property entirely.
{x:Bind}
要求
Converter
{StaticResource}
查找。
Converter={x:Null}
可以编译,但生成的代码会调用
LookupConverter("")
,返回null后会被解引用——首次激活绑定时会抛出
Resource Dictionary Key can only be String-typed
/
NullReferenceException
异常。如果不需要转换器,直接省略该属性。

Prefer
x:Bind
static functions over
IValueConverter

优先使用
x:Bind
静态函数而非
IValueConverter

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}" />
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

Acrylic和
ThemeShadow
渲染规则

  • BackgroundSizing
    defaults to
    InnerBorderEdge
    on both
    Border
    and
    Control
    , which correctly clips acrylic to the inner stroke. The hazard is the opposite of intuition: don't change it to
    OuterBorderEdge
    on a bordered acrylic surface — that's what makes the material bleed past the stroke.
  • ThemeShadow
    casts a shadow from the caster's
    Translation
    Z. Microsoft's recommended elevations are
    16
    for tooltips,
    32
    for popup/flyout UI,
    128
    for dialogs — pick by surface type. For non-popup casters, add the surfaces it should land on to
    ThemeShadow.Receivers
    ; otherwise the shadow has nothing to fall on and looks clipped.
  • BackgroundSizing
    Border
    Control
    上默认值为
    InnerBorderEdge
    ,可正确将亚克力效果裁剪至内边框。误区与直觉相反:不要在带边框的亚克力表面将其改为
    OuterBorderEdge
    ——这会导致材质溢出边框。
  • ThemeShadow
    从投射器的
    Translation
    Z轴投射阴影。微软推荐的高度值为:工具提示16、弹出/浮出UI32、对话框128——根据表面类型选择。对于非弹出式投射器,需将阴影应落在的表面添加至
    ThemeShadow.Receivers
    ;否则阴影没有投射目标,看起来会被裁剪。

Theming rules (short version)

主题设置规则(精简版)

  • {ThemeResource ...}
    at usage sites (updates on theme switch).
    {StaticResource}
    inside
    ThemeDictionaries
    for theme-local definitions;
    SystemAccentColor
    /
    SystemColor*
    are the exceptions and stay
    {ThemeResource}
    .
  • Custom theme dictionaries cover
    Light
    ,
    Dark
    , and
    HighContrast
    explicitly — never
    Default
    .
  • Name resources by purpose (
    CardBackgroundBrush
    ,
    DangerTextBrush
    ), not hue.
  • Light/Dark working ≠ High Contrast working. Test in a Contrast theme separately.
  • Never set
    HighContrastAdjustment="None"
    unless your app already supplies system-aware brushes throughout.
  • 使用站点处使用
    {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
NavigationView
Left
Pick the closest row in the silhouette table; hero / document / utility shapes are equally valid
Treat brand colour or tinted backdrop as off-patternOverriding
SystemAccentColor
or using a tinted
DesktopAcrylicBackdrop
is how Microsoft's own first-party apps differentiate
Tiny content island on an oversized windowEither size the window to the content (see Window sizing) or let content fill the available space
Custom pill / segmented tab switcher built by hand
NavigationView
Top or
SelectorBar
Equal-width 50/50 column split where one pane is structuralStable size for the structural pane, flexible for content — only if a structural pane is part of the silhouette at all
Hard-coded color literals (
#RRGGBB
,
White
)
{ThemeResource}
brushes by semantic name
ScrollViewer
wrapped around a
ListView
/
GridView
The collection control already scrolls — give it a constrained height
Custom
ControlTemplate
for a standard control
Built-in control + lightweight style overrides
Placeholder text used as the only field labelAlways provide a visible label
Required commands hidden at small widths with no routeOverflow menu, secondary surface, or a responsive promotion rule
Modal
ContentDialog
for non-blocking hints
TeachingTip
,
InfoBar
, or inline status
Destructive action (Delete / Discard / Reset) fired without confirmation
ContentDialog
with verb-labelled primary action and
Cancel
secondary; surface item identity (name, count) in the body
Custom list control when
ListView
/
GridView
fits
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.
❌ 不要做✅ 建议做法
习惯性地将所有应用都构建为左侧
NavigationView
布局
选择形态表中最接近的布局;核心展示/文档/工具形态同样合理
将品牌色或着色背景视为不符合模式重写
SystemAccentColor
或使用着色
DesktopAcrylicBackdrop
是微软自家第一方应用实现差异化的方式
超大窗口中放置极小内容区域要么根据内容调整窗口尺寸(见窗口尺寸设置),要么让内容填充可用空间
手动构建自定义胶囊/分段标签切换器使用
NavigationView
顶部布局或
SelectorBar
等宽50/50分栏,其中一个窗格为结构性布局结构性窗格使用固定尺寸,内容窗格使用自适应尺寸——前提是结构性窗格属于所选形态的一部分
使用硬编码颜色字面量(如
#RRGGBB
White
使用按语义命名的
{ThemeResource}
画刷
ListView
/
GridView
外包裹
ScrollViewer
集合控件本身已支持滚动——为其设置固定高度即可
为标准控件自定义
ControlTemplate
使用内置控件 + 轻量级样式重写
仅使用占位文本作为字段标签始终提供可见标签
在小尺寸下隐藏必要命令且无替代入口使用溢出菜单、次级界面或响应式升级规则
对非阻塞提示使用模态
ContentDialog
使用
TeachingTip
InfoBar
或内联状态提示
执行破坏性操作(删除/丢弃/重置)前不进行确认使用带明确主操作和
Cancel
次操作的
ContentDialog
;在对话框正文中显示操作对象标识(名称、数量)
ListView
/
GridView
适用时仍自定义列表控件
使用平台集合控件 + 虚拟化功能
仅当以下所有条件满足时才构建自定义UI:无平台/Gallery/Toolkit控件适用;你将实现键盘、焦点、UI自动化、主题资源、高对比度和响应式行为;你有默认/悬停/按下/禁用/选中/聚焦/错误状态的规范;你已通过键盘和对比度主题测试。

References (load on demand)

参考资料(按需加载)

FileLoad when…
references/brushes-and-icons.md
Looking up a brush key by purpose, picking between
Icon
/
IconSource
slots, choosing among
FontIcon
/
SymbolIcon
/
PathIcon
/ etc.
references/theme-accessibility.md
Authoring theme dictionaries, custom brushes/styles/templates, or High Contrast support.
references/layout-review.md
Reviewing responsive behaviour, breakpoints, or empty/loading/error coverage on a data-driven page.
文件加载时机…
references/brushes-and-icons.md
根据用途查找画刷键、选择
Icon
/
IconSource
插槽、在
FontIcon
/
SymbolIcon
/
PathIcon
等中做选择时
references/theme-accessibility.md
编写主题字典、自定义画刷/样式/模板或实现高对比度支持时
references/layout-review.md
评审数据驱动页面的响应式行为、断点或空状态/加载状态/错误状态覆盖情况时