WPF项目中使用Caliburn.Micro框架实现日志和主题切换

news2024/9/27 4:53:09

目录

一、添加Caliburn.Micro框架

 二、配置Serilog日志

三、实现主题切换

        Caliburn.Micro是MVVM模式的轻量级WPF框架,简化了WPF中的不少用法。这个框架中所有的页面控制都是通过ViewModel去实现的。

        以下内容是自己在进行项目实战的同时进行记录的,对于文件的创建以及分类是考虑了实际扩展性等问题,不太适合初学者观看。 

一、添加Caliburn.Micro框架

创建一个WPF项目,删除项目的MainWindow.xaml,并为其添加Caliburn.Micro包

修改App.xaml文件内容,删掉StartupUri="MainWindow.xmal"语句,这个框架不需要通过这个语句去查找启动窗口

在项目中创建好View,Model和ViewModel文件夹

创建一个AppBootstrapper类继承BootstrapperBase,它是Caliburn.Micro框架的一个启动类(大部分代码都是固定的可以直接使用,除了Ioc注入以及OnStartup()函数中的内容可能需要更改)

using Caliburn.Micro;
using ProjectM.ViewModels;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using System.Windows.Threading;

namespace ProjectM.Components
{
    public class AppBootstrapper : BootstrapperBase
    {
        private CompositionContainer _container;
        private IWindowManager _windowManager;
        private IEventAggregator _eventAggregator;

        public AppBootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            
            var aggregateCatalog = new AggregateCatalog(AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>());
            // 管理和解析依赖关系
            var batch = new CompositionBatch();
            // 批量处理依赖注入
            _container = new CompositionContainer(aggregateCatalog);
            // 注册依赖注入
            batch.AddExportedValue(_container);

            //注入IoC,我们在其他文件中可以直接通过Ioc.Get<T>()来获取依赖注入的实例
            _windowManager = new WindowManager();// 注册窗体管理器
            batch.AddExportedValue(_windowManager);
            _eventAggregator = new EventAggregator();// 注册事件聚合器
            batch.AddExportedValue(_eventAggregator);

            _container.Compose(batch);
        }

        protected override void OnUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            base.OnUnhandledException(sender, e);
        }

        protected override object GetInstance(Type service, string key)
        {
            string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(service) : key;

            var exports = _container.GetExportedValues<object>(contract);

            if (exports.Any())
                return exports.First();

            throw new Exception($"找不到实例 {contract}。");
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _container.GetExportedValues<object>(AttributedModelServices.GetContractName(service));
        }

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            var assemblies = new List<Assembly>()
            {
                Assembly.GetEntryAssembly(),
                Assembly.GetExecutingAssembly(),
            };

            return assemblies.Where(x => x != null)
                .Distinct();
        }

        protected override void BuildUp(object instance)
        {
            _container.SatisfyImportsOnce((ComposablePart)instance);
        }

        protected override void OnStartup(object sender, System.Windows.StartupEventArgs e)
        {
            // 这里使用的实例是ViewModel
            var viewModel = new ShellViewModel();
            _windowManager.ShowWindow(viewModel);
        }
    }
}

 在ViewModel中继承Caliburn.Micro框架中的Screen,最后启动项目,项目成功被启动(这里是通过我们前面的AppBootstrapper文件中的OnStartup函数中配置的内容实现的窗口启动)

public class ShellViewModel : Screen
{

}

 二、配置Serilog日志

安装Serilog相关的包(第二个是将日志写入文件,还有其他的选项,比如打印到控制台等按需下载即可)

 我们新建一个类用于单独存放静态共用字段

public static class Fields
{
    public const string AppName = "ProjectM";

    public const string AppDataPath = "E:\\ProjectM\\" + AppName;

    public const string LogFileName = "log.txt";
}

 再建一个类存放动态字段

public class Environments
{
    private static string _appDataPath;
    private static string _logFilePath;

    /// <summary>
    /// 应用程序数据路径
    /// </summary>
    public static string AppDataPath
    {
        get
        {
            if (string.IsNullOrEmpty(_appDataPath))
            {
                _appDataPath = Environment.ExpandEnvironmentVariables(Fields.AppDataPath);
            }
            if (!Directory.Exists(_appDataPath))
            {
                Directory.CreateDirectory(_appDataPath);
            }
            return _appDataPath;
        }
    }

