包含GDI+绘图的窗体滚动

包含GDI+绘图的窗体滚动

上面例子只使用了windows系统标准控件,而下面我们将面对另一种情形。这时,我们要不使用 或不仅仅使用windows标准控件(这样做有很多理由,或许因为标准控件并不能获得用户界面需要的灵活性),要在屏幕上自行绘图。很明显,这是使用 GDI+绘图的领域(GDI+绘图包括许多相关的知识,这里不展开讲解,你可以参见专门的文章),这一领域下的文档管理与windows对标准控件的管理 有很大的不同。

1、滚动管理差异

GDI+下的文档管理与windows对标准控件的管理有很大的不同。尤其地,应用程序不能自行管理窗口的滚动,除非你在代码中进行了相应的编程。也就是说,我们需要帮助窗体Form实例确定何时以及如何滚动。

同样地,为了说明这种情形,我们新建C# windows项目TestGDIPlus。为实现定制绘图,我们重载Paint事件如下:

protected override void OnPaint(PaintEventArgs e)             {             Graphics g=e.Graphics;	//获取绘图对象             Brush b=new SolidBrush(Color.Green);//初始化画笔             g.FillEllipse(b,0,0,400,350);//绘制填充椭圆             }             //我们也设定窗体初值为矩形区间300*300:             private void InitializeComponent()             {             …             this.Size = new System.Drawing.Size(300,300);             …             }

代码很简单,以屏幕上点(0,0)为左上角起点,以长400、宽350的外接矩形绘制了一个填充 椭圆。从代码上分析,当前窗口(300*300)小于文档尺寸(400*350),应用程序初始化时应该产生滚动条,运行后改变窗体尺寸也可能产生滚动 条。但编译运行应用程序却发现事实并非如此,这两种情况下滚动条都不会出现。即便我们显式设置窗体AutoScroll属性为 true(this.AutoScroll=true),并且无论怎样拉伸以改变窗体尺寸,滚动条也不会出现。

2、为包含GDI+绘图的窗体添加滚动条

在这里,没有滚动条的原因是窗体不知道是否需要滚动条-它不知道我们自行产生的绘图区域有多大。 也就是说,系统不能自动管理这些绘图区域,除非它明确当前绘图区域的大小符合滚动条出现的条件。那么,如何确定绘图区域的大小呢?通常,绘图区域与文档区 域相一致,而在我们的示例中,文档区域是从文档的左上角(或者是在进行任何滚动前的客户区域左上角)开始向下延伸,其大小应足以包含整个文档的矩形区间。 从代码中绘制椭圆的语句可以得出,这里的文档区域应是矩形区间(400,350)。它也就是我们需要的绘图区域。

确定绘图区域后,告诉窗体文档有多大就很容易了。Form实例的 AutoScrollMinSize属性用于设置自动滚动的最小尺寸,实际使用中给它赋予绘图区域,窗体就能够知道文档的大小并管理滚动条的出现。下述代 码用于设定当前的文档区域为(400*350),这意味着当窗体小于Size(400,350)所表示的矩形区间时,系统将产生滚动条(或横向或纵向):

public Form1()             {             InitializeComponent();             this.AutoScrollMinSize=new Size(400,350);//设置自动滚动的最小尺寸             …             }

(需要指出的是,这里之所以使用AutoScrollMinSize设置自动滚动的最小尺寸,是 因为从代码中能够确定文档区域的大小,进而可确定相应屏幕区域的大小。并且,在运行应用程序的过程中,这个文档区域的大小是不会改变的。这种情况下,你能 够使用确定的数值表示滚动尺寸(象上面的代码那样),这也是很普遍的情况。但是,如果应用程序执行并不能确定文档区域的大小,如显示文件的内容的操作,或 者执行某些改变屏幕区域的操作,就需要在代码中动态设置AutoScrollMinSize属性值。)

之后,重新编译并运行应用程序,得到正确显示图形的屏幕。窗体正确设置了滚动条位置及大小,以指 定文档正确显示的比例。试着在运行样例时改变(拉伸或缩放窗体而不是使用滚动条)窗口的大小,可以发现滚动条会正确响应,甚至如果使窗口变得足够大,不再 需要滚动条时,滚动条就会消失。

3、全局变形-全局坐标到页面坐标的转换

然而,使用一下上面产生的应用程序滚动条,向下滚动它,错误出现了:窗体不能正确显示文档!

可见,仅仅设置AutoScrollMinSize是不够的。

实际上,出错原因是我们没有在OnPaint()重载方法代码中考虑滚动条的位置。看看前面绘制 椭圆的代码,它告诉Graphics实例使用窗口客户区域左上角(0,0)为起点绘制一个椭圆。Graphics实例在默认情况下把坐标解释为相对于客户 窗口(系统初始化时客户窗口的左上角与文档的左上角是一致的),它不知道滚动条的情况,而滚动条的使用改变了文档左上角的起点位置,但代码却没有为滚动条 的位置相应调整坐标,这样,错误出现了。

如果你理解.Net中的坐标系统及其转换,你就能清楚错误的原因所在。

· .Net坐标系统

GDI+ 使用三个坐标空间:全局、页面和设备。与这里有关的是全局坐标和页面坐标。其中,全局坐标(也叫世界坐标)是指要测量的点距离文档区域左上角的位置。而页面坐标是指要测量的点距离客户区域左上角的位置。

从以上定义不难看出,页面坐标空间的原点总是屏幕工作区的左上角点,这是不会变化的。而全局坐标 左上角点取决于当前文档的位置。通常,使用Graphics对象的Draw系列方法绘图默认在全局坐标空间中进行,在系统初始化时,全局坐标空间的原点也 在屏幕工作区的左上角,因此页面坐标与全局坐标相同,这时不需要转换也能正确绘图(这就是上面的示例第一次启动总能正确显示窗体的原因所在)。但是,当使 用滚动条时,窗体中文档的位置将发生改变,使文档左上角点不再与客户区域左上角点重合。这时,为正确显示文档,就需要进行转换,使之总是对应于客户区域的 左上角点。显然,这里需要将全局坐标转换为页面坐标。.Net中,把全局坐标映射到页面坐标的变形称为"全局变形"。

· 实施全局变形

从数学变换的角度来看,如果我们知道窗体滚动的距离,全局变形就很简单。Form实例的AutoScrollPosition属性正是用于获取或设置自动滚动定位的位置,通过它就能获得窗体滚动的距离。下面的语句用以获取滚动在X、Y方向的偏移量:

int xOffset=this.AutoScrollPosition.X;//获取x轴方向偏移距离             int yOffset=this.AutoScrollPosition.Y;//获取y轴方向偏移距离

这两个值就是全局坐标点转换为对应页面坐标点的横、纵偏移量。把它们与全局空间下的坐标点进行简 单的矢量加运算,就可以变换为页面空间坐标。Graphics 类提供了方法TranslateTransform来执行这个计算,我们给它传送水平和垂直坐标,表示客户区域左上角点相对于文档左上角点的坐标分量,然 后Graphics设备考虑客户区域相对于文档区域的位置,处理这些坐标。以下语句实现了这一过程:

myGraphics.TranslateTransform(xOffset,yOffset);

 

示例代码

综合上述,修改TestGDIPlust项目文件OnPaint方法如下:

protected override void OnPaint(PaintEventArgs e)             {             Graphics g=e.Graphics;	//获取绘图对象             g.TranslateTransform(this.AutoScrollPosition.X,this.AutoScrollPosition.Y);             //应用平移变换             Brush b=new SolidBrush(Color.Green);//定义画笔             Point topleft=new Point(0,0);//定义起点坐标             Size size=new Size(400,350);//构造椭圆外接矩形             Rectangle r=new Rectangle(topleft,size);//构造矩形位置及大小             g.FillEllipse(b,r);//绘制填充矩形             base.OnPaint(e);//调用基类绘图事件             }

作为对照,这里也提供另一种实现方式,它通过自行计算而不是借助于TranslateTransform方法实现坐标变换,这是这两种方式唯一的区别。

protected override void OnPaint(PaintEventArgs e)             {             Graphics g=e.Graphics;	//获取绘图对象             int xOffset=this.AutoScrollPosition.X;//获取滚动在x轴方向偏移距离             int yOffset=this.AutoScrollPosition.Y;//获取滚动在y轴方向偏移距离             Size offset=new Size(xOffset,yOffset);//构造矩形区域             Brush b=new SolidBrush(Color.Green);//定义画笔             Point topleft=new Point(0,0);//定义起点坐标             Size size=new Size(400,350);//构造椭圆外接矩形外框             Rectangle r=new Rectangle(topleft+offset,size);//平移转换定义椭圆外接矩形             g.FillEllipse(b,r);//绘制填充矩形             base.OnPaint(e);//调用基类绘图事件             }

编译并运行,应用程序正确显示屏幕图形,改变窗体尺寸使出现滚动条并拖动,文档各部分正确显示。

 

其它

需要指出的是,单就窗口滚动而言,以上代码是完全可行的了。但实际工作中还需要考虑绘图的效率,这时,以上代码还应该加以完善。

首先,应该判断文档的剪切区域以确定只在需要的时候进行图形绘制;

其次,应该将绘图辅助对象Pen、Brush等声明为类成员变量以减少初始化次数。

posted on 2012-05-02 10:13  刺客mrchenzh  阅读(376)  评论(0编辑  收藏  举报

导航