接下来研究一下菜单和工具栏的使用。
FontAwesome.Sharp包是一个专门为 .NET 桌面应用(重点支持 WPF、WinForms、Avalonia)打造的开源图标库封装包,
核心是将 Font Awesome 图标字体 封装成了桌面应用可直接使用的控件,
让开发者无需手动处理字体文件、字符编码,就能快速在界面中嵌入美观的图标。
简单的说,就是用字体方式显示已经预定义好的图标(大约有几百个)。
还有一种SVG方式显示图标,这种方式相当于是通过图标数据画矢量图的图标画出来。在仅仅静态显示的时候,两种方式区别就比较小。
下一节研究一下SVG这种方式。

界面:

解决方案:

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
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:local="clr-namespace:WpfApp1"
xmlns:conv="clr-namespace:WpfApp1.Converters"
xmlns:fa="clr-namespace:FontAwesome.Sharp;assembly=FontAwesome.Sharp"
mc:Ignorable="d"
Title="WPF 菜单+工具栏示例(.NET 8 适配版)" Height="450" Width="800">
<Window.Resources>
<!-- 1. 注册字符串相等转换器 -->
<conv:StringEqualsConverter x:Key="StringEqualsConverter"/>
<!-- 2. 自定义工具栏样式 -->
<Style x:Key="CustomToolBarStyle" TargetType="{x:Type ToolBar}">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToolBar}">
<Border Background="#F5F5F5" BorderThickness="0,1,0,0" BorderBrush="#E0E0E0">
<StackPanel Orientation="Horizontal" Margin="10,5,10,5" IsItemsHost="True"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- 3. 工具栏按钮样式(用Converter实现选中逻辑) -->
<Style x:Key="ToolBarButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,8,0"/>
<Setter Property="Padding" Value="8,6"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="#333333"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="btnBorder" Background="{TemplateBinding Background}" CornerRadius="4">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- FontAwesome图标(字体方式) -->
<TextBlock x:Name="btnIcon"
FontFamily="pack://application:,,,/FontAwesome.Sharp;component/fonts/#Font Awesome 6 Free Solid"
Text="{TemplateBinding Tag}"
FontSize="24" Margin="0,0,4,0"/>
<TextBlock x:Name="btnText" Text="{TemplateBinding Content}" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<!-- 鼠标悬浮 -->
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#E9E9E9"/>
</Trigger>
<!-- 修复:用Converter判断选中状态(避开Condition的Value绑定限制) -->
<DataTrigger Binding="{Binding ActiveToolBarButton, Converter={StaticResource StringEqualsConverter}, ConverterParameter={TemplateBinding Content}}" Value="True">
<Setter TargetName="btnBorder" Property="Background" Value="#2ECC71"/>
<Setter TargetName="btnIcon" Property="Foreground" Value="White"/>
<Setter TargetName="btnText" Property="Foreground" Value="White"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<DockPanel>
<!-- 顶部菜单 -->
<Menu DockPanel.Dock="Top" Background="White" Foreground="#333333">
<MenuItem Header="文件">
<MenuItem Header="打开" Command="{Binding FileOpenCommand}" InputGestureText="Ctrl+O"/>
<MenuItem Header="保存" Command="{Binding FileSaveCommand}" InputGestureText="Ctrl+S"/>
</MenuItem>
</Menu>
<!-- 工具栏 -->
<ToolBar DockPanel.Dock="Top" Style="{StaticResource CustomToolBarStyle}">
<Button Content="配置" Tag="" Command="{Binding ToolBarConfigCommand}" Style="{StaticResource ToolBarButtonStyle}"/>
<Button Content="流程" Tag="" Command="{Binding ToolBarFlowCommand}" Style="{StaticResource ToolBarButtonStyle}"/>
<Button Content="生产" Tag="" Command="{Binding ToolBarProductionCommand}" Style="{StaticResource ToolBarButtonStyle}"/>
<Separator Margin="8,0" Width="1" Background="#E0E0E0"/>
<Button Content="保存" Tag="" Command="{Binding ToolBarSaveCommand}" Style="{StaticResource ToolBarButtonStyle}"/>
</ToolBar>
<!-- 内容区域 -->
<Grid Background="White">
<TextBlock Text="内容区域" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DockPanel>
</Window>App.xaml.cs
using Autofac;
using System.Configuration;
using System.Data;
using System.Windows;
using WpfApp1.Bootstrap;
using WpfApp1.ViewModels;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
// 全局Autofac容器
public static IContainer? Container { get; private set; }
protected override void OnStartup(StartupEventArgs e)
{
// 1. 初始化Autofac容器
Container = AutofacConfig.BuildContainer();
if (Container == null)
{
MessageBox.Show("Autofac 容器初始化失败", "错误");
Shutdown();
return;
}
// 2. 从容器中解析 MainWindowViewModel(依赖注入获取实例)
var mainViewModel = Container.Resolve<MainWindowViewModel>();
// 3. 实例化主窗口并绑定 DataContext
var mainWindow = new MainWindow();
mainWindow.DataContext = mainViewModel;
mainWindow.Show();
// 禁用默认启动逻辑(避免重复加载窗口)
base.OnStartup(e);
}
}
}App.xaml
<Application x:Class="WpfApp1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1"> <Application.Resources> </Application.Resources> </Application>
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApp1.ViewModels
{
/// <summary>
/// 主窗口ViewModel(继承ViewModelBase获得INotifyPropertyChanged实现)
/// </summary>
public partial class MainWindowViewModel : ObservableObject
{
// 用于绑定工具栏按钮的选中状态(示例:默认"配置"按钮选中)
private string _activeToolBarButton = "配置";
public string ActiveToolBarButton
{
get => _activeToolBarButton;
set => SetProperty(ref _activeToolBarButton, value);
}
#region 菜单命令
/// <summary>
/// 「文件-打开」命令(RelayCommand 简化命令创建,无需手动实现ICommand)
/// </summary>
[RelayCommand]
private void FileOpen()
{
MessageBox.Show("执行「文件-打开」操作", "提示");
}
/// <summary>
/// 「文件-保存」命令
/// </summary>
[RelayCommand]
private void FileSave()
{
MessageBox.Show("执行「文件-保存」操作", "提示");
}
#endregion
#region 工具栏命令
/// <summary>
/// 「配置」按钮命令
/// </summary>
[RelayCommand]
private void ToolBarConfig()
{
ActiveToolBarButton = "配置";
MessageBox.Show("切换到「配置」功能页面", "提示");
}
/// <summary>
/// 「流程」按钮命令
/// </summary>
[RelayCommand]
private void ToolBarFlow()
{
ActiveToolBarButton = "流程";
MessageBox.Show("切换到「流程」功能页面", "提示");
}
/// <summary>
/// 「生产」按钮命令
/// </summary>
[RelayCommand]
private void ToolBarProduction()
{
ActiveToolBarButton = "生产";
MessageBox.Show("切换到「生产」功能页面", "提示");
}
/// <summary>
/// 「保存」按钮命令
/// </summary>
[RelayCommand]
private void ToolBarSave()
{
MessageBox.Show("执行工具栏「快速保存」操作", "提示");
}
#endregion
}
}StringEqualsConverter.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace WpfApp1.Converters
{
/// <summary>
/// 字符串相等判断转换器
/// </summary>
public class StringEqualsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// 比较ViewModel的ActiveToolBarButton(value)和按钮的Content(parameter)是否相等
return value?.ToString() == parameter?.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}AutofacConfig.cs
using Autofac;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1.ViewModels;
namespace WpfApp1.Bootstrap
{
/// <summary>
/// Autofac 依赖注入配置
/// </summary>
public static class AutofacConfig
{
/// <summary>
/// 构建容器并返回
/// </summary>
/// <returns></returns>
public static IContainer BuildContainer()
{
var builder = new ContainerBuilder();
// 注册 ViewModel(单例模式,适合窗口ViewModel;后续业务服务可注册为瞬时/作用域)
builder.RegisterType<MainWindowViewModel>().SingleInstance();
// 后续扩展:注册业务服务(例如:builder.RegisterType<OrderService>().As<IOrderService>().InstancePerDependency();)
// 构建并返回容器
return builder.Build();
}
}
}FontAwesome.Sharp画图标的过程如下:
WPF 渲染
TextBlock时,先解析FontFamily,通过 Pack URI 找到FontAwesome.Sharp.dll中的fonts目录。在该目录下,找到字体家族名称为
Font Awesome 6 Free Solid的.ttf字体文件,加载该字体。解析
Text="{TemplateBinding Tag}",Tag属性中填写的是 Font Awesome 图标的「Unicode 十六进制编码实体」:&#x:表示这是十六进制的 Unicode 编码。f013:是「设置」图标的专属 Unicode 编码(每个 Font Awesome 图标都有唯一的编码)。;:编码实体的结束符。WPF 用加载好的 Font Awesome 字体,将
这个 Unicode 字符渲染成对应的图标,而不是普通文本,最终就显示出了我们看到的图标样式。