上篇博客对GDI+的基本绘图方法汇总整理了一遍,本来以为不会学习到有关GDI+更深一步的内容了,但随着项目的进行发现事实并非如此,GDI+不仅仅是封装了图形绘制的功能,它还封装了经常使用的打印功能。今天来看看.NET的打印是如何实现的。
先来讨论下打印的一个流程,当要打印时我们往往需要首先打开打印的文档,那抽象一下就是文档类;然后编辑文档的内容,页面之类的东西,这里可以抽象出页面设置;在设置完成后大部分人会选择先预览下文档的内容和打印格式是否满足要求,那么我们又抽象出了一个打印预览;上面的操作完成后,接下来就要真正的打印了,这里要想想在开始打印前是不是还要有一个界面来选择打印机、打印类型等的界面?是的,在打印前往往还会有一个打印选项的功能,最后抽象出了一个打印选项。
上面从打印开始,按照打印的流程一步步分析了打印时需要有的类和窗口,从流程来看要想实现打印必须至少会有四类,这四类也是.NET在打印时需要使用到的内容。
从.NET封装的打印需要的内容上来看,其实打印需要的内容被封存在两个命名空间内(如下图),这两个命名空间结合使用才能实现灵活的打印的目的,另外Printing空间下的类提供了对打印的一些操控,而在Forms空间下的类提供了对打印的一些设置,而且还提供了窗体的显示。
当最后调用PrintDocument的Print方法时,会触发三个事件BeginPrint、OnPrint和EndPrint,相信菜鸟级的程序猿可能会天真的认为Print方法和三个事件的定义都是写在PrintDocument类中,其实不然。想想上图的PrintController类应该知道,其实真正的打印功能是在该类中实现的。为了解答这个问题使用反编译器对System.Drawing.dll文件进行反编译,得到了如下的代码
//PrintDocument类的部分代码 internal void _OnBeginPrint(PrintEventArgs e) { this.OnBeginPrint(e); } protected virtual void OnBeginPrint(PrintEventArgs e) { if (this.beginPrintHandler == null) return; this.beginPrintHandler((object) this, e); } internal void _OnEndPrint(PrintEventArgs e) { this.OnEndPrint(e); } protected virtual void OnEndPrint(PrintEventArgs e) { if (this.endPrintHandler == null) return; this.endPrintHandler((object) this, e); } internal void _OnPrintPage(PrintPageEventArgs e) { this.OnPrintPage(e); } protected virtual void OnPrintPage(PrintPageEventArgs e) { if (this.printPageHandler == null) return; this.printPageHandler((object) this, e); } internal void _OnQueryPageSettings(QueryPageSettingsEventArgs e) { this.OnQueryPageSettings(e); } protected virtual void OnQueryPageSettings(QueryPageSettingsEventArgs e) { if (this.queryHandler == null) return; this.queryHandler((object) this, e); } public void Print() { if (!this.PrinterSettings.IsDefaultPrinter && !this.PrinterSettings.PrintDialogDisplayed) System.Drawing.IntSecurity.AllPrinting.Demand(); this.PrintController.Print(this); } public override string ToString() { return "[PrintDocument " + this.DocumentName + "]"; }
从PrintDocument的源码中来看,至少它的Print方法真正实现并不是在它本身类中,而是在PrintController类中实现的,但是对于在打印中触发的实现的确是在PrintDocument类中定义的,而具体的调用呢?当然会是在PrintController类中。
//PrintController类中的打印方法 internal void Print(PrintDocument document) { IntSecurity.SafePrinting.Demand(); PrintEventArgs e = new PrintEventArgs(!this.IsPreview ? (document.PrinterSettings.PrintToFile ? PrintAction.PrintToFile : PrintAction.PrintToPrinter) : PrintAction.PrintToPreview); document._OnBeginPrint(e); if (e.Cancel) { document._OnEndPrint(e); } else { this.OnStartPrint(document, e); if (e.Cancel) { document._OnEndPrint(e); this.OnEndPrint(document, e); } else { bool flag = true; try { flag = this.PrintLoop(document); } finally { try { try { document._OnEndPrint(e); e.Cancel = flag | e.Cancel; } finally { this.OnEndPrint(document, e); } } finally { if (!IntSecurity.HasPermission(IntSecurity.AllPrinting)) { IntSecurity.AllPrinting.Assert(); document.PrinterSettings.PrintDialogDisplayed = false; } } } } } }
PrintController类的Print方法为什么声明为internal类型?为了封装,这很好体现了编程中的迪米特法则,PrintDocument类即声明了基本的打印事件并提供给外界公有的打印方法,而PrintController类才是对PrintDocument类进行组装,类的内聚性很高。
上节中对Printing命名空间中的两个类的运行进行了讨论,从代码中能清晰的看出其实真正的Print方法是在PrintController中实现的,很好的体现了编程原则,另外对于基本的打印的设置却是在Forms中的,接下来就看看Forms中的几个类。
要说这几个Dialog类就不得不来说说它们的父类CommonDialog类,从解析的源码中也可以看出这几个Dialog类并没有提供对外的Show和ShowDialog方法,那这两个方法在哪里呢?很快就能想到是在父类中定义的,对的,它的定义和使用方法和上节中说到的两个打印类相似,在具体的Dialog类中声明并重写了要显示的内容方法,而在父类中定义了显示的方式。
清单一:CommonDialog类,CommonDialog是所有Dialog类的基类,它封装了ShowDialog的业务过程,而具体要显示的内容是在RunDialog中定义的,另外RunDialog方法是被子类重写来实现的,所以具体要显示的内容是在具体的子类Dialog中来实现的,这样做提高了代码的复用性,很好的体现了面向对象的思想。
protected abstract bool RunDialog(IntPtr hwndOwner); public DialogResult ShowDialog() { return this.ShowDialog((IWin32Window) null); } //CommonDialog类中定义的ShowDialog方法 public DialogResult ShowDialog(IWin32Window owner) { IntSecurity.SafeSubWindows.Demand(); if (!SystemInformation.UserInteractive) throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive")); NativeWindow nativeWindow = (NativeWindow) null; IntPtr num = IntPtr.Zero; try { if (owner != null) num = Control.GetSafeHandle(owner); if (num == IntPtr.Zero) num = UnsafeNativeMethods.GetActiveWindow(); if (num == IntPtr.Zero) { nativeWindow = new NativeWindow(); nativeWindow.CreateHandle(new CreateParams()); num = nativeWindow.Handle; } if (CommonDialog.helpMsg == 0) CommonDialog.helpMsg = SafeNativeMethods.RegisterWindowMessage("commdlg_help"); NativeMethods.WndProc wndproc = new NativeMethods.WndProc(this.OwnerWndProc); this.hookedWndProc = Marshal.GetFunctionPointerForDelegate((Delegate) wndproc); IntPtr userCookie = IntPtr.Zero; try { this.defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef((object) this, num), -4, wndproc); if (Application.UseVisualStyles) userCookie = UnsafeNativeMethods.ThemingScope.Activate(); Application.BeginModalMessageLoop(); try { return this.RunDialog(num) ? DialogResult.OK : DialogResult.Cancel; //调用子类重写的RunDialog方法 } finally { Application.EndModalMessageLoop(); } } finally { IntPtr windowLong = UnsafeNativeMethods.GetWindowLong(new HandleRef((object) this, num), -4); if (IntPtr.Zero != this.defOwnerWndProc || windowLong != this.hookedWndProc) UnsafeNativeMethods.SetWindowLong(new HandleRef((object) this, num), -4, new HandleRef((object) this, this.defOwnerWndProc)); UnsafeNativeMethods.ThemingScope.Deactivate(userCookie); this.defOwnerWndProc = IntPtr.Zero; this.hookedWndProc = IntPtr.Zero; GC.KeepAlive((object) wndproc); } } finally { if (nativeWindow != null) nativeWindow.DestroyHandle(); } }
//Dialog子类重写的RunDialog方法 protected override bool RunDialog(IntPtr hwndOwner) { IntSecurity.SafePrinting.Demand(); NativeMethods.WndProc hookProcPtr = new NativeMethods.WndProc(((CommonDialog) this).HookProc); bool flag; if (!this.UseEXDialog || Environment.OSVersion.Platform != PlatformID.Win32NT || Environment.OSVersion.Version.Major < 5) { NativeMethods.PRINTDLG printdlg = PrintDialog.CreatePRINTDLG(); flag = this.ShowPrintDialog(hwndOwner, hookProcPtr, printdlg); } else { NativeMethods.PRINTDLGEX printdlgex = PrintDialog.CreatePRINTDLGEX(); flag = this.ShowPrintDialog(hwndOwner, printdlgex); } return flag; }
另外每个具体的Dialog子类封装了有关打印文档设置的内容,它们大部分都是以属性的方式在类中进行定义的。
上面的内容对打印进行了解析,微软把具体的打印功能封装到了两个命名空间下面,另外从分析中可以看出它们的具体实现过程都是定义到了其它类中这样不但符合了编程原则,而且提高了代码的复用性,这种编程理念需要我们学习。希望上面的解析能帮助程序猿更好的在编程中操作使用两个类来控制打印,文章写到这里还没有完结,接下来讨论打印的实现和打印类的属性。