WPF 治具软件开发框架 开源分享(含导航/日志/DI)

44 阅读4分钟

概述

为提升自动化设备或治具类PC端上位机软件的开发效率,设计并开源了一套通用WPF上位机软件模板。该模板基于现代化WPF开发实践构建,支持快速重命名与二次开发,避免重复搭建项目框架。

主要技术栈

功能模块技术/库
MVVM框架CommunityToolkit.Mvvm
依赖注入(DI)Microsoft.Extensions.DependencyInjection
UI框架WPFUI
日志系统NLog
配置管理System.Text.Json
多语言支持resx + WPFLocalizeExtension

运行环境

项目重命名方法参考网页链接(请补充)

程序功能介绍

本模板适用于自动化设备/治具类PC端简易上位机软件的快速开发,具备以下核心功能:

  • 基于 MVVM 模式 实现界面与逻辑解耦

  • 使用 依赖注入(DI) 管理服务生命周期

  • 提供 页面导航机制

  • 支持 JSON 格式配置文件读写

  • 集成 结构化日志记录(Info/Error分级)

  • 支持 多语言界面切换

软件文件架构

功能实现

1、导航功能

实现基于 Frame 的页面导航系统,通过 NavigationService 统一管理页面跳转。

服务注册(App.xaml.cs)

var container = new ServiceCollection();

// 注册导航服务
container.AddSingleton<Common.Services.NavigationService>();

// ... 其他注册
Services = container.BuildServiceProvider();
主窗口布局(MainWindow.xaml)
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="50"/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <!-- 标题栏 -->
    <Grid>
        <ui:TitleBar Title="JIG_SoftTemplate_V1.0.0" Background="#e5e5e5">
            <ui:TitleBar.Icon>
                <ui:ImageIcon Source="/Resources/Image/Company_logo.png"/>
            </ui:TitleBar.Icon>
        </ui:TitleBar>
    </Grid>

    <!-- 内容区域 -->
    <Grid Grid.Row="1" x:Name="ShowGrid">
        <Frame x:Name="rootFrame"/>
        <ui:SnackbarPresenter x:Name="rootSnackbarPresenter" Margin="0,0,0,-15"/>
        <ContentPresenter x:Name="rootContentDialog"/>
    </Grid>
</Grid>

主窗口初始化(MainWindow.xaml.cs)

public partial class MainWindow
{
    public MainWindow(MainWindowViewModel viewModel, Common.Services.NavigationService navigationService)
    {
        InitializeComponent();
        DataContext = viewModel;

        // 设置主Frame用于导航
        navigationService.SetMainFrame(rootFrame);

        // 设置通知与对话框服务
        App.Current.Services.GetRequiredService<ISnackbarService>().SetSnackbarPresenter(rootSnackbarPresenter);
        App.Current.Services.GetRequiredService<IContentDialogService>().SetDialogHost(rootContentDialog);
    }
}

导航服务实现(NavigationService.cs)

public class NavigationService
{
    private Frame? mainFrame;

    public void SetMainFrame(Frame frame) => mainFrame = frame;

    private Type? FindView<VM>()
    {
        return Assembly
            .GetAssembly(typeof(VM))
            ?.GetTypes()
            .FirstOrDefault(t => t.Name == typeof(VM).Name.Replace("ViewModel", ""));
    }

    public void Navigate<VM>() where VM : ViewModelBase
        => Navigate<VM>(null);

    public void Navigate<VM>(Dictionary<string, object?>? extraData) where VM : ViewModelBase
    {
        var viewType = FindView<VM>();
        if (viewType is null) return;

        var page = App.Current.Services.GetService(viewType) as Page;
        mainFrame?.Navigate(page, extraData);
    }
}

使用示例

navigationService.Navigate<HomePageViewModel>();

注意:View 与 ViewModel 必须命名匹配,如 HomePage.xamlHomePageViewModel.cs,否则 FindView 将无法找到对应页面。

2、程序配置

使用 JSON 文件持久化保存程序配置,支持自动创建默认配置。

服务注册(App.xaml.cs)

// 注册JSON配置文件服务
container.AddSingleton<Common.Services.JsonConfigService>();
配置模型定义(AppConfigModel.cs)
public class AppConfigModel
{
    public CommonConfig? Common { get; set; }
    public JIGCommConfig? JIGComm { get; set; }
}

public class CommonConfig
{
    public string? DataStoragePath { get; set; }
    public string? SelectedLang { get; set; }
}

public class JIGCommConfig { }
配置服务实现(JsonConfigService.cs)
public class JsonConfigService
{
    private const string ConfigFileName = "AppSettings.json";
    private readonly LoggerService loggerService;