    /// <summary>
    /// 日志文件路径
    /// </summary>
    public static string LogFilePath
    {
        get
        {
            if (string.IsNullOrEmpty(_logFilePath))
            {
                _logFilePath = Path.Combine(AppDataPath, Fields.LogFileName);
            }
            return _logFilePath;
        }
    }
}

创建一个ILogger接口并实现接口

public interface ILogger
{
    void Error(Exception exception, string messageTemplate);

    void Error(Exception exception, string messageTemplate, params object[] args);

    void Infomation(string message);

    void Infomation(string messageTemplate, params object[] args);

    void Warning(string message);

    void Warning(string messageTemplate, params object[] args);
}
public class Logger : Contracts.ILogger
{
    private static Serilog.Core.Logger _logger;
    private static Logger _instance;

    // 确保日志的唯一性(单例模式)
    public static Logger Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }
            return _instance;
        }
    }

    public void Error(Exception exception, string messageTemplate)
    {
        InitializeLogger();
        _logger?.Error(exception, messageTemplate);
    }

    public void Error(Exception exception, string messageTemplate, params object[] args)
    {
        InitializeLogger();
        _logger?.Error(exception, messageTemplate, args);
    }

    public void Infomation(string message)
    {
        InitializeLogger();
        _logger?.Information(message);
    }

    public void Infomation(string messageTemplate, params object[] args)
    {
        InitializeLogger();
        _logger?.Information(messageTemplate, args);
    }

    public void Warning(string message)
    {
        InitializeLogger();
        _logger?.Warning(message);
    }

    public void Warning(Exception exception, string messageTemplate, params object[] args)
    {
        InitializeLogger();
        _logger?.Warning(exception, messageTemplate, args);
    }

    public void Warning(string messageTemplate, params object[] args)
    {
        InitializeLogger();
        _logger?.Warning(messageTemplate, args);
    }

    /// <summary>
    /// 初始化_logger
    /// </summary>
    private void InitializeLogger()
    {
        if (_logger == null)
        {
            var logFilePath = Environments.LogFilePath;

            // 日志文件按天分割,最大文件数为30
            _logger = new LoggerConfiguration()
                 .WriteTo.File(logFilePath, rollingInterval: RollingInterval.Day, retainedFileCountLimit: 30)
                 .CreateLogger();
        }
    }
}

最后我们可以创建一个ViewModelBase作为基类,让它去继承Screen。我们所有的ViewModel都可以继承这个 基类 然后在文件中声明我们注入的对象,从而优化代码。(这里能使用IoC.Get直接去获取是因为我们在AppBootstrapper类注入了Ioc)

public class ViewModelBase : Screen
{
    public IWindowManager WindowManager => IoC.Get<IWindowManager>();

    public IEventAggregator EventAggregator => IoC.Get<IEventAggregator>();

    public ILogger Logger => IoC.Get<ILogger>();
}

三、实现主题切换

我们首先定义两个资源文件,分别是Light.xaml和Dark.xaml,设置不同主题下的背景颜色和字体颜色

 在App.xaml中引入我们定义好的资源文件(这里引入一个我们打开的默认显示主题就行)

 定义一个枚举类存放我们的主题

public enum AppTheme
{
    Light = 0,
    Dark = 1
}

 定义一个接口类,用于声明切换主题的方法

public interface IThemeManager
{
    void UpdateTheme(AppTheme theme);
}

实现这个方法(通过在资源字典中找到主题资源设置,删除原来的资源,将新的主题资源添加进去,从而实现了主题切换的效果)

public class ThemeManager : IThemeManager
{
    /// <summary>
    /// 切换思路:在资源字典中找到主题资源,替换掉原来的资源,这样就实现了切换主题的效果
    /// </summary>
    /// <param name="theme"></param>
    public void UpdateTheme(AppTheme theme)
    {
        for(int i = 0; i < Application.Current.Resources.MergedDictionaries.Count; i++)
        {
            var dictionary = Application.Current.Resources.MergedDictionaries[i];
            if (string.IsNullOrEmpty(dictionary.Source?.OriginalString))
            {
                continue;
            }
            if(dictionary.Source.OriginalString.StartsWith("/ProjectM.UI;component/Themes/"))
            {
                Application.Current.Resources.MergedDictionaries.Remove(dictionary);
                var resourceDictionary = new ResourceDictionary()
                {
                    Source = new Uri($"/ProjectM.UI;component/Themes/{theme}.xaml", UriKind.RelativeOrAbsolute)
                };
                Application.Current.Resources.MergedDictionaries.Insert(i,resourceDictionary);
                return;
            }
        }
    }
}

