银河

SKYIV STUDIO

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

我们在用 C# 语言编写 WinForm 程序时,如果在程序中需要打印一些东东的话,经常需要先使用页面设置对话框进行一些设置。而 Microsoft .NET Framework Base Class Library 已经为我们考虑得很周到了,我们只需要使用 System.Windows.Forms 命名空间中的 PageSetupDialog 类就行了。但是这个类有个小小的 BUG,下面就是相应的测试程序 PageSetupTester.cs:

001:  using System;
002:  using System.Drawing;
003:  using System.Windows.Forms;
004:  using System.Globalization;
005:  using System.Drawing.Printing;
006:  
007:  namespace Skyiv.Tester
008:  {
009:    sealed class PageSetupTester : Form
010:    {
011:      PageSetupDialog setupB;
012:  
013:      PageSetupTester()
014:      {
015:        Text = "页面设置测试";
016:        Width = 380;
017:  
018:        var lbl = new Label();
019:        lbl.Parent = this;
020:        lbl.Text = string.Format(
021:          " OS: {1}{0}CLR: {2}  ( {3} ){0}{4}{5}{0}{0}{6}{0}{7}{0}{8}{0}{9}{0}{10}",
022:          Environment.NewLine,
023:          Environment.OSVersion,
024:          Environment.Version,
025:          RuntimeFramework.CurrentFramework,
026:          "控制面板的区域选项的度量衡系统为",
027:          (RegionInfo.CurrentRegion.IsMetric ? "公制。" : "英制。"),
028:          "当度量衡系统为公制,且操作系统为Windows时,",
029:          "显示在页面设置对话框中的当前页边距是以公制为单位的数值,",
030:          "按下“确定”时,PageSetupDialog类内部却把屏幕上显示的",
031:          "页边距数值按英制为单位对页面进行设定,",
032:          "造成页边距不断减少的BUG。");
033:        lbl.Top = 10;
034:        lbl.AutoSize = true;
035:  
036:        var docA = new PrintDocument();
037:        docA.PrintPage += PrintPage;
038:        var setupA = new PageSetupDialog();
039:        setupA.Document = docA;
040:  
041:        var btnPageSetupA = new Button();
042:        btnPageSetupA.Parent = this;
043:        btnPageSetupA.Text = "页面设置A(可能有BUG)";
044:        btnPageSetupA.Top = 145;
045:        btnPageSetupA.Width = 150;
046:        btnPageSetupA.Click += (sender, e) => setupA.ShowDialog();
047:  
048:        var btnPreviewA = new Button();
049:        btnPreviewA.Parent = this;
050:        btnPreviewA.Text = "打印预览A";
051:        btnPreviewA.Top = btnPageSetupA.Top;
052:        btnPreviewA.Left = btnPageSetupA.Right + 5;
053:        btnPreviewA.Width = 100;
054:        btnPreviewA.Click += (sender, e) => PreviewPrint(docA);
055:  
056:        var docB = new PrintDocument();
057:        docB.PrintPage += PrintPage;
058:        setupB = new PageSetupDialog();
059:        setupB.Document = docB;
060:  
061:        var btnSetupB = new Button();
062:        btnSetupB.Parent = this;
063:        btnSetupB.Text = "页面设置B(修正)";
064:        btnSetupB.Top = btnPageSetupA.Top + 30;
065:        btnSetupB.Width = 150;
066:        btnSetupB.Click += PageSetupBClick;
067:  
068:        var btnPreviewB = new Button();
069:        btnPreviewB.Parent = this;
070:        btnPreviewB.Text = "打印预览B";
071:        btnPreviewB.Top = btnSetupB.Top;
072:        btnPreviewB.Left = btnSetupB.Right + 5;
073:        btnPreviewB.Width = 100;
074:        btnPreviewB.Click += (sender, e) => PreviewPrint(docB);
075:      }
076:  
077:      void PrintPage(object o, PrintPageEventArgs e)
078:      {
079:        Graphics g = e.Graphics;
080:        Rectangle r = e.MarginBounds;
081:        g.DrawRectangle(new Pen(Color.Red), r);
082:        g.DrawString(r.ToString(), new Font("宋体", 40), new SolidBrush(Color.Blue), r);
083:      }
084:  
085:      void PreviewPrint(PrintDocument doc)
086:      {
087:        var ppc = new PreviewPrintController();
088:        doc.PrintController = ppc;
089:        doc.Print();
090:        new PreviewPrintDialog(ppc.GetPreviewPageInfo()).ShowDialog();
091:      }
092:  
093:      void PageSetupBClick(object o, EventArgs e)
094:      {
095:        // 当前线程所使用的区域选项的度量衡系统为公制,且操作系统为Windows时,
096:        // 显示在页面设置对话框中的当前页边距是以公制为单位的数值,
097:        // 按下“确定”时,PageSetupDialog类内部却把屏幕上显示的
098:        // 页边距数值按英制为单位对页面进行设定,
099:        // 造成页边距不断减少的BUG。
100:        // 以下代码就是为了纠正这个BUG的。
101:        if (setupB.ShowDialog() == DialogResult.OK && RegionInfo.CurrentRegion.IsMetric
102:          && Environment.OSVersion.Platform != PlatformID.Unix)
103:        {
104:          setupB.PageSettings.Margins = PrinterUnitConvert.Convert
105:            (setupB.PageSettings.Margins, PrinterUnit.Display, PrinterUnit.TenthsOfAMillimeter);
106:        }
107:      }
108:  
109:      [STAThread]
110:      static void Main()
111:      {
112:        Application.EnableVisualStyles();
113:        Application.Run(new PageSetupTester());
114:      }
115:    }
116:  }

 

