Avalonia 学习笔记07. Control Themes(控件主题)
视频链接:https://youtu.be/M3CFj0x-tts?si=n3HtIDE9YrcAKqVm
在本章节中,我们的目标是创建一个可复用的、带图标的按钮控件,以简化我们在视图(View)中编写的XAML代码。当前,每创建一个带图标的按钮,都需要在 <Button> 内部嵌套一个 <StackPanel> 和两个 <Label>,这非常繁琐。
我们将创建一个名为 IconButton 的新控件,它天生就包含一个图标区域和一个内容区域,使得我们可以像这样使用它:<IconButton IconText="..." Content="..."/>。我们将通过控件主题(ControlTheme)来实现这个功能,这是一种深度自定义控件外观的强大方式。
7.1 Controls\IconButton.axaml
首先,我们在项目中新建一个名为 Controls 的文件夹。然后右键点击该文件夹,选择“添加” -> “新建项”,在 Avalonia 分类中选择 Templated Control(模板化控件),并将其命名为 IconButton.axaml。
创建完成后,我们会得到一个 .axaml 文件和一个对应的 .axaml.cs 后台代码文件。
我们的目标是让 IconButton 拥有标准按钮的所有外观和行为,并在此基础上增加一个图标。最简单的方式就是复制 Avalonia Fluent 主题中 Button 的默认模板,然后进行修改。
以下代码就是从 Avalonia 源码中复制并修改而来的 Button 的 ControlTheme。
这个初始的模板可以在这个链接获取:https://github.com/AvaloniaUI/Avalonia/blob/master/src/Avalonia.Themes.Fluent/Controls/Button.xaml
关键修改点解释:
-
<ControlTheme x:Key="{x:Type IconButton}" TargetType="IconButton">- 用途:这行代码声明了我们正在定义一个控件主题。
TargetType="IconButton"指定了这个主题是为我们自己的IconButton控件设计的。它会成为IconButton的默认外观。
- 用途:这行代码声明了我们正在定义一个控件主题。
-
<ContentPresenter.ContentTemplate>- 用途:我们不再让
ContentPresenter简单地显示内容,而是为它提供了一个DataTemplate(数据模板)。这个模板定义了内容的具体结构:一个水平排列的StackPanel,里面包含一个用于显示图标的Label和一个用于显示主要内容的ContentControl。
- 用途:我们不再让
-
图标绑定:
Content="{Binding $parent[IconButton].IconText}"- 用途:这是
DataTemplate内部的绑定语法。$parent[IconButton]的意思是“从当前位置向上查找,找到第一个名为IconButton的父控件”,然后.IconText表示绑定到该控件的IconText属性。这样,我们在XAML中设置的IconText就能正确地显示为图标了。 - 注意:
Label的Classes="icon"是为了能让AppDefaultStyles.axaml中定义的图标字体样式应用到这个Label上。
- 用途:这是
-
内容和数据上下文绑定
- 用途:
<ContentControl DataContext="{...}" Content="{...}" />这一部分是为了修复一个在视频后面会遇到的DataContext(数据上下文)问题。当我们在IconButton内部放置一个需要绑定到视图模型(ViewModel)的控件时(比如侧边栏可以折叠的文字),这个设置可以确保数据上下文被正确地传递下去,让绑定能够正常工作。
- 用途:
<!-- 备注:这是 IconButton 的视觉模板定义文件。 -->
<!-- 它决定了 IconButton 在界面上看起来是什么样子。 -->
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:ClassModifier="internal">
<!-- Design.PreviewWith 用于在设计器中预览控件效果,不影响程序运行 -->
<Design.PreviewWith>
<Border Padding="20">
<StackPanel Spacing="20">
<IconButton IconText="" Content="Click Me!" />
<IconButton IconText="" Classes="accent" Content="Click Me!" />
<Button Content="Click Me!" />
<Button Classes="accent" Content="Click Me!" />
</StackPanel>
</Border>
</Design.PreviewWith>
<!-- 这是 IconButton 控件的主题定义 -->
<ControlTheme x:Key="{x:Type IconButton}" TargetType="IconButton">
<!-- 下面是按钮在各种状态下的默认样式设置(背景、前景、边框等) -->
<Setter Property="Background" Value="{DynamicResource ButtonBackground}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ButtonBorderThemeThickness}" />
<Setter Property="CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="Padding" Value="{DynamicResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="RenderTransform" Value="none" />
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.075" />
</Transitions>
</Setter>
<!-- Template 定义了控件的内部结构 (ControlTemplate) -->
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter x:Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding CornerRadius}"
Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<!-- 这里是我们自定义的内容模板,用于显示图标和文字 -->
<ContentPresenter.ContentTemplate>
<DataTemplate DataType="x:Object">
<StackPanel Orientation="Horizontal" Spacing="8">
<!-- 这个 Label 用于显示图标,它的 Content 绑定到 IconButton 的 IconText 属性 -->
<Label Classes="icon" Content="{Binding $parent[IconButton].IconText}"></Label>
<!-- 这个 ContentControl 用于显示 IconButton 的主要内容,并修复了 DataContext 的问题 -->
<ContentControl DataContext="{Binding $parent[IconButton].DataContext}" Content="{Binding $parent[IconButton].Content}" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</ControlTemplate>
</Setter>
<!-- 下面是按钮在不同交互状态(如鼠标悬浮、按下)下的样式变化 -->
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed">
<Setter Property="RenderTransform" Value="scale(0.98)" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource ButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource ButtonForegroundDisabled}" />
</Style>
<!-- 这是对拥有 "accent" 样式的按钮的特殊处理 -->
<Style Selector="^.accent">
<Style Selector="^ /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrush}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForeground}" />
</Style>
<Style Selector="^:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPointerOver}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPointerOver}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPointerOver}" />
</Style>
<Style Selector="^:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundPressed}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushPressed}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundPressed}" />
</Style>
<Style Selector="^:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{DynamicResource AccentButtonBackgroundDisabled}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentButtonBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource AccentButtonForegroundDisabled}" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>
7.2 Controls\IconButton.axaml.cs
这是 IconButton 的后台代码文件。
关键修改点解释:
-
public class IconButton : Button- 用途:我们将基类从默认的
TemplatedControl修改为了Button。这是至关重要的一步。通过继承Button,我们的IconButton自动获得了按钮的所有核心功能,例如Click事件、Command绑定、可点击性等。它现在“是”一个按钮了。
- 用途:我们将基类从默认的
-
IconTextProperty- 用途:我们在这里定义了一个新的
StyledProperty(样式化属性),名为IconText。StyledProperty是 Avalonia 中的一种特殊属性,它支持数据绑定、样式设置和在模板中使用。这使得我们可以在 XAML 文件中像这样<IconButton IconText="..."/>来给它赋值。
- 用途:我们在这里定义了一个新的
// 备注:这是 IconButton 的后台逻辑代码文件。
using Avalonia;
using Avalonia.Controls;
namespace AvaloniaApplication2.Controls;
// 关键:让 IconButton 继承自 Button,这样它就拥有了按钮的所有功能
public class IconButton : Button
{
// 定义一个名为 IconText 的新属性,这样我们就可以在 XAML 中设置它
// StyledProperty 是一种支持数据绑定和样式的特殊属性
public static readonly StyledProperty<string> IconTextProperty = AvaloniaProperty.Register<IconButton, string>(
nameof(IconText));
// 这是 IconText 属性的常规 C# 包装器
public string IconText
{
get => GetValue(IconTextProperty);
set => SetValue(IconTextProperty, value);
}
}
7.3 Styles\AppDefaultStyles.axaml
关键修改点解释:
:is(Button)选择器- 用途:我们将之前所有针对
Button的样式选择器,例如Style Selector="Button",都修改为了Style Selector=":is(Button)"。 - 原因:
:is()是一个伪类选择器,它会匹配所有符合括号内条件的控件。:is(Button)不仅会匹配标准的<Button>,还会匹配任何继承自Button的控件。因为我们的IconButton继承自Button,所以这个修改可以确保我们为普通按钮编写的通用样式(如圆角、字体大小、颜色等)也能自动应用到IconButton上,保持了应用的视觉统一性。
- 用途:我们将之前所有针对
<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">
<!-- 此处预览代码未更改 -->
<StackPanel Spacing="12">
<Image Source="{SvgImage /Assets/Images/logo.svg}" Width="200"></Image>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="Home"></Label>
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="Process"></Label>
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="Actions"></Label>
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="Macros"></Label>
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="Reporter"></Label>
</StackPanel>
</Button>
<Button HorizontalAlignment="Stretch">
<StackPanel Orientation="Horizontal">
<Label Classes="icon" Content=""></Label>
<Label Classes="akko" Content="History"></Label>
</StackPanel>
</Button>
<Button>
<Label Classes="icon-only" Content=""></Label>
</Button>
<Button Classes="transparent" Grid.Row="1">
<Label Classes="icon-only" Content=""></Label>
</Button>
</StackPanel>
</Border>
</Design.PreviewWith>
<!-- 未更改的样式 -->
<Style Selector="Window">
<!-- <Setter Property="FontFamily" Value="{DynamicResource AkkoPro}"></Setter> -->
</Style>
<Style Selector="Border">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Width" Duration="0:0:1"></DoubleTransition>
</Transitions>
</Setter>
</Style>
<Style Selector="Label.icon, Label.icon-only">
<Setter Property="FontFamily" Value="{DynamicResource Phosphor-Fill}"></Setter>
<Setter Property="Margin" Value="0 2 5 0"></Setter>
<Setter Property="FontSize" Value="19"></Setter>
</Style>
<Style Selector="Label.icon-only">
<Setter Property="Margin" Value="0"></Setter>
</Style>
<Style Selector="Button, Label.akko">
<Setter Property="FontFamily" Value="{DynamicResource AkkoPro}"></Setter>
</Style>
<!-- 关键修改:将 "Button" 选择器改为 ":is(Button)",以使其能应用到 IconButton -->
<Style Selector=":is(Button)">
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="CornerRadius" Value="10"></Setter>
<Setter Property="Foreground" Value="{DynamicResource PrimaryForeground}"></Setter>
<Setter Property="Background" Value="{DynamicResource PrimaryBackground}"></Setter>
</Style>
<Style Selector=":is(Button) /template/ ContentPresenter">
<Setter Property="RenderTransform" Value="scale(1)"></Setter>
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Foreground" Duration="0:0:0.1"></BrushTransition>
<BrushTransition Property="Background" Duration="0:0:0.1"></BrushTransition>
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.1"></TransformOperationsTransition>
</Transitions>
</Setter>
</Style>
<Style Selector=":is(Button).transparent:pointerover Label">
<Setter Property="RenderTransform" Value="scale(1.2)"></Setter>
</Style>
<Style Selector=":is(Button):pointerover /template/ ContentPresenter">
<Setter Property="Foreground" Value="{DynamicResource PrimaryHoverForeground}"></Setter>
<Setter Property="Background" Value="{DynamicResource PrimaryHoverBackground}"></Setter>
</Style>
<Style Selector=":is(Button).active /template/ ContentPresenter">
<Setter Property="Background" Value="{DynamicResource PrimaryActiveBackground}"></Setter>
</Style>
<Style Selector=":is(Button).transparent">
<Setter Property="Background" Value="Transparent"></Setter>
</Style>
<Style Selector=":is(Button).transparent Label.icon-only">
<Setter Property="FontFamily" Value="{DynamicResource Phosphor}"></Setter>
</Style>
<Style Selector=":is(Button).transparent:pointerover /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent"></Setter>
</Style>
</Styles>
7.4 App.axaml
关键修改点解释:
<MergeResourceInclude Source="/Controls/IconButton.axaml"/>- 用途:这行代码的作用是将我们刚刚创建的
IconButton.axaml文件(它是一个资源字典)合并到应用的主资源中。 - 原因:如果不进行这一步,整个应用程序将不知道
IconButton的样式定义在哪里,导致IconButton无法被正确渲染,会显示为一个没有样式的空白控件。这一步使得IconButton的主题在整个应用范围内都是可用的。
- 用途:这行代码的作用是将我们刚刚创建的
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvaloniaApplication2.App"
xmlns:local="clr-namespace:AvaloniaApplication2"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator></local:ViewLocator>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="Styles/AppDefaultStyles.axaml"></StyleInclude>
</Application.Styles>
<!-- 在这里添加了对 IconButton 样式文件的引用 -->
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- 这行代码将 IconButton.axaml 中定义的样式合并到整个应用中 -->
<MergeResourceInclude Source="/Controls/IconButton.axaml"></MergeResourceInclude>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<!-- 此处其他资源未更改 -->
<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="PrimaryActiveBackground">#6633dd</SolidColorBrush>
<SolidColorBrush x:Key="PrimaryHoverForeground">White</SolidColorBrush>
<FontFamily x:Key="AkkoPro">/Assets/Fonts/AkkoPro-Regular.ttf#Akko Pro</FontFamily>
<FontFamily x:Key="AkkoProBold">/Assets/Fonts/AkkoPro-Bold.ttf#Akko Pro</FontFamily>
<FontFamily x:Key="Phosphor">/Assets/Fonts/Phosphor.ttf#Phosphor</FontFamily>
<FontFamily x:Key="Phosphor-Fill">/Assets/Fonts/Phosphor-Fill.ttf#Phosphor</FontFamily>
</Application.Resources>
</Application>
7.5 App.axaml.cs
关键修改点解释:
[assembly: XmlnsDefinition(...)]- 用途:这是一个程序集级别的特性(Attribute),它将一个 C# 命名空间(
AvaloniaApplication2.Controls)映射到一个 XML 命名空间(https://github.com/avaloniaui)。 - 原因:这是一个非常有用的“语法糖”。添加它之后,我们就可以在 XAML 中直接使用
<IconButton />,而不需要先在文件顶部定义一个像xmlns:c="clr-namespace:AvaloniaApplication2.Controls"这样的前缀,然后再使用<c:IconButton />。它让我们的自定义控件使用起来和 Avalonia 的内置控件一样方便、自然。
- 用途:这是一个程序集级别的特性(Attribute),它将一个 C# 命名空间(
using System;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Metadata; // 需要引入这个命名空间
using AvaloniaApplication2.Data;
using AvaloniaApplication2.Factories;
using AvaloniaApplication2.ViewModels;
using Microsoft.Extensions.DependencyInjection;
// 关键:添加这个程序集特性,让我们可以不带前缀地在 XAML 中使用 IconButton
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "AvaloniaApplication2.Controls")]
namespace AvaloniaApplication2;
public partial class App : Application
{
// 此文件中的其他代码在本节教程中未发生变更
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
var collection = new ServiceCollection();
collection.AddSingleton<MainViewModel>();
collection.AddTransient<ActionsPageViewModel>();
collection.AddTransient<HomePageViewModel>();
collection.AddTransient<MacrosPageViewModel>();
collection.AddTransient<ProcessPageViewModel>();
collection.AddTransient<ReporterPageViewModel>();
collection.AddTransient<HistoryPageViewModel>();
collection.AddTransient<SettingsPageViewModel>();
collection.AddSingleton<Func<ApplicationPageNames, PageViewModel>>(x => name => name switch
{
ApplicationPageNames.Home => x.GetRequiredService<HomePageViewModel>(),
ApplicationPageNames.Process => x.GetRequiredService<ProcessPageViewModel>(),
ApplicationPageNames.Macros => x.GetRequiredService<MacrosPageViewModel>(),
ApplicationPageNames.Actions => x.GetRequiredService<ActionsPageViewModel>(),
ApplicationPageNames.Reporter => x.GetRequiredService<ReporterPageViewModel>(),
ApplicationPageNames.History => x.GetRequiredService<HistoryPageViewModel>(),
ApplicationPageNames.Settings => x.GetRequiredService<SettingsPageViewModel>(),
_ => throw new InvalidOperationException()
});
collection.AddSingleton<PageFactory>();
var services = collection.BuildServiceProvider();
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainView
{
DataContext = services.GetService<MainViewModel>()
};
}
base.OnFrameworkInitializationCompleted();
}
}
7.6 Views\MainView.axaml
关键修改点解释:
- 替换为
<IconButton>:这里是应用我们新控件最直观的地方。之前所有左侧菜单的按钮都是由<Button><StackPanel><Label/><Label/></StackPanel></Button>这样的复杂结构组成的。 - 简化XAML:现在,我们用一行简洁的
<IconButton>就完成了同样的功能。图标通过IconText属性设置,而需要折叠的文字Label则直接作为IconButton的内容(Content)传入。这使得 XAML 代码大大减少,并且更易于阅读和维护。
<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="1400" d:DesignHeight="800"
Width="1400" Height="800"
MinWidth="1400" MinHeight="800 "
x:Class="AvaloniaApplication2.MainView"
xmlns:vm="clr-namespace:AvaloniaApplication2.ViewModels"
xmlns:view="clr-namespace:AvaloniaApplication2.Views"
x:DataType="vm:MainViewModel"
Title="AvaloniaApplication2">
<Design.DataContext><vm:MainViewModel></vm:MainViewModel></Design.DataContext>
<Grid Background="{DynamicResource PrimaryBackground}" ColumnDefinitions="Auto, *">
<ContentControl Grid.Column="1" Content="{Binding CurrentPage}" />
<Border Padding="20" Background="{DynamicResource PrimaryBackgroundGradient}">
<Grid RowDefinitions="*, Auto">
<StackPanel Spacing="12">
<Image PointerPressed="InputElement_OnPointerPressed" Source="{SvgImage /Assets/Images/logo.svg}" Width="220" IsVisible="{Binding SideMenuExpanded}"></Image>
<Image PointerPressed="InputElement_OnPointerPressed" Source="{SvgImage /Assets/Images/icon.svg}" Width="22" IsVisible="{Binding !SideMenuExpanded}"></Image>
<!-- 关键修改:将原来的复杂 Button 结构替换为简洁的 IconButton -->
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding HomePageIsActive}" Command="{Binding GoToHomeCommand}">
<Label Classes="akko" Content="Home" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding ProcessPageIsActive}" Command="{Binding GoToProcessCommand}">
<Label Classes="akko" Content="Process" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding ActionsPageIsActive}" Command="{Binding GoToActionsCommand}">
<Label Classes="akko" Content="Actions" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding MacrosPageIsActive}" Command="{Binding GoToMacrosCommand}">
<Label Classes="akko" Content="Macros" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding ReporterPageIsActive}" Command="{Binding GoToReporterCommand}">
<Label Classes="akko" Content="Reporter" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
<IconButton IconText="" HorizontalAlignment="Stretch" Classes.active="{Binding HistoryPageIsActive}" Command="{Binding GoToHistoryCommand}">
<Label Classes="akko" Content="History" IsVisible="{Binding SideMenuExpanded}"></Label>
</IconButton>
</StackPanel>
<!-- 这个设置按钮未在本节修改,但因为它也是Button,所以会受到 :is(Button) 样式的影响 -->
<Button Classes="transparent" Grid.Row="1" Classes.active="{Binding SettingsPageIsActive}" Command="{Binding GoToSettingsCommand}">
<Label Classes="icon-only" Content=""></Label>
</Button>
</Grid>
</Border>
</Grid>
</Window>
7.7 Views\SettingsPageView.axaml
与 MainView 类似,SettingsPageView 中的所有带图标的按钮也被替换成了我们新的 IconButton。
关键修改点解释:
- 代码简化:这里的修改效果更加明显。对于那些内容只是简单文本的按钮,例如“Release License”,之前的XAML结构被极大地简化了。
- 统一接口:现在,无论是只需要文本的按钮,还是需要图标+文本的按钮,我们都使用同一个
IconButton控件,通过IconText和Content属性来控制其显示,使得API非常统一和清晰。
<UserControl 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"
xmlns:vm="clr-namespace:AvaloniaApplication2.ViewModels"
mc:Ignorable="d" d:DesignWidth="1100" d:DesignHeight="900"
Background="{DynamicResource PrimaryBackground}"
Foreground="{DynamicResource PrimaryForeground}"
x:DataType="vm:SettingsPageViewModel"
x:Class="AvaloniaApplication2.Views.SettingsPageView">
<Design.DataContext><vm:SettingsPageViewModel></vm:SettingsPageViewModel></Design.DataContext>
<Grid ColumnDefinitions="*, *" RowDefinitions="Auto, *">
<!-- Header 未更改 -->
<Grid Name="HeaderGrid" Grid.ColumnSpan="2">
<Image Source="{SvgImage /Assets/Images/background-settings.svg}" Height="160" Stretch="UniformToFill"></Image>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Label HorizontalAlignment="Center" Content="Settings"></Label>
<Label HorizontalAlignment="Center" Content="Version 3.0.0.2 Beta"></Label>
<Label HorizontalAlignment="Center" Content="Compiled Jul 07 2025"></Label>
</StackPanel>
</Grid>
<!-- Left side content -->
<StackPanel Grid.Column="0" Grid.Row="1" Spacing="10" Margin="15">
<!-- General -->
<StackPanel>
<Label Content="General"></Label>
<Grid ColumnDefinitions="*, Auto" RowDefinitions="Auto, Auto, Auto">
<!-- Release license -->
<TextBlock TextWrapping="Wrap" Text="Remove license of BatchProcess from this machine and release the license back to the server ready to be transferred to another machine."></TextBlock>
<!-- 关键修改:替换为 IconButton -->
<IconButton IconText="" Content="Release License" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
<!-- Skip Files -->
<TextBlock Grid.Row="1" Grid.Column="0" TextWrapping="Wrap" Text="Skip files if only Open, Save (Optional) and Close are Valid actions."></TextBlock>
<CheckBox Grid.Row="1" Grid.Column="1"></CheckBox>
<!-- Duplicate Entries -->
<TextBlock Grid.Row="2" Grid.Column="0" TextWrapping="Wrap" Text="Allow duplicate entries of the same file in project list"></TextBlock>
<CheckBox Grid.Row="2" Grid.Column="1"></CheckBox>
</Grid>
</StackPanel>
<!-- Location -->
<StackPanel>
<Label Content="Location"></Label>
<Grid ColumnDefinitions="*, Auto">
<StackPanel>
<TextBlock TextWrapping="Wrap" Text="Add or remove the locations to search for Reporter Templates, Macros, Actions and other custom files or templates."></TextBlock>
<TextBlock TextWrapping="Wrap" Text="All sub-directories will be searched automatically"></TextBlock>
</StackPanel>
<Button Grid.Column="1" Content="+ Folder" HorizontalAlignment="Stretch"></Button>
</Grid>
<ListBox ItemsSource="{Binding LocationPaths}">
</ListBox>
</StackPanel>
</StackPanel>
<!-- Right Side Content -->
<StackPanel Grid.Column="1" Grid.Row="1" Spacing="10" Margin="15">
<!-- SolidWorks Host (未更改) -->
<StackPanel>
<Label Content="SolidWorks Host"></Label>
<TextBlock TextWrapping="Wrap">
BatchProcess can work locally on the current machine, or on any machine accessible
over the network or even internet.<LineBreak /><LineBreak />
Enter the machines IP address, network name or localhost for this machine.
</TextBlock>
<ComboBox></ComboBox>
<Label Content="Connection established"></Label>
</StackPanel>
<!-- PDM Enterprise -->
<StackPanel Spacing="15">
<Label Content="PDM Enterprise"></Label>
<TextBlock TextWrapping="Wrap" Text="If you are using PDM Enterprise enter the credentials below and test login. BatchProcess can then automatically handle checking in and out files from PDM Enterprise."></TextBlock>
<Grid ColumnDefinitions="*, *, *">
<ComboBox HorizontalAlignment="Stretch"></ComboBox>
<TextBox Grid.Column="1"></TextBox>
<TextBox Grid.Column="2"></TextBox>
</Grid>
<StackPanel Orientation="Horizontal">
<!-- 关键修改:替换为 IconButton -->
<IconButton IconText="" Content="Login" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
<IconButton IconText="" Content="Refresh Vault" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
</StackPanel>
<Label Content="Connection Established"></Label>
</StackPanel>
<!-- Setting Cache -->
<StackPanel Spacing="15">
<Label Content="Setting Cache"></Label>
<TextBlock TextWrapping="Wrap">
Various settings are stored locally including Processes, Actions, Macros, Reports and History. <LineBreak /><LineBreak />
If you are experiencing issues you can try clearing the cache (this won't remove the license).
</TextBlock>
<StackPanel Orientation="Horizontal">
<!-- 关键修改:替换为 IconButton -->
<IconButton IconText="" Content="Clear Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
<IconButton IconText="" Content="Export Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
<IconButton IconText="" Content="Import Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</UserControl>
7.8 当前目录结构
│ App.axaml
│ App.axaml.cs
│ app.manifest
│ AvaloniaApplication2.csproj
│ Program.cs
│ ViewLocator.cs
│
├─Assets
│ ├─Fonts
│ │ AkkoPro-Bold.ttf
│ │ AkkoPro-Regular.ttf
│ │ Phosphor-Fill.ttf
│ │ Phosphor.ttf
│ │
│ └─Images
│ background-actions.svg
│ background-settings.svg
│ icon.svg
│ logo.svg
│
├─Controls
│ IconButton.axaml
│ IconButton.axaml.cs
│
├─Data
│ ApplicationPageNames.cs
│
├─Factories
│ PageFactory.cs
│
├─Styles
│ AppDefaultStyles.axaml
│
├─ViewModels
│ ActionsPageViewModel.cs
│ HistoryPageViewModel.cs
│ HomePageViewModel.cs
│ MacrosPageViewModel.cs
│ MainViewModel.cs
│ PageViewModel.cs
│ ProcessPageViewModel.cs
│ ReporterPageViewModel.cs
│ SettingsPageViewModel.cs
│ ViewModelBase.cs
│
└─Views
ActionsPageView.axaml
ActionsPageView.axaml.cs
HistoryPageView.axaml
HistoryPageView.axaml.cs
HomePageView.axaml
HomePageView.axaml.cs
MacrosPageView.axaml
MacrosPageView.axaml.cs
MainView.axaml
MainView.axaml.cs
ProcessPageView.axaml
ProcessPageView.axaml.cs
ReporterPageView.axaml
ReporterPageView.axaml.cs
SettingsPageView.axaml
SettingsPageView.axaml.cs
浙公网安备 33010602011771号