我们在AppBootstrapper.cs文件中注入我们的IThemeManager(注入后我们可以再任何地方直接使用,不用去new对象,达到解耦的目的)

 最后进行测试,在界面定义一个Button和文本内容

 在ViewModel中实现SwitchTheme方法

bool isLight = true;
public void SwitchTheme()
{
    if (isLight)
    {
        ThemeManager.UpdateTheme(AppTheme.Dark);
    }
    else
    {
        ThemeManager.UpdateTheme(AppTheme.Light);
    }
    isLight =!isLight;
}

最后运行项目,点击Button,主题成功进行切换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2168829.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【08】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-Scroll容器与Tabs组件

序言&#xff1a; 本文详细讲解了关于我们在页面上经常看到的可滚动页面和导航栏在鸿蒙开发中如何用Scroll和Tabs组件实现&#xff0c;介绍了Scroll和Tabs的基本用法与属性。 笔者也是跟着B站黑马的课程一步步学习&#xff0c;学习的过程中添加部分自己的想法整理为笔记分享出…

晶圆厂如何突破多网隔离实现安全稳定又快速的跨网域文件传输?

在当今数字化时代&#xff0c;晶圆厂作为高科技产业的核心&#xff0c;其生产效率和数据安全性直接影响到整个半导体行业的竞争力。晶圆厂内部网络通常被划分为多个安全域&#xff0c;如生产网络、研发网络、办公网络等&#xff0c;以确保数据安全和防止敏感信息泄露。然而&…

【RabbitMQ 项目】服务端:服务器模块

文章目录 一.编写思路二.代码实践三.服务端模块关系总结 一.编写思路 成员变量&#xff1a; muduo 库中的 TCP 服务器EventLoop 对象&#xff1a;用于主线程循环监控连接事件协议处理句柄分发器&#xff1a;用于初始化协议处理器&#xff0c;便于把不同请求派发给不同的业务处理…

大语言模型在构建UNSPSC 分类数据中的应用

UNSPSC 是联合国标准产品和服务代码。UNSPSC由联合国开发计划署&#xff08;UNDP&#xff09;和Dun & Bradstreet公司&#xff08;D & B&#xff09;于1998年联合制定&#xff0c;自2003年以来一直由GS1 US管理。GS1 US 将在 2024 年底前将 UNSPSC 的管理权移交给 UNDP…

【HarmonyOS】TaskPool非阻塞UI

TaskPool方法不会阻塞UI&#xff0c;如果做上传图片的功能加载Loading记得使用TaskPool&#xff0c;Promise、Async/Await都会阻塞UI 【引言】 发现Promise可能会阻塞UI&#xff0c;尝试使用async或await&#xff0c;但发现它们仍然会导致阻塞。后来看到chaoxiaoshu回复的Tas…

数字孪生平台,助力制造设备迈入超感知与智控新时代!

痛点剖析 当前&#xff0c;制造业面临系统分散导致的数据孤岛问题&#xff0c;严重阻碍了有效监管与统计分析&#xff1b;同时&#xff0c;设备多样化且兼容性不足&#xff0c;增加了管理难度&#xff1b;台账记录方式混乱&#xff0c;工单审批流程繁琐且效率低下&#xff1b;…

electron使用npm install出现下载失败的问题

我在使用electron进行下载时&#xff0c;经常出现一个错误。 HTTPError: Response code 404 (Not Found) for https://registry.npmmirror.com/v21.4.4/electron-v21.4.4-win32-x64.zip 这个时候需要修改一些npm的配置。使用命令npm config list -ls 滑到下面&#xff0c;找到一…

第一个maven web工程(eclipse)

1、点击file--》new--》Maven Project&#xff0c;如下&#xff1a; 2、直接next&#xff0c;如下 3、搜索web原型&#xff0c;如下 4、填写项目的坐标&#xff0c;如下 5、创建完成后&#xff0c;需要自己补一个文件夹&#xff0c;名称为java&#xff0c;如下&#xff1a; …

class 023 随机快速排序

这篇文章是看了“左程云”老师在b站上的讲解之后写的, 自己感觉已经能理解了, 所以就将整个过程写下来了。 这个是“左程云”老师个人空间的b站的链接, 数据结构与算法讲的很好很好, 希望大家可以多多支持左程云老师, 真心推荐. https://space.bilibili.com/8888480?spm_id_f…

云中红队系列 | 使用 AWS API 配置Fireprox进行 IP轮换

