背景
有时我们做配置界面的时候,有很多配置项是枚举。通过我们会用一个Combobox实现,如果能直接用Combobox绑定枚举,那将会非常方便。这里绑定将涉及到两个方面,一个是数据源的绑定,还有就是当前选择项的绑定。最后我们要将选择项的信息进行保存,下次打开的时候就刚好是我们上次选择的项。
枚举数据源的绑定
要想直接绑定枚举作为源,我们得首先准备一个类,这里我直接给出源代码,内容可以先不用理解。
public class EnumBindingSourceExtension : MarkupExtension
{
private Type _enumType;
public Type EnumType
{
get { return _enumType; }
set
{
if (value != _enumType)
{
if (null != value)
{
var enumType = Nullable.GetUnderlyingType(value) ?? value;
if (!enumType.IsEnum)
{
throw new ArgumentException("Type must bu for an Enum");
}
}
_enumType = value;
}
}
}
public EnumBindingSourceExtension()
{
}
public EnumBindingSourceExtension(Type enumType)
{
EnumType = enumType;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (null == _enumType)
{
throw new InvalidOperationException("The EnumTYpe must be specified.");
}
var actualEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType;
var enumValues = Enum.GetValues(actualEnumType);
if (actualEnumType == _enumType)
{
return enumValues;
}
var tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
enumValues.CopyTo(tempArray, 1);
return tempArray;
}
}
然后在前台引用这个类,并且指点你声明得枚举类型即可:
这里有个小疑惑,就是我声明的类叫做EnumBindingSourceExtension,但是这里绑定的时候却可以写成(EnumBindingSource),也就是Extension 可以省略。所以不管你是否带上Extension 绑定都是工作的。
<ComboBox
ItemsSource="{Binding Source={tools:EnumBindingSource {x:Type vm:MaskDetection}}}"/>
此时你就会发现已经绑定成功了!就是这么简单!
枚举选择项的绑定
<ComboBox
SelectedValue="{Binding saveInfo.enumMaskDetection, Source={x:Static md:GlobalData.Instance}}"
ItemsSource="{Binding Source={tools:EnumBindingSource {x:Type vm:MaskDetection}}}"/>
注意这里一定要使用SelectedValue,而不是 SelectedItem。至于:
saveInfo.enumMaskDetection, Source={x:Static md:GlobalData.Instance}
这句话其实是静态绑定的语法。 因为这些选择项作为配置,都是需要保存的,所以实现了一个单例模式的全局类(GlobalData)以及信息类(SaveInfo ),专门用于保存全局变量。
全局静态绑定
构建保存数据类
首先我构造一个信息类,这里面都声明的是枚举变量
public class SaveInfo
{
public ColorSpace enumColorSpace { get; set; }
public Illuminant enumIlluminant { get; set; }
public Observer enumObserver { get; set; }
public MeasurementMethod enumMeasurementMethod { get; set; }
public MeasurementArea enumMeasurementArea { get; set; }
public SpecularComponent enumSpecularComponent { get; set; }
public MaskDetection enumMaskDetection { get; set; }
}
和界面的中的选择项是一一对应的:
最终这些选项选择的内容都会保存到该类中。
构建全局类
public class GlobalData
{
/// <summary>
/// 单例模式
/// </summary>
public static GlobalData Instance { private set; get; } = new GlobalData();
public SaveInfo saveInfo { get; set; }
private GlobalData()
{
var a = JsonConfigCtrl.Read<SaveInfo>("GlobalData");
if (a == null)
{
saveInfo = new SaveInfo();
}
else
{
saveInfo = a;
}
}
public void Save()
{
JsonConfigCtrl.Save<SaveInfo>(saveInfo, "GlobalData");
}
}
序列化和反序列化
这里,要说明的是这个GlobalData是一个单例模式的类,注意他的构造函数是私有类型(确保了单例)。但是这会导致一个问题,就是无法序列化,因为序列要求有一个public的无参构造函数!
其实直接序列化GlobalData也不好,所以我用单独建立了SaveInfo 类,然后将SaveInfo 类作为他的成员对象!
然后在构造GlobalData时候,反序列化SaveInfo,由于做了静态绑定,所以反序列化SaveInfo后,界面会跟着配置改变。(这里注意,首次的数据到界面的同步,绑定是不需要通知函数的!而界面到数据同步是绑定默认的)。
在save里,我们继续序列化操作,将当前界面的数据保存到本地。
后台使用配置变量
由于直接绑定的就是枚举类型,所以无需转换直接赋值就好了!!!
setCalcCondition.ColorSpace = GlobalData.Instance.saveInfo.enumColorSpace;
setCalcCondition.Illuminant = GlobalData.Instance.saveInfo.enumIlluminant;
setCalcCondition.Observer = GlobalData.Instance.saveInfo.enumObserver;
小结
通过这种方式绑定枚举、配置参数的使用,以及序列化和反序列化,真的是超级方便。
附录
序列化的相关代码
namespace ColorTest.Tools
{
public class JsonConfigCtrl
{
static JsonSerializerOptions options;
static JsonConfigCtrl()
{
options = new JsonSerializerOptions();
options.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(UnicodeRanges.All);
}
/// <summary>
/// 序列化操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
static public void Save<T>(T obj, string filename = "config")
{
string _filePath = Directory.GetCurrentDirectory() + $"\\Config\\{filename}.json";
FileInfo fi = new FileInfo(_filePath);
if (!Directory.Exists(fi.DirectoryName))
{
Directory.CreateDirectory(fi.DirectoryName);
}
//StreamWriter yamlWriter = File.CreateText(_filePath);
string str = System.Text.Json.JsonSerializer.Serialize(obj, options);
str = JsonHelper.FormatJson(str);
File.WriteAllText(_filePath, str);
//Serializer yamlSerializer = new Serializer();
//yamlSerializer.Serialize(yamlWriter, obj);
//yamlWriter.Close();
}
/// <summary>
/// 泛型反序列化操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="FileNotFoundException"></exception>
static public T Read<T>(string filename = "config") where T : class, new()
{
string _filePath = Directory.GetCurrentDirectory() + $"\\Config\\{filename}.json";
if (!File.Exists(_filePath))
{
return default;
}
//读取持久化对象
try
{
T info = JsonSerializer.Deserialize<T>(File.ReadAllText(_filePath));
return info;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return default;
}
}
}
public class JsonHelper
{
private const string INDENT_STRING = " ";
public static string FormatJson(string str)
{
var indent = 0;
var quoted = false;
var sb = new StringBuilder();
for (var i = 0; i < str.Length; i++)
{
var ch = str[i];
switch (ch)
{
case '{':
case '[':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, ++indent).MyForEach(item => sb.Append(INDENT_STRING));
}
break;
case '}':
case ']':
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, --indent).MyForEach(item => sb.Append(INDENT_STRING));
}
sb.Append(ch);
break;
case '"':
sb.Append(ch);
bool escaped = false;
var index = i;
while (index > 0 && str[--index] == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
sb.Append(ch);
if (!quoted)
{
sb.AppendLine();
Enumerable.Range(0, indent).MyForEach(item => sb.Append(INDENT_STRING));
}
break;
case ':':
sb.Append(ch);
if (!quoted)
sb.Append(" ");
break;
default:
sb.Append(ch);
break;
}
}
return sb.ToString();
}
}
static class Extensions
{
public static void MyForEach<T>(this IEnumerable<T> ie, Action<T> action)
{
foreach (var i in ie)
{
action(i);
}
}
}
}