Avalonia Canvas 画仪表盘:3 段 XAML,0 行 C#
【免费下载链接】AvaloniaDevelop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The future of .NET UI项目地址: https://gitcode.com/GitHub_Trending/ava/Avalonia
用 Avalonia 里的一个 Canvas 加四种基础图形,做出一个带刻度、指针和读数的速度仪表盘。不写一行 C#,不装任何额外依赖,XAML 粘进任意窗口就能跑。
先弄清 3 件事
- 入口控件在哪:画板是
Canvas,实现在 Canvas.cs;直线、矩形、椭圆、路径这四种图形都放在 Shapes 目录里。 - 原点和方向:画板左上角是原点 (0,0),x 向右增大、y 向下增大,和数学课的坐标系反着来。
150,150就是"向右 150、向下 150"。 - 单位是 DIP:DIP 即设备无关像素(跨平台统一的"虚拟像素"),支持小数,比如
27.5。同一份 XAML 在 Windows、macOS、Linux 上渲染一致。 - Canvas 不做布局:它不给子元素自动排列,测量时自己报 0,0 大小,所以能叠在任何面板上当绘图层。但反过来,坐标不钉住,子元素全堆在左上角。
核心机制:一个比喻讲透
Canvas 像一块贴在窗口上的透明玻璃。玻璃本身没花纹、不占布局空间,可以盖在任何面板上当绘图层。每个子控件是一张贴纸,用Canvas.Left/Top手动按在玻璃某个位置。两张贴纸重叠时谁在上?不是谁先贴的算,而是ZIndex说了算,数字大的在前。这个属性定义在 Visual.cs,所有图形都能用。
说白了,Canvas 只回答一个问题:"在这个坐标上画什么"。表盘、刻度、指针全是几何图形,渲染器按 ZIndex 顺序遍历:用Stroke描边,用Fill填充内部。换个说法,XAML 负责"画什么",Canvas 负责"画在哪",ZIndex 负责"谁在前"。
上面是仓库里的一张几何渲染测试图,两条弧线和表盘一样,走的都是"几何图形 + 填充/描边"这套机制。
API 速查(一张表搞定)
| 名称 | 干什么 | 填什么值 |
|---|---|---|
Canvas.Left/Canvas.Top | 钉住元素左边、上边位置 | 像素数,50 |
Canvas.Right/Canvas.Bottom | 从右、下边缘反向锚定,适合"贴底"元素 | 像素数,50 |
StartPoint/EndPoint | Line 的两个端点 | 点,150,150 |
Stroke/StrokeThickness | 轮廓颜色、粗细 | Black、2 |
Fill/RadiusX/RadiusY | 内部颜色、圆角半径 | Red、12 |
Data(仅 Path) | 任意轮廓命令串 | M 50,100 L 150,100 Z |
Canvas.ZIndex | 叠放层次,大的在上 | 整数,1 |
Text(TextBlock) | 读数文字 | 60 km/h |
Left 和 Right 同时设时 Left 生效,Top 和 Bottom 同设时 Top 生效。你可能会问为什么不让两边平均一下——避免歧义,每个方向只认一个。Data记住四个命令就够:M移动画笔、L画直线、C贝塞尔曲线、Z闭合,闭合后Fill才会填整个区域。
从 0 到能跑:三段递进
照常新建一个 Avalonia 应用(官方模板依赖都配好了),把MainWindow.axaml里 Window 的内容换成下面每一段的 Canvas,就能直接看效果。每段代码都是完整画板,不用拼。八条刻度的坐标不是瞎编:表盘中心 (150,150),刻度半径 125,0° 方向就是 (150,25);45° 方向 125×0.707≈88,所以拐角在 (239,61),其余六个点照此对称。
铺底 + 打刻度
这一段只画静态部分:两个椭圆做出表盘,八条线打出刻度。画到这儿,仪表盘已经占了一半。
<!-- 外灰内白:两个椭圆自然形成环形边框,省掉描边 --> <Canvas Width="300" Height="300"> <Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260" Fill="#D3D3D3"/> <Ellipse Canvas.Left="28" Canvas.Top="28" Width="244" Height="244" Fill="White"/> <!-- 刻度起点贴边、终点朝内收 15 像素,比一整圈实线更好认 --> <Line StartPoint="150,25" EndPoint="150,40" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,61" EndPoint="228,72" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="275,150" EndPoint="260,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,239" EndPoint="228,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="150,275" EndPoint="150,260" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,239" EndPoint="72,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="25,150" EndPoint="40,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,61" EndPoint="72,72" Stroke="Black" StrokeThickness="2"/> </Canvas>插一根会转的针
这一步比上一段多了两个元素:从中心伸到表盘边缘的红针,和盖住针尾的中心红点。两者都用 ZIndex 显式声明层次,后面无论怎么挪位置都不会被刻度压住。指针指向 (210,85),角度大约 35°,正好对应后面 60 km/h 的读数;指针朝哪都行,它就是一根线段。
<Canvas Width="300" Height="300"> <Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260" Fill="#D3D3D3"/> <Ellipse Canvas.Left="28" Canvas.Top="28" Width="244" Height="244" Fill="White"/> <Line StartPoint="150,25" EndPoint="150,40" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,61" EndPoint="228,72" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="275,150" EndPoint="260,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,239" EndPoint="228,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="150,275" EndPoint="150,260" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,239" EndPoint="72,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="25,150" EndPoint="40,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,61" EndPoint="72,72" Stroke="Black" StrokeThickness="2"/> <!-- 指针:中心 (150,150) 到边缘,ZIndex=1 保证压在刻度之上 --> <Line StartPoint="150,150" EndPoint="210,85" Stroke="Red" StrokeThickness="4" Canvas.ZIndex="1"/> <!-- 中心红点盖住针尾汇聚处,ZIndex=2 提到最前 --> <Ellipse Canvas.Left="142" Canvas.Top="142" Width="16" Height="16" Fill="Red" Canvas.ZIndex="2"/> </Canvas>贴上数字标签
最后一步只多一行:表盘下方放一个TextBlock显示速度。文字和图形互不干扰,它在 Canvas 眼里也只是又一个子元素。
<Canvas Width="300" Height="300"> <Ellipse Canvas.Left="20" Canvas.Top="20" Width="260" Height="260" Fill="#D3D3D3"/> <Ellipse Canvas.Left="28" Canvas.Top="28" Width="244" Height="244" Fill="White"/> <Line StartPoint="150,25" EndPoint="150,40" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,61" EndPoint="228,72" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="275,150" EndPoint="260,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="239,239" EndPoint="228,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="150,275" EndPoint="150,260" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,239" EndPoint="72,228" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="25,150" EndPoint="40,150" Stroke="Black" StrokeThickness="2"/> <Line StartPoint="61,61" EndPoint="72,72" Stroke="Black" StrokeThickness="2"/> <!-- 指针与中心点:ZIndex 锁住层次,指针不会掉到刻度下面 --> <Line StartPoint="150,150" EndPoint="210,85" Stroke="Red" StrokeThickness="4" Canvas.ZIndex="1"/> <Ellipse Canvas.Left="142" Canvas.Top="142" Width="16" Height="16" Fill="Red" Canvas.ZIndex="2"/> <!-- 读数:放在表盘下方,文字只是 Canvas 的又一个子元素 --> <TextBlock Canvas.Left="120" Canvas.Top="285" Text="60 km/h" FontSize="14"/> </Canvas>到这儿仪表盘成型了:椭圆、直线、红点、文字四种元素,加上两层 ZIndex,静态部分齐活。
新手最常翻车的 3 个点
这三个坑最多人栽,看一行就能避开。
不写坐标,子元素叠成一层
❌<Ellipse Width="80" Height="80" Fill="Red"/>✅<Ellipse Canvas.Left="50" Canvas.Top="50" Width="80" Height="80" Fill="Red"/>
Canvas 不做布局,Left/Top 缺了,所有子元素全堆在原点 (0,0),看起来像"没渲染"。
Left 和 Right 同设,Right 被忽略
❌<Rectangle Canvas.Left="50" Canvas.Right="50" Width="100"/>✅<Rectangle Canvas.Left="50" Width="100"/>
同设时 Left 生效、Right 直接无视,Top 和 Bottom 同理。每个方向只写一个。
靠 XAML 顺序调层次,一挪全乱
❌ 把指针挪到刻度前面一行,结果指针被刻度盖住 ✅ 指针显式加Canvas.ZIndex="1"
默认"后写的在上",但元素一旦移动,顺序就跟着乱了,重要层次用 ZIndex 钉死。
下一步能玩什么
- 给指针挂上
RotateTransform,用DoubleAnimation驱动AngleProperty改角度,仪表盘指针就能随读数真实摆动;这条能力只管转,图形大小不变。 - 把
Slider的Value绑到某个图形的Width和Height,拖动滑块看几何实时变化,这就是数据驱动图形的入口。
仪表盘到这里就算跑通了。坐标系、层次、绑定这几套机制,在 Avalonia 其他控件上完全通用,这套 Canvas 经验可以整个带走。
【免费下载链接】AvaloniaDevelop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The future of .NET UI项目地址: https://gitcode.com/GitHub_Trending/ava/Avalonia
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考