C# WPF MVVM 标签打印管理软件标准框架结构

下面提供一个完整、可扩展的标准框架结构,涵盖项目分层、目录组织、核心模块,适合作为标签打印管理软件的起点。


一、解决方案整体结构

LabelPrintManager.sln
│
├── src/
│   ├── LabelPrint.Domain/              # 领域层(实体、枚举、接口契约)
│   ├── LabelPrint.Infrastructure/      # 基础设施层(数据访问、打印、日志)
│   ├── LabelPrint.Application/         # 应用层(业务服务、DTO、校验)
│   ├── LabelPrint.WPF/                 # 表示层(MVVM、View、ViewModel)
│   └── LabelPrint.Tests/               # 单元测试
│
├── docs/                               # 文档
├── build/                              # 构建脚本
└── README.md

依赖方向(单向):

WPF → Application → Domain
WPF → Infrastructure → Domain
Application → Infrastructure(通过接口,DI 注入)

二、各层目录结构详解

1. LabelPrint.Domain(领域层)

LabelPrint.Domain/
├── Entities/
│   ├── LabelTemplate.cs        # 标签模板
│   ├── LabelElement.cs         # 标签元素(文本/条码/二维码/图片)
│   ├── PrintJob.cs             # 打印任务
│   ├── PrintRecord.cs          # 打印记录
│   ├── PrinterConfig.cs        # 打印机配置
│   └── Product.cs              # 产品/物料数据源
├── Enums/
│   ├── ElementType.cs          # Text/Barcode/QRCode/Image/Line
│   ├── BarcodeType.cs          # Code128/EAN13/QRCode...
│   ├── PrintStatus.cs          # Pending/Printing/Success/Failed
│   └── PrinterConnectionType.cs# USB/Network/Driver
├── ValueObjects/
│   ├── Position.cs
│   ├── Size.cs
│   └── LabelSize.cs
├── Events/
│   └── PrintCompletedEvent.cs
└── Interfaces/
    ├── IRepository<T>.cs
    ├── ILabelTemplateRepository.cs
    ├── IPrintRecordRepository.cs
    ├── IPrinterService.cs
    └── IBarcodeGenerator.cs

2. LabelPrint.Infrastructure(基础设施层)

LabelPrint.Infrastructure/
├── Data/
│   ├── AppDbContext.cs               # EF Core 上下文(SQLite)
│   ├── Configurations/               # Fluent API 配置
│   ├── Repositories/
│   │   ├── RepositoryBase.cs
│   │   ├── LabelTemplateRepository.cs
│   │   └── PrintRecordRepository.cs
│   └── Migrations/
├── Printing/
│   ├── RawPrinterService.cs          # 原始指令打印(ZPL/EPL/TSPL)
│   ├── WindowsPrinterService.cs      # 系统驱动打印
│   ├── PrinterDetector.cs            # 打印机探测
│   └── Commands/
│       ├── ZplCommandBuilder.cs
│       ├── TsplCommandBuilder.cs
│       └── EplCommandBuilder.cs
├── Barcode/
│   └── ZXingBarcodeGenerator.cs      # 基于 ZXing.Net
├── Serialization/
│   └── JsonTemplateSerializer.cs
├── Logging/
│   └── SerilogConfigurator.cs
└── DependencyInjection/
    └── InfrastructureServiceRegistration.cs

3. LabelPrint.Application(应用层)

LabelPrint.Application/
├── DTOs/
│   ├── LabelTemplateDto.cs
│   ├── PrintJobDto.cs
│   └── PrinterDto.cs
├── Services/
│   ├── ITemplateService.cs / TemplateService.cs
│   ├── IPrintService.cs    / PrintService.cs
│   ├── IPrinterManager.cs  / PrinterManager.cs
│   └── IDataImportService.cs
├── Mappings/
│   └── AutoMapperProfile.cs
├── Validators/
│   └── LabelTemplateValidator.cs     # FluentValidation
├── Common/
│   ├── Result.cs                     # 统一返回结果
│   └── PagedResult.cs
└── DependencyInjection/
    └── ApplicationServiceRegistration.cs

4. LabelPrint.WPF(表示层,MVVM 核心)

