Sender, CancelEventArgs e) { this.LoadFile(fileOpenDialog.FileName); } protected void menuFileOpen_Click(object sender, EventArgs e) { fileOpenDialog.ShowDialog(); } protected void menuFileExit_Click(object sender, EventArgs e) { this.Close(); } 下面介绍LoadFile()方法。这个方法处理文件的打开和读取操作,并确保引发Paint事件,使用新文件重新绘图: private void LoadFile(string FileName) { StreamReader sr = new StreamReader(FileName); string nextLine; documentLines.Clear(); nLines = 0; TextLineInformation nextLineInfo; while ( (nextLine = sr.ReadLine()) != null) { nextLineInfo = new TextLineInformation(); nextLineInfo.Text = nextLine; documentLines.Add(nextLineInfo); ++nLines; } sr.Close(); documentHasData = (nLines>0) ? true : false; CalculateLineWidths(); CalculateDocumentSize(); this.Text = standardTitle + \this.Invalidate(); } 这个功能的大部分都是标准的文件读取过程(参见第25章)。注意在读取文件时,给documentLines ArrayList添加文本行,使这个数组最终按顺序包含每行文本的信息。在读取文件后,设置documentHasDat标记,指定是否要显示信息。下一个任务是确定要显示内容的位置,之后确定显示文件的客户区域有多大-- 即用于设置滚动条的文档大小。最后,设置标题栏文本,并调用Invalidate()。 Invalidate()是Microsoft提供的一个非常重要的方法,所以下一节介绍这个方法的应用,之后介绍CalculateLineWidths()和CalculateDocument Size()方法的代码。
33.15.1 Invalidate()方法
Invalidate()是System.Windows.Forms.Form的一个成员,它把客户窗口区域标记为无效,因此在需要重新绘制时,它可以确保引发Paint事件。Invalidate()有两个重载方法:可以给它传送一个矩形,指定(使用页面坐标)需要重新绘制哪个窗口区域,如果不提供任何参数,它就把整个客户区域标记为无效。
如果知道需要绘制某些内容,为什么不调用OnPaint()或直接完成绘制任务的其他方法?一般情况下,最好不要直接调用绘图例程,如果代码要完成某些绘图任务,一般应调用Invalidate()。其原因如下所示:
● 绘图总是GDI+应用程序执行的一种处理器密集型的任务。在其他工作的中间进行绘图会妨碍其他工作的进行。在前面的示例中,如果在LoadFile()方法中直接调用一个方法来完成绘图,LoadFile()方法就将在绘图工作完成后才能返回。在这段时间里,应用程序不会响应其他事件。另一方面,通过调用Invalidate(),在从LoadFile返回之前,就可以让Windows引发一个Paint事件。接着Windows就可以检查等待处理的事件了。其内部的工作方式是事件被当作消息队列中一个消息。Windows会定期检查该队列,如果其中有事件,Windows就选择它,并调用相应的事件处理程序。现在Paint事件是队列中的唯一事件,所以OnPaint()会被立即调用。但是,在一个比较复杂的应用程序中,可能会有其他事件,其中一些的优先权比OnPaint()高。特别是如果用户已决定退出应用程序,该事件就会用消息WM_QUIT来标记。
● 如果有一个比较复杂的多线程应用程序,就会希望用一个线程处理所有的绘图操作。使用Invalidate()可以把所有的绘图操作传递到消息队列中,这有助于确保无论其他线程请求什么绘图操作,都由同一个线程完成所有的绘图操作(无论什么线程负责消息队列,都是由线程Application.Run()处理绘图操作)。
● 还有一个与性能有关的原因。假定在某一时刻有几个不同的屏幕绘制请求,也许代码仅能修改文档,以确保显示更新的文档,而同时用户刚刚移开另一个覆盖部分客户区域的窗口。调用Invalidate(),可以让Windows注意到发生的事件。Windows就会在需要时合并Paint事件,合并无效的区域,这样绘图操作就只执行一次。
● 最后,执行绘图的代码可能是应用程序中最复杂的代码部分,特别是当有一个比较专业化的用户界面时,就更是如此。需要长时间维护该代码的人员希望我们把所有的绘图代码都放在一个地方,且尽可能简单-- 如果程序的其他部分没有过多的路径进入该代码部分,维护就更容易。 其底线是最好把所有的绘图代码都放在OnPaint()例程中,或者在该方法中调用的其他方法。但是要维持一个平衡。如果要在屏幕上替换一个字符,最好不要影响到已经绘制好的其他内容,此时可能不需要使用Invalidate(),而只需编写一个独立的绘图例程。 注意: 在非常复杂的应用程序中,甚至可以编写一个完整的类,专门负责在屏幕上绘图。几年前MFC仍是GDI密集型应用程序的标准技术,MFC就遵循这个模式,使用一个C++类C
计算出文档大小后,就设置Form.AutoScrollMinSize属性,告诉Form实例文档有多大。完成后,后台就会发生一些有趣的事。在设置这个属性的过程中,客户区域会标记为无效,引发Paint事件。改变文档的大小就意味着需要添加或修改滚动条,需要重新绘制整个客户区域。为什么说这很有趣?如果回过头来看看LoadFile()的代码,就会发现在该方法中调用Invalidate()是多余的。在设置文档大小时,肯定要使客户区域无效。在LoadFile()方法中显式调用Invalidate(),说明了在一般情况下应如何完成任务。实际上在这个示例中,再次调用Invalidate()将重复请求Paint事件,这是不必要的。但是,这论证了前面论述的Invalidate()给Windows一个优化性能的机会。第二个Paint事件实际上并不会被引发:Windows发现队列中已经有一个Paint事件了,就会比较请求的无效区域,看看是否需要合并它们。在本例中,两个Paint事件都指定了整个客户区域,所以不需要合并,Windows会撤消第二个Paint请求。当然,这个过程会占用处理器一定的时间,但与某些绘图操作所占用的时间相比,它可以忽略不计。
33.15.3 OnPaint()
相关推荐: