我们知道,大多数软件都需要有一个“关于本软件”的对话框,用于告诉用户该软件的一些重要信息,最主要的是该软件的版本号。例如下图就是“锦书背单词”软件的“关于本软件”对话框:
现在让我们来写一个通用的“关于本软件”对话框,封装为 Skyiv.Common.AboutDialog 类。这样,我们在开发应用程序时只需要直接使用该类就行了,避免了重复劳动。测试程序 AboutDialogTester.cs 如下所示:
01: using System.Reflection; 02: using Skyiv.Common; 03: 04: namespace Skyiv.Tester 05: { 06: sealed class AboutDialogTester 07: { 08: static void Main() 09: { 10: var assembly = new AssemblyInformation(Assembly.GetEntryAssembly()); 11: var dlg = new AboutDialog(assembly, Ben.Name, Ben.HomePageUrl); 12: dlg.AddChangeLog("版本 1.1.7.3: Sat 2010-12-25"); 13: dlg.AddChangeLog(" 1.实现了 A 功能。"); 14: dlg.AddChangeLog(" 2.修正了 B 故障。"); 15: dlg.AddChangeLog(""); 16: dlg.AddChangeLog("版本 1.0.0.0: Fri 2010-12-24"); 17: dlg.AddChangeLog(" 原始版本,仅实现基本功能。"); 18: dlg.AddChangeLog(" 能够对 AboutDialog 类进行测试。"); 19: dlg.ShowDialog(); 20: } 21: } 22: }
也就是说,只需要实例化一个 AboutDialog 类,然后调用其 ShowDialog 方法就可以显示“关于本软件”对话框了。就这么简单。
上面这个程序的运行效果如下图所示:
慢着,上图中的程序名称和程序版本等信息是哪里来的?哦,还需要一个 AssemblyInfo.cs 源程序文件:
01: using System.Reflection; 02: using System.Runtime.InteropServices; 03: 04: [assembly: AssemblyTitle("测试程序")] 05: [assembly: AssemblyDescription("用于测试 AboutDialog 类")] 06: [assembly: AssemblyCompany("Skyiv Studio")] 07: [assembly: AssemblyProduct("AboutDialogTester")] 08: [assembly: AssemblyCopyright("Copyright © 2010")] 09: [assembly: Guid("f713107d-ad02-4736-ae8c-2cfa5f4e9225")] 10: [assembly: AssemblyVersion("1.1.7.3")]
这个 AssemblyInfo.cs 可以由 Visual Studio 2010 自动生成。当然,你也可以自己手工编写。如果是自己手工编写,第 09 行的 GuidAttribute 可使用 System.Guid.NewGuid 方法生成。也可以干脆省略第 09 行,这样,该软件的 Guid 会被指定为值为零的 Guid.Empty 。
上图中的作者和主页信息来自以下 Ben.cs:
01: namespace Skyiv.Common 02: { 03: /// <summary> 04: /// 银河的个人信息 05: /// </summary> 06: public static class Ben 07: { 08: public static readonly string Name = "银河"; 09: public static readonly string HomePageUrl = "http://www.cnblogs.com/skyivben"; 10: } 11: }
当然,这个需要修改为你自己的信息了。
下面是用于获取程序信息的类的 C# 源程序文件 AssemblyInformation.cs,上图中的大部分信息都来源于此类:
01: using System; 02: using System.IO; 03: using System.Reflection; 04: using System.Runtime.InteropServices; 05: 06: namespace Skyiv.Common 07: { 08: /// <summary> 09: /// 用于获取程序集信息的东东 10: /// </summary> 11: public sealed class AssemblyInformation 12: { 13: Assembly assembly; 14: 15: public Version Version { get { return assembly.GetName().Version; } } 16: public string Name { get { return assembly.GetName().Name; } } 17: public string ExecuteFileFullName { get { return assembly.Location; } } 18: public string ExecuteFileDirectory { get { return Path.GetDirectoryName(ExecuteFileFullName); } } 19: public DateTime BuildTime { get { return Utilities.GetPe32Time(ExecuteFileFullName); } } 20: 21: public AssemblyInformation(Assembly assembly) 22: { 23: this.assembly = assembly; 24: } 25: 26: public string Title 27: { 28: get 29: { 30: var attrs = assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute), false); 31: if (attrs.Length > 0) 32: { 33: var attr = (AssemblyTitleAttribute)attrs[0]; 34: if (attr.Title != "") return attr.Title; 35: } 36: return Name; 37: } 38: } 39: 40: public Guid Guid 41: { 42: get 43: { 44: var attrs = assembly.GetCustomAttributes(typeof(GuidAttribute), false); 45: return (attrs.Length == 0) ? Guid.Empty : new Guid(((GuidAttribute)attrs[0]).Value); 46: } 47: } 48: 49: public string Company 50: { 51: get 52: { 53: var attrs = assembly.GetCustomAttributes(typeof(AssemblyCompanyAttribute), false); 54: return (attrs.Length == 0) ? "" : ((AssemblyCompanyAttribute)attrs[0]).Company; 55: } 56: } 57: 58: public string Description 59: { 60: get 61: { 62: var attrs = assembly.GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false); 63: return (attrs.Length == 0) ? "" : ((AssemblyDescriptionAttribute)attrs[0]).Description; 64: } 65: } 66: } 67: }
上述程序中第 19 行的 Utilities.GetPe32Time 方法来源于 Utilities.cs:
01: using System; 02: using System.IO; 03: 04: namespace Skyiv.Common 05: { 06: public static class Utilities 07: { 08: public static string GetComputerName() 09: { 10: var name = Environment.MachineName; 11: if (Environment.UserDomainName != name) name = Environment.UserDomainName + "\\" + name; 12: return name; 13: } 14: 15: public static DateTime GetPe32Time(string fileName) 16: { 17: int seconds; 18: using (var br = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read))) 19: { 20: var bs = br.ReadBytes(2); 21: var msg = "非法的PE32文件"; 22: if (bs.Length != 2) throw new Exception(msg); 23: if (bs[0] != 'M' || bs[1] != 'Z') throw new Exception(msg); 24: br.BaseStream.Seek(0x3c, SeekOrigin.Begin); 25: var offset = br.ReadByte(); 26: br.BaseStream.Seek(offset, SeekOrigin.Begin); 27: bs = br.ReadBytes(4); 28: if (bs.Length != 4) throw new Exception(msg); 29: if (bs[0] != 'P' || bs[1] != 'E' || bs[2] != 0 || bs[3] != 0) throw new Exception(msg); 30: bs = br.ReadBytes(4); 31: if (bs.Length != 4) throw new Exception(msg); 32: seconds = br.ReadInt32(); 33: } 34: return DateTime.SpecifyKind(new DateTime(1970, 1, 1), DateTimeKind.Utc). 35: AddSeconds(seconds).ToLocalTime(); 36: } 37: } 38: }
关于这个 GetPe32Time 方法的详细解释,请参见我在 2010-12-18 发表的“浅谈 .NET 程序的编译时间”一文。
最后,终于轮到我们这次的主角 AboutDialog.cs 登场了:
01: using System; 02: using System.Windows.Forms; 03: using Skyiv.Common; 04: 05: namespace Skyiv.Common 06: { 07: /// <summary> 08: /// 通用的“关于本软件”对话框 09: /// </summary> 10: public partial class AboutDialog : Form 11: { 12: AssemblyInformation assembly; 13: string author; 14: string homePageUrl; 15: 16: public AboutDialog(AssemblyInformation assembly, string author, string homePageUrl) 17: { 18: this.assembly = assembly; 19: this.author = author; 20: this.homePageUrl = homePageUrl; 21: InitializeComponent(); 22: } 23: 24: private void AboutForm_Load(object sender, EventArgs e) 25: { 26: splMain.Panel2Collapsed = true; 27: FillAbout(); 28: } 29: 30: private void btnToggleChangeLog_Click(object sender, EventArgs e) 31: { 32: splMain.Panel2Collapsed = !splMain.Panel2Collapsed; 33: var size = Size; 34: if (splMain.Panel2Collapsed) size.Height -= 100; 35: else size.Height += 100; 36: Size = size; 37: btnToggleChangeLog.Text = (splMain.Panel2Collapsed ? "显示" : "隐藏") + "版本历史(&V)"; 38: } 39: 40: void FillAbout() 41: { 42: Add("程序名称", assembly.Title + " - " + assembly.Name); 43: Add("程序版本", string.Format("{0} (Build: {1:yyyy-MM-dd HH:mm:ss})", 44: assembly.Version, assembly.BuildTime)); 45: Add("程序标识", assembly.Guid.ToString()); 46: Add("程序说明", assembly.Description); 47: Add("作者", author + " ( " + assembly.Company + " )"); 48: Add("主页", homePageUrl); 49: Add("计算机用户", Environment.UserName + " @ " + Utilities.GetComputerName()); 50: Add("操作系统", Environment.OSVersion.ToString()); 51: Add("公共语言运行库", Environment.Version.ToString()); 52: Add("程序文件名称", assembly.ExecuteFileFullName); 53: Add("程序内存使用", Environment.WorkingSet.ToString("N0") + " bytes"); 54: } 55: 56: void Add(string key, string value) 57: { 58: dgvMain.Rows.Add(new string[] { key, value }); 59: } 60: 61: public void AddChangeLog(string msg) 62: { 63: tbxChangeLog.AppendText(msg + Environment.NewLine); 64: } 65: } 66: }
下面是用于编译以上 C# 源程序的响应文件 mak.rsp:
现在让我们在 Windows Vista 操作系统的 Visual Studio 2010 命令行环境下编译和运行吧:
在 openSUSE 11.3 操作系统的 mono 2.8.1 环境下编译和运行:
从上图可以看出,mono 2.8.1 对 Environment.WorkingSet 直接返回零了,而没有获取映射到进程上下文的物理内存量。除此之外,都实现得还不错。上图一点也不漂亮,比 Windows 操作系统的难看多了。是的,mono 对 WinForm 程序支持得不是很好,但是这是 mono 的副业,mono 推荐的 GUI 界面是 Gtk#。点击这里可以看到很多漂亮的使用 mono 的 GUI 程序。
在 Ubuntu 10.10 操作系统的 mono 2.6.7 环境下编译和运行:
在 Ubuntu 10.10 操作系统的 mono 2.8.1 环境下编译和运行:
确认一下 openSUSE 操作系统和 mono 环境的版本信息:
再确认一下 Ubuntu 操作系统和 mono 环境的版本信息:
哦,还有一个由 Visual Studio 2010 生成的 AboutDialog.Designer.cs:
001: namespace Skyiv.Common 002: { 003: partial class AboutDialog 004: { 005: /// <summary> 006: /// Required designer variable. 007: /// </summary> 008: private System.ComponentModel.IContainer components = null; 009: 010: /// <summary> 011: /// Clean up any resources being used. 012: /// </summary> 013: /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> 014: protected override void Dispose(bool disposing) 015: { 016: if (disposing && (components != null)) 017: { 018: components.Dispose(); 019: } 020: base.Dispose(disposing); 021: } 022: 023: #region Windows Form Designer generated code 024: 025: /// <summary> 026: /// Required method for Designer support - do not modify 027: /// the contents of this method with the code editor. 028: /// </summary> 029: private void InitializeComponent() 030: { 031: this.btnClose = new System.Windows.Forms.Button(); 032: this.splMain = new System.Windows.Forms.SplitContainer(); 033: this.dgvMain = new System.Windows.Forms.DataGridView(); 034: this.KeyColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); 035: this.ValueColumn = new System.Windows.Forms.DataGridViewTextBoxColumn(); 036: this.tbxChangeLog = new System.Windows.Forms.TextBox(); 037: this.btnToggleChangeLog = new System.Windows.Forms.Button(); 038: this.splMain.Panel1.SuspendLayout(); 039: this.splMain.Panel2.SuspendLayout(); 040: this.splMain.SuspendLayout(); 041: ((System.ComponentModel.ISupportInitialize)(this.dgvMain)).BeginInit(); 042: this.SuspendLayout(); 043: // 044: // btnClose 045: // 046: this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 047: this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; 048: this.btnClose.Location = new System.Drawing.Point(478, 271); 049: this.btnClose.Name = "btnClose"; 050: this.btnClose.Size = new System.Drawing.Size(75, 23); 051: this.btnClose.TabIndex = 2; 052: this.btnClose.Text = "关闭(&C)"; 053: this.btnClose.UseVisualStyleBackColor = true; 054: // 055: // splMain 056: // 057: this.splMain.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 058: | System.Windows.Forms.AnchorStyles.Left) 059: | System.Windows.Forms.AnchorStyles.Right))); 060: this.splMain.FixedPanel = System.Windows.Forms.FixedPanel.Panel1; 061: this.splMain.Location = new System.Drawing.Point(3, 3); 062: this.splMain.Name = "splMain"; 063: this.splMain.Orientation = System.Windows.Forms.Orientation.Horizontal; 064: // 065: // splMain.Panel1 066: // 067: this.splMain.Panel1.Controls.Add(this.dgvMain); 068: // 069: // splMain.Panel2 070: // 071: this.splMain.Panel2.Controls.Add(this.tbxChangeLog); 072: this.splMain.Size = new System.Drawing.Size(550, 262); 073: this.splMain.SplitterDistance = 233; 074: this.splMain.TabIndex = 3; 075: // 076: // dgvMain 077: // 078: this.dgvMain.AllowUserToAddRows = false; 079: this.dgvMain.AllowUserToDeleteRows = false; 080: this.dgvMain.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells; 081: this.dgvMain.AutoSizeRowsMode = System.Windows.Forms.DataGridViewAutoSizeRowsMode.AllCells; 082: this.dgvMain.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; 083: this.dgvMain.ColumnHeadersVisible = false; 084: this.dgvMain.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { 085: this.KeyColumn, 086: this.ValueColumn}); 087: this.dgvMain.Dock = System.Windows.Forms.DockStyle.Fill; 088: this.dgvMain.Location = new System.Drawing.Point(0, 0); 089: this.dgvMain.MultiSelect = false; 090: this.dgvMain.Name = "dgvMain"; 091: this.dgvMain.ReadOnly = true; 092: this.dgvMain.RowHeadersVisible = false; 093: this.dgvMain.RowTemplate.Height = 23; 094: this.dgvMain.Size = new System.Drawing.Size(550, 233); 095: this.dgvMain.TabIndex = 0; 096: // 097: // KeyColumn 098: // 099: this.KeyColumn.Frozen = true; 100: this.KeyColumn.HeaderText = "Key"; 101: this.KeyColumn.Name = "KeyColumn"; 102: this.KeyColumn.ReadOnly = true; 103: this.KeyColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; 104: this.KeyColumn.Width = 5; 105: // 106: // ValueColumn 107: // 108: this.ValueColumn.HeaderText = "Value"; 109: this.ValueColumn.Name = "ValueColumn"; 110: this.ValueColumn.ReadOnly = true; 111: this.ValueColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True; 112: this.ValueColumn.Width = 5; 113: // 114: // tbxChangeLog 115: // 116: this.tbxChangeLog.Dock = System.Windows.Forms.DockStyle.Fill; 117: this.tbxChangeLog.Location = new System.Drawing.Point(0, 0); 118: this.tbxChangeLog.Multiline = true; 119: this.tbxChangeLog.Name = "tbxChangeLog"; 120: this.tbxChangeLog.ReadOnly = true; 121: this.tbxChangeLog.ScrollBars = System.Windows.Forms.ScrollBars.Both; 122: this.tbxChangeLog.Size = new System.Drawing.Size(550, 25); 123: this.tbxChangeLog.TabIndex = 0; 124: // 125: // btnToggleChangeLog 126: // 127: this.btnToggleChangeLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 128: this.btnToggleChangeLog.Location = new System.Drawing.Point(358, 271); 129: this.btnToggleChangeLog.Name = "btnToggleChangeLog"; 130: this.btnToggleChangeLog.Size = new System.Drawing.Size(114, 23); 131: this.btnToggleChangeLog.TabIndex = 0; 132: this.btnToggleChangeLog.Text = "显示版本历史(&V)"; 133: this.btnToggleChangeLog.UseVisualStyleBackColor = true; 134: this.btnToggleChangeLog.Click += new System.EventHandler(this.btnToggleChangeLog_Click); 135: // 136: // AboutDialog 137: // 138: this.AcceptButton = this.btnToggleChangeLog; 139: this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); 140: this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 141: this.CancelButton = this.btnClose; 142: this.ClientSize = new System.Drawing.Size(554, 297); 143: this.Controls.Add(this.btnToggleChangeLog); 144: this.Controls.Add(this.splMain); 145: this.Controls.Add(this.btnClose); 146: this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 147: this.MaximizeBox = false; 148: this.MinimizeBox = false; 149: this.Name = "AboutDialog"; 150: this.ShowInTaskbar = false; 151: this.Text = "关于本软件"; 152: this.Load += new System.EventHandler(this.AboutForm_Load); 153: this.splMain.Panel1.ResumeLayout(false); 154: this.splMain.Panel2.ResumeLayout(false); 155: this.splMain.Panel2.PerformLayout(); 156: this.splMain.ResumeLayout(false); 157: ((System.ComponentModel.ISupportInitialize)(this.dgvMain)).EndInit(); 158: this.ResumeLayout(false); 159: 160: } 161: 162: #endregion 163: 164: private System.Windows.Forms.Button btnClose; 165: private System.Windows.Forms.SplitContainer splMain; 166: private System.Windows.Forms.DataGridView dgvMain; 167: private System.Windows.Forms.TextBox tbxChangeLog; 168: private System.Windows.Forms.Button btnToggleChangeLog; 169: private System.Windows.Forms.DataGridViewTextBoxColumn KeyColumn; 170: private System.Windows.Forms.DataGridViewTextBoxColumn ValueColumn; 171: } 172: }
希望本文对大家有所帮助。
本文中的所有源代码可以到 https://bitbucket.org/ben.skyiv/aboutdialog/downloads 页面下载。
也可以使用 hg clone https://ben.skyiv@bitbucket.org/ben.skyiv/aboutdialog 命令下载。
关于 hg,请参阅 Mercurial 备忘录。