LabelPrint.WPF/
├── App.xaml / App.xaml.cs            # DI 容器启动
├── AppSettings.json                  # 配置文件
│
├── Views/
│   ├── MainWindow.xaml               # 主窗体(含导航)
│   ├── Pages/
│   │   ├── DashboardView.xaml        # 首页
│   │   ├── TemplateDesignerView.xaml # 模板设计器
│   │   ├── TemplateListView.xaml     # 模板管理
│   │   ├── PrintCenterView.xaml      # 打印中心
│   │   ├── PrintHistoryView.xaml     # 打印记录
│   │   ├── PrinterSettingsView.xaml  # 打印机设置
│   │   └── SettingsView.xaml         # 系统设置
│   ├── Dialogs/
│   │   ├── AddPrinterDialog.xaml
│   │   └── ConfirmDialog.xaml
│   └── Controls/
│       ├── LabelCanvas.xaml          # 标签画布控件
│       ├── ElementToolbox.xaml       # 元素工具箱
│       └── PropertyPanel.xaml        # 属性面板
│
├── ViewModels/
│   ├── MainWindowViewModel.cs
│   ├── Base/
│   │   ├── ViewModelBase.cs          # INotifyPropertyChanged
│   │   ├── PageViewModelBase.cs
│   │   └── DialogViewModelBase.cs
│   ├── Pages/
│   │   ├── DashboardViewModel.cs
│   │   ├── TemplateDesignerViewModel.cs
│   │   ├── TemplateListViewModel.cs
│   │   ├── PrintCenterViewModel.cs
│   │   ├── PrintHistoryViewModel.cs
│   │   ├── PrinterSettingsViewModel.cs
│   │   └── SettingsViewModel.cs
│   └── Dialogs/
│       └── AddPrinterDialogViewModel.cs
│
├── Models/                           # 仅 UI 状态模型
│   ├── NavigationItem.cs
│   └── CanvasElement.cs
│
├── Services/                         # UI 相关服务
│   ├── INavigationService.cs
│   ├── NavigationService.cs
│   ├── IDialogService.cs
│   ├── DialogService.cs
│   ├── IMessageService.cs
│   └── MessageService.cs
│
├── Behaviors/                        # 附加行为
│   ├── DragBehavior.cs
│   ├── ResizeBehavior.cs
│   └── SelectionBehavior.cs
│
├── Converters/
│   ├── BoolToVisibilityConverter.cs
│   ├── EnumToStringConverter.cs
│   └── InverseBoolConverter.cs
│
├── Resources/
│   ├── Styles/
│   │   ├── Colors.xaml
│   │   ├── Buttons.xaml
│   │   ├── TextBoxes.xaml
│   │   └── DataGrids.xaml
│   ├── Themes/
│   │   ├── Light.xaml
│   │   └── Dark.xaml
│   └── Icons/
│
├── Helpers/
│   ├── RelayCommand.cs
│   ├── AsyncRelayCommand.cs
│   └── VisualTreeHelperEx.cs
│
└── DependencyInjection/
    └── WpfServiceRegistration.cs

三、核心代码骨架

1. 领域实体示例

// Domain/Entities/LabelTemplate.cs
public class LabelTemplate : EntityBase
{
    public string Name { get; set; } = string.Empty;
    public string Description { get; set; } = string.Empty;
    public double WidthMm { get; set; }
    public double HeightMm { get; set; }
    public int Dpi { get; set; } = 203;
    public string? BackgroundImagePath { get; set; }
    public List<LabelElement> Elements { get; set; } = new();
    public DateTime UpdatedAt { get; set; }
}
// Domain/Entities/LabelElement.cs
public class LabelElement
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public ElementType Type { get; set; }
    public double X { get; set; }          // mm
    public double Y { get; set; }
    public double Width { get; set; }
    public double Height { get; set; }
    public double Rotation { get; set; }
    public string? BindingField { get; set; } // 数据源字段绑定
    public string? StaticText { get; set; }
    public string FontFamily { get; set; } = "Arial";
    public double FontSize { get; set; } = 10;
    public bool Bold { get; set; }
    public BarcodeType? BarcodeType { get; set; }
}

