Avalonia 学习笔记07. Control Themes(控件主题)

在本章节中,我们的目标是创建一个可复用的、带图标的按钮控件,以简化我们在视图(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

关键修改点解释:

  1. <ControlTheme x:Key="{x:Type IconButton}" TargetType="IconButton">

    • 用途:这行代码声明了我们正在定义一个控件主题。TargetType="IconButton" 指定了这个主题是为我们自己的 IconButton 控件设计的。它会成为 IconButton 的默认外观。
  2. <ContentPresenter.ContentTemplate>

    • 用途:我们不再让 ContentPresenter 简单地显示内容,而是为它提供了一个 DataTemplate(数据模板)。这个模板定义了内容的具体结构:一个水平排列的 StackPanel,里面包含一个用于显示图标的 Label 和一个用于显示主要内容的 ContentControl
  3. 图标绑定:Content="{Binding $parent[IconButton].IconText}"

    • 用途:这是 DataTemplate 内部的绑定语法。$parent[IconButton] 的意思是“从当前位置向上查找,找到第一个名为 IconButton 的父控件”,然后 .IconText 表示绑定到该控件的 IconText 属性。这样,我们在XAML中设置的 IconText 就能正确地显示为图标了。
    • 注意Label 的 Classes="icon" 是为了能让 AppDefaultStyles.axaml 中定义的图标字体样式应用到这个 Label 上。
  4. 内容和数据上下文绑定

    • 用途<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="&#xe7f2;" Content="Click Me!" />
        <IconButton IconText="&#xe3ee;" 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 的后台代码文件。

关键修改点解释:

  1. public class IconButton : Button

    • 用途:我们将基类从默认的 TemplatedControl 修改为了 Button。这是至关重要的一步。通过继承 Button,我们的 IconButton 自动获得了按钮的所有核心功能,例如 Click 事件、Command 绑定、可点击性等。它现在“是”一个按钮了。
  2. IconTextProperty

    • 用途:我们在这里定义了一个新的 StyledProperty(样式化属性),名为 IconTextStyledProperty 是 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

关键修改点解释:

  1. :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="&#xE2C2;"></Label>
                            <Label Classes="akko" Content="Home"></Label>
                        </StackPanel>
                    </Button>
                    <Button HorizontalAlignment="Stretch">
                        <StackPanel Orientation="Horizontal">
                            <Label Classes="icon" Content="&#xE346;"></Label>
                            <Label Classes="akko" Content="Process"></Label>
                        </StackPanel>
                    </Button>
                    <Button HorizontalAlignment="Stretch">
                        <StackPanel Orientation="Horizontal">
                            <Label Classes="icon" Content="&#xE7F2;"></Label>
                            <Label Classes="akko" Content="Actions"></Label>
                        </StackPanel>
                    </Button>
                    <Button HorizontalAlignment="Stretch">
                        <StackPanel Orientation="Horizontal">
                            <Label Classes="icon" Content="&#xE3EE;"></Label>
                            <Label Classes="akko" Content="Macros"></Label>
                        </StackPanel>
                    </Button>
                    <Button HorizontalAlignment="Stretch">
                        <StackPanel Orientation="Horizontal">
                            <Label Classes="icon" Content="&#xEB7A;"></Label>
                            <Label Classes="akko" Content="Reporter"></Label>
                        </StackPanel>
                    </Button>
                    <Button HorizontalAlignment="Stretch">
                        <StackPanel Orientation="Horizontal">
                            <Label Classes="icon" Content="&#xE03A;"></Label>
                            <Label Classes="akko" Content="History"></Label>
                        </StackPanel>
                    </Button>
                    <Button>
                        <Label Classes="icon-only" Content="&#xE272;"></Label>
                    </Button>
                    <Button Classes="transparent" Grid.Row="1">
                        <Label Classes="icon-only" Content="&#xE272;"></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

关键修改点解释:

  1. <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

关键修改点解释:

  1. [assembly: XmlnsDefinition(...)]
    • 用途:这是一个程序集级别的特性(Attribute),它将一个 C# 命名空间(AvaloniaApplication2.Controls)映射到一个 XML 命名空间(https://github.com/avaloniaui)。
    • 原因:这是一个非常有用的“语法糖”。添加它之后,我们就可以在 XAML 中直接使用 <IconButton />,而不需要先在文件顶部定义一个像 xmlns:c="clr-namespace:AvaloniaApplication2.Controls" 这样的前缀,然后再使用 <c:IconButton />。它让我们的自定义控件使用起来和 Avalonia 的内置控件一样方便、自然。
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="&#xE2C2;" HorizontalAlignment="Stretch" Classes.active="{Binding HomePageIsActive}" Command="{Binding GoToHomeCommand}">
                        <Label Classes="akko" Content="Home" IsVisible="{Binding SideMenuExpanded}"></Label>
                    </IconButton>
                    <IconButton IconText="&#xE346;" HorizontalAlignment="Stretch" Classes.active="{Binding ProcessPageIsActive}" Command="{Binding GoToProcessCommand}">
                        <Label Classes="akko" Content="Process" IsVisible="{Binding SideMenuExpanded}"></Label>
                    </IconButton>
                    <IconButton IconText="&#xE7F2;" HorizontalAlignment="Stretch" Classes.active="{Binding ActionsPageIsActive}" Command="{Binding GoToActionsCommand}">
                        <Label Classes="akko" Content="Actions" IsVisible="{Binding SideMenuExpanded}"></Label>
                    </IconButton>
                    <IconButton IconText="&#xE3EE;" HorizontalAlignment="Stretch" Classes.active="{Binding MacrosPageIsActive}" Command="{Binding GoToMacrosCommand}">
                        <Label Classes="akko" Content="Macros" IsVisible="{Binding SideMenuExpanded}"></Label>
                    </IconButton>
                    <IconButton IconText="&#xEB7A;" HorizontalAlignment="Stretch" Classes.active="{Binding ReporterPageIsActive}" Command="{Binding GoToReporterCommand}">
                        <Label Classes="akko" Content="Reporter" IsVisible="{Binding SideMenuExpanded}"></Label>
                    </IconButton>
                    <IconButton IconText="&#xE03A;" 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="&#xE272;"></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="&#xE2FE;" 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="&#xE23E;" Content="Login" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
                    <IconButton IconText="&#xE094;" 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="&#xEC54;" Content="Clear Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
                    <IconButton IconText="&#xE5DE;" Content="Export Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
                    <IconButton IconText="&#xE20C;" Content="Import Cache" Grid.Column="1" HorizontalAlignment="Stretch"></IconButton>
                </StackPanel>
            </StackPanel>
        </StackPanel>
        
    </Grid>
</UserControl>

 

posted @ 2025-09-23 19:47  Gordon管  阅读(32)  评论(0)    收藏  举报