转载链接:Avalonia 学习笔记01. Images & Buttons(图片与按钮) - simonoct - 博客园
我对软件的图形化界面很感兴趣,查看了很多框架我最后选定C#的Avalonia作为我第一个学习的框架,不过我发现这个UI框架教程貌似很缺乏。
后面到YouTube上搜,看到一个作者制作了一系列教程,虽然是英文,但是我通过语音转文字+AI翻译学习,感觉还是挺不错的。
视频地址:https://youtu.be/ort9IqKAnL4?si=uTEjZ88osw1BLcyk
资源下载地址:https://github.com/angelsix/youtube/tree/develop/Avalonia BatchProcess
1.1 App.axaml
<Application xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"x:Class="AvaloniaApplication2.App"RequestedThemeVariant="Default"><!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --><Application.Styles><FluentTheme /><StyleInclude Source="Styles/AppDefaultStyles.axaml"></StyleInclude></Application.Styles><Application.Resources><SolidColorBrush x:Key="PrimaryForeground">#CFCFCF</SolidColorBrush><SolidColorBrush x:Key="PrimaryBackground">#14172D</SolidColorBrush><LinearGradientBrush x:Key="PrimaryBackgroundGradient" StartPoint="0%, 0%" EndPoint="100%, 0%"><GradientStop Offset="0" Color="#111214"></GradientStop><GradientStop Offset="1" Color="#151E3E"></GradientStop></LinearGradientBrush><SolidColorBrush x:Key="PrimaryHoverBackground">#333B5A</SolidColorBrush><SolidColorBrush x:Key="PrimaryHoverForeground">White</SolidColorBrush></Application.Resources> </Application>
-
<Application ...>: 这是根标签,代表你的整个应用程序。
xmlns="..."
: 这些是“命名空间”,可以理解为代码的“地址簿”。它告诉编译器<Button>
、<Grid>
这些标签应该去哪里找它们的定义。初学时不用太关心,知道是必须的就行。RequestedThemeVariant="Default"
: 设置应用的主题。Default 会跟随你的操作系统是亮色模式还是暗色模式。你也可以强制设为 Dark 或 Light。
-
<Application.Styles>
: 应用的“样式规则手册”。这里面定义了整个应用的外观规则。<FluentTheme />
: 引入 Avalonia 官方提供的 Fluent UI 主题。它给了所有控件(按钮、文本框等)一个现代化的、符合 Windows 11 风格的默认外观。<StyleInclude Source="Styles/AppDefaultStyles.axaml" />
: 包含我们自己的样式文件。这行代码告诉“大管家”:“除了官方的 Fluent 主题,再把我们自己写的那个 AppDefaultStyles.axaml 文件里的样式规则也加载进来。” Source 就是文件的路径。使用自定义样式文件可以统一覆盖控件的默认样式,例如直接使用<Button>
创建的按钮颜色、鼠标悬停颜色等等,如果一个个button指定颜色不仅混乱,如果遇到修改主题也会变得血雨腥风。
-
<Application.Resources>
: 应用的“公共资源仓库”。这里存放着可以在整个应用中反复使用的“材料”,比如颜色、画刷、图标等。这样做的好处是,如果要改一个颜色,只需要改这里一处,所有用到它的地方都会自动更新。例如AppDefaultStyles.axaml里面就引用了默认的背景颜色、前景颜色,想要变更直接修改颜色即可。 -
<SolidColorBrush x:Key="PrimaryForeground">#CFCFCF</SolidColorBrush>
:SolidColorBrush
: 定义一个纯色画刷。你可以把它想象成一支只能画一种颜色的油漆刷。x:Key="PrimaryForeground"
: 给这支油漆刷贴上一个标签(唯一的钥匙),名字叫 PrimaryForeground。之后我们就可以通过这个名字来使用它。- #CFCFCF: 这是颜色的十六进制代码。
-
<LinearGradientBrush x:Key="PrimaryBackgroundGradient" ...>
: 定义一个线性渐变画刷。 StartPoint="0%, 0%"
: 渐变从左上角开始。EndPoint="100%, 0%"
: 渐变到右上角结束。所以这是一个从左到右的水平渐变。<GradientStop Offset="0" Color="..." />
: 在渐变轴的起点 (0%) 放置这个颜色。<GradientStop Offset="1" Color="..." />
: 在渐变轴的终点 (100%) 放置这个颜色。
假设StartPoint="0%, 0%",EndPoint="100%, 100%",那么渐变轴就是右下角方向了。(0%, 0%) <-- 起点 (StartPoint)+-------------------------+ (100%, 0%)| \ || \ <-- 渐变轴 (Gradient Axis)| \ || \ |+-------------------------+(0%, 100%) (100%, 100%) <-- 终点 (EndPoint)
1.2 Styles/AppDefaultStyles.axaml
新建一个名为Styles的文件夹,然后模板文件可以用Rider新建的时候选择Avalonia Styles,命名为AppDefaultStyles.axaml即可。
这个文件专门用来写我们自定义的样式规则,让应用看起来更个性化。
<Styles xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"><Design.PreviewWith><Border Padding="20" Background="{DynamicResource PrimaryBackgroundGradient}" Width="200"><!-- Add Controls for Previewer Here --><Button Foreground="White" Content="Hello World"></Button></Border></Design.PreviewWith><!-- Add Styles Here --><Style Selector="Button"><Setter Property="FontSize" Value="20"></Setter><Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}"></Setter><Setter Property="Background" Value="{DynamicResource PrimaryBackground}"></Setter></Style><Style Selector="Button:pointerover /template/ ContentPresenter"><Setter Property="Foreground" Value="{DynamicResource PrimaryHoverForeground}"></Setter><Setter Property="Background" Value="{DynamicResource PrimaryHoverBackground}"></Setter></Style> </Styles>
-
<Styles ...>
: 这是一个专门存放样式规则的容器。 -
<Design.PreviewWith>
: “设计师预览区”。这部分代码非常特别,它只在 IDE 的预览窗口里显示,不会被编译到最终的应用里。它的作用是给你提供一个“画板”,让你在设计样式的时候能立刻看到效果,不然就只能靠重新编译或者脑子想象。 -
<Style Selector="Button">
: 定义一条样式规则。Selector="Button"
: 这个是“选择器”,意思是“这条规则将应用到我项目里所有的<Button>
控件上”。<Setter Property="FontSize" Value="20" />
:Setter
: “设置器”,表示要在这里设置一个属性。Property="FontSize"
: 要设置的属性是“字体大小”。Value="20"
: 把字体大小的值设为 20。
<Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}" />
: 把文字颜色(Foreground)设置为我们之前在 App.axaml 里定义的、名叫 PrimaryForeground 的那个颜色资源。{DynamicResource ...} 的意思就是“去公共仓库按名字找资源”。
-
<Style Selector="Button:pointerover /template/ ContentPresenter">
: 定义一条更特殊的规则。Selector="Button:pointerover"
: 这个选择器更具体,它说:“这条规则只在鼠标指针悬停在按钮上时才生效”。:pointerover 就是“鼠标悬停”这个特殊状态。/template/ ContentPresenter
: 这是个固定写法。它的意思是,为了可靠地改变按钮在悬停时的背景和前景,我们需要更深入地指定到按钮内部一个叫 ContentPresenter 的部件。- 里面的两个
<Setter>
就定义了当鼠标悬停时,按钮的文字颜色和背景色应该变成我们定义的“Hover”颜色。
1.3 MainWindow.axaml
这是用户能看到的、实际的主窗口。它负责把各种控件(按钮、图片等)组合、摆放起来,形成最终的界面。
<Window xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"mc:Ignorable="d" d:DesignWidth="1024" d:DesignHeight="600"Width="1024" Height="600"x:Class="AvaloniaApplication2.MainWindow"Title="AvaloniaApplication2"><Grid Background="{DynamicResource PrimaryBackground}" ColumnDefinitions="Auto, *"><Border Padding="20" Background="{DynamicResource PrimaryBackgroundGradient}"><Grid RowDefinitions="*, Auto"><StackPanel Spacing="12"><Image Source="{SvgImage /Assets/Images/logo.svg}" Width="200"></Image><Button Content="Home" HorizontalAlignment="Stretch"></Button><Button Content="Process" HorizontalAlignment="Stretch"></Button><Button Content="Actions" HorizontalAlignment="Stretch"></Button><Button Content="Macros" HorizontalAlignment="Stretch"></Button><Button Content="Reporter" HorizontalAlignment="Stretch"></Button><Button Content="History" HorizontalAlignment="Stretch"></Button></StackPanel><Button Grid.Row="1" Content="Setting"></Button></Grid></Border></Grid></Window>
<Window ...>
: 窗口。这是所有内容的根容器。d:DesignWidth/Height
: 仅在设计预览时的窗口宽高,方便你设计。Width/Height
: 程序真正运行时的窗口宽高。Title
: 显示在窗口左上角的标题。
<Grid ...>
: 网格。这是布局控件,就像一个看不见的 Excel 表格。Background="{...}"
: 把窗口的背景设置为我们定义的 PrimaryBackground 颜色。ColumnDefinitions="Auto, *"
: 定义网格的列。这行代码把网格分成了两列:Auto
: 第一列的宽度由其内部最宽的内容自动决定。*
: 第二列将占据所有剩余的可用空间。这是创建“侧边栏 + 主内容区”布局的经典手法。
<Border ...>
: 边框容器。它像一个可以设置背景、边框和内边距的盒子。这里它被放在了 Grid 的第一列(Auto 列),用来创建侧边栏。Padding="20"
: 在这个盒子的四边都留出 20 个单位的内部空白,让内容不会紧贴着边缘。Background="{...}"
: 把这个侧边栏的背景设置为我们定义的那个渐变色画刷。
<Grid RowDefinitions="*, Auto">
: 在侧边栏内部又放了一个网格。这个网格用来做垂直布局。RowDefinitions="*, Auto"
: 把这个内部网格分成了两行:*
: 第一行占据所有剩余的垂直空间。Auto
: 第二行的高度由其内容(设置按钮)自动决定。- 效果:这会把“设置”按钮“钉”在侧边栏的底部,而上面的部分则会填满剩余空间。
<StackPanel Spacing="12">
: 堆叠面板。它是一个简单的布局控件,会把里面的东西一个接一个地堆起来(默认是垂直方向)。Spacing="12"
: 在每个被堆起来的控件之间,增加 12 个单位的间距。类似于word的行间距。- 这个 StackPanel 被放在了内部 Grid 的第一行 (* 行)。
<Image ...>
: 图片。Source="{SvgImage /Assets/Images/logo.svg}"
: 设置图片的来源。{SvgImage ...}
是用来加载 SVG 格式矢量图的特殊语法,路径是相对于项目根目录的。注意在Rider里面编辑图片属性,将Bulid Action修改为:AvaloniaResource,不然在构建程序的时候这些资源是不会构建的。Width="200"
: 设置图片显示的宽度为 200。- 注意需要安装一个名为Svg.Controls.Skia.Avalonia的包,教程展示用的是过时版本。
<Button ...>
: 按钮。Content="Home"
: 按钮上显示的文字。HorizontalAlignment="Stretch"
: 水平对齐方式。Stretch 意味着让按钮的宽度拉伸,以填满其父容器(StackPanel)分配给它的所有水平空间。
<Button Grid.Row="1" Content="Setting" />
: 放在底部的设置按钮。Grid.Row="1"
: 这是一个附加属性。它是在告诉父级的<Grid RowDefinitions="*, Auto">
:“请把我这个按钮放在你的第二行(索引从0开始,所以1是第二行)!”,这是实现Setting图标“钉”在底部的关键。- 父级的
<Grid RowDefinitions="*, Auto">
:第一行 (Row="0")** 的高度是*
。意思是:“请占据所有可用的、剩余的垂直空间”,第二行 (Row="1") 的高度是Auto
。意思是:“我的高度刚刚好能包住我的内容就行”。Row="1"
就是Auto部分,实现setting在侧边栏的底部。 - setting会在左侧的原因貌似有些复杂,根据文档所说:HorizontalAlignment和VerticalAlignment默认值都是Stretch,也就是拉伸。但是这里表现却是和显式指定
Left
一致。一个可能的解释是:因为父 Grid 列是 Auto。Auto 列的宽度由子控件的“理想尺寸”决定。虽然按钮的 HorizontalAlignment 默认是 Stretch,但它必须先报告一个尺寸给 Auto 列。这个尺寸就是它包裹其内容(HorizontalContentAlignment 为 Left)所需的最小尺寸。因此,Grid 列和按钮本身都收缩了,Stretch 没有机会发挥作用。 - 显式指定
HorizontalAlignment="Stretch"
时,默认保守策略会被覆盖,找一个最大且具体(非无限宽)的宽度,然后拉伸。这里因为<Image ... Width=200>
,所以最大宽度就是200。如果想让图标也居中,则需要额外指定HorizontalContentAlignment="Center"
1.4 <Grid>
Grid 的核心在于先定义网格,再放置内容。
1.4.1 定义行和列
用 <Grid.ColumnDefinitions>
和<Grid.RowDefinitions>
来定义网格的结构。每一列或每一行的大小有三种定义方式:
- Auto (按需分配):列宽或行高由其内部最宽/最高的内容决定。它会“收缩/撑开”以正好包裹住内容。
<Grid ColumnDefinitions="Auto, *"><Button Grid.Column="0">短内容</Button><Button Grid.Column="1">这里是很长很长的内容</Button> </Grid>
|-- 由“短内容”决定 --|---------- 占据所有剩余空间 -----------| | [ 短内容 ] | [ 这里是很长很长的内容 ] | | (Auto) | (*) |
- *** (星号/比例分配):这是最强大的方式。它会贪婪地抢占所有“剩余”的空间**。如果多个列/行都是 *,它们会按比例瓜分。
*, *
:两个都分 1 份,所以是 1:1,即对半分。*, 2*
:一个分 1 份,一个分 2 份,总共 3 份,所以是 1:2 的比例。
假设宽度是900px
|----------- 300px (1份) -----------|----------------- 600px (2份) -----------------|
| | |
| (*) | (2*) |
- 固定值 (Fixed Value):直接指定一个固定的数字,单位是设备无关像素。
<Grid ColumnDefinitions="100, *"><Button Grid.Column="0">固定100宽</Button><Button Grid.Column="1">剩余所有</Button> </Grid>
|---- 100px ----|------------------ 占据所有剩余空间 ------------------| | [ 固定100宽 ] | [ 剩余所有 ] | | (Fixed) | (*) |
1.4.2 放置控件
使用 Grid.Row 和 Grid.Column 附加属性,告诉每个控件应该去哪个“房间”(单元格)。索引从 0 开始。
<!-- 一个 2x2 的网格 --> <Grid ColumnDefinitions="*,*" RowDefinitions="*,*"><!-- 左上角 (0,0) --><Button Grid.Row="0" Grid.Column="0" Content="A"/><!-- 右上角 (0,1) --><Button Grid.Row="0" Grid.Column="1" Content="B"/><!-- 左下角 (1,0) --><Button Grid.Row="1" Grid.Column="0" Content="C"/><!-- 右下角 (1,1) --><Button Grid.Row="1" Grid.Column="1" Content="D"/> </Grid>
Column 0 (*) Column 1 (*)+-----------------+-----------------+| | | Row 0 (*)| [ A ] | [ B ] || (0,0) | (0,1) || | |+-----------------+-----------------+| | | Row 1 (*)| [ C ] | [ D ] || (1,0) | (1,1) || | |+-----------------+-----------------+
1.4.3 Grid 可以包含StackPanel吗?
正如上方教程展示的代码,Grid可以包含StackPanel,后续视频教程可能会深入这里我就简单找一例:
<Grid ColumnDefinitions="Auto, *"><!-- Grid的第0列: 放置一个StackPanel来管理一堆按钮 --><StackPanel Grid.Column="0" Margin="10" Spacing="8"><Button>首页</Button><Button>产品</Button><Button>设置</Button><Button>关于</Button></StackPanel><!-- Grid的第1列: 放置主内容 --><Border Grid.Column="1" Background="LightGray"><TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">主内容区域</TextBlock></Border></Grid>
<---------------------------------- Grid (整体框架) -----------------------------------> +---------------------------------+---------------------------------------------------+ | <-- StackPanel (在第0列) ------> | | | | | | +-------------------------+ | | | | [ 首页 ] | | | | +-------------------------+ | | | | [ 产品 ] | | | | +-------------------------+ | 主内容区域 | | | [ 设置 ] | | (第1列) | | +-------------------------+ | | | | [ 关于 ] | | | | +-------------------------+ | | | | | +---------------------------------+---------------------------------------------------+ | <------- Col 0 (Auto) --------> | <-------------------- Col 1 (*) -----------------> |
1.5 <StackPanel>
StackPanel 的核心是按顺序线性排列。它只有两个关键属性你需要关心。
1.5.1 Orientation (方向)
- Vertical (默认值): 从上到下堆叠。
- Horizontal: 从左到右排列。
<!-- 垂直方向 (默认) --> <StackPanel><Button>A</Button><Button>B</Button><Button>C</Button> </StackPanel>
+-----------+ | [ A ] | +-----------+ | [ B ] | +-----------+ | [ C ] | +-----------+
<!-- 水平方向 --> <StackPanel Orientation="Horizontal"><Button>A</Button><Button>B</Button><Button>C</Button> </StackPanel>
+---------+---------+---------+ | [ A ] | [ B ] | [ C ] | +---------+---------+---------+
1.5.2 Spacing (间距)
在每个子控件之间添加固定的空白。
<StackPanel Spacing="10"><Button>A</Button><Button>B</Button> </StackPanel>
+-----------+
| [ A ] |
+-----------+
|-- 10px空隙--| <-- Spacing
+-----------+
| [ B ] |
+-----------+
1.5.3 StackPanel 可以包含 Grid 吗?
可以
可能的场景:当你需要构建一个列表,而列表中的每一项本身又是一个复杂的布局时。例如,一个显示用户信息的垂直列表。
<StackPanel Spacing="10"><!-- 第一张用户卡片 (一个 Grid) --><Grid ColumnDefinitions="Auto, *"><Image Grid.Column="0" Source="avatar1.png" Width="50"/><StackPanel Grid.Column="1" Margin="10,0,0,0"><TextBlock>张三</TextBlock><TextBlock>软件工程师</TextBlock></StackPanel></Grid><!-- 第二张用户卡片 (另一个 Grid) --><Grid ColumnDefinitions="Auto, *"><Image Grid.Column="0" Source="avatar2.png" Width="50"/><StackPanel Grid.Column="1" Margin="10,0,0,0"><TextBlock>李四</TextBlock><TextBlock>UI 设计师</TextBlock></StackPanel></Grid></StackPanel>
+---------------------------------------------+ <-- StackPanel 开始 | | | +-----------------------------------------+ | <-- 第一个 Grid 开始 | | | | | | | [头像1] | 张三 | | | | (Auto) | 软件工程师 | | | | | (*) | | | +-----------------------------------------+ | <-- 第一个 Grid 结束 | | |----------------- 10px 间距 -----------------| | | | +-----------------------------------------+ | <-- 第二个 Grid 开始 | | | | | | | [头像2] | 李四 | | | | (Auto) | UI 设计师 | | | | | (*) | | | +-----------------------------------------+ | <-- 第二个 Grid 结束 | | +---------------------------------------------+ <-- StackPanel 结束