本来嘛,在 C# 程序要得到一个页面设置对话框是非常简单的事,只要象上述程序中第 46 行那样给“页面设置”按钮的 Click 事件注册一个调用 System.Windows.Forms.PageSetupDialog 类的 ShowDialog 方法的 Lambda 表达式就行了。

 

编译响应文件 mak.rsp 的内容如下:

-t:winexe
-r:System.Drawing.dll
-r:System.Windows.Forms.dll
PageSetupTester.cs
PreviewPrintDialog.cs
RuntimeFramework.cs

用于打印预览的 PreviewPrintDialog.cs 将在本文最后给出。而 RuntimeFramework.cs 请参见我在2009年12月13日写的“.NET Framework CLR 版本检测”一文。

 

让我们在 Windows Vista 操作系统的 .NET Framework 4 环境下编译和运行:

E:\work> C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc @mak.rsp
Microsoft(R) Visual C# 2010 编译器 4.0.30319.1 版
版权所有(C) Microsoft Corporation。保留所有权利。

E:\work> PageSetupTester

 

然后点击“打印预览B”按钮,得到的结果如下图(图1)所示:

图1

 

再点击“页面设置B(修正)”按钮,得到的结果如下图(图2)所示:

图2

 

点击上图中的“确定”按钮后,再次点击主窗体中的“打印预览B”按钮,得到的结果依然如图1所示。这是页面设置对话框应该有的正确行为。实际上这个行为是经过 PageSetupTester.cs 中第 104 行到第 105 行的语句修正后的结果。

 

现在让我们点击主窗体中的“打印预览A”按钮,得到的结果也是如图1如示,这也是正常的结果。然后再点击主窗体中的“页面设置A(可能有BUG)”按钮,得到的得到如图2所示,这也是预期的结果。然后再点“确定”按钮。现在,让我们再次点击主窗体中的“打印预览A”按钮,结果如下图所示:

 

这就不正常了。

 

再次点击主窗体中的“页面设置A(可能有BUG)”按钮,得到的得到如下图所示:

 

从上图中可以看到,页边距由图2中的“10毫米”变为现在的“3.94毫米”了。

我们知道,在控制面板的区域选项的度量衡系统设为英制时,页边距单位是“1/10英寸”。我们还知道“1/10英寸≈2.54毫米”。而“10/2.54≈3.94”,这个公式能够说明为什么页边距由图2中的“10毫米”变为现在的“3.94毫米”了。可能是 BCL 的 PageSetupDialog 类做了一次不必要的从公制到英制的转换。而 PageSetupTester.cs 中的第 104 到第 105 行通过调用 PrinterUnitConvert 类的 Convert 方法对页边距做了一次从英制到公制的转换,也就是从 PrinterUnit.Display(0.01英寸) 到 PrinterUnit.TenthsOfAMillimeter(0.1毫米) 的转换,以修正这个BUG。

 