    public JsonConfigService(LoggerService loggerService)
    {
        this.loggerService = loggerService;
    }

    public async Task<T> LoadConfigAsync<T>(T defaultValue = default) where T : new()
    {
        try
        {
            var filePath = GetConfigFilePath();
            if (!File.Exists(filePath))
            {
                loggerService.Info("配置文件不存在,返回默认值");
                await SaveConfigAsync(defaultValue ?? new T());
                return defaultValue ?? new T();
            }

            await using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            return await JsonSerializer.DeserializeAsync<T>(fs) ?? new T();
        }
        catch (Exception ex)
        {
            loggerService.Error("加载配置文件失败", ex);
            return defaultValue ?? new T();
        }
    }

    public async Task SaveConfigAsync<T>(T config)
    {
        try
        {
            var filePath = GetConfigFilePath();
            var dirPath = Path.GetDirectoryName(filePath);
            if (!Directory.Exists(dirPath)) Directory.CreateDirectory(dirPath);

            var options = new JsonSerializerOptions
            {
                WriteIndented = true,
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            };

            await using var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
            await JsonSerializer.SerializeAsync(fs, config, options);
            loggerService.Info("配置文件保存成功");
        }
        catch (Exception ex)
        {
            loggerService.Error("保存配置文件失败", ex);
            throw;
        }
    }

    private static string GetConfigFilePath()
    {
        return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName);
    }
}
使用示例
// 读取配置
string DataStoragePath = "";
var loadedConfig = await jsonConfigService.LoadConfigAsync(new AppConfigModel());
if (loadedConfig?.Common != null)
{
    DataStoragePath = loadedConfig.Common.DataStoragePath;
}

// 保存配置
private AppConfigModel appConfig = new()
{
    Common = new CommonConfig { DataStoragePath = DataStoragePath }
};
await jsonConfigService.SaveConfigAsync(appConfig);

3、日志功能

集成 NLog 实现结构化日志输出,区分 Info 与 Error 级别,按日归档。

服务注册(App.xaml.cs)

// 注册日志服务
container.AddSingleton<Common.Services.LoggerService>();

NLog 配置文件(NLog.config)

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true">
	<targets>
		<target name="InfoFile" xsi:type="File"
				fileName="LogFile/LogInfo/${shortdate}-Info.log"
				layout="${date:format=yyyy-MM-dd HH\:mm\:ss.fff} [INFO] ${message}${newline}"
				encoding="UTF-8" archiveEvery="Day" maxArchiveFiles="100"
				concurrentWrites="true" keepFileOpen="false"/>

		<target name="ErrorFile" xsi:type="File"
				fileName="LogFile/LogError/${shortdate}-Error.log"
				layout="${date:format=yyyy-MM-dd HH\:mm\:ss.fff} [ERROR] [ThreadID:${threadid}] ${message}${newline}[StackTrace:${exception:format=Message}]${newline}"
				encoding="UTF-8" archiveEvery="Day" maxArchiveFiles="100"
				concurrentWrites="true" keepFileOpen="false"/>
	</targets>
	<rules>
		<logger name="MyLog" minlevel="Info" maxlevel="Info" writeTo="InfoFile" final="true" />
		<logger name="MyLog" minlevel="Error" maxlevel="Error" writeTo="ErrorFile" final="true" />
	</rules>
</nlog>

日志服务封装(LoggerService.cs)

public class LoggerService
{
    private static readonly NLog.ILogger Logger = NLog.LogManager.GetLogger("MyLog");

    public void Info(string message) => Logger.Info(message);
    public void Error(string message, Exception? ex = null) => Logger.Error(ex, message);
}

使用示例

private void FunA()
{
    try
    {
        loggerService.Info("程序成功");
    }
    catch (Exception ex)
    {
        loggerService.Error("程序出错", ex);
    }
}

界面介绍

总结

本模板提供了一套完整、可复用的 WPF 治具上位机软件基础架构,涵盖:

  • MVVM + DI 架构清晰

  • 导航系统灵活易用

  • 配置持久化

  • 日志分级管理

  • 多语言支持

目标:通过项目重命名即可快速生成新项目,大幅提升开发效率。

说明:项目仍在持续优化中,欢迎提出建议与改进意见!

项目地址

gitee.com/your-repo/j…

关键词

WPF、治具软件、上位机、MVVM、DI依赖注入、NLog日志、JSON配置、多语言、WPFUI、CommunityToolkit.Mvvm、.NET 8.0、快速开发模板

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:Dragonet-Z 

出处:cnblogs.com/dragonet-Z/p/19104358

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!