银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

我们知道,大多数软件都需要有一个“关于本软件”的对话框,用于告诉用户该软件的一些重要信息,最主要的是该软件的版本号。例如下图就是“锦书背单词”软件的“关于本软件”对话框:

 

现在让我们来写一个通用的“关于本软件”对话框,封装为 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:

-t:winexe
-r:System.Drawing.dll
-r:System.Windows.Forms.dll
AboutDialogTester.cs
AboutDialog.cs
AboutDialog.Designer.cs
AssemblyInfo.cs
AssemblyInformation.cs
Ben.cs
Utilities.cs

 

现在让我们在 Windows Vista 操作系统的 Visual Studio 2010 命令行环境下编译和运行吧:

E:\CS\AboutDialog> csc @mak.rsp
Microsoft(R) Visual C# 2010 编译器 4.0.30319.1 版
版权所有(C) Microsoft Corporation。保留所有权利。

E:\CS\AboutDialog> AboutDialogTester
E:\CS\AboutDialog>

 

在 openSUSE 11.3 操作系统的 mono 2.8.1 环境下编译和运行:

ben@ben1520:~/work/AboutDialog> dmcs @mak.rsp
ben@ben1520:~/work/AboutDialog> mono AboutDialogTester.exe
ben@ben1520:~/work/AboutDialog> 

从上图可以看出,mono 2.8.1 对 Environment.WorkingSet 直接返回零了,而没有获取映射到进程上下文的物理内存量。除此之外,都实现得还不错。上图一点也不漂亮,比 Windows 操作系统的难看多了。是的,mono 对 WinForm 程序支持得不是很好,但是这是 mono 的副业,mono 推荐的 GUI 界面是 Gtk#点击这里可以看到很多漂亮的使用 mono 的 GUI 程序。

 

在 Ubuntu 10.10 操作系统的 mono 2.6.7 环境下编译和运行:

ben@ben-1520:~/work/AboutDialog$ gmcs @mak.rsp
ben@ben-1520:~/work/AboutDialog$ ./AboutDialogTester.exe
ben@ben-1520:~/work/AboutDialog$ 

 

在 Ubuntu 10.10 操作系统的 mono 2.8.1 环境下编译和运行:

ben@ben-1520:~/work/AboutDialog$ /opt/mono-2.8.1/bin/dmcs @mak.rsp
ben@ben-1520:~/work/AboutDialog$ /opt/mono-2.8.1/bin/mono AboutDialogTester.exe
ben@ben-1520:~/work/AboutDialog$ 

 

确认一下 openSUSE 操作系统和 mono 环境的版本信息:

ben@ben1520:~> uname -a
Linux ben1520 2.6.34.7-0.5-desktop #1 SMP PREEMPT 2010-10-25 08:40:12 +0200 x86_64 x86_64 x86_64 GNU/Linux
ben@ben1520:~> cat /etc/issue
Welcome to openSUSE 11.3 "Teal" - Kernel \r (\l).

ben@ben1520:~> mono --version
Mono JIT compiler version 2.8.1 (tarball Fri Nov 12 14:37:21 UTC 2010)
Copyright (C) 2002-2010 Novell, Inc and Contributors. www.mono-project.com
	TLS:           __thread
	SIGSEGV:       altstack
	Notifications: epoll
	Architecture:  amd64
	Disabled:      none
	Misc:          debugger softdebug 
	LLVM:          yes(2.8svn-mono)
	GC:            Included Boehm (with typed GC and Parallel Mark)
ben@ben1520:~> dmcs --version
Mono C# compiler version 2.8.1.0

 

再确认一下 Ubuntu 操作系统和 mono 环境的版本信息:

ben@ben-1520:~$ uname -a
Linux ben-1520 2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 02:41:37 UTC 2010 x86_64 GNU/Linux
ben@ben-1520:~$ cat /etc/issue
Ubuntu 10.10 \n \l

ben@ben-1520:~$ mono --version
Mono JIT compiler version 2.6.7 (Debian 2.6.7-3ubuntu1)
Copyright (C) 2002-2010 Novell, Inc and Contributors. www.mono-project.com
	TLS:           __thread
	GC:            Included Boehm (with typed GC and Parallel Mark)
	SIGSEGV:       altstack
	Notifications: epoll
	Architecture:  amd64
	Disabled:      none
ben@ben-1520:~$ gmcs --version
Mono C# compiler version 2.6.7.0
ben@ben-1520:~$ /opt/mono-2.8.1/bin/mono --version
Mono JIT compiler version 2.8.1 (tarball 2010年 12月 25日 星期六 12:27:53 CST)
Copyright (C) 2002-2010 Novell, Inc and Contributors. www.mono-project.com
	TLS:           __thread
	SIGSEGV:       altstack
	Notifications: epoll
	Architecture:  amd64
	Disabled:      none
	Misc:          debugger softdebug 
	LLVM:          supported, not enabled.
	GC:            Included Boehm (with typed GC and Parallel Mark)
ben@ben-1520:~$ /opt/mono-2.8.1/bin/dmcs --version
Mono C# compiler version 2.8.1.0
ben@ben-1520:~$ 

 

哦,还有一个由 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 备忘录

posted on 2010-12-25 11:25  银河  阅读(4893)  评论(10编辑  收藏  举报