C#简单配置类及数据绑定
简介
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:
- 保存配置到json文件
- 从文件或实例加载配置类的属性值
- 数据绑定到界面控件
一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
配置基类
为了方便管理,项目中的配置一般是按用途划分到不同的配置类中,保存时也是保存到多个配置文件。所以,我们需要实现一个配置基类,然后再派生出不同用途的配置类。
配置基类需要引用Json.NET,继承数据绑定基类BindableBase,实现从其它实例加载数据的功能(参考C# 两个具有相同属性的类赋值),基类代码如下:
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace TestConfigDll
{
/// <summary>
/// 配置基类,实现配置类的加载、保存功能
/// </summary>
/// <typeparam name="T"></typeparam>
public class ConfigBase<T> : BindableBase where T : class
{
/// <summary>
/// 文件保存路径
/// </summary>
private string filePath = "";
/// <summary>
/// 设置文件保存路径
/// </summary>
/// <param name="filePath"></param>
public virtual void SetPath(string filePath)
{
this.filePath = filePath;
}
/// <summary>
/// 保存到本地文件
/// </summary>
public virtual void Save()
{
string dirStr = Path.GetDirectoryName(filePath);
if (!Directory.Exists(dirStr))
{
Directory.CreateDirectory(dirStr);
}
string jsonStr = JsonConvert.SerializeObject(this);
File.WriteAllText(filePath, jsonStr);
}
/// <summary>
/// 从本地文件加载
/// </summary>
public virtual void Load()
{
if (File.Exists(filePath))
{
var config = JsonConvert.DeserializeObject<T>(File.ReadAllText(filePath));
foreach (PropertyInfo pro in typeof(T).GetProperties())
{
pro.SetValue(this, pro.GetValue(config));
}
}
}
/// <summary>
/// 从其它实例加载
/// </summary>
/// <param name="config"></param>
public virtual void Load(T config)
{
foreach (PropertyInfo pro in typeof(T).GetProperties())
{
pro.SetValue(this, pro.GetValue(config));
}
}
/// <summary>
/// 从其它实例加载,仅加载指定的属性
/// </summary>
/// <param name="config"></param>
/// <param name="proName"></param>
public virtual void Load(T config, IEnumerable<string> proName = null)
{
foreach (PropertyInfo pro in typeof(T).GetProperties())
{
if (proName == null || proName.Contains(pro.Name))
{
pro.SetValue(this, pro.GetValue(config));
}
}
}
}
}
数据绑定基类BindableBase的实现参考WPF之数据绑定基类,代码如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TestConfigDll
{
public class BindableBase : INotifyPropertyChanged
{
/// <summary>
/// 属性值更改时发生
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 检查属性是否已与设置值相等,设置属性并仅在必要时通知侦听器。
/// </summary>
/// <typeparam name="T">属性的类型</typeparam>
/// <param name="storage">对同时具有getter和setter的属性的引用</param>
/// <param name="value">属性的所需值</param>
/// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
/// <returns>如果值已更改,则为True;如果现有值与所需值匹配,则为false。</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
/// <summary>
/// 检查属性是否已与设置值相等,设置属性并仅在必要时通知侦听器。
/// </summary>
/// <typeparam name="T">属性的类型</typeparam>
/// <param name="storage">对同时具有getter和setter的属性的引用</param>
/// <param name="value">属性的所需值</param>
/// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
/// <param name="onChanged">属性值更改后调用的操作。</param>
/// <returns>如果值已更改,则为True;如果现有值与所需值匹配,则为false。</returns>
protected virtual bool SetProperty<T>(ref T storage, T value, Action onChanged, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value)) return false;
storage = value;
onChanged?.Invoke();
RaisePropertyChanged(propertyName);
return true;
}
/// <summary>
/// 引发此对象的PropertyChanged事件。
/// <param name="propertyName">用于通知侦听器的属性的名称,此值是可选的,从支持CallerMemberName的编译器调用时可以自动提供。</param>
protected void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// 引发此对象的PropertyChanged事件。
/// </summary>
/// <param name="args">PropertyChangedEventArgs参数</param>
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
PropertyChanged?.Invoke(this, args);
}
}
}
派生配置类
配置类的属性定义不能使用缩写形式,不用单例模式时可删除“配置单例”的代码,配置类代码如下:
class TestConfig :ConfigBase<TestConfig>
{
#region 配置单例
/// <summary>
/// 唯一实例
/// </summary>
public static TestConfig Instance { get; private set; } = new TestConfig();
/// <summary>
/// 静态构造函数
/// </summary>
static TestConfig()
{
Instance.SetPath("Config\\" + nameof(TestConfig) + ".json");
Instance.Load();
}
#endregion
#region 属性定义
private string configStr = "";
public string ConfigStr
{
get
{
return this.configStr;
}
set
{
SetProperty(ref this.configStr, value);
}
}
private int configInt =0;
public int ConfigInt
{
get
{
return this.configInt;
}
set
{
if (value > 100)
{
SetProperty(ref this.configInt, value);
}
}
}
private bool configBool = false;
public bool ConfigBool
{
get
{
return this.configBool;
}
set
{
SetProperty(ref this.configBool, value);
}
}
#endregion
}
数据绑定
一般数据绑定会定义一个Model类、ViewModel类,本文为了演示方便使用配置类同时承担两者的角色。
Winform中的数据绑定
先设计一个简单的界面,如下所示:

