本文使用 DraftDepot(稿仓 https://github.com/xland/DraftDepot )编辑并发布,DraftDepot 可以把一篇文章一键发布到多个平台。
在学习本文章之前,请先看一下本文章之前的一篇文章
圆角矩形
如果是绘制一些简单的图形
那么 UI Composition 就提供了对应的组件对象,比如圆角,如下代码所示:
cpp
//winrt::Windows::UI::Composition::CompositionRoundedRectangleGeometry clipGeo{ nullptr };
clipGeo = win->compositor.CreateRoundedRectangleGeometry();
visual.Clip(win->compositor.CreateGeometricClip(clipGeo));
//cornerRadius是圆角半径
//win->dpi是当前窗口的缩放比率,确保圆角在不同分辨率下视觉大小一致。
//w和h是visual的尺寸,当控件尺寸小于圆角直径时,自动将圆角半径缩小为宽/高的一半。
const float r = std::min({ cornerRadius * win->dpi, w * 0.5f, h * 0.5f });
clipGeo.Size({ w, h });
clipGeo.CornerRadius({ r, r });
上面的代码把一个 CompositionRoundedRectangleGeometry 对象挂到了 visual 上,背景和 visual 会一起被裁剪。
(创建一个几何裁剪区域,应用到目标 visual 上,使其超出圆角矩形的部分被隐藏。)
CreateGeometricClip:相比普通的矩形裁剪,支持任意几何形状,性能优于 XAML 的 CornerRadius 属性
如果你的visual有背景色,那么这个时候你就能看到一个有背景色的圆角矩形了
typescript
winrt::Windows::UI::Color color = winrt::Windows::UI::ColorHelper::FromArgb(a, r, g, b);
visual.Brush(win->compositor.CreateColorBrush(color));
圆角矩形的边框
如果你想为圆角矩形绘制边框,那么就在上述代码的基础上增加如下代码逻辑:
typescript
// winrt::Windows::UI::Composition::ShapeVisual borderVisual{ nullptr };
// winrt::Windows::UI::Composition::CompositionRoundedRectangleGeometry borderGeo{ nullptr };
// winrt::Windows::UI::Composition::CompositionSpriteShape borderShape{ nullptr };
borderVisual = win->compositor.CreateShapeVisual();
borderGeo = win->compositor.CreateRoundedRectangleGeometry();
borderShape = win->compositor.CreateSpriteShape(borderGeo);
borderShape.StrokeBrush(win->compositor.CreateColorBrush(borderColor.getUIColor()));
borderVisual.Shapes().Append(borderShape);
// 顶层:保证边框盖在所有子节点之上
visual.Children().InsertAtTop(borderVisual);
const float bw = borderWidth * win->dpi;
const float inset = bw * 0.5f;
// stroke 以路径为中线双向扩散,路径内缩 bw/2 才能让外沿贴 (0,0)-(w,h)
borderVisual.Size({ w, h });
borderVisual.Offset({ 0.f, 0.f, 0.f });
// Geometry 无 Offset 属性,把位移写在 Shape 上(CompositionShape.Offset)
borderShape.Offset({ inset, inset });
borderGeo.Size({ std::max(0.f, w - bw), std::max(0.f, h - bw) });
// 圆角边框:外圆角 = cornerRadius,描边中线半径 = outerR - bw/2
const float outerR = std::min({ cornerRadius * win->dpi, w * 0.5f, h * 0.5f });
const float midR = std::max(0.f, outerR - inset);
borderGeo.CornerRadius({ midR, midR });
borderShape.StrokeThickness(bw);
这段代码中:
ShapeVisual: 相当于一个轻量级的"画布",负责管理变换(Offset/Size)和子图形列表。通过 InsertAtTop 确保边框始终覆盖在内容之上。
CompositionSpriteShape: 相当于"画笔设置"。它持有 Geometry 的引用,并定义了描边颜色 (StrokeBrush)、线宽 (StrokeThickness) 以及形状级别的偏移 (Offset)。
CompositionRoundedRectangleGeometry: 相当于"路径数据"。只关心纯粹的几何尺寸和圆角半径,不包含任何渲染样式。
Composition API(以及大多数底层图形 API)的描边是以几何路径为中心线向两侧等宽扩散的。如果直接将 Geometry 设为 (w, h),描边的一半会溢出到 Visual 外部被裁切,导致视觉上线宽只有设定值的一半。
代码通过 "几何内缩 + 形状外推" 解决了这个问题:
- 计算物理线宽:bw = borderWidth * win->dpi 将逻辑像素转换为物理像素,保证高 DPI 下线条不模糊
- 计算内缩量:inset = bw * 0.5f 描边向外扩散的距离正好是半线宽
- 形状外推:borderShape.Offset({ inset, inset }) Geometry 本身没有 Offset 属性,所以把位移加在 Shape 上,将缩小的路径"推回"到正确位置
- 几何内缩:borderGeo.Size({ w - bw, h - bw }) 宽高各减去一个完整线宽,使路径外沿+半线宽正好等于 (w, h)
- 圆角修正:midR = outerR - inset 外圆角减去半线宽 = 描边中线的实际圆角半径,保证内外圆弧同心
此时你就能看到圆角矩形的边框了。

绘制文本是另一节的内容,我们以后再介绍。