在上图中点击“确定”按钮后,继续点击主窗体中的“打印预览A”按钮,结果如下图所示:

 

可以看出,页边距进一步缩小了。

 

让我们在 Windows Vista 操作系统的 .NET Framework 3.5 环境下编译和运行:

E:\work\> C:\Windows\Microsoft.NET\Framework\v3.5\csc @mak.rsp
适用于 Microsoft(R) .NET Framework 3.5 版的 Microsoft(R) Visual C# 2008 编译器 3.5.30729.1 版
版权所有(C) Microsoft Corporation。保留所有权利。

E:\work> PageSetupTester

重复上面的测试过程,得到的结果也是一样的。

 

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

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

 

点击主窗体的“打印预览A”按钮,得到的结果如下图(图3)所示:

图3

 

可以看到,文字超出矩形框部分被截断了,而不是象 Windows 操作系统中那样折行显示,这应该是 mono 实现的 WinForm 的一个缺点。

再点击主窗体中的“页面设置A(可能有BUG)”按钮,得到的结果如下图(图4)所示:

图4

 

点击“OK”按钮后,再点击主窗体中的“打印预览A”按钮,得到的结果如下图(图5)所示:

图5

 

这可能是页边距微调。再反复点击主窗体中的“页面设置A(可能有BUG)”按钮和“打印预览A”按钮,得到的结果一直如图4和图5所示,不会象在 Windows 操作系统中一样有BUG。

如果点击主窗体中的“页面设置B(修正)”按钮和“打印预览B”按钮,结果和点击“A”系列按钮一样。这是可以预期的,因为在 PageSetupTester.cs 中的第 102 行就已经判断如果是 Unix 类的操作系统的话,就不进行修正,从而“A”和“B”两组按钮的行为是一样的。

 

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

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

测试结果和 mono 2.8.1 环境的一样。

 

让我们在 Windows Vista 操作系统的 mono 2.8.1 环境下编译和运行:

E:\work> dmcs @mak.rsp
E:\work> mono PageSetupTester.exe

 

点击主窗体的“打印预览A”按钮,得到的结果如下图(图6)所示:

图6

 

对照前面如图3所示的 Ubuntu 10.10 操作系统 mono 2.8.1 环境下的打印预览,发现这次文字超出矩形框部分不会被截断了,而是正确地进行了折行处理。看来 mono 对 WinForm 的实现跟操作系统还是有关系的。

再点击主窗体中的“页面设置A(可能有BUG)”按钮,得到的结果如下图(图7)所示:

图7

 

点击“OK”按钮后,再点击主窗体中的“打印预览A”按钮,得到的结果如下图(图8)所示:

图8

 

这可能是页边距微调。再反复点击主窗体中的“页面设置A(可能有BUG)”按钮和“打印预览A”按钮,得到的结果一直如图7和图8所示,不会象在 Windows 操作系统的 .NET Framework 4 环境中一样有BUG,虽然这也是在 Windows 操作系统中运行,但这次是 mono 2.8.1 环境。

 

我们这次点击主窗体中的“打印预览B”按钮,结果如图6所示,这是正常的。再点击主窗体中“页面设置B(修正)”按钮,结果如图7所示,然后点击“OK”按钮,再次点击主窗体中的“打印预览B”按钮,结果如下图所示:

 

咦,页边距变大了。再次点击主窗体中的“页面设置B(修正)”按钮,结果如下图所示:

 

可以看出,页边距由图7中的“25毫米”变为现在的“63毫米”。这是 PageSetupTester.cs 程序中第 104 行到第 105 行的从英制到公制的转换造成的:“25 * 2.54 = 63.5”。程序中第 102 行认为不是 Unix 类的操作系统就需要进行修正,而实际上在 Windows 操作系统的 mono 环境中 PageSetupDialog 类没有这个 BUG,不需要修正。在 PageSetupDialog 类没有 BUG 的情况下去修正反而会出问题的。

 