2. ViewModelBase(MVVM 基础)

// WPF/ViewModels/Base/ViewModelBase.cs
public abstract class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string? name = null)
        => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? name = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(name);
        return true;
    }

    public virtual Task InitializeAsync() => Task.CompletedTask;
}

3. RelayCommand

// WPF/Helpers/RelayCommand.cs
public class RelayCommand : ICommand
{
    private readonly Action<object?> _execute;
    private readonly Predicate<object?>? _canExecute;

    public RelayCommand(Action<object?> execute, Predicate<object?>? canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object? p) => _canExecute?.Invoke(p) ?? true;
    public void Execute(object? p) => _execute(p);
    public event EventHandler? CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

4. 导航服务

// WPF/Services/INavigationService.cs
public interface INavigationService
{
    void NavigateTo<TViewModel>() where TViewModel : PageViewModelBase;
    void NavigateTo(string key);
    event Action<PageViewModelBase>? Navigated;
}

// WPF/Services/NavigationService.cs
public class NavigationService : INavigationService
{
    private readonly IServiceProvider _sp;
    private readonly Dictionary<string, Type> _routes = new();

    public NavigationService(IServiceProvider sp) => _sp = sp;

    public event Action<PageViewModelBase>? Navigated;

    public void Register(string key, Type vmType) => _routes[key] = vmType;

    public void NavigateTo<TViewModel>() where TViewModel : PageViewModelBase
    {
        var vm = _sp.GetRequiredService<TViewModel>();
        Navigated?.Invoke(vm);
    }

    public void NavigateTo(string key)
    {
        if (_routes.TryGetValue(key, out var type))
        {
            var vm = (PageViewModelBase)_sp.GetRequiredService(type);
            Navigated?.Invoke(vm);
        }
    }
}

5. 打印服务接口 & 实现骨架

// Domain/Interfaces/IPrinterService.cs
public interface IPrinterService
{
    Task<IReadOnlyList<PrinterInfo>> GetAvailablePrintersAsync();
    Task<bool> PrintAsync(LabelTemplate template, IDictionary<string, string> data, PrinterConfig printer);
}
// Infrastructure/Printing/RawPrinterService.cs
public class RawPrinterService : IPrinterService
{
    private readonly ICommandBuilderFactory _builderFactory;
    private readonly ILogger<RawPrinterService> _logger;

    public RawPrinterService(ICommandBuilderFactory f, ILogger<RawPrinterService> l)
        => (_builderFactory, _logger) = (f, l);

    public Task<IReadOnlyList<PrinterInfo>> GetAvailablePrintersAsync()
        => Task.Run(() => PrinterDetector.GetInstalledPrinters().ToList());

    public async Task<bool> PrintAsync(LabelTemplate template, IDictionary<string, string> data, PrinterConfig printer)
    {
        try
        {
            var builder = _builderFactory.Create(printer.Language);
            var commands = builder.Build(template, data);
            await RawPrinterHelper.SendBytesToPrinterAsync(printer.Name, commands);
            return true;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "打印失败");
            return false;
        }
    }
}

6. App.xaml.cs — 依赖注入

public partial class App : Application
{
    public static IServiceProvider Services { get; private set; } = null!;
    private IHost _host = null!;

    protected override async void OnStartup(StartupEventArgs e)
    {
        _host = Host.CreateDefaultBuilder()
            .UseSerilog()
            .ConfigureServices((ctx, services) =>
            {
                services.AddInfrastructure(ctx.Configuration);
                services.AddApplication();
                services.AddWpfServices();

                // 注册 ViewModels
                services.AddTransient<MainWindowViewModel>();
                services.AddTransient<DashboardViewModel>();
                services.AddTransient<TemplateDesignerViewModel>();
                services.AddTransient<PrintCenterViewModel>();
                // ...

                services.AddSingleton<MainWindow>();
            })
            .Build();

        await _host.StartAsync();
        Services = _host.Services;

        var main = Services.GetRequiredService<MainWindow>();
        main.Show();
        base.OnStartup(e);
    }

    protected override async void OnExit(ExitEventArgs e)
    {
        await _host.StopAsync();
        _host.Dispose();
        base.OnExit(e);
    }
}