配置数据的加载、保存不用对每个控件进行操作,后台代码如下:
public partial class Form1 : Form
{
private TestConfig testConfig = new TestConfig();
private List<string> proName = new List<string>();
public Form1()
{
InitializeComponent();
string textProName = nameof(textBox1.Text);
textBox1.DataBindings.Add(textProName, testConfig, nameof(testConfig.ConfigStr));
textBox2.DataBindings.Add(textProName, testConfig, nameof(testConfig.ConfigInt));
string checkedProName= nameof(checkBox1.Checked);
checkBox1.DataBindings.Add(checkedProName, testConfig, nameof(testConfig.ConfigBool));
proName.Add(textBox1.DataBindings[0].BindingMemberInfo.BindingField);
proName.Add(textBox2.DataBindings[0].BindingMemberInfo.BindingField);
proName.Add(checkBox1.DataBindings[0].BindingMemberInfo.BindingField);
}
private void button1_Click(object sender, EventArgs e)
{
testConfig.Load(TestConfig.Instance, proName);
}
private void button2_Click(object sender, EventArgs e)
{
TestConfig.Instance.Load(testConfig, proName);
TestConfig.Instance.Save();
}
}
如上所示,testConfig作为中转,可以根据需求加载、保存配置类的部分或全部属性。如果对Winform下的数据绑感兴趣,可以参考Winform 普通控件的双向绑定。
WPF下的数据绑定
先设计一个简单的界面,如下所示:
<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"
mc:Ignorable="d"
Title="MainWindow" Height="208" Width="306.667">
<Grid>
<Label Content="ConfigStr:" HorizontalAlignment="Left" Margin="18,16,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="113,20,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="156" Text="{Binding Path=ConfigStr ,Mode=TwoWay}"/>
<Label Content="ConfigInt:" HorizontalAlignment="Left" Margin="18,44,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="113,48,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="156" Text="{Binding Path=ConfigInt ,Mode=TwoWay}"/>
<Label Content="ConfigBool:" HorizontalAlignment="Left" Margin="18,74,0,0" VerticalAlignment="Top"/>
<CheckBox Content="" HorizontalAlignment="Left" Margin="118,84,0,0" VerticalAlignment="Top" IsChecked="{Binding Path=ConfigBool ,Mode=TwoWay}"/>
<Button Content="加载" HorizontalAlignment="Left" Margin="24,129,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<Button Content="保存" HorizontalAlignment="Left" Margin="194,129,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click_1"/>
</Grid>
</Window>