最后,就是提供打印预览功能的 PreviewPrintDialog.cs 了:

01:  using System.Drawing;
02:  using System.Drawing.Printing;
03:  using System.Windows.Forms;
04:  
05:  namespace Skyiv
06:  {
07:    sealed class PreviewPrintDialog : Form
08:    {
09:      Label lbl;
10:      PictureBox pbx;
11:      Button btnPrevPage;
12:      Button btnNextPage;
13:      PreviewPageInfo[] ppi;
14:  
15:      public PreviewPrintDialog(PreviewPageInfo[] ppi)
16:      {
17:        this.ppi = ppi;
18:  
19:        Text = "打印预览";
20:        StartPosition = FormStartPosition.CenterScreen;
21:        Width = 400;
22:        Height = 610;
23:        ShowInTaskbar = false;
24:  
25:        var pnlMain = new Panel();
26:        pnlMain.Parent = this;
27:        pnlMain.Dock = DockStyle.Fill;
28:        pnlMain.AutoScroll = true;
29:  
30:        var pnlTop = new Panel();
31:        pnlTop.Parent = this;
32:        pnlTop.Dock = DockStyle.Top;
33:        pnlTop.Height = 30;
34:  
35:        var page = 0;
36:  
37:        lbl = new Label();
38:        lbl.Parent = pnlTop;
39:        lbl.Text = string.Format("第{0}页 共{1}页", page + 1, ppi.Length);
40:        lbl.Left = 5;
41:        lbl.Top = 8;
42:        lbl.Width = 95;
43:  
44:        btnPrevPage = new Button();
45:        btnPrevPage.Parent = pnlTop;
46:        btnPrevPage.Text = "上页(&V)";
47:        btnPrevPage.Left = 100;
48:        btnPrevPage.Top = 3;
49:        btnPrevPage.Width = 80;
50:        btnPrevPage.Enabled = (page > 0);
51:        btnPrevPage.Click += delegate { if (page > 0) --page; PageHandler(page); };
52:  
53:        btnNextPage = new Button();
54:        btnNextPage.Parent = pnlTop;
55:        btnNextPage.Text = "下页(&N)";
56:        btnNextPage.Left = 200;
57:        btnNextPage.Top = 3;
58:        btnNextPage.Width = 80;
59:        btnNextPage.Enabled = (page < ppi.Length - 1);
60:        btnNextPage.Click += delegate { if (page < ppi.Length - 1) ++page; PageHandler(page); };
61:  
62:        var btnClose = new Button();
63:        btnClose.Parent = pnlTop;
64:        btnClose.Text = "关闭(&C)";
65:        btnClose.Left = 300;
66:        btnClose.Top = 3;
67:        btnClose.Width = 80;
68:        btnClose.DialogResult = DialogResult.OK;
69:  
70:        var size = ppi[page].Image.Size;
71:  
72:        var pnlImage = new Panel();
73:        pnlImage.Parent = pnlMain;
74:        pnlImage.Width = pnlMain.Width - 10;
75:        pnlImage.Height = pnlImage.Width * size.Height / size.Width;
76:        pnlImage.BackColor = Color.White;
77:  
78:        pbx = new PictureBox();
79:        pbx.Parent = pnlImage;
80:        pbx.ClientSize = pnlImage.ClientSize;
81:        pbx.Image = ppi[page].Image;
82:        pbx.BorderStyle = BorderStyle.FixedSingle;
83:        pbx.SizeMode = PictureBoxSizeMode.StretchImage;
84:      }
85:  
86:      void PageHandler(int page)
87:      {
88:        btnPrevPage.Enabled = (page > 0);
89:        btnNextPage.Enabled = (page < ppi.Length - 1);
90:        lbl.Text = string.Format("第{0}页 共{1}页", page + 1, ppi.Length);
91:        pbx.Image = ppi[page].Image;
92:      }
93:    }
94:  }
posted on 2011-01-01 22:41  银河  阅读(3247)  评论(34编辑  收藏  举报