在渗透测试评估期间&#xff0c;某些活动需要一定程度的自动化&#xff0c;例如从 LinkedIn 等网站抓取网页以收集可用于社会工程活动、密码喷洒登录门户或测试时盲注的有效员工姓名列表网络应用程序。但是&#xff0c;从单个源 IP 地址执行这些活动可能会导致在测试期间被拦截…

【TabBar嵌套Navigation案例-新特性页面-代码位置 Objective-C语言】

一、接下来,我们来说这个新特性页面 1.首先,看一下我们的示例程序,这里改一下,加一个叹号, command + R, 好,首先啊,这里边有一个新特性页面,当我这个程序是第一次安装、第一次运行、还有呢、就是当这个应用程序更新的时候,我应该去加载这个新特性页面, 然后呢,这…

JPEG图像的DCT(Discrete Cosine Transform)变换公式代码详解

引 言 网络上图像在传输过程中为节省内存空间主要采用jpeg格式。jpeg图属于有损压缩图像的一种。在图像篡改检测过程中&#xff0c;可以利用jpeg图像的单双压缩伪影的不同而判别图像为伪造图并可以定位伪造区域。RGB图像变成jpeg图像过程中涉及从RGB图变成YCbCr图像&#xff0c…

使用离火插件yoloV8数据标注,模型训练

1. 启动 2.相关配置 2.1 data.yaml path: D:/yolo-tool/yaunshen-yolov8/YOLOv8ys/YOLOv8-CUDA10.2/1/datasets/ceshi001 train: images val: images names: [蔡徐坤,篮球] 2.2 cfg.yaml # Ultralytics YOLOv8, GPL-3.0 license # Default training settings and hyp…

物联网行业中通信断线重连现象介绍以及如何实现

01 概述 断线重连是指在计算机网络中&#xff0c;当网络连接遇到异常中断或者断开时&#xff0c;系统会自动尝试重新建立连接&#xff0c;以保证网络通信的连续性和稳定性。这是一种常见的网络通信技术&#xff0c;广泛应用于各种计算机网络场景&#xff0c;包括互联网、局域…

蓝队技能-应急响应篇Web内存马查杀Spring框架型中间件型JVM分析Class提取

知识点&#xff1a; 1、应急响应-Web框架内存马-分析&清除 2、应急响应-Web中间件内存马-分析&清除 注&#xff1a;框架型内存马与中间件内存马只要网站重启后就清除了。 目前Java内存马具体分类&#xff1a; 1、传统Web应用型内存马 Servlet型内存马&#xff1a;…

探索EasyCVR视频融合平台:在视频编解码与转码领域的灵活性优势

随着视频监控技术的飞速发展&#xff0c;各类应用场景对视频数据的处理需求日益复杂多样。从公共安全到智慧城市&#xff0c;再到工业监控&#xff0c;高效、灵活的视频处理能力成为衡量视频融合平台性能的重要标准。在众多解决方案中&#xff0c;EasyCVR视频融合平台凭借其在视…

Java面试题之JVM20问

1、说说 JVM 内存区域 这张图就是一个 JVM 运行时数据图&#xff0c;「紫色区域代表是线程共享的区域」&#xff0c;JAVA 程序在运行的过程中会把他管理的内存划分为若干个不同的数据区域&#xff0c;「每一块儿的数据区域所负责的功能都是不同的&#xff0c;他们也有不同的创建…

Django设计批量导入Excel数据接口(包含图片)

Django设计批量导入Excel数据接口(包含图片) 目录 Django设计批量导入Excel数据接口(包含图片)示例xlsx文件接口详情前端上传FormData后端APIView调用函数 Django 4.2.7 openpyxl 3.1.5示例xlsx文件 接口详情 前端上传FormData …

2-104 基于MATLAB的动态模式分解(Dynamic Mode Decomposition,DMD)

基于MATLAB的动态模式分解&#xff08;Dynamic Mode Decomposition&#xff0c;DMD&#xff09;,从人类步行数据中提取信息.动态模式分解是一种降维算法&#xff0c;在流体力学领域引入的。与提供内部坐标系和相应投影的SVD相似&#xff0c;DMD为您提供随不同时间行为演变的特定…

【架构】前台、中台、后台

文章目录 前台、中台、后台1. 前台&#xff08;Frontend&#xff09;特点&#xff1a;技术栈&#xff1a; 2. 中台&#xff08;Middleware&#xff09;特点&#xff1a;技术栈&#xff1a; 3. 后台&#xff08;Backend&#xff09;特点&#xff1a;技术栈&#xff1a; 示例场景…