第14章Window应用程序开发在本书第1章中,曾经向读者介绍了Windows窗体应用程序开发的基础知识,本章将接着第1章的内容继续向读者介绍一些关于Windows窗体应用程序开发的更高级知识。简单的程序可能只包含一个线程执行,而复杂的程序就会同时运行多个线程,可能一个线程从文件读数据,另一个线程执行计算,而第3个线程在屏幕上运行动画,于是在VS2010中提供了Tread类来支持多线程的开发。这一章的主要内容如下:认识线程掌握多线程的应用应用SDI和MDI窗体使用各种对话框GDI+的应用14.1多线程编程线程是程序中的执行序列,使用C#编写任何程序时,都有一个入口Main()方法,程序从Main()方法的第一个语句开始执行,直到这个方法返回为止。线程是使用Tread类来处理的,该类在Sytem.Threading名称空间中,一个Thread实例表示一个线程,即执行序列。通过简单实例化一个Thread对象,就可以创建一个线程。ThreadPool类表示由系统管理的工作线程的集合,用来执行各种任务。Monitor类封装了一个监控锁,用来实现对共享资源的监视。14.1.1线程的基本概念在介绍线程概念之前,先介绍两个基本概念同步和应用程序域。同步元素是指一次只能被一个线程访问的元素。在多线程环境中,能够访问对象的是在特定时间占用该对象的锁的线程,锁可以是监控器锁、互斥锁或读写锁。线程可以获的锁、释放锁或等待另一个线程通知它们锁现在可用。应用程序域的出现是.NET处理进程和线程方式的一个重大革新,它提供了在同一个进程中隔离线程的手段。在以前,开发人员只有一种选择,则必须在不同的进程中隔离组件。应用程序域却允许隔离在同一个进程中运行的不同代码块,每个应用程序域被分配一块进程的虚拟内存,CLR的类型安全检查确保一个应用域不能访问另一个域的数据。14.1.1线程的基本概念属性说明ApartmentState检索或设置线程的单元状态。CurrentCulture返回指定线程的文化信息。CurrentUICulture获得或设置线程的UI文化信息。CurrentContext返回执行线程的当前上下文。CurrentPrincipal获得或设置线程的当前主体。CurrentThread检索对当前执行的线程的引用。IsAlive当线程已启动并活动时返回true。IsBackground当线程在后台运行时返回true。Name检索或指定线程的名称。Priority获得或设置线程的优先级。ThreadState返回线程的状态。14.1.2线程的优先级和执行状态在一个过程中,可以为不同的线程指定不同的优先级。一般情况下,如果有优先级较高的线程在工作,就不会给优先级低的线程分配任何时间片。高优先级的线程可以完全阻止低优先级的线程执行,所以在改变优先级时要特别小心。线程的优先级可以定义为ThreadPriorityEnumeration的值,即Highest、AboveNormal、Normal、BelowNormal和Lowest。每一个进程都有一个基本优先级,这些值与过程的优先级是有关系的。给线程指定较高的优先级,可以确保它在该过程中比同一过程中的其他线程优先执行。要注意,Window给自己的操作系统线程指定高优先级。14.1.2线程的优先级和执行状态常量说明Aborted线程已被异常中止。AbortRequested已调用Abort()方法,但线程还没中止。Background线程正在后台运行。RunningStart()方法已被调用,线程正在执行。Stopped线程已经停止。StopRequested线程已被要求停止。Suspended线程已被挂起。SuspendRequested线程正被要求挂起。Unstarted线程还没有调用Start()方法。WaitSleepjoin线程因为调用了Wait()、Sleep()或Join()方法而处于阻塞状态。14.1.3线程同步为了确保在某一时刻只有一个线程可以访问变量,出现了同步这一概念,专门用来保护变量的安全。同步问题的产生本质是因为在C#源代码中,往往看起来是一个语句,被编译为汇编语言后就会成为许多句。只要一个C#语句翻译为多个本机代码命令,线程的时间片就有可能在执行该语句的进程中终止,如果是这样,同一个过程中的另一个线程就会获得一个时间片,如果涉及到这个语句的变量访问不是同步的,那么另一个线程可能读写同一个变量,那么就会出现它访问的是新信息还是旧信息了?上面说的情况比较简单,如果在执行比较复杂的语句时,某个变量很有可能在某个较短的时间内有一个没有定义的值,如果另一个线程刚好此时要读取这个值,将只会什么也读不到。更严重的是,如果两个线程同时给一个变量写入数据,该变量在不同时刻肯定会包含不同的值。14.1.3线程同步方法说明Increment()递增指定的值,然后返回更新后的值。Decrement()递减指定的值,然后返回更新后的值。Exchange()把lacation的值设置为newValue。CompareExchange()比较dastValue和compareValue.如果两个值相等,前者用replaceValue来替换。方法说明Enter()试图获得指定对象的监控器锁。Exit()释放指定对象的监控器锁。Pulse()被持有指定对象的线程调用,以通知等待队列中的所有线程在对象状态方面的变化。TryEnter()试图在特定的条件下获取指定对象的监控器锁Wait()释放监控器锁并把调用线程放到等待队列中。14.2SDI窗体和MDI窗体应用程序VS2010支持3种不同的窗体应用程序,即SDI应用程序、MDI应用程序和给予对话框的应用程序。SDI应用程序只支持打开一个文档。而MDI应用程序每次可以读写多个文件或文档,可以同时有多个子窗口,对多个文档进行操作。14.2.1SDI窗体应用程序SDI应用程序就是处理单一文档的应用程序。通常SDI应用程序只用于完成单一的任务,涉及单一的文档。与其他两种窗体应用程序相比,涉及的操作比较多。14.2.2MDI窗体应用程序MDI(多文档界面)应用程序用于同时显示多个文档,每个文档显示在各自的窗口中。MDI应用程序中通常包含子菜单的“窗口”菜单,用于在窗口或文档之间进行切换。14.3使用各种对话框本节将向读者介绍在Windows窗体应用程序中经常出现的对话框。分别为打开文件对话框、保存文件对话框、还有关于打印的各种控件、字体和颜色对话框。14.3.1打开文件对话框OpenFileDialogOpenFileDialog组件是Window窗体应用程序中最常见的一种对话框。在各种应用程序中,这种对话框随处可见,例如在VS2010中单击工具栏上的“打开”按钮,就会弹出“打开文件”对话框。该组件OpenFileDialog继承自CommonDialog类。14.3.1打开文件对话框OpenFileDialog14.3.2保存文件对话框SaveFileDialog在现在的各种应用程序中,基本都有“保存”和“另存为”功能,两者的区别在于“保存”只有在不存在文件的时候弹出对话框,提示输入文件名;而“另存为”在任何情况下都会弹出对话框。SaveFileDialog控件是一个预先配置的对话框,它与Windows使用的标准保存文件对话框相同,都继承自CommonDialog类。SaveFileDialog控件的用法与openFileDialog控件的用法基本一样。14.3.3打印在VS2010中,提供了5种控件来支持打印。这5种打印控件分别为PrintDialog、PrintDocument、PrintPreview、PrintPreviewDialog和PrintPreviewControl。14.3.4其他对话框FontDialog和ColorDialog控件与前两小节介绍的对话框相比较为简单,主要功能是设置字体、字体的大小、颜色。打开上面小节中的项目添加“字体设置”按钮和“颜色设置”按钮以及“字体设置”控件和“颜色设置”控件。编写下面的代码使单击该按钮时出现各自的对话框。privatevoid字体ToolStripMenuItem_Click(objectsender,EventArgse){fontDialog1.ShowDialog();}privatevoid颜色ToolStripMenuItem_Click(objectsender,EventArgse){colorDialog1.ShowDialog();}14.4GDI+GDI+(GraphicsDeviceInterface)在GDI的基础上进行了改进,添加了新的功能并改进了原有的功能。其提供了大量的辅助类,可以让开发人员迅速的完成大多数通用图形任务。GDI+类拥有6个命名空间和大约共300个类和枚举。不过,没有必要在编写应用程序之前全部弄清楚它们。14.4.1GDI+使用最初,所有的图形操作都是通过一个名为DC(DeviceContext,设备语境)的GDI元素来发送的,DC负责实现设备独立性。通过设备驱动器的使用,DC可以把高层次的方法调用转化成“在某个特定的设备上画图所需要的”最低层次的指令。实现了开发人员在编写图形程序的时候,不用担心到底是哪个设备——显示器还是打印机来显示图形。GDI+中的操作几乎都是通过调用System.Drawing命名空间中的Graphics类的实例方法来实现的。Graphics类封装一个GDI+绘图图面,表示GDI+绘图表面,是用于创建图形图像的对象。绘制图形的步骤如下:(1)获取Graphics对象。(2)获取Graphics对象的一个工具。(3)调用Graphics对象的方法来画图。(4)释放Graphics对象和画图工具。14.4.2坐标系统和颜色当设计一个绘制复杂图形的程序时,最重要的是代码能够绘制出想要的图形。但是一个放错地方的像素会对图形有很大的影响,所以在执行绘图操作时,应在什么地方绘制那些像素是很重要的。GDI+的坐标系统建立在通过像素中心的假想数学直线上。这些直线从0开始,左上角的交点是X=0,Y=0(简短标记是(0,0))。用于绘图的每个窗口都有自己的坐标。在绘图的时候,常常用3种结构指定坐标,即Point、Size和Rectangle。1.使用Point2.使用Size3.使用Rectangle14.4.3绘制线条【范例14-6】可以使用Graphics类的DrawLine方法绘制直线。具体的实现如下。(1)创建Windows窗体应用程序,添加一个“直线”按钮。(2)为“直线”按钮的Click事件添加处理代码,实现画直线功能。14.4.3绘制线条14.4.4绘制图形【范例14-7】本小节接着上一小节的例子,在窗体上绘制圆,具体的实现如下。(1)打开上一小节中项目,添加“图形”按钮。(2)为“图形”按钮的Click事件添加处理代码,实现绘制图形。14.4.4绘制图形14.4.5绘制文本【范例14-8】本小节接着上一小节的例子,在窗体上绘制文本,具体的实现如下。(1)打开上一小节中项目,添加“文本”按钮。(2)为“文本”按钮的Click事件添加处理代码,实现绘制文本功能。14.4.6使用图像进行绘制【范例14-9】本小节接着上一小节的例子,在窗体上使用图形绘制,具体的实现如下。(1)打开上一小节中项目,添加“图像”按钮。(2)为“图像”按钮的Click事件添加处理代码,实现绘制图形功能。14.5小结本章介绍了Window窗体应用程序设计中的高级话题,如多线程编程、SDI和MDI应用程序、使用各种对话框以及GDI+绘图工具的使用。在多线程编程方面分别介绍了线程的基本概念、优先级、执行状态和同步。在介绍SDI和MDI应用程序的同时,也介绍了各种对话框。如打开文件对话框、保存文件对话框、打印对话框、字体设置对话框和颜色设置对话框;最后,还介绍了各种绘图工具如线条、图形、文本和图像等工具。这些内容在复杂的应用程序中会经常用到,因此,读者应该对这部分知识有一