相对于Winform,WPF控件绑定的操作由XAML实现,后台代码如下:
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
private TestConfig testConfig = new TestConfig();
public MainWindow()
{
InitializeComponent();
this.DataContext = testConfig;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
testConfig.Load(TestConfig.Instance);
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
TestConfig.Instance.Load(testConfig);
TestConfig.Instance.Save();
}
}
上面的代码比较粗糙,没有记录已经绑定的属性,实际使用时可以进一步优化。
附件
C#简单配置类及数据绑定的更多相关文章
- ssm简单配置
MyBatis 是一个可以自定义SQL.存储过程和高级映射的持久层框架. MyBatis 摒除了大部分的JDBC代码.手工设置参数和结果集重获. MyBatis 只使用简单的XML 和注解来配置和映射 ...
- .net config文件 配置类的集合
1,appconfig文件 <configSections> <section name="ToolConfig" type="DMTools.Tool ...
- mybatis 简单配置
一.com/book/map包下有两个配置文件: 1.MyBatisConfig.xml <?xml version="1.0" encoding="UTF-8&q ...
- Struts 2简单配置分析
要配置Struts 2,首先先要有Struts 2的Jar包,可以去Struts的官网下载(http://struts.apache.org/),这里有3个GA版本可以选择下载,我选择的是最新的2.2 ...
- Log4j简单配置
Log4j是一组强大的日志组件,在项目中时常需要用它提供一些信息,这两天学习了一下它的简单配置. 第一步,我们需要导入log4j-1.2.14.jar到lib目录下 第二步,在src下建立log4j. ...
- spring注解开发中常用注解以及简单配置
一.spring注解开发中常用注解以及简单配置 1.为什么要用注解开发:spring的核心是Ioc容器和Aop,对于传统的Ioc编程来说我们需要在spring的配置文件中邪大量的bean来向sprin ...
- Fluent Nhibernate code frist简单配置
Fluent Nhibernate code frist简单配置 前言 在以前的项目开发过程中使用nhibernate做完orm映射工具需要编写大量的xml映射文件,项目过程中往往会因为一个字段等 ...
- Orleans简单配置
Orleans简单配置 这是Orleans系列文章中的一篇.首篇文章在此 话说曾几何时,我第一次看到xml文件,心中闪过一念想:"这<>是什么鬼?"-用ini或者jso ...
- SpringMVC简单配置
SpringMVC简单配置 一.eclipse安装Spring插件 打开help下的Install New Software 点击add,location中输入http://dist.springso ...
随机推荐
- 《HelloGitHub》第 66 期
兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这里有实战项目.入门教程.黑科技.开源书籍.大厂开源项目等,涵盖多种编程语言 Pyt ...
- bootstrap inputfile 使用-上传,回显
近期用bootstrap 做前端的上传,功能涉及到上传时就是召网上的教程随便弄一搜一大把,但是做到修改页面时候不知道页面该如何回显,折腾了一阵子才完成遂记录下来希望能给看到的小伙伴有点启发吧. 首先是 ...
- Win10下部署VS+Qt+OpenCV+darknet(YOLO)集成环境
VS+Qt集成环境 下载VS与Qt并安装. VS:官网链接 Qt:下载链接 将Qt的bin目录加入PATH路径 bin目录举例:D:\development\Qt\5.12.0\msvc2017_64 ...
- mysql8.0.20安装配置教程
mysql配置安装教程 1.下载mysql8.0.20安装包 下载地址: https://dev.mysql.com/downloads/mysql/.找到安装包后下载.(官网为英文,如果看不懂的小伙 ...
- TypeScript 枚举指南
枚举是受 TypeScript 支持的数据类型.枚举允许您定义一组命名常量.使用它们可以更轻松地记录意图或创建一组不同的案例.枚举大多数用于面向对象的编程语言(如 Java 和 C#)中,现在也可以 ...
- 使用 PyTorch Lightning 将深度学习管道速度提高 10 倍
前言 本文介绍了如何使用 PyTorch Lightning 构建高效且快速的深度学习管道,主要包括有为什么优化深度学习管道很重要.使用 PyTorch Lightning 加快实验周期的六种 ...
- CF125E MST company (凸优化+MST)
qwq自闭的一个题 我来修锅辣!!!!!! 这篇题解!可以\(hack\)全网大部分的做法!!! 首先,我们可以把原图中的边,分成两类,一类是与\(1\)相连,另一类是不与\(1\)相连. 原题就转化 ...
- AIApe问答机器人Scrum Meeting 5.3
Scrum Meeting 6 日期:2021年5月3日 会议主要内容概述:汇报两日工作. 一.进度情况 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 李明昕 后端 与前端对 ...
- seata代码控制回滚和临时挂起分布式事物
seata代码控制回滚和临时挂起分布式事物 一.说明 二.功能实现 1.手动回滚分布式事物 2.临时挂起分布式事物 三.完整代码 四 参考链接 一.说明 此处只是简单的记录一下,使用了 Seata后, ...
- Noip模拟74 2021.10.11
T1 自然数 考场上当我发现我的做法可能要打线段树的时候,以为自己百分之百是考虑麻烦了 但还是打了,还过掉了所有的样例,于是十分自信的就交了 正解还真是线段树,真就第一题数据结构 但是包括自己造的小样 ...