为了使我们的应用程序对用户友好,需要记住应用程序上次退出时的设置,以便再次运行这个应用程序恢复上次退出时的场景。
在上面所示的应用程序中,“每章单词数”就是需要记住的应用程序设置。
那么,让我们来写一个通用的抽象基类来做这件事吧。下面就是 AppConfigureBase.cs 源程序文件
01: using System; 02: using System.IO; 03: using System.Xml; 04: using System.Collections.Generic; 05: 06: namespace Skyiv.Common 07: { 08: /// <summary> 09: /// 用于读取和保存应用程序设置的抽象基类 10: /// </summary> 11: public abstract class AppConfigureBase 12: { 13: string configureFileName; 14: protected Dictionary<string, string> KeyValues; 15: 16: protected AppConfigureBase(string executeFileFullName) 17: { 18: configureFileName = Path.Combine(Path.GetDirectoryName(executeFileFullName), 19: Path.GetFileNameWithoutExtension(executeFileFullName) + ".xml"); 20: KeyValues = new Dictionary<string, string>(); 21: Load(); 22: } 23: 24: void Load() 25: { 26: KeyValues.Clear(); 27: try 28: { 29: var doc = new XmlDocument(); 30: doc.Load(configureFileName); 31: foreach (XmlNode node in doc.DocumentElement.ChildNodes) 32: KeyValues.Add(node.Name, node.InnerText); 33: } 34: catch 35: { 36: // 当配置文件不存在时,不应该抛出异常。 37: // 此时,派生类应该使用缺省值进行配置。 38: } 39: } 40: 41: public void Save() 42: { 43: var doc = new XmlDocument(); 44: doc.LoadXml(Api.XmlFileHeaderString + "<Settings />"); 45: foreach (var kvp in KeyValues) 46: { 47: var elem = doc.CreateElement(kvp.Key); 48: elem.InnerText = kvp.Value; 49: doc.DocumentElement.AppendChild(elem); 50: } 51: doc.Save(configureFileName); 52: } 53: 54: protected string GetString(string key, string defaultValue) 55: { 56: string value; 57: if (!KeyValues.TryGetValue(key, out value)) return defaultValue; 58: return value; 59: } 60: 61: protected void SetString(string key, string value) 62: { 63: KeyValues[key] = value; 64: } 65: 66: protected long GetInt64(string key, long defalutValue) 67: { 68: return long.Parse(GetString(key, defalutValue.ToString())); 69: } 70: 71: protected void SetInt64(string key, long value) 72: { 73: SetString(key, value.ToString()); 74: } 75: 76: protected DateTime GetDateTime(string key, DateTime defaultValue) 77: { 78: return DateTime.FromBinary(GetInt64(key, defaultValue.ToBinary())); 79: } 80: 81: protected void SetDateTime(string key, DateTime value) 82: { 83: SetInt64(key, value.ToBinary()); 84: } 85: } 86: }
上述程序中第 54 行到第 84 行的 GetXXX 和 SetXXX 方法被声明为 protected,供派生类使用。如有需要,可自行增加获取和设置不同数据类型的方法。
哦,上述程序第 44 行中用到的 Api 类所在的 Api.cs 源程序文件如下所示:
01: using System.Text; 02: 03: namespace Skyiv.Common 04: { 05: /// <summary> 06: /// 一些通用的东东 07: /// </summary> 08: public static class Api 09: { 10: public static string XmlFileHeaderString = @"<?xml version=""1.0"" encoding=""UTF-8""?>"; 11: public static readonly Encoding GB18030Encoding = Encoding.GetEncoding("GB18030"); 12: } 13: }
我们的应用程序应该从 AppConfigureBase 这个抽象基类派生出自己的应用程序配置类,例如下面的 AppConfigure.cs:
01: using Skyiv.Common; 02: 03: namespace Skyiv.PowerWord2Snb 04: { 05: /// <summary> 06: /// 应用程序设置 07: /// </summary> 08: sealed class AppConfigure : AppConfigureBase 09: { 10: static readonly string WordsPerChapterString = "WordsPerChapter"; 11: 12: public AppConfigure(string executeFileFullName) 13: : base(executeFileFullName) 14: { 15: } 16: 17: /// <summary> 18: /// 每章单词数 19: /// </summary> 20: public long WordsPerChapter 21: { 22: get { return GetInt64(WordsPerChapterString, 20); } 23: set { SetInt64(WordsPerChapterString, value); } 24: } 25: } 26: }
下面就是用于保存应用程序设置的 PowerWord2Snb.xml 文件:
1: <?xml version="1.0" encoding="UTF-8"?> 2: <Settings> 3: <WordsPerChapter>20</WordsPerChapter> 4: </Settings>
注意,如果我们的应用程序运行时还不存在这个 XML 文件,不应该报错,而是应该使用的缺省值进行配置。然后在退出应用程序时生成一个 XML 文件来保存应用程序的设置。
下面就是应用程序主窗体对应的 MainForm 类所在的 MainForm.cs 源程序文件中和应用程序设置相关的内容:
01: namespace Skyiv.PowerWord2Snb 02: { 03: public partial class MainForm : Form 04: { 05: AppConfigure appConfigure; 06: 07: private void MainForm_Load(object sender, EventArgs e) 08: { 09: appConfigure = new AppConfigure(assembly.ExecuteFileFullName); 10: nudWordsPerChapter.Value = appConfigure.WordsPerChapter; 11: } 12: 13: private void MainForm_FormClosed(object sender, FormClosedEventArgs e) 14: { 15: appConfigure.WordsPerChapter = (long)nudWordsPerChapter.Value; 16: appConfigure.Save(); 17: } 18: } 19: }
在上述源程序中,在应用程序主窗体装入时,初始化一个 AppConfigure 类的实例,然后再用其 WordsPerChapter 属性来设置主窗体中的 NumericUpDown 控件的值。最后,用户退出应用程序时,引发 FormClosed 事件,在这个事件处理程序中将 NumericUpDown 控件的值赋给 AppConfigure 类的 WordsPerChapter 属性,接着调用 AppConfigureBase 类的 Save 方法将应用程序的设置写入 XML 文件。
就这么简单。
我们已经看到了一个 GUI 界面的应用程序的例子。好吧,我们再来一个 CUI 界面的应用程序,演示稍微复杂一点的应用程序设置,这次的设置包括一个字符串、一个日期、一个整数。下面就是 AppConfigureTester.cs:
01: using System; 02: 03: namespace Skyiv.Tester 04: { 05: class AppConfigureTester 06: { 07: static void Main() 08: { 09: try 10: { 11: var app = new AppConfigureTester(); 12: var cfg = new AppConfigure("AppConfigureTester.exe"); 13: app.Show(cfg); // 显示应用程序本次运行的设置 14: cfg.Count += 1; // 每次运行增加一块金币 15: cfg.Birth = new DateTime(1997, 7, 1); // 设置出生日期 16: cfg.Save(); // 退出之前保存应用程序设置 17: } 18: catch (Exception ex) 19: { 20: Console.WriteLine(ex.Message); 21: } 22: } 23: 24: void Show(AppConfigure cfg) 25: { 26: Console.WriteLine("姓名: " + cfg.Name); 27: Console.WriteLine("出生: " + cfg.Birth.ToString("ddddd yyyy-MM-dd HH:mm:ss")); 28: Console.WriteLine("金币: " + cfg.Count); 29: } 30: } 31: }
相应的 AppConfigure.cs 如下所示:
01: using System; 02: using Skyiv.Common; 03: 04: namespace Skyiv.Tester 05: { 06: /// <summary> 07: /// 应用程序设置 08: /// </summary> 09: sealed class AppConfigure : AppConfigureBase 10: { 11: static readonly string NameString = "Name"; 12: static readonly string BirthString = "Birth"; 13: static readonly string CountString = "Count"; 14: 15: public AppConfigure(string executeFileFullName) 16: : base(executeFileFullName) 17: { 18: } 19: 20: public string Name 21: { 22: get { return GetString(NameString, "无名氏"); } 23: set { SetString(NameString, value); } 24: } 25: 26: public DateTime Birth 27: { 28: get { return GetDateTime(BirthString, DateTime.Now); } 29: set { SetDateTime(BirthString, value); } 30: } 31: 32: public long Count 33: { 34: get { return GetInt64(CountString, 20); } 35: set { SetInt64(CountString, value); } 36: } 37: } 38: }
该 CUI 程序第一次运行的结果如下所示:
这是因为此时还没有保存应用程序设置的 XML 文件,因此就采用了缺省值,姓名是“无名氏”,出生日期是现在的时间,金币数量为20。
再次运行,结果如下所示:
可以看出,姓名不变,出生日期在 AppConfigureTester.cs 第 15 行中设置为1997年7月1日。金币数量在第 14 行中增加了一块,变为21了。
检查一下 AppConfigureTester.xml 文件,如下所示:
1: <?xml version="1.0" encoding="UTF-8"?> 2: <Settings> 3: <Count>22</Count> 4: <Birth>630033120000000000</Birth> 5: </Settings>
这个文件中第 3 行的 22 说明下次运行时金币数量将增加到 22 块。第 44 行的这个很大的整数是1997年7月1日这个日期在 DateTime 结构的内部表示。而姓名一直没有修改过,所以没有保存到 XML 文件中,应用程序的每次运行都将使用缺省值“无名氏”。