7. MainWindow 导航壳

<!-- Views/MainWindow.xaml -->
<Window x:Class="LabelPrint.WPF.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="标签打印管理" Height="800" Width="1280">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="220"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <!-- 左侧导航 -->
        <ListBox Grid.Column="0" ItemsSource="{Binding NavigationItems}"
                 SelectedItem="{Binding SelectedNavigation}"
                 Style="{StaticResource NavListStyle}"/>

        <!-- 内容区(DataTemplate 映射 ViewModel → View) -->
        <ContentControl Grid.Column="1" Content="{Binding CurrentPage}"/>
    </Grid>
</Window>
<!-- App.xaml 中集中注册 View-ViewModel 映射 -->
<Application.Resources>
    <DataTemplate DataType="{x:Type vm:DashboardViewModel}">
        <views:DashboardView/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:TemplateDesignerViewModel}">
        <views:TemplateDesignerView/>
    </DataTemplate>
    <!-- ... -->
</Application.Resources>

四、关键设计原则

关注点 做法
分层解耦 Domain 无外部依赖;Application 仅依赖 Domain 接口;Infrastructure 实现接口
依赖注入 使用 Microsoft.Extensions.Hosting 统一管理
ViewModel 定位 View 通过 DataTemplate 映射,View 不引用 ViewModel
命令绑定 RelayCommand / AsyncRelayCommand,禁用 code-behind 业务
通信 使用 IMessenger(CommunityToolkit.Mvvm)或自定义 EventAggregator
日志 Serilog 全局接入
配置 AppSettings.json + IOptions<T>
数据库 SQLite + EF Core,方便部署
条码 ZXing.Net 生成图片;打印优先用指令(ZPL/TSPL)直发
模板序列化 JSON 存模板,便于版本管理与导入导出
MVVM 工具库 推荐 CommunityToolkit.Mvvm(源生成器 [ObservableProperty]/[RelayCommand])

五、核心业务流程

1. 模板管理
   TemplateListView → TemplateService → Repository → SQLite

2. 模板设计
   TemplateDesignerViewModel 维护 Elements 集合
   → Canvas 显示(ItemsControl + Canvas)
   → 拖拽/缩放/属性面板双向绑定
   → 保存为 JSON 入库

3. 打印中心
   选择模板 + 选择打印机 + 绑定数据(手工/CSV/数据库)
   → PrintService 渲染数据 → CommandBuilder 生成指令
   → RawPrinterService 通过 Win32 API 直发打印机
   → 写入 PrintRecord

4. 打印历史
   PrintHistoryViewModel → PrintRecordRepository → DataGrid 分页

六、NuGet 依赖建议

包 用途
CommunityToolkit.Mvvm MVVM 源生成器
Microsoft.Extensions.Hosting DI / 配置 / 日志宿主
Microsoft.EntityFrameworkCore.Sqlite 数据持久化
Serilog.Extensions.Hosting + Serilog.Sinks.File 日志
ZXing.Net + ZXing.Net.Bindings.SkiaSharp 条码/二维码
FluentValidation DTO 校验
AutoMapper DTO 映射
MaterialDesignThemes / HandyControl 可选 UI 主题
Newtonsoft.Json / System.Text.Json 模板序列化

七、扩展建议

  1. 插件化打印指令:ICommandBuilder + 工厂,支持 ZPL / TSPL / EPL / CPCL 动态切换。
  2. 模板版本控制:模板表加 Version 字段,保留历史。
  3. 数据源适配器:IDataSourceProvider(CSV / Excel / SQL / HTTP)。
  4. 批量打印:PrintJob 队列 + 后台服务(IHostedService)。
  5. 权限模块:接入 AspNetCore.Identity 或轻量 IUserService。
  6. 国际化:ResX + ILocalizer。
  7. 自动更新:Squirrel.Windows 或 ClickOnce。

这个结构可直接作为项目脚手架使用,各层职责清晰、易测试、易替换实现(如把 SQLite 换成 SQL Server、把 ZPL 换成 TSPL)。如需我把上面某一模块(如 模板设计器的画布拖拽 或 ZPL 指令生成器)展开成完整可编译代码,可以继续告诉我。