第一部分 GeoGeo脚本基础 第9章 窗口与事件驱动

9章窗口与事件驱动

GeoGeo同许多基于Windows的应用一样能够创建窗口应用,并可以使用Windows的事件驱动机制。本章讲述如何创建用户窗口以及如何响应窗口事件。

9.1建立窗口

GeoGeo可以建立8种类型的窗口,它们是:

0. FRAME类型窗口基本窗口框架,不带视口的窗口。

1. VIEW类型窗口这是一种基本的用于图形图像和文字输出的基本窗口类型。

2. SCROLLVIEW类型窗口带上下左右滚动的窗口,只要用于大范围图形图像输出以及文字输出。

3. EDITVIEW类型窗口主要用于文本编辑。

4. FORMVIEW类型窗口主要用于安放控件等的基础窗口。

5. LISTVIEW 类型窗口主要用于列表视图的管理。

6. TREEVIEW类型窗口主要用于树状视图的管理。

7. DIALOG类型窗口实现对话框功能。

9.1.1创建窗口

创建窗口使用 CreateWindow函数,该函数的原型如下:

int CreateWindow (STRING caption, int wintype);

int CreateWindow (STRING caption, STRING strwtype);

函数的第1个参数是窗口的标题,是一个字符串类型的表达式。第2个参数是指定的需要创建的窗口类型,可以是上述8种类型的其中之一,取值0~7。也可以使用上述8中类型窗口的标识串“VIEW”、“SCROLLVIEW”等。如:

int n = CreateWindow(“窗口1”,2);

或者写为

int n = CreateWindow(“窗口1”,”SCROLLVIEW”);

一个应用可以同时创建多个窗口,GeoGeo为每一个窗口赋予一个ID值用于识别不同窗口。这个函数的返回值就是该窗口的ID。保存好这个返回的ID值,后续每次的窗口操作都需要使用这个ID

函数的返回值小于等于0时表示窗口创建失败。

下面是一个创建窗口的实例代码:

程序清单 9.1  9-1-创建窗口

main(){

    int nID =CreateWindow("新建窗口",2);

}

运行上述代码,产生一个滚动窗口(图9.1)。

图 9.1 程序清单9.1输出的空白窗口

9.1.2销毁窗口

销毁窗口使用DestroyWindow函数,原型为:

int DestroyWindow(int id);

函数的参数为待销毁窗口的ID,函数成功时返回1,失败返回0

将上述程序清单稍加改造,创建窗口后休眠2秒,然后销毁

程序清单 9.2 创建窗口0.c

main(){

    int nID =CreateWindow("新建窗口",2);

    Sleep(2000);

    DestroyWindow(nID);

}

运行上述代码,可见创建窗口2秒后,窗口消失。

9.1.3移动和设置窗口

GeoGeo所指的窗口是一个泛指的概念,实际上除了有真正的“窗口”和对话框的区别外,窗口又分为不同的类别,如本章开头介绍的窗口类别。再细分,窗口还包括框架(Frame)和视口(View)两部分。框架除了收纳视口以外还可以收纳一些其他的元素,如菜单、工具条等。而视口主要是用来容纳和显示图像图像以及文本等数据。

    9.1.3.1 移动窗口

移动窗口是对窗口框架的移动,框架移动了,其中所包含的元素也随之一起移动。使用MoveWindow函数移动窗口。

int MoveWindow(int id,int x,int y,int width,int height);

函数的第1个参数是创建窗口时返回的 ID值,第23个参数是窗口左上角的坐标位置,为桌面的像素位置,第45个参数是窗口的宽度和高度,单位是像元。

    9.1.3.2 获得窗口位置

获得窗口位置使用GetWindowRect函数。

int[] GetWindowRect(int winID);

函数的参数是窗口的 ID值,返回值为1个有4个元素的1维数组,第1个元素为窗口左上角的x坐标,第2个元素为y坐标,第3个元素为窗口的宽度,第4个元素为窗口的高度。

    9.1.3.3 设置窗口框架

设置窗口简单地将窗口设置成无边框或者缺省窗口状态,使用ModifyStyle函数来完成。

int ModifyStyle(int id,int flag,int pos = 0);

函数的第1个参数是创建窗口时返回的 ID值;第2个参数是窗口边框的指示,0表示将窗口设置为无边框窗口;第3个参数是窗口无边框时窗口上部能够拖动窗口区域的高度,可以不设置,当窗口为对话框时这个参数无效,整个窗口区域都可以拖动窗口。

下面是一个设置和移动窗口的示例

程序清单 9.3 更改窗口位置和式样0.c

main(){

     int i;

     int nWinID = CreateWindow("新建窗口——VIEW窗口",1);

     Sleep(2000);

     for(i=0;i<10; i = i+1){

         MoveWindow(nWinID,i*60,i*40,1200,600);

         Sleep(500);

     }

     ModifyStyle(nWinID,0,20);

}

运行上述代码,可见开始时创建一个窗口,休眠2秒后,窗口从左上角向右下角依次移动10次,每次间隔半秒。然后窗口变为无边框状态,将光标放在窗口上沿拖动鼠标,可见窗口随鼠标一起移动,将光标放在窗口其它部位拖动鼠标则窗口不随之移动。这时因为在ModifyStyle函数指定了距窗口上沿20个像元的区域为拖动区域。

拖动区域不影响窗口显示和操作。

9.1.4移动和获得视口位置

    9.1.4.1 移动视口

不设置和移动视口时,视口通常占满了整个框架的客户区,如果需要在框架中留出部分区域放置空间或者标记一些其它应用可以对视口进行重新布局。

使用MoveView函数设置视口的位置

int MoveView(int id,int x1,int y1,int x2,int y2, int flag);

函数的第1个参数是创建窗口时返回的 ID值;第2个参数是距框架客户区左边界的距离(像元数),第345个参数分别是距框架客户区上、右、下边界距离的像元数。

最后一个参数是指示是否保留View的下沉的边框的标识,指定为1时保留下沉边框(如图9.2),0(或者不指定)时不保留该边框,此时FrameView看不出边界。

下面代码设置一个框架中View的位置

程序清单 9.4  9-4-移动视口

main(){

    int nWinID =CreateWindow("新建窗口——VIEW窗口",3);

    MoveView(nWinID,200,20,20,20,1);

}

未设置视口时(缺省状态)窗口外观如图9.1,运行上述代码后窗口如图9.2。视口距框架客户区左、上、右、下各边界的距离像元数各自为200202020像元。

图 9.2 移动视口输出的空白窗口

    9.1.4.2 获得视口位置

获得视口位置使用GetViewPosition函数。

int[] GetViewPosition(int winID);

函数的参数为窗口ID,返回值为1个有2个元素的1维数组,第1个元素为视口左上角的x坐标,第2个元素为y坐标。

9.2窗口的事件函数

9.2.1窗口事件

窗口事件这里分为窗口内建事件、菜单命令事件、控件响应事件和消息驱动事件。

窗口内建事件包括鼠标键盘事件、窗口移动和变化等事件。例如,用户在某窗口按下了鼠标左键,这时就产生一个WM_LBUTTONDOWN消息,GeoGeo就开始寻找是否有事件响应函数OnLButtonDown存在,如果存在这个函数,则执行它,否则忽略这个事件的消息。

这类事件的一个重要特点是,事件发送的消息是固定的,因此为事件定义了固定名称的事件函数,如上述的为鼠标左键按下定义的OnLButtonDown函数。同样如果窗口尺寸发生了变化,有对应的OnSize函数,等等。GeoGeo定义的窗口内建事件函数参见相应章节。

菜单命令事件是由点击菜单项产生的。菜单是由用户任意添加的,每添加一个菜单项为该菜单项产生一个菜单ID。由于无法预先知道用户添加什么样的菜单,所以也无法设置固定的菜单响应函数。为这类事件添加事件响应函数的方法是在设置菜单时指定一个事件响应函数名。点击菜单项时根据这个函数名查找对应的事件函数。

控件响应事件的情况稍复杂一些。每当为窗口添加一个控件时,该控件可以出发多种事件。例如按钮点击、得到或者失去焦点、编辑内容变化等。这样会为每一个控件添加多个事件响应函数。GeoGeo设计了一系列的Tie-To函数,将每一个控件事件与事件响应函数关联起来。

消息事件是由用户程序发送与定义消息给指定窗口,然后由消息事件函数响应。

9.2.2事件函数

GeoGeo使用单独一类函数来响应事件。这些函数由关键字 event开始,然后是函数名和参数表。如鼠标左键按下的响应函数如下。

event OnLButtonDown(int nID,int x, int y,int nFlag){

       …

}

event关键字之后的OnLButtonDown是函数名,参数表中的第一个参数int nID是窗口ID,用来说明该消息来自哪个窗口。第2个参数是鼠标点击时窗口中视口的x坐标,第3个参数是y坐标。第4个参数是状态指示。再接下来是函数体。

一些事件函数的参数表不可以随便写,个数、类型和次序均有固定要求,使用时请参阅相关技术说明。

除了参数表不可以任意书写外,事件函数与线程函数非常相似。事件函数的参数是传值而不是引用,不能向调用函数回传。事件函数没有返回值。

另一个值得注意的是事件函数以单独的线程运行。使用时需注意这些线程的执行顺序。

9.2.3禁止和允许事件函数

事件函数以单独线程启动,就有和主线程或子线程间竞争。GeoGeo设计和线程函数类似的被挂起和唤醒。不同的是事件函数被挂起并非是已经启动了之后被挂起,而是事先阻塞了事件消息,这里称之为事件函数的禁止。同样,唤醒事件函数也只是解除了事件消息的阻塞,称之为允许。

禁止事件函数使用SuspendEvent函数。

int SuspendEvent(STRINT name);

函数的参数为被禁止事件函数的函数名。

重新允许事件函数使用ResumeEvent函数。

int ResumeEvent(STRINT name);

函数的参数为重新允许事件函数的函数名。

缺省时事件函数处于被允许状态。

9.3为窗口添加菜单

9.3.1创建菜单

菜单由一个个的菜单项组成,直接显示在窗口菜单栏内的菜单项为根菜单的菜单项。每一个菜单项又分为两种:一种是弹出下一级菜单的菜单项,另一种是直接发送菜单命令的菜单项。

创建两种菜单项都使用相同的函数:

int AddMenu(int id, STRING fatherName, STRING name, STRING eFuncName);

该函数的第1个参数是窗口ID,指定希望创建菜单的窗口;第2个参数是父菜单项名称,如果这个参数是一个字符长度为0的空串,则该菜单项创建在根菜单上。如果该串不为空,则必须是一个弹出菜单项;第3个参数是本次创建菜单项的名称,如果没有第4个参数,这个菜单项就是一个弹出菜单项,如果有第4个参数,则这个菜单项是一个命令菜单项;第4个参数(如果有)指定响应该菜单项的事件函数的函数名。例如:

AddMenu(1, “”, “File”);

ID1的窗口的根菜单建立一个名为“File”的弹出菜单项。而

AddMenu(1, “File”,”Open”,”OnFileOpen”);

在上述建立的弹出菜单“File”下建立一个名为“Open”的命令菜单项。菜单命令的事件响应函数为“OnFileOpen”。

下述代码演示了如何创建一个窗口菜单

程序清单 9.5  9-5-窗口添加菜单

main(){

    int nWinID;

    nWinID = CreateWindow("新建窗口",1);

    Sleep(1000);

    AddMenu(nWinID,"","File");

    AddMenu(nWinID,"File","二级菜单");

    AddMenu(nWinID,"二级菜单","三级菜单");

    AddMenu(nWinID,"三级菜单","Click Me","OnClickMe");

    AddMenu(nWinID,"","Edit","OnEdit");

}

 

图 9.3 为窗口添加菜单

注意:弹出菜单的菜单项是通过菜单名称检索的,因此,一个窗口内的弹出菜单项不要重名。

9.3.2添加菜单命令响应函数

在程序清单9.5

AddMenu(nWinID,"三级菜单","Click Me","OnClickMe");

一行中建立了一个名为“Click Me”的菜单项,并且指定了其事件函数名为“OnClickMe”。在上述代码后面增加如下代码

event OnClickMe(int nID){

    STRING str;

    Format(str,"ID %d的窗口响应的菜单命令 Click Me",nID);

    MessageBox(str);

}

将这些代码与程序清单 9.5放在一起运行,点击“Click Me”,此时运行OnClickMe事件函数,出现图9.3的运行结果。

图 9.3 为窗口添加菜单

注意:所有的菜单命令事件函数均带有一个参数,是发送该菜单命令窗口的ID值。这样才会使用户程序知道究竟是哪个窗口发送来的菜单命令。

9.4内建事件函数

内建事件函数这里指的是一些固定函数名和函数参数的事件函数

9.4.1 OnDraw函数

OnDraw函数是图形GDI的一个重要函数,窗口移动、遮挡和重画时都要调用该函数。当用户对图形图像数据更新后需要重画时,也可以通过调用UpdateWindow函数强制重画调用该函数。图形窗口的绘图等工作推荐在该函数中完成。

event OnDraw(int id);

OnDraw函数由event关键字开始,是1个事件函数,它只有1个参数,用于通知用户需要重画的是哪个窗口。下面的示例代码创建2个窗口,第1个窗口画一条左上角到右下角的直线,第2个窗口画一条右上角到左下角的直线。

程序清单 9.6  9-6-eventOnDraw.c

int id[2];

main(){

    id[0] = CreateWindow("新建窗口——窗口1",1);

    id[1] = CreateWindow("新建窗口——窗口2",1);

    UpdateWindow(id[0]);

    UpdateWindow(id[1]);

}

event OnDraw(int nID){

    int rc[4];rc =GetViewRect(nID);

    if( nID == id[0]){

       DrawLine(nID,rc[0],rc[1],rc[0]+rc[2],rc[1]+rc[3]);

    }

    if( nID == id[1]){

       DrawLine(nID,rc[0]+rc[2],rc[1],rc[0],rc[1]+rc[3]);

    }

}

由上述代码可见,main函数仅创建了2个窗口并用UpdateWindow函数进行了刷新,刷新动作发送窗口重画消息,从而触发OnDrwa事件函数。不使用UpdateWindow函数强制重画只有在窗口变化需要重画时才能更新显示。

OnDrwa事件函数首先用GetViewRect函数取来窗口的视口区域,返回到一个有4个元素的数组中,其中第12个元素是视口的左上角坐标,第34个元素是视口的宽度和高度。

OnDraw函数根据窗口ID在不同的窗口上使用DrawLine函数画一条直线。

运行上述代码,结果如图9.4

图 9.4 OnDraw函数在不同窗口的输出

注意:OnDraw函数的参数,也就是下传的窗口ID在窗口创建时可能不正确(0值)。产生这种现象的原因是,窗口创建时发出重画窗口的消息,该消息马上启动一个新的线程刷新窗口。与此同时,窗口创建后顺序进行窗口注册登记等系列初始化过程。如果刷新窗口的线程抢在窗口初始化和登记之前进了重画,这样就得不到正确的窗口ID

尽管如此,GeoGeo仍然将OnDraw函数保留为线程函数而不是普通函数,这是考虑到大量地理数据重绘需要较多的处理成本,在线程函数中重绘会有更高的效率。

该问题的解决办法是,主线程创建窗口等工作完成前先禁止OnDraw事件函数,创建窗口后允许事件函数并用UpdateWindow函数刷新窗口。

熟悉VC++/MFC窗口机制的读者知道,MFC中每个窗口都有自己的OnDraw/OnPaint之类的绘图函数,即这些绘图类函数是由窗口管理的。GeoGeo中所有的窗口使用同一个OnDraw函数,有不同的管理和调度机制。后面介绍的一些内建事件函数与此相同。

9.4.2鼠标点击事件函数

鼠标点击类事件函数包括如下:

event OnLButtonDown(int nID,int x, int y,int nFlag);

event OnLButtonUp(int nID,int x, int y,int nFlag);

event OnLButtonDblClk (int nID,int x, int y,int nFlag);

event OnRButtonDown(int nID,int x, int y,int nFlag);

event OnRButtonUp(int nID,int x, int y,int nFlag);

event OnRButtonDblClk (int nID,int x, int y,int nFlag);

event OnMButtonDown(int nID,int x, int y,int nFlag);

event OnMButtonUp(int nID,int x, int y,int nFlag);

event OnMButtonDblClk (int nID,int x, int y,int nFlag);

9个函数分别是鼠标左键按下抬起双击、右键按下抬起双击和中键按下抬起双击。这些函数函数名也是固定不变的,并且有同样的参数表。

函数的第1个参数是窗口ID,标明这个鼠标点击事件发生的窗口;第23个参数是鼠标点击窗口位置的xy坐标。第4个参数是一个状态标志,用于指示虚拟建按下,可以是下列值的:

1 键盘SHIFT键按下

2 键盘CONTROL键按下

3 键盘SHIFT键和CONTROL键同时按下

下面以鼠标左键按下为例演示鼠标点击类事件函数的应用

程序清单 9.7  9-7-event鼠标点击.c

int nWindowID,X,Y;

int prex = 0;

int prey = 0;

main(){

     nWindowID = CreateWindow("新建窗口",1);

}

event OnDraw(int nID){

     if(nID == nWindowID){

         DrawLine(nWindowID,prex,prey,X,Y);

         prex = X;

         prey = Y;

     }

}

event OnLButtonDown(int nID,int x,int y,int nFlag){

     STRING str;

     nWindowID = nID;

     X = x;

     Y = y;

     if( 1 == nFlag ){

         Format( str,"SHIFT键按下,x=%d, y=%d",X,Y);

     }

     else{

         if( 2 == nFlag){

              Format( str,"CTRL键按下,x=%d, y=%d",X,Y);

         }

         else {

              if( 3 == nFlag){

                   Format( str,"SHIFT+CTRL键按下,x=%d, y=%d",X,Y);

              }

              else {

                   Format( str,"x=%d, y=%d",X,Y);

              }

         }

     }

     TextOut(nID,x,y,str);

     UpdateWindow(nID);

}

上述代码主函数只有1行,创建一个窗口。另外有2个事件响应函数。第1个事件函数是OnDraw函数,第2个函数是OnLButtonDown。运行上述代码,在窗口内分别按住SHIFT键和CTRL键点击鼠标,运行结果如图9.5

图 9.5 鼠标点击事件函数的输出

9.4.3鼠标移动事件函数

鼠标移动事件使用OnMouseMove函数,原型如下

event OnMouseMove(int nID,int x, int y,int nFlag);

OnMouseMove函数的参数与鼠标点击事件函数的参数一样,下面的代码将鼠标移动的位置显示在新建窗口的左上角

程序清单 9.8   9-8-event鼠标移动.c

main(){

    CreateWindow("新建窗口1",1);

    CreateWindow("新建窗口2",1);

}

event OnMouseMove(int nID,int x,int y,int nFlag){

    STRING str;             

    Format( str,"x=%d, y=%d",x,y);

    TextOut(nID,0,0,"                                   ");

    TextOut(nID,0,0,str);

}

上面代码创建2个窗口,鼠标移动到哪个窗口时就在哪个窗口的左上角显示出鼠标的位置,见图9.6

图 9.6 鼠标移动事件函数的输出

9.4.4鼠标滚轮事件函数

鼠标移动事件使用OnMouseWheel函数,原型如下

event OnMouseWheel(int nID,int x, int y,int nFlag,int zDelta);

函数的前4个参数与鼠标点击事件函数相同,最后一个参数是用于指示滚轮的滚动方向和距离的参数。大于0时表示滚轮向前滚动,小于0时表示滚轮向后滚动。值的大小表示滚动距离。

下面是示范鼠标滚轮的示例代码

程序清单 9.9   9-9-event鼠标滚轮.c

main(){

    CreateWindow("新建窗口",1);

}

event OnMouseWheel(int nID,int x,int y,int nFlag,int zDelta){

    if( zDelta > 0){

       TextOut(nID,0,0,"滚轮向前滚动");

    }

    else{

       TextOut(nID,0,0,"滚轮向后滚动");

    }

}

运行上述代码,在窗口中分别将鼠标滚轮向前和向后滚动,观察窗口左上角的输出。

9.4.5键盘事件函数

键盘事件函数包括字符输入的响应函数

event OnChar(int nID,int nChar, int nRepCnt,int nFlag);

键盘按键按下的响应函数

event OnKeyDown(int nID,int nChar, int nRepCnt,int nFlag);

和按键抬起的响应函数

event OnKeyUp(int nID,int nChar, int nRepCnt,int nFlag);

函数的第1个参数是窗口ID,第2个参数是按键输入的字符,第3个参数是重复次数,表示一个键按住不动输入字符的个数。第4个参数表示键盘扫描码、转换码等附加信息。

请看下面代码

程序清单 9.10   9-10-event按键.c

int line = 0;

main(){

    CreateWindow("新建窗口",1);

}

event OnChar(int nID,int nChar,int nRepCnt,int nFlag){

    STRING str;

    Format(str,"输入字符为:%c",nChar);

    TextOut(nID,0,line*16,str);

    line = line+1;

}

该程序主函数只有1行,创建一个窗口。OnChar函数响应键盘输入,每输入一个字符,显示位置向下移动16个像元高度。输出结果见图9.7

图 9.7 键盘输入事件函数的输出

9.4.6窗口获得和失去焦点事件函数

窗口获得和失去焦点事件函数如下:

event OnSetFocus(int nID)

event OnKillFocus(int nID)

函数只有窗口ID 1个参数,窗口得到或失去焦点时,用户在这两个函数中添加代码进行自己需要的处理。

下面是示例窗口得到和失去焦点的代码

程序清单 9.11   9-11-event窗口焦点.c

main(){

    CreateWindow("新建窗口1",1);

    CreateWindow("新建窗口2",1);

}

event OnSetFocus(int nID){

    TextOut(nID,0,0,"窗口获得焦点");

}

event OnKillFocus(int nID){

    TextOut(nID,0,0,"窗口失去焦点");

}

这几行代码创建两个窗口,运行代码,轮番点击两个窗口,被点击的窗口得到焦点,另一个窗口就失去焦点,图9.8

图 9.8 得到和失去焦点事件函数的输出

9.4.7窗口移动和改变事件函数

窗口移动事件函数为

event OnMove(int nID,int cx,int cy);

函数的第1个参数为出发窗口移动事件的窗口ID;后面两个参数是窗口左上角坐标的xy位置。

窗口大小变化的事件函数为

event OnSize(int nID,int x,int y,int flag);

函数的第1个参数为出发窗口移动事件的窗口ID;第23个参数是新窗口的宽度和高度;第4个参数是窗口最大最小标志,1表示窗口已经最小化,2表示窗口最大化,0位恢复原窗口大小。

下面的代码演示了窗口移动和大小变化

程序清单 9.12   9-12-event移动窗口.c

int id[2];

int pos[4] = {100,100,400,300};

main(){

    id[0] = CreateWindow("新建窗口——窗口1",1);

    id[1] = CreateWindow("新建窗口——窗口2",1);

    MoveWindow(id[0],pos[0],pos[1],pos[2],pos[3]);

    MoveWindow(id[1],pos[0]+pos[2],pos[1],pos[2],pos[3]);

}

event OnMove(int nID,int x,int y){

    if( nID == id[0]){

       pos = GetWindowRect(nID);

       MoveWindow(id[1],pos[0]+pos[2],pos[1],pos[2],pos[3]);

       TextOut(id[1],0,0,"跟随窗口一起移动");

    }

}

event OnSize(int nID,int x,int y,int flag){

    if( nID == id[0]){

       pos = GetWindowRect(nID);

       MoveWindow(id[1],pos[0]+pos[2],pos[1],pos[2],pos[3]);

       TextOut(id[1],0,0,"跟随窗口变化大小");

    }

}

上述代码main函数创建2个窗口,然后定义了一个OnMove函数和一个OnSize函数。当有窗口移动时,触发OnMove函数。在这个函数中,如果移动的是第2个窗口,就什么也不做,如果移动的是第1个窗口,则将第2个窗口移动到到第一个窗口的右侧。这看起来好像第2个窗口粘在第一个窗口旁边一样,一起移动。

当窗口大小发生变化时,触发OnSize函数。在这个函数中,如果第2个窗口大小发生变化,也是不做任何事情。当第1个窗口大小发生变化时,同时将第2个窗口设置成同样大小,移动到第1个窗口的右侧。

图 9.9 两个一起移动和同样变化大小的窗口

9.4.8窗口销毁事件函数

销毁窗口时触发下述事件函数

event OnDestroy(int nID);

该函数只有1个参数,指示已经销毁窗口的窗口ID。用户可以在这里对销毁的窗口进行一些后续清理工作。

9.5控件

可以在窗口添加一些控件使用户的交互操作更加便利。GeoGeo控件可以通过代码方便地实现。

9.5.1静态文本控件

  9.5.1.1 设置控件字体

设置控件字体使用SetCtrlFont函数

int SetCtrlFont(int id, int fHeight,STRING name, int etl, int escp , int fWeight );

函数的第一个参数是控件所在窗口的ID;第2个参数是字体的高度;第3个参数是是否为斜体,1为斜体,0为正体;第4个参数是字基线倾斜,正值基线向有上倾斜,负值基线向右下倾斜,值的大小是倾斜角度乘10;最后一个参数是字重,取值在0~1000,值越大笔画越粗。

  9.5.1.2 创建静态文本控件

创建静态文本控件使用CreateStaticCtrl函数

int CreateStaticCtrl(int id, STRING text, int x, int y, int width, int height);

函数的第一个参数是控件所在窗口的ID;第2个参数是控件文本;第34个参数是控件左上角坐标;第56个参数是控件的宽度和高度。

下述代码设置控件字体,并创建一个静态文本控件。

程序清单 9.13   9-13-创建静态文本控件.c

main(){

    int id =CreateWindow("新建窗口",1);

    SetCtrlFont(id,24,"",0,0,1000);

    CreateStaticCtrl(id,"静态文本 Static Text",20,20,400,140);

}

设置字号高24,字体使用缺省值,正体字,基线不倾斜,字重200。创建静态文本如图9.9

图 9.9 不同字体的静态文本控件 a)上述代码输出 b)字重900  c)斜体  d)基线倾斜 -60

9.5.2按钮控件

  9.5.2.1 创建按钮控件

创建按钮控件使用CreateButtonCtrl函数

int CreateButtonCtrl(int winID, STRING text, int x, int y, int width, int height);

参数int winID为控件所在窗口IDSTRING text为文本控件的文本;第34个参数是按钮控件左上角的坐标位置;第56个参数是按钮控件的宽度和高度。

创建按钮控件的函数如果成功返回控件的ID,否则返回值为0

  9.5.2.2 为按钮控件添加点击事件响应函数

控件被点击时通常有控件点击事件的响应函数,可以使用Tie_BN_CLICKED_To函数为按钮控件添加事件响应函数

int Tie_BN_CLICKED_To(int winID,int ctrlID,STRING funcName);

函数的第1个参数是控件所在窗口的ID;第2个参数是控件ID;第3个参数是按钮点击时的事件响应函数的函数名。函数成功时返回1,否则返回0

  9.5.2.3 按钮点击事件响应函数

按钮事件响应函数的函数名由Tie_BN_CLICKED_To函数指定,如:

event OnBNClicked(int winID,int bnID);

其中,函数名OnBNClickedTie_BN_CLICKED_To函数的最后一个参数。函数通常有2个参数,第1个参数是控件所在窗口的ID,第2个参数是控件的ID

下面的例子创建6个按钮,并在按钮事件响应函数中输出哪个窗口的哪个按钮被按下

程序清单 9.14   9-14-创建按钮控件.c

1 main(){

2    int i;

3    int winID[2];

4    int ctrlID[2][6];

5    STRING str;

6    winID[0]  = CreateWindow("新建窗口1",1);

7    winID[1]  = CreateWindow("新建窗口2",2);

8    for( i=0; i<6; i=i+1)  {

9        Format(str,"Button %d",i+1);

10       ctrlID[0][i] = CreateButtonCtrl(winID[0],str,i*110,0,100,36);

11       ctrlID[1][i] = CreateButtonCtrl(winID[1],str,i*110,0,100,36);

12       Tie_BN_CLICKED_To(winID[0],ctrlID[0][i],"OnBNClicked");

13       Tie_BN_CLICKED_To(winID[1],ctrlID[1][i],"OnBNClicked");

14   }

15 }

16 event OnBNClicked(int winID,int bnID){

17   Print("窗口ID为%d ,按钮ID 为%d 的按钮按下",winID,bnID);

18 }

上述代码第67行创建两个窗口,在8~14行的循环中,1011行分别在两个窗口创建了6个按钮,并在1213行中为所有按钮指定了一个共用的事件响应函数,也可以为不同的按钮指定不同的事件响应函数。

16~18行定义了事件响应函数,根据参数中的窗口ID和按钮ID,来确定点击了哪个窗口的哪个按钮。

代码的运行结果如图9.10

图 9.10 创建按钮控件

在上述的两个窗口中,依次点击第1个窗口的“Button 1”、第2个窗口的“Button 1”、第1个窗口的“Button 2”和第2个窗口的“Button 2”。输出如下:

窗口ID1,按钮ID1的按钮按下

窗口ID2,按钮ID2的按钮按下

窗口ID1,按钮ID3的按钮按下

窗口ID2,按钮ID4的按钮按下

注意按钮ID是按照创建次序排列的,即无论在哪个窗口创建的按钮控件,其ID都顺序统一排列。应用上,用户可以根据创建按钮时返回的ID与按钮事件响应函数参数的ID进行比较来确定是否自己需要的按钮而无需关心具体的ID值到底是多少。

也可以通过Tie_BN_CLICKED_To函数为不同的按钮指定不同的事件响应函数。

9.5.3编辑控件

  9.5.3.1 创建编辑控件

创建编辑控件使用CreateEditCtrl函数

int CreateEditCtrl(int winID, int x, int y, int width, int height);

参数int winID为控件所在窗口ID;第23个参数是按钮控件左上角的坐标位置;第45个参数是按钮控件的宽度和高度。

创建编辑控件的函数如果成功返回控件的ID,否则返回值为0

  9.5.3.2 为编辑控件添加失去焦点事件响应函数

为编辑控件添加失去焦点事件响应函数的函数为

int Tie_EN_KILLFOCUS_To(int winID,int ctrlID,STRING funcName);

函数的第1个参数是控件所在窗口的ID;第2个参数是控件ID;第3个参数是控件失去焦点的事件响应函数的函数名。函数成功时返回1,否则返回0

  9.5.3.3 为编辑控件添加编辑变化事件响应函数

为编辑控件添加编辑变化事件响应函数的函数为

int Tie_EN_CHANGE_To(int winID,int ctrlID,STRING funcName);

函数的第1个参数是控件所在窗口的ID;第2个参数是控件ID;第3个参数是编辑框中的内容发生变化时的事件响应函数的函数名。函数成功时返回1,否则返回0

  9.5.3.4 失去焦点事件响应函数

编辑框失去焦点的事件响应函数的函数名由Tie_EN_KILLFOCUS_To函数指定,如:

event OnEditKillFocus(int winID,int bnID,STRING text);

其中,函数名OnEditKillFocusTie_EN_KILLFOCUS_To函数的最后一个参数。函数通常有3个参数,第1个参数是控件所在窗口的ID;第2个参数是控件的ID;第3个参数是编辑框的文本内容。

  9.5.3.5 编辑框内容变化事件响应函数

编辑框内容变化时的事件响应函数的函数名由Tie_EN_CHANGE_To函数指定,如:

event OnEditChange(int winID,int bnID,STRING text);

其中,函数名OnEditChangeTie_EN_CHANGE_To函数的最后一个参数。函数通常有3个参数,第1个参数是控件所在窗口的ID;第2个参数是控件的ID;第3个参数是编辑框的文本内容。

演示编辑框的示例代码见程序清单9.17

程序清单 9.15   9-15-创建编辑控件.c

main(){

    int nCtrlID[2];

    int nWinID =CreateWindow("新建窗口",1);

    nCtrlID[0] = CreateEditCtrl(nWinID,20,20,300,30);

    Tie_EN_KILLFOCUS_To(nWinID,nCtrlID[0],"OnEditKillFocus");

    Tie_EN_CHANGE_To(nWinID,nCtrlID[0],"OnEditChange");

    nCtrlID[1] = CreateEditCtrl(nWinID,20,60,300,30);

    Tie_EN_KILLFOCUS_To(nWinID,nCtrlID[1],"OnEditKillFocus");

    Tie_EN_CHANGE_To(nWinID,nCtrlID[1],"OnEditChange");

}

event OnEditKillFocus(int winID,int ctrlID,STRING str){

    Print("窗口 ID %d 编辑框 ID %d失去焦点编辑框文本内容:%s",winID,ctrlID,str);

}

event OnEditChange(int winID,int ctrlID,STRING str){

    Print("窗口 ID %d 编辑框 ID %d内容更改事件编辑框文本内容:%s",winID,ctrlID,str);

}

 

图 9.11 创建编辑框控件

9.5.4组合框控件

  9.5.4.1 创建组合框控件

创建组合框控件使用CreateComboCtrl函数

int CreateComboCtrl(int id, int x,int y, int width, int height );

函数的第1个参数是控件所在窗口的ID;第23个参数是控件左上角坐标;第4个参数是控件的宽度;第5个参数是控件的高度,这个高度是下拉列表的高度。

函数的返回值在创建控件成功时返回控件ID,否则返回0

  9.5.4.2 为组合框控件添加列表项

控件创建成功后还要向其列表添加列表项,这样在点击控件的下拉按钮时才能列出所有的内容。使用AddComboString函数向组合框控件逐条添加列表项。函数如下

int AddComboString(int winID, int ctrlID, STRING text);

函数的第1个参数是控件所在窗口的ID;第2个参数是需要添加文本内容的组合框控件ID;第3个参数是需要添加的列表项文本。

函数成功时返回1,失败时返回0

  9.5.4.3 为组合框控件添加失去焦点响应函数

为组合框控件添加失去焦点事件响应函数的函数为

int Tie_CBN_KILLFOCUS_To(int winID,int ctrlID,STRING funcName);

函数的第1个参数是控件所在窗口的ID;第2个参数是控件ID;第3个参数是控件失去焦点的事件响应函数的函数名。

函数成功时返回1,否则返回0

  9.5.4.4 为组合框控件添加选择变化事件响应函数

为组合框控件添加选择变化事件响应函数的函数为

int Tie_CBN_SELCHANGE_To(int winID,int ctrlID,STRING funcName);

函数的第1个参数是控件所在窗口的ID;第2个参数是控件ID;第3个参数是控件选择变化的事件响应函数的函数名。

函数成功时返回1,否则返回0

  9.5.4.5 失去焦点事件响应函数

组合框失去焦点的事件响应函数的函数名由Tie_CBN_KILLFOCUS_To函数指定。例如在Tie_CBN_KILLFOCUS_To函数中

Tie_CBN_KILLFOCUS_To(1,1,” OnComboKillFocus”);

指定的失去焦点事件的响应函数的函数名为OnComboKillFocus。则组合框失去焦点事件响应函数可写为:

event OnComboKillFocus(int winID,int bnID,int sel,STRING text){

       …

}

其中,函数名OnComboKillFocusTie_CBN_KILLFOCUS_To函数的最后一个参数。

失去焦点事件响应函数通常有3个参数,第1个参数是控件所在窗口的ID;第2个参数是控件的ID;第3个参数是选择的列表项,注意这个值是从0开始的,如果值为0表示是第1个列表项被选中;第4个参数是组合框当前选择的文本内容。

  9.5.4.6 选择变化事件响应函数

组合框选择变化事件响应函数的函数名由Tie_CBN_SELCHANGE_To函数指定。例如在Tie_CBN_SELCHANGE_To函数中

Tie_CBN_SELCHANGE_To(1,1,” OnComboSelChange”);

指定的选择变化事件的响应函数的函数名为OnComboKillFocus。则组合框失去焦点事件响应函数可写为:

event OnComboSelChange(int winID,int bnID,int sel, STRING text){

       …

}

其中,函数名OnComboSelChangeTie_CBN_SELCHANGE_To函数的最后一个参数。

选择变化事件响应函数通常有3个参数,第1个参数是控件所在窗口的ID;第2个参数是控件的ID;第3个参数是选择的列表项,是从0开始的,也就是说如果值为0表示是第1个列表项被选中;第4个参数是组合框当前选择的文本内容。

  9.5.4.7 组合框示例代码

下述代码演示创建组合框、向组合框添加列表项以及如何选择组合框的内容

程序清单 9.16   9-16-创建组合框控件.c

1 main(){

2    int i;

3    STRING str;

4    STRING strArray[5] = {11111,22222,33333,55555,44444};

5    int nWinID = CreateWindow("新建窗口",7);

6    int nCtrlID = CreateComboCtrl(nWinID,20,20,200,160);

7    for(i=0; i<5; i=i+1){

8        AddComboString(nWinID,nCtrlID,strArray[i]);

9    }

10   Tie_CBN_KILLFOCUS_To(nWinID,nCtrlID,"OnComboKillFocus");

11   Tie_CBN_SELCHANGE_To(nWinID,nCtrlID,"OnComboSelChange");

12 }

13 event OnComboKillFocus(int winID,int ctrlID,int nSel,STRING str){

14   Print("失去焦点:窗口ID=%d,组合框ID=%d,选项%d,组合框文本:%s",winID,ctrlID,nSel,str);

15 }

16 event OnComboSelChange(int winID,int ctrlID,int nSel,STRING str){

17   Print("变更选择:窗口ID=%d,组合框ID=%d,选项%d,组合框文本:%s",winID,ctrlID,nSel,str);

18 }

代码在第5行创建一个新窗口;第6行在这个新窗口中创建一个组合框;第8行循环向组合框添加5条列表内容;第10行指定了一个失去焦点时的事件响应函数,并在第13~15行定义了该函数;第11行指定了一个选择变化事件响应函数,在第16~18行进行了定义。

运行上述代码,结果如图9.12

图 9.12 创建组合框控件

在组合框中进行不同的选择,结果见图19.13

图 9.13 组合框控件的输出

9.6图形图像

9.6.1窗口的绘图区

9.1节讲述了如何建立、移动和设置窗口,这里进一步讨论在窗口中绘图的区域。除对话框窗口(DIALOG)类型外,窗口通常有2层,底层是窗口框架(Frame),上层是视(View)。窗口框架有自己的用户区域(客户区),用户可以在其上绘图。视也有自己的绘图区(客户区)用户的输出大多在视口中。

通常视口占据了全部的窗口客户区,窗口区域全部被遮挡所以也无所谓在窗口客户区的绘图。但经常需要视口占据窗口客户区的一部分(图9.2),而需要在窗口输出。

这样就可以简单地归结为有2个绘图区域,即通常的视口和框架的客户区。

    9.6.1.1 获取视口区域

使用GetViewRect函数获取视口区域

int[] GetViewRect(int winID);

函数的参数是窗口ID,返回值是1个具有4个元素的整型1维数组,数组的第1个元素是视口左上角的x坐标,第2个元素是y坐标,第3个元素是视口的宽度,第4个元素是视口的高度。

某些情况下视口需要显示的区域比视口实际显示区域(窗口区域)要大。如SCROLLVIEW就是如此。这是使用GetViewRect函数得到的是实际显示区域(窗口区域)。如果需要进一步知道当前显示的区域的位置,使用GetScrollPosition函数。

int[] GetScrollPosition(int winID);

函数的参数是窗口ID,返回值是1个具有2个元素的整型1维数组,第1个元素是窗口区域左上角在需要显示区域中的x坐标,第2个元素是y坐标。

    9.6.1.2 设置和获取滚动区域的大小

SCROLLVIEW中可显示的滚动区域可以设置的比窗口大,这样可以通过视口移动观察滚动区域的局部。设置滚动区域的大小使用SetScrollSize函数。

int SetScrollSize(int winID, int width, int height, int pageX, int pageY, int lineX, int lineY);

函数的第1个参数是窗口ID;第2个参数int width是设置滚动区域的宽度;第3个参数int height是设置滚动区域的高度;第45个参数int pageXint pageY是滚动时页面滚动的逻辑单位,这里是像元数,也就是点击滚动条上空白时滚动的水平和垂直距离;第67个参数int lineXint lineY是滚动1行移动的逻辑单位,也是像元数,是点击滚动条上的小箭头时水平和垂直移动的像元数。

4~7个参数可以不设置,这是使用Windows的缺省缺省值。

读取滚动区域的大小使用GetScrollSize函数。

int[] GetScrollSize(int winID);

函数参数是窗口ID,返回值是有2个元素的数组,第1个数组元素是滚动区域的宽度,第2个单数是滚动区域的高度。

    9.6.1.3 滚动视口

要想在视口中显示可显示区域的某一部分,可以在程序运行时通过滚动条滚动视口。也可以使用ScrollToPosition函数来滚动视口。

int ScrollToPosition(int winID, int x, int y);

函数的第1个参数是窗口ID,第23个参数是视口左上角在滚动区域的坐标。

    9.6.1.4 刷新窗口

窗口内容更新或者需要强制重画时可以使用UpdateWindow函数刷新窗口。

int UpdateWindow(int winID);

视口位置的演示代码如下:

程序清单 9.17   9-17-视口位置.c

main(){

    int rc[4];

    int pgX = 100;

    int pgY = 100;

    int lnX = 10;

    int lnY = 10;

    int nWinID;

    STRING str;

    nWinID = CreateWindow("新建窗口SCROLLVIEW",2);

    MoveView(nWinID,200,20,20,20);

    rc = GetViewPosition(nWinID);//视口在框架中的位置

    Format(str,"视口在框架中位置 x=%d y=%d",rc[0],rc[1]);

    Print(str);

    rc = GetViewRect(nWinID);

    Format(str,"视口左上角 x=%d y=%d width=%d height=%d",rc[0],rc[1],rc[2],rc[3]);

    Print(str);

    SetScrollSize(nWinID,10000,10000,pgX,pgY,lnX,lnY);

    rc = GetScrollSize(nWinID);

    Format(str,"显示区域 %d * %d",rc[0],rc[1]);

    Print(str);

    ScrollToPosition(nWinID,100,200);

    rc = GetScrollPosition(nWinID);//视的滚动位置

    Format(str,"视口左上角位置 x=%d y=%d",rc[0],rc[1]);

    Print(str);

}

运行上述代码,结果如图9.14

图 9.14 SCROLLVIEW视口位置代码输出

这段代码在视口移动后没有对框架进行更新,在更改框架大小时会显示未更新的内容。

    9.6.1.5 窗口框架的绘图区

本节开始部分提到通常框架的绘图区被视口覆盖,但通过MoveView函数可以让出部分区域用于安放控件或者设置背景等。

可以通过下述函数获取窗口框架绘图区的大小

int[] GetFrameRect(int winID);

函数的参数为窗口ID,返回值是11维数组,第12个数组元素是框架客户区左上角位置,第34数组元素为宽度和高度。

9.6.2设置窗口背景

设置窗口视口的背景可以在事件函数OnEraseBkgnd中进行。实际上可以在事件函数OnDraw中或其它任何可以绘图的函数中绘制视口的背景,但是频繁地重绘背景不是一个好的办法。在OnEraseBkgnd函数中背景需要重绘时绘制是较好的背景重绘办法。

同样,窗口框架背景重绘也有一个事件函数用于重绘OnEraseFrameBkgnd背景。

    9.6.2.1 设置视口的背景颜色

DrawBkColor函数用于填充背景颜色,实际上该函数可以在视口绘图区的任何矩形区域填充颜色。

int DrawBkColor(int winID, int R, int G, int B, int x, int y, int width, int height);

函数的第1个参数是窗口ID;第2~4个参数是填充颜色的红绿蓝分量,取值在0~255之间;第56个参数是要填充区域在视口中的左上角坐标,第78个参数是填充区域的宽度和高度。如果不指定第5~8个参数,填充整个视口的客户区。

下面的代码在视口填充指定颜色。

程序清单 9.18  9-18-视口背景颜色.c

int nWinID;

main(){

    nWinID = CreateWindow("新建窗口",1);

    MoveView(nWinID,200,20,20,20);

}

event OnEraseBkgnd(int nID)

{

    if(nID == nWinID)

    {

       DrawBkColor(nID,128,128,255);

    }

}

 

图 9.15 视口背景色

    9.6.2.2 设置框架的背景颜色

PaintBkColor函数用于填充框架的背景颜色。

int PaintBkColor(int winID, int R, int G, int B, int x, int y, int width, int height);

函数的参数同DrawBkColor

下面的代码在框架填充指定颜色。

程序清单 9.19   9-19-框架背景颜色.c

int nWinID;

main(){

    nWinID = CreateWindow("新建窗口",2);

    MoveView(nWinID,200,20,20,20);

}

event OnEraseFrameBkgnd(int nID){

    if(nID == nWinID){

       PaintBkColor(nID,255,128,0);

    }

}

输出结果如图9.16。框架背景被添色后,重画的视口遮挡了背景色。

图 9.16 框架背景色

9.6.3背景贴图

背景贴图与填充背景颜色类似。在窗口框架中贴图使用PaintPicture函数。

int PaintPicture(int winID, STRING PaintPicture, int x, int y, int width, int height);

函数的第1个参数是窗口ID,第2个参数是1个图像文件路径名,指向124位色彩的bmp图像文件;第34个参数是图片左上角在窗口框架中的位置;第56个参数是图片在框架中的宽度和高度,如果实际图像的宽度和高度与这个指定的值不一致,图片被强制适应这个范围。

3~6个参数可以不指定,此时填充图片的范围为整个窗口框架。

下面是在窗口框架中设置背景图片的代码。

程序清单 9.22 框架背景贴图.c

int nWinID;

STRING str ="F:\\Tulips.bmp";

main(){

    nWinID = CreateWindow("新建窗口",2);

    MoveView(nWinID,300,20,20,20);

}

event OnEraseFrameBkgnd(int nID){

    if(nID == nWinID)

    {

       PaintPicture(nID,str);

    }

}

执行上述代码,输出结果如图9.17

图 9.17 图片框架背景

在视口中贴背景图使用DrawPicture函数

int DrawPicture(int winID, STRING PaintPicture, int x, int y, int width, int height);

DrawPicture函数与PaintPicture函数的参数一样。

下面是在视中设置背景图片的代码。

程序清单 9.21   9-21-视背景贴图.c

int nWinID;

STRING str ="F:\\Penguins.bmp";

main(){

    nWinID = CreateWindow("新建窗口",1);

    MoveView(nWinID,200,20,20,20);

}

event OnEraseBkgnd(int nID)

{

    if(nID == nWinID)

    {

       DrawPicture(nID,str);

    }

}

这里DrawPicture函数在事件OnEraseBkgnd函数中,而不是在OnEraseFrameBkgnd函数中。代码运行输出结果如图9.18

图 9.18 图片视背景

9.6.4绘制图形——设置绘图方式

设置绘图方式决定绘图像素与窗口背景的结合关系,用SetROP2函数设置。ROP(Raster Operations Units)是栅格设备的操作单元,例如我们使用的显示器就是栅格设备。

int SetROP2(int winID, int rop);

函数的第1个参数为窗口ID;第2个参数是rop值。这个值在Windows SDK中定义如下:

R2_BLACK            1

R2_NOTMERGEPEN    2

R2_MASKNOTPEN     3

R2_NOTCOPYPEN      4

R2_MASKPENNOT      5

R2_NOT               6

R2_XORPEN           7

R2_NOTMASKPEN      8

R2_MASKPEN          9

R2_NOTXORPEN       10

R2_NOP               11

R2_MERGENOTPEN    12

R2_COPYPEN          13

R2_MERGEPENNOT    14

R2_MERGEPEN        15

R2_WHITE            16

R2_LAST              16

1R2_BLACK)表示黑色,10R2_NOTXORPEN)表示将绘图色与背景色异或后再取反后绘图,等。详细内容请参阅Windows相关文档。

9.6.5绘制图形——设置和读取像素值

在视口中的指定坐标位置设置像素使用DrawPixel函数。

int DrawPixel(int winID, int x, int y, int R , int G , int B);

函数的第1个参数是窗口ID;第23个参数是像素坐标;第4~6个参数是像素的红绿蓝分量。

从视口中的指定坐标位置读取像素值使用GetViewPixel函数。

int[] GetViewPixel(int winID, int x, int y);

函数的3个参数与DrawPixel函数的前3个参数相同。

函数的返回值是1个具有3个元素的1维整型数组,依次存放返回像素的红绿蓝分量。

9.6.6绘制图形——画线

    9.6.6.1 创建画笔

先创建画笔,然后由窗口选择画笔在窗口绘图。创建画笔使用CreatePen函数

int CreatePen(int penStyle, int penWidth, int R, int G, int B);

函数的第1个参数是线型,取值1~5,依次为实线PS_SOLID、划线PS_DASH、点线PS_DOT、点划线PS_DASHDOT和点点划线PS_DASHDOTDOT;第2个参数是线宽,为象素数;第3~5个参数是线颜色的红绿蓝分量。

函数的返回值是画笔的ID,选择画笔时使用这个ID值。

    9.6.6.2 选择画笔

选择创建的画笔就可以使用这个画笔画线,用户可能创建不止一个画笔,交替选择不同的画笔可以分别画出不同的线型。选择画笔使用SelectPen函数。

int SelectPen(int winID, int penID);

函数的第1个参数是窗口ID,表明在这个窗口绘图时使用这个画笔,也就是说是为窗口选择的画笔;函数的第2个参数是画笔ID,是创建画笔函数CreatePen的返回值。

    9.6.6.3 画线

创建和选择了画笔后就可以在窗口画线,在视口画线使用DrawLine函数。

int DrawLine(int winID, int x1, int y1, int x2, int y2);

函数的第一个参数是窗口ID;第2~5个参数是线段的起止坐标,单位为像元数。

程序清单 9.22  9-22-创建画笔与划线1.c

main(){

    int winID,i;

    int penID[6];

    for(i=0; i<3; i=i+1){

       penID[i] = CreatePen(i+1,1,0,0,0);

    }

    penID[3] = CreatePen(1,1,255,0,0);

    penID[4] = CreatePen(1,2,0,255,0);

    penID[5] = CreatePen(1,3,0,0,255);

    winID = CreateWindow("新建窗口",1);

    for(i=0; i<6; i=i+1){

       SelectPen(winID,penID[i]);

       DrawLine(winID,20,i*16+20,400,i*16+20);

    }

}

在这段代码中先创建了6只画笔,然后创建了一个窗口。依次用这些画笔画线,结果见图9.19

图 9.19 在视口中画线

当这个窗口被遮挡或者改变大小时,窗口中的线条就不见了。要想解决这个问题,可以在OnDraw事件函数中使用DrawLine函数画线。这样一旦窗口变化时OnDraw函数就自动重画。改动后的代码如下:

程序清单 9.23  9-23-创建画笔与划线2.c

int winID;

int penID[6];

main(){

    int i;

    SuspendEvent("OnDraw");

    winID = CreateWindow("新建窗口",1);

    for(i=0; i<3; i=i+1){

       penID[i] = CreatePen(i+1,1,0,0,0);

    }

    penID[3] = CreatePen(1,1,255,0,0);

    penID[4] = CreatePen(1,2,0,255,0);

    penID[5] = CreatePen(1,3,0,0,255);

    MoveView(winID,200,20,20,20);

    ResumeEvent("OnDraw");

    UpdateWindow(winID);

}

event OnDraw(int nID){

    int i;

    if(nID == winID){

       for(i=0; i<6; i=i+1){

           SelectPen(winID,penID[i]);

           DrawLine(nID,20,i*16+20,400,i*16+20);

       }

    }

}

这一段代码将画线部分移到了一个单独的OnDraw函数中。OnDraw函数与main函数是在不同的线程中运行,这一点非常重要。

首先将winIDpenID两个变量移动到了main函数外面,这样便成了公有变量可以有两个不同线程中的函数存取。这两个变量在两一个线程未启动之前,先由main函数设置其值,后面对它们的操作都是只读,不会引起同步方面的问题。

main函数中

SuspendEvent("OnDraw");

1行在一开始就阻塞了OnDraw事件函数,并且是在创建窗口之前。这是因为在创建窗口一旦完成,马上就开始窗口刷新调用OnDraw函数,而OnDraw函数需要使用画笔画线,但是此时main函数还没有创建画笔,因此程序会引起问题。所以,先阻塞OnDraw函数,在窗口和画笔创建等动作全部完成确定可以画图后,使用

ResumeEvent("OnDraw");

函数允许函数绘图,并使用

UpdateWindow(winID);

函数强制更新绘图。这时在函数中确认是正确的窗口后,就可以执行画线等动作了。

运行上述代码,并移动或改变窗口的大小,线条不会消失了。

由上述可见OnDraw函数不与主线程在一个线程好像引起了许多不必要的麻烦,但多个窗口大量的图形图像数据需要绘制时会提高效率和不阻塞主进程的运行,尤其是在地学领域这种应用是非常常见的。

    9.6.6.4 在窗口框架画线

如果希望这些线输出到窗口框架背景上,可以使用PaintLine函数。

int PaintLine(int winID, int x1, int y1, int x2, int y2);

函数的参数同DrawLine函数.

下面的代码在窗口框架北极光上绘图

程序清单 9.24  9-24-创建画笔与划线3.c

int winID;

int penID[6];

main(){

    int i;

    SuspendEvent("OnPaint");

    SuspendEvent("OnDraw");

    for(i=0; i<3; i=i+1){

       penID[i] = CreatePen(i+1,1,0,0,0);

    }

    penID[3] = CreatePen(1,1,255,0,0);

    penID[4] = CreatePen(1,2,0,255,0);

    penID[5] = CreatePen(1,3,0,0,255);

    winID = CreateWindow("新建窗口",1);

    MoveView(winID,300,20,20,20);

    ResumeEvent("OnPaint");

    UpdateFrame();

    ResumeEvent("OnDraw");

    UpdateView();

}

event OnPaint(int nID){

    int i;

    if(nID == winID){

       for(i=0; i<6; i=i+1){

           SelectPen(winID,penID[i]);

           PaintLine(nID,20,i*16+20,280,i*16+20);

       }

    }

}

event OnDraw(int nID){

    int i;

    if(nID == winID){

       for(i=0; i<6; i=i+1){

           SelectPen(winID,penID[i]);

           DrawLine(nID,20,i*16+20,280,i*16+20);

       }

    }

}

程序的运行结果见图9.20

图 9.20在框架背景画线

注意:窗口及其画笔是公有资源,OnDraw函数与OnPaint函数属于不同的线程。某一线程设置了窗口的当前画笔会造成另一线程中正在使用的画笔的更改。

    9.6.6.5 画Polyline

用一系列的坐标画线时使用DrawPloyline函数

int DrawPloyline(int winID, int n, int points[]);

函数的第一个参数是窗口ID;函数的第2个参数是坐标对的点数(包含第1个点);第3个参数是坐标对的数组,成对排列,x坐标在前,y坐标在后,数组的元素数是函数第2个参数的2倍。

下面的代码画1Polyline

程序清单 9.25  9-25-画Poliline.c

int winID;

main(){

    winID = CreateWindow("新建窗口",1);

    UpdateWindow(winID);

}

event OnDraw(int nID){

    int data[] = {20,20,400,20,400,400,20,400};

    if(nID == winID) {

       DrawPolyline(nID,4,data);

    }

}

    9.6.6.6 画Polygone

画多边形(Polygone)数据与Polyline函数出函数名不同其余均相同。

int DrawPloygone(int winID, int n, int points[]);

将上述代码中的DrawPloyline函数改为DrawPloygone函数,可见绘制的封闭四周的矩形。

    9.6.6.7 检索线段与多边形

(暂无内容)

9.6.7绘制图形——几何图形

    9.6.7.1 创建画刷

创建画刷使用CreateBrush函数

int CreateBrush(int style , int R, int G, int B);

函数的第1个参数是画刷类型,取值0~6

依次为0(无)、1水平线(HS_HORIZONTAL)、2垂直线(HS_VERTICAL)、3反斜线(HS_FDIAGONAL)、4正斜线(HS_BDIAGONAL)、5十字线(HS_CROSS)、6 X线(HS_DIAGCROSS);第2~4个参数是颜色的红绿蓝分量。

函数的返回值是画刷的ID,选择画刷时使用这个ID值。

    9.6.7.2 选择画刷

选择创建的画刷就可以使用这个画刷绘画,用户可能创建不止一个画刷,交替选择不同的画刷可以分别画出不同的背景图案。选择画刷使用SelectBrush函数。

int SelectBrush(int winID, int brushID);

函数的第1个参数是窗口ID,表明在这个窗口绘图时使用这个画刷,也就是说是为窗口选择的画刷;函数的第2个参数是画刷的ID,是创建画刷函数CreateBrush的返回值。

    9.6.7.3 画图形

使用DrawRectangle函数画矩形

int DrawRectangle(int winID, int x, int y, int width, int height);

DrawRectangle函数的第1个参数是窗口ID;第23个参数是矩形的左上角坐标;第45个参数是矩形的宽度和高度。在绘制的矩形内用画刷填充。

画椭圆使用DrawRectangle函数

int DrawRectangle(int winID, int x, int y, int width, int height);

这个函数的参数指定一个矩形范围,在此矩形范围内画一个内接椭圆,椭圆内部由选择的画刷填充。

下面的代码绘制一个矩形和一个椭圆。

程序清单 9.26  9-26-创建画刷.c

main(){

    int winID;

    int brushID[2];

    brushID[0] = CreateBrush(6,0,0,255);

    brushID[1] = CreateBrush(0,255,0,0);

    winID = CreateWindow("新建窗口",1);

    SelectBrush(winID,brushID[0]);

    DrawRectangle(winID,20,20,200,100);

    SelectBrush(winID,brushID[1]);

    DrawEllipse(winID,220,20,200,100);

}

运行上述代码,结果如图9.21

图 9.21绘制几何图形

9.6.7绘制图形——图标

(暂无内容)

9.6.8位图

位图同窗口一样也是全局对象,加载或者创建一个位图对象后也由一个ID对位图进行标识。

    9.6.8.1 加载和删除位图

加载位图使用LoadBitmap函数。

int LoadBitmap(STRING name);

函数的参数是一个BMP格式的24位位图文件的路径名。函数成功时返回该BMP对象的ID,失败时返回0。注意检查该函数的返回值确认位图文件加载成功。

删除位图使用DeleteBitmap函数。

int DeleteBitmap(int bmpID);

函数的参数是位图ID。成功时函数返回1

    9.6.8.2 显示位图

使用DrawBitmap函数显示位图。

int DrawBitmap(int winID,int bmpID, int x, int y, int width, int height);

函数的第1个参数是窗口ID,指示在哪个窗口显示位图;第2个参数是位图ID;第34个参数是位图左上角在窗口中的显示位置,如果不指定这两个参数,左上角位置为00;第56个参数是在窗口中显示的宽度和高度,如果指定了这两个参数,位图通过缩放显示在该矩形框内,如果不指定这两个参数,按位图像素的实际比例显示。

下面的代码演示了加载和显示1个文图文件。

程序清单 9.27  9-27-打开和显示位图.c

int winID,bmpID;

main(){

 

    STRING str ="F:\\Tulips.bmp";

    bmpID = LoadBitmap(str);

    SuspendEvent("OnDraw");

    winID = CreateWindow("新建窗口",1);

    ResumeEvent("OnDraw");

    UpdateView(winID);

}

event OnDraw(int id){

    if(id == winID)  {

       DrawBitmap(winID,bmpID);

    }

}

运行上述代码,窗口中显示1个位图的图像。

    9.6.8.3 读取位图信息

读取位图信息使用GetBitmapInfo函数。

int[4] = GetBitmapInfo(int bmpID);

函数的参数为位图ID。返回值是1个有4个元素的1维数组,为位图信息的查询结果。第1个元素是位图的高度;第2个元素是位图的宽度;第3个元素是位图数据的字节数;最后1个数组元素是每像素的比特数,这里总是24

注意:这里返回值数据排列高度在前宽度在后。通常的涉及到显示设备位置坐标等均为x坐标在前,y坐标在后,宽度在前,高度在后。但涉及光栅数据等经常使用与此相反的排列顺序。例如,多通道栅格数据可能按通道、高度、宽度的次序排列。这样连续存储的数据排列格式为BSQ

    9.6.8.4 读取位图数据

读取位图信息使用GetBitmapData函数。

int GetBitmapData(int bmpID, BYTE data[][][]);

函数的第1个参数为位图ID;第2个参数是存放位图数据的数组,数组的第1维为颜色通道,取值为3,顺序表示红绿蓝,第2维为位图高度,第3维为位图宽度。

函数成功时返回1,否则返回0

    9.6.8.5 从数组创建位图

可以使用数据和数据的高度宽度直接创建位图。创建位图使用CreateBitmap函数。

int CreateBitmap(int height, int width, BYTE data[][][]);

函数的第一个参数是待创建位图的高度,第2个参数是宽度;第3个参数是位图数据,这是一个3维数组,无符号单字节整型,数据排列与GetBitmapData函数相同。

下面的例子首先加载一个位图文件,创建IDbmpID1的位图,读取这个位图信息和位图数据,然后删除掉这个位图。

接下来使用位图信息中的图像高度和宽度以及读到的位图数据创建1个新位图,其IDbmpID。最后创建窗口并在OnDraw函数刷新和显示位图。

程序清单 9.28  9-28-数据创建位图.c

int winID,bmpID1,bmpID2;

main(){

    int biinfo[4];

    STRING str ="F:\\Tulips.bmp";

    bmpID1 = LoadBitmap(str);       //加载一个BMP图像

    biinfo = GetBitmapInfo(bmpID1); //读取图像信息

    BYTE data[3][biinfo[0]][biinfo[1]];

    GetBitmapData(bmpID1,data);    //读取图像数据

    DeleteBitmap(bmpID1);       //删掉加载的图像

    SuspendEvent("OnDraw");

    bmpID2 = CreateBitmap(biinfo[0],biinfo[1],data);//从得到的图像数据创建1个新图像

    winID = CreateWindow("新建窗口",1);

    ResumeEvent("OnDraw");

    UpdateView(winID);

}

event OnDraw(int wid){

    if(wid == winID){

       DrawBitmap(winID,bmpID2);

    }

}

 9.6.9 DC缓存

DC (Device Context) 这里仅指用于显示设备的设备描述(打印机等暂未支持)。为其创建1个缓存空间,将图形图像输出到这个缓存空间然后再刷新到显示设备上可以提高显示效率。

    9.6.9.1 创建/删除DC缓存

创建DC缓存使用SetViewDCBuffer函数。

int SetViewDCBuffer(int winID, int width, int height);

函数的第1个参数是窗口ID;后两个参数分别是DC缓存的宽度和高度。创建成功函数返回1,否则返回0

DC 缓存是窗口资源的一部分,每个窗口只有1DC缓存,因此它没有ID。并且现只为SCOLLVIEW类窗口创建。

DC缓存一旦创建,就会自动将自己映射到显示窗口(VIEW)中。

为了释放存储空间等目的可以删除1个建好的DC缓存,使用DeleteDCBuffer函数删除。

int DeleteDCBuffer(int winID);

函数的参数为窗口ID,如果这个窗口存在DC缓存就将其删除,函数返回1,否则函数返回0

下面是创建DC缓存的例子。

程序清单 9.29  9-29-创建DC缓存

main(){

    int winID;

    winID = CreateWindow("新建窗口",2);

    SetViewDCBuffer(winID,10000,10000);

}

 SetViewDCBuffer(winID,10000,10000)一行创建了一个10000*10000像元的显示DC缓存,同时SCROLLVIEW调整响应的滚动区域(Scroll Size)。滚动区域的大小除与DC缓存有关外还与显示比例有关,请参阅显示比例缩放等相关章节。

运行上述代码,输出如图9.22

图 9.22显示DC缓存

    9.6.9.2 向DC缓存填色/添加位图

DC缓存的初始状态是一块用0填充的内存,所以显示后出现黑色窗口。可以使用SetDCBufferColor函数设置颜色。

int SetDCBufferColor(int winID, int R, int G, int Bint x, int y, int width, int height);

函数的第1个参数是窗口ID;第2~4个参数是颜色分量;第56个参数是需要填充颜色区域的左上角坐标,不设置时为00;第78个参数是填充区域的宽度和高度,不设置时为整个DC缓存区域。

DC缓存添加位图使用BitmapToDCBuffer函数。

int BitmapToDCBuffer(int winID, int bmpID, int x, int y, double intrans);

函数的第1个参数是窗口ID;第2个参数是希望添加的位图的ID;第34个参数是希望添加到窗口的位置,也就是位图左上角在缓存中的坐标;最后一个参数是不透明系数,指明位图像素与DC缓存背景像素的合成比例,取值0~1之间。例如,此值为1时,位图像素覆盖掉背景像素,为0.5时,位图像素和背景像素各占50%。此外,如果背景像素RGB均为0时(全黑色),此置相当于1

这个函数的参数xy不能省略,并且没有指定位图目标区域的宽度和高度。函数使用的宽度和高度默认为来源图像的宽度和高度。

函数成功时返回1。失败时返回0

示例代码如下:

程序清单 9.30  9-30-位图加载到DC缓存.c

main(){

    STRING str1 ="F:\\Tulips.bmp";

    STRING str2 ="F:\\Penguins.bmp";

    int bmpID1 =LoadBitmap(str1);

    int bmpID2 =LoadBitmap(str2);

    int winID =CreateWindow("新建窗口",2);

    SetViewDCBuffer(winID,10000,10000);

    BitmapToDCBuffer(winID,bmpID1,10,10,0.5);

    BitmapToDCBuffer(winID,bmpID2,100,100,0.5);

}

这几行代码向DC缓存添加了两个位图。GeoGeo自动将缓存中的内容隐射到显示窗口上。BitmapToDCBuffer(winID,bmpID1,10,10,0.5);一行指定图像像元与背景像元合成的不同明度为0.5,实际上由于是新创建的DC缓存,背景像元为纯黑,这个值不起作用。添加第2个位图时指定的不同明度将两幅图像重叠的区域像元各取50%合成新像元。

图 9.23 DC缓存中的像元合成

    9.6.9.3 从DC缓存保存位图

DC缓存区的数据可以创建位图对象并保存到位图文件中。DC缓存创建位图对象使用DCBufferToBitmap函数。

int DCBufferToBitmap(int winID);

函数的参数为窗口ID。如果窗口存在DC缓存并成功创建位图,函数返回位图ID,否则返回0。窗口是否存在DC缓存可以使用IsDCBuffer函数查询。

位图对象保存为位图文件(.bmp)使用SaveBitmap函数。

int SaveBitmap(int bmpID, STRING str);

函数的第1个参数是位图ID;第2个参数是保存文件的路径名。保存成功时返回1。失败时返回0

下面是保存位图文件的示例代码。

程序清单 9.31   9-31-保存位图.c

main(){

    STRING str1 ="F:\\Tulips.bmp";

    STRING str2 ="F:\\Penguins.bmp";

    int bmpID1 =LoadBitmap(str1);

    int bmpID2 =LoadBitmap(str2);

    int winID =CreateWindow("新建窗口",2);

    SetViewDCBuffer(winID,1700,900);

    BitmapToDCBuffer(winID,bmpID1,10,10,0.5);

    BitmapToDCBuffer(winID,bmpID2,600,100,0.5);

    int bmpID3 =DCBufferToBitmap(winID);

    BitmapToDCBuffer(winID,bmpID3,10,10,1);

    SaveBitmap(bmpID3,"D:\\MyTestBitmap.bmp");

}

这段代码将两个位图文件加载以后,创建一个DC缓冲区,再将两个为图选入到缓存中。接下来从DC缓存创建一个IDbmpID3的新的位图对象,最后将这个新的位图对象保存为一个名为MyTestBitmap.bmp的位图文件。

查看这个为图文件,结果如图9.24

图 9.24 DC缓存保存为位图文件

9.7大图(Big Map)

在对数组的定义时我们知道可以定义很大的数组(大数据集,见8.4节),与之对应的是对这些大数据集的显示。大数据集需要大窗口进行可视化演示,大窗口可能需要多屏拼屏构成,因为单个显示器即使增大到很大也可能跟不上大区域高空间分辨率大图的显示需要。

大数据集与大窗口之间是大图(Big Map),大图接收来自大数据集的数据合成可视化图像,然后传递给大窗口。因此大图在大数据集的处理和表达中具有关键地位。

9.7.1创建大图

大图使用8.4节同样的TFSTile File System)。它是由一系列的Tile文件描述的一个巨大的绘图区,使用时自动将需要的区域映射到计算机内存。从程序员的角度看,只要按照绘图区域位置索引操作即可,无需关心这些图像数据处于内存或者外部存储空间上。

大图与DC缓存空间对应(见前面的创建DC缓存空间),GeoGeo创建一个巨大的2维存储空间。理论上这个巨大的存储空间不受限制仅取决于计算机的存储空间,实际应用上这个2维空间的高度/宽度由于受系统限制,每个方向不要大于2G

2维的大图空间并非意味着只能处理2维数据。而是前两维在2维空间上分布,后面仍然是多维(如果有)。祥见第8章的大数据集。

创建大图的DC缓存空间使用SetViewDCBufferEx函数。

int SetViewDCBufferEx(int winID,int width ,int height);

函数的第1个参数为窗口ID,表明该大图所隶属的窗口,同时也可以看出不同的窗口可以拥有自己的大图空间。函数的第23个参数是大图DC缓存的宽度和高度。函数成功时返回1,否则返回0

可以用下列示例代码创建大图。

程序清单 9.32   9-32-创建大图DC缓存.c

main(){

    int winID =CreateWindow("新建窗口",2);

    SetViewDCBufferEx(winID,480000,320000);

}

上述代码仅有2行,创建1个窗口,然后为这个窗口创建1个水平48万象元垂直32万象元的DC缓存空间。运行上述代码,仅出现一个黑色背景的窗口。滚动这个窗口,显示区域能够遍历整个DC缓冲区。

查看GeoGeo系统的临时空间目录(如D:\RsdTmp),可能产生一个名为GGTfsxxxxxDIB的目录,xxxxx是随机数字。路经里面是一系列的扩展名为.tlf的文件,这些文件占大约460G的磁盘空间。占有磁盘空间的多少与设置大图DC缓存的宽度和高度有关,宽度乘高度的值越大,占有磁盘空间越多。申请大图DC缓存前仔细检查计算机系统的临时目录所在的磁盘剩余空间,以确保不会因磁盘空间不足引起申请失败。

.tlf文件的个数、大小以及内存影射文件的大小影响磁盘的访问频率以及显示刷新速度等。如有这些方面的调整需求请联系开发人员。

9.7.2关闭大图

大图数据的建立和数据的处理通常需要较多的处理成本,有理由保存这些数据供后续的处理使用。大图是与窗口关联的,上一节由SetViewDCBufferEx创建的大图在窗口关闭时默认保留这些大图数据。

这里提供了一个DeleteDCBufferEx函数来显式指定销毁或保留这些大图DC缓存的数据。DeleteDCBufferEx函数可以在关闭大图DC缓存时销毁这些数据或者仅关闭DC缓存而保留这些数据。

int DeleteDCBufferEx(int winID, int delTileFlag);

函数的第一个参数是窗口ID;第2个参数是是否销毁大图数据的指示,指定0时表示不销毁大图数据,1或者不指定时销毁大图数据。

9.7.3打开和保存大图

重新打开一个大图使用OpenDCBufferEx函数。

int OpenDCBufferEx(int winID, STRING str);

函数的第1个参数是窗口ID,第2个参数是大数据路径。

注意:GeoGeo在打开大图数据时根据函数参数提供的路径名,在该路径下查找一个文件名与路径相同扩展名为.inf的文件。如果路径名与该文件名不一致会导致无法找到该文件导致打开失败。

一个打开大图数据的例子可能如下:

程序清单 9.35

main(){

     int winID = CreateWindow("新建窗口",2);

     OpenDCBufferEx(winID,"D:\\RsdTmp\\GGTfs14598DIB");

}

保存大图数据使用

int SaveDCBufferEx(int winID, STRING pathname, int compflag);

函数的第1个参数是窗口ID;第2个参数是保存大图数据的路径名;第3个参数是压缩方式。

(该函数暂未使用)

9.7.4查询DC缓存的大小

使用SizeDCBuffer函数查询DC缓存的大小。

int SizeDCBuffer(int winID , int size[]);

函数的第1个参数是窗口ID;第2个参数是1个有2个元素的二维数组,返回DC缓存的大小,第1个数组元素是DC缓存的高度,第2个参数是宽度。

9.7.5向大图DC缓存添加位图

向大图的DC缓存添加位图使用BitmapToDCBufferEx函数,与普通DC缓存添加位图除函数名不同外其它基本相同。

int BitmapToDCBufferEx(int winID, int bmpID, int x, int y );

函数的第一个参数为窗口ID,第2个参数为位图ID,第34个参数是位图左上角在大图DC缓存中的位置。

下面的代码将2个大小为1024*768bmp图片多次加载到DC缓存中。加载完毕后,使用FlushDCBufferEx函数将内存映射与TFS刷新。

程序清单 9.34  9-34-大图DC缓存添加位图.c

main(){

    STRING str1 ="G:\\Tulips.bmp";

    STRING str2 ="G:\\Penguins.bmp";

    int bmpID1 =LoadBitmap(str1);

    int bmpID2 =LoadBitmap(str2);

    int winID =CreateWindow("新建窗口",2);

    SetViewDCBufferEx(winID,72000,48000);

    BitmapToDCBufferEx(winID,bmpID1,0,0);

    BitmapToDCBufferEx(winID,bmpID2,1024,0);

    BitmapToDCBufferEx(winID,bmpID1,2048,0);

    BitmapToDCBufferEx(winID,bmpID2,3072,0);

    BitmapToDCBufferEx(winID,bmpID2,0,768);

    BitmapToDCBufferEx(winID,bmpID1,1024,768);

    BitmapToDCBufferEx(winID,bmpID2,2048,768);

    BitmapToDCBufferEx(winID,bmpID1,3072,768);

    FlushDCBufferEx(winID);

    UpdateWindow(winID);

}

9.7.6大图DC缓存创建位图

可以从大图的DC缓存的一部分创建位图,然后将位图对象保存为位图文件。创建位图使用DCBufferToBitmapEx函数。

int DCBufferToBitmapEx(int winID, int x, int y, int width, int height);

1个参数是窗口ID;第23个参数是DC缓存中要创建位图对象的开始位置,第45个参数是位图对象的宽度和高度。

在上述清单9.36的后面添加下述代码

程序清单 9.35  9-35-大DC缓存到位图.c

     int bmpID3 = DCBufferToBitmapEx(winID,0,0,4096,1536);

     SaveBitmap(bmpID3,"D:\\MyTestBitmap.bmp");

代码第1行使用DCBufferToBitmapEx函数从大图DC缓存的一部分创建一个位图对象,第2行使用SaveBitmap函数将其保存为1个名为MyTestBitmap.bmpbmp文件。

运行上述代码,可见创建了一个新的bmp文件,用图像查看器打开这个bmp文件,如图9.25

图 9.25 大图DC缓存保存为位图文件

9.7.7 DC缓存绘制图形

(暂无内容)

9.7.8 DC缓存的缩放显示

对大图的缩放显示不是真正的图像缩放,真正的图像缩放是对原始图像的像素插值或抽线等操作生成新的图像。显示缩放不改变图像/DC缓存的大小,但是使显示在窗口的图像看起来放大或者缩小。

DC缓存有一个自己的Zoom因子,Zoom因子为1.0时图像按11显示,Zoom因子大于1或者小于1时,按Zoon in 或者 Zoom out显示图像。可以通过设置Zoom因子实现显示缩放。

使用SetDCZoomFactor函数设置Zoom因子。

int SetDCZoomFactor(int winID, double zoomFactor);

9.7.9图像金字塔

(暂无内容)

9.8多屏显示

一些计算机系统安装了多于1个的显示屏,可以在这些扩展屏幕上显示大图。

9.8.1查询显示器的数目

查询显示器数目使用Monitors函数

int Monitors()

该函数返回计算机系统安装的显示器的数目。

9.8.2查询主显示器

计算机系统安装多个显示器时其中有一个是主显示器,可以使用PrimaryMonitor函数查询哪台为主显示器。

int PrimaryMonitor()

函数的返回值即为主显示器的显示器号。

9.8.3查询显示器的分辨率

可以使用GetDesktopSize函数查询当前设置的显示器分辨率。

int[] = GetScreenSize(int monitor);

函数的参数是显示器号,范围在Monitors的返回值范围之内。函数返回值是一个有2个元素的一维数组,第一个元素为显示器高度方向的像素数,第2个元素为宽度像素数。

下面是一个查询显示器的例子。

程序清单 9.36  9-36-获得监视器的数量和尺寸.c

main(){

    int n =Monitors();

    int m =PrimaryMonitor();

    Print("本机连接了 %d 个显示器,显示器 %d 为主显示器。",n,m);

    int i;

    int sz[n][2];

    for(i=0; i<n; i=i+1){

       sz[i] = GetScreenSize(i+1);

       Print(" %d个显示器的显示分辨率为 %d*%d",i+1,sz[i][1],sz[i][0]);

    }

}

运行上述代码,输出结果如下:

图 9.26 获得监视器的数量和尺寸.c的输出结果

9.8.4在指定的显示器显示窗口

常用的主显示器和扩展显示器在水平方向扩展(在显示方向为纵向时,旋转90时仍为水平方向),通常第1显示器排列在左侧,第2显示器排列在右侧。

主显示器的左上角位置总为显示坐标(00)。当第1显示器为主显示器时,第2显示器左上角坐标就是(显示器1宽度,0)。当第2显示器为主显示器时,第1显示器左上角坐标就出现了负值,为(-显示器1宽度,0)。知道了它们的坐标位置,就可以通过MoveWindow函数来安排窗口位置。

下面的代码在从显示器上显示一个窗口

程序清单 9.37  9-37-在从显示器上显示窗口

main(){

    int n =Monitors();

    int m =PrimaryMonitor();

    int i;

    int sz[n][2];

    for(i=0; i<n; i=i+1){

       sz[i] = GetScreenSize(i+1);

    }

    int xpos = 0;

    if( 1 == m)  {

       xpos = xpos+sz[0][1];

    }

    else  {

       xpos = xpos-sz[0][1];

    }

    int id =CreateWindow("ddd",1);

    MoveWindow(id,xpos,0,800,600);

}

轮换将两个显示分别设置成主显示器,运行上述代码,会发现窗口总出现在从显示器的左上角位置。

9.9分布式窗口与事件

6章已经讲过GeoGeo可以部署自己的函数或者进程代码在多个节点机(Slave)上运行。这就有理由希望在多个节点机上不仅仅完成计算等后台任务,还可能希望在这些节点节点机上直接展示自己的或者由主机(Master)指定的可视化结果。比如多个节点机的显示系统排列成一个巨大的显示矩阵来显示9.7节讨论的大图(Big Map)或者8.4节的大数据集。

9.9.1在过程进程中创建窗口

6.4节定义GeoGeo的过程是在一个文件中的独立的执行单元。它可以由主进程控制在节点机上创建独立的分支进程。这些分支进程称之为过程进程。在节点机上创建的另一种进程称之为函数进程,它是由 process关键字上名的函数在节点机上创建的进程。

GeoGeo设计为可以在主函数或者其它任意的普通函数中创建窗口和其它UI元素,但禁止在线程函数中创建窗口。其中一个重要理由是线程函数完成任务后自然死亡,不继续维护持久存在的窗口资源。这类似于工作者线程(Worker Thread)的工作原理。

本质上,无论GeoGeo过程主函数还是线程函数都是单独的线程,但是过程主函数及其有关代码被设计为持久存在的线程以维护窗口等持久资源和对象。

在节点机上启动带窗口的过程进程与6.3节完全一样,都是使用ProcessCall函数。节点机上的过程代码也与主机的代码一样。见下例。

程序清单 9.38  9-38-Slave创建窗口.c

main(){

     ProcessBegin();

     ProcessCall("G:\\MyProjects\\脚本-Curent\\程序清单\\9-1-创建窗口.c");

     Sleep(10000);

     ProcessEnd();

}

确定在节点机上已经启动GeoGeo服务程序,程序清单9.40的文件在主机存在。在主机上运行程序清单9.39,此时GeoGeo在节点机上执行程序清单9.40

运行结果是,在Slave机上创建了一个窗口。10秒后,窗口消失,这是由于程序清单9.39

ProcessEnd();

一行关闭了节点机上的进程。

上述代码创建的窗口出现在第1个节点机上(参见第6章),也可以使用ProcessTo函数指定节点机来显示窗口。

不要在进程函数或者线程函数中创建窗口,因为对于GeoGeo来说,窗口是持久资源,线程函数或者进程函数在MasterSlave上创建子线程,执行完毕后自然死亡,可能会遗留无人管理的窗口资源。过程(带入口的独立程序)在启动时虽然也是子进程(相对于GeoGeo解释程序或GeoGeo服务程序)但有特别设计的公有资源管理机制。

9.9.2过程进程中的窗口事件

    9.9.2.1 在节点机本机响应窗口事件

在节点机响应本机的窗口事件与在主机上响应事件一样,在过程进程代码中写入相应的event函数即可。

将程序清单9.39ProcessCall函数的参数换成

     ProcessCall("D:\\9-7-event鼠标点击.c");

运行后在节点机上出现一个窗口,在节点机上用鼠标点击这个窗口,出现一些线和坐标数据,结果如同图9.5

10秒后,这个窗口消失。

    9.9.2.2 主机/节点机事件响应

各个节点机的窗口和事件如果各自为政,就如同在各台计算机上分别启动的可执行文件。这显然不能满足多机协调工作的目的。虽然节点机的执行代码由主机控制和发送,但是一旦节点机进程启动,主机就无法更改节点机上的控制代码,使主机和节点机的交互受限。再者,大量的节点机工作时是无人干预的,但是可以通过向节点机发送模拟事件消息如模拟的键盘鼠标操作以及一些其它消息由节点机相应。也就是说,不但主机可以响应节点机事件,节点机也可以响应主机的模拟事件。为此,GeoGeo设计了主机/节点机间的事件响应机制。

1.  主机响应节点机事件

首先,节点机选择性向主机发送事件消息。多个节点机时刻产生大量的事件,从性能和需求方面考虑,没有必要将这些事件都发送到主机,而只筛选需要的事件发送。方法是在过程进程代码中加入哑事件函数。这些哑事件函数可以是真正的哑函数,也可以是具有有意义的执行代码的事件函数。重要的是,GeoGeo服务程序查找到该事件相应函数/哑函数存在时,就将该事件消息发送到主机,否则就直接丢弃该消息。

如节点机的 OnDraw事件的哑函数可以写为:

event OnDraw(){

}

首先,如果是不执行任何代码的真正哑函数,可以不指定函数的参数,不书写函数体。其作用仅仅是告诉GeoGeo服务需要发送这个哑函数对应的事件消息。如果需要该函数执行代码动作,就按照标准的事件函数格式书写该函数。

第二步,主机接收和响应节点机发送来的事件消息。对于需要处理的节点机事件,写一个事件响应函数,没有对事件响应函数的消息被丢弃。

节点机的内建事件响应函数的函数名也是固定的,与本机的事件响应函数不同的是在标准的时间相应函数名的后面加上“Slave”以示区别。例如,主机上的节点机重绘事件响应函数的函数名为OnDrawSlave

此外,函数的参数也有变化。内建事件响应函数的参数是固定的,其第一个参数通常是窗口ID,例如鼠标左键按下的事件响应函数为

event OnLButtonDown(int nID,int x, int y,int nFlag)

1个参数是窗口ID,第23个参数是鼠标点击位置的xy坐标,等。对应的节点机事件响应函数可以写为

event OnLButtonDownSlave(int rowSlave,int colSlave,int winID,int x, int y,int nFlag)

除函数名不同外,函数增加了2和参数,第1个参数rowSlave是节点机所在的行,第2colSlave是节点机所在的列。这两个参数标识该事件消息来自哪一台节点机。后面的参数如winID指示节点机上的窗口ID等均与本机的事件函数相同。

第三步,对节点机窗口进行操作控制。

不能在主机上直接对节点机窗口进行操作,假如我们有这样的控制机制如

int DrawBitmapSlave(int rowSlave,int colSlave,int winID,int bmpID, int x, int y, int width, int height);

函数等一系列的操作函数,就可以直接操控每个节点机的图形和界面等操作,如上,就可以在指定的节点机上指定的窗口内指定位置绘制一个BMP对象。可是GeoGeo目前还没有提供类似的这一系列的函数。

当然,实现这些也不是不可以,但是又需要考虑代码和数据的频繁传输等问题,如上述位图对象是在主机还是节点机上、大对象是否传输等都是严重的问题。

GeoGeo对节点机窗口进行控制是在节点机上启动一个线程函数。由于节点机上的窗口是该进程的公有资源,可以由不同的线程进行控制操作,以实现与主机的交互。

例如可以在主机的事件响应函数中写

process MyFunc(){…}

 之类的函数使其在节点机上运行。

讨论:节点机产生一个事件后将该事件发送到主机,主机响应后为该节点机指派一个函数由节点机执行,这与直接在节点机上由事件响应函数执行这个被指派函数的功能有差别吗?除了主机/节点机之间的消息和代码旅行还带来什么更多的灵活性?

下述是1个演示在节点机上创建1个窗口,并在窗口点击鼠标左键,然后将该事件消息发送到主机的例子。

程序清单 9.39  9-39-Master响应Slave事件.c

main(){

    ProcessBegin();

    ProcessCall("G:\\MyProjects\\脚本-Curent\\程序清单\\9-40-哑函数.c");

    Sleep(10000);

    ProcessEnd();

}

event OnLButtonDownSlave(int row,int col,int winID,int x,int y,int flag){

    Print("%d%d列节点机窗口%d x=%d y=%d位置鼠标左键按下",row+1,col+1,winID,x,y);

}

程序清单 9.40  9-40-哑函数.c

main(){

    CreateWindow("新建窗口",1);

    CreateWindow("新建窗口",2);

}

event OnLButtonDown(){

}

程序清单9.39在第1个节点机上启动一个过程进程,该过程进程创建2个窗口(程序清单9.40),10秒后销毁这个进程。事件函数OnLButtonDownSlave在节点机上发生鼠标点击事件时响应,并显示在哪一台节点机的那个窗口发生了鼠标点击事件。

程序清单9.40主函数创建2个窗口,并有1个哑函数OnLButtonDown。这个哑函数的存在使得节点机将鼠标左键按下的消息发送到主机。

运行程序清单9.39,输出结果见图。

图 9.27  9-39-并行-响应OnLButtonDown.c的输出结果

去掉程序清单9.40的哑函数,重新运行程序清单9.39。同样在节点机上点击鼠标左键,会发现程序清单9.39OnLButtonDownSlave函数无法响应。

2.  主机向节点机发送消息

如同本机向窗口发送消息一样,Master机也可以将消息、命令和事件等。比方说Master机更新了Slave的共享显示数据通知Slave刷新显示等。

int SendMessageSlave(int row, int col, int winID, STRING msg)

函数的第12个参数是目标节点机所在的行列,也就是消息发送到这个节点机上;第3个参数是节点机窗口ID,即该节点机的这个窗口接收此消息;第4个参数是消息串。

消息串分消息和参数两部分,也可能只有消息没有参数部分。例如发送窗口显示更新消息“GGM_DRAW”时没有附加的参数,而发送模拟的鼠标左键点击“GGM_LBUTTONDOWN”消息则需要附带模拟鼠标点击位置的坐标和按键flag信息。GeoGeo消息格式以及参数设置请参阅相关文档。

Master机向Slave机发送自定义消息时使用下列消息格式

GGM_XXX…X YYY…Y ZZZ…Z …

XXX…X 表示任意数量的连续字符,与GGM_一起构成消息本身,中间不能有空格

YYY…Y  ZZZ…Z 是任意个数的由空格分隔的参数,与消息一起构成一个消息串。

这个串作为SendMessageSlave函数的第4个参数发送到Slave机。在Slave机上,这些消息不同于内建的窗口消息可以由GeoGeo服务自动识别,也就无法定位由哪个消息/事件响应函数来响应这个消息。因此在Slave机上就需要一种类似 MFC的自定义消息映射机制。

GeoGeo设计了一个MapMessage函数来实现这一机制

int MapMessage(STRING msgName, STRING funcNme);

函数的第1个参数是自定义消息名,第2个参数是消息响应函数的函数名。在Slave机代码的开始部分(激活消息响应函数之前)将所有消息使用该函数映射一次,这是在GeoGeo服务程序中就建立了一个消息映射表,Slave在收到消息后查找对用的消息响应函数。

自定义消息响应函数统一设置了2个参数,第1个是窗口ID,第2个参数是一个串,用户根据Master机和Slave机之前的约定自行解析这个串。

下面是一个刷新节点机显示窗口的例子。在这个例子中,首先在Master机打开一个BMP图片,读取图像数据后将其发送到Slave机上并在Slave机显示该图像。休眠5秒后,主机打开另一幅图像,读取图像数据后,刷新Slave机上的图像数据。然后发送消息通知Slave机重新显示更新后的图象数据。再休眠5秒后,关闭Slave机的进程。

程序清单 9.41  9-41-向Slave机发送消息.c

BYTE bmpbits[3][768][1024];

main(){

    STRING str ="G:\\Tulips.bmp";

    int bmpID =LoadBitmap(str);

    GetBitmapData(bmpID,bmpbits);

    DeleteBitmap(bmpID);

    ProcessBegin(NULL);

    FlushVar(bmpbits);

    int b =ProcessCall("G:\\MyProjects\\脚本-Curent\\程序清单\\9-42-刷新Slave显示.c");

    if(!b){

       return;

    }

    Sleep(5000);

    str = "G:\\Penguins.bmp";

    bmpID = LoadBitmap(str);

    GetBitmapData(bmpID,bmpbits);

    DeleteBitmap(bmpID);        

    FlushVar(bmpbits);

    Sleep(1000);

    SendMessageSlave(1,1,1,"GGM_NEWBITMAP");

    Sleep(5000);

    ProcessEnd();

}

上述代码首先声明一个全局变量bmpbits用于接收图像数据。获得数据后,在ProcessBegin()一行将该变量同步到Slave机。在Slave机上启动“9-42-刷新Slave显示.c”并休眠后,加载另一个位图。取得位图数据后,由FlushVar函数强制刷新Slave机上的变量bmpbitsSendMessageSlave(1,1,1,"GGM_NEWBITMAP");一行强制刷新Slave机上的图像显示。

程序清单 9.42  9-42-刷新Slave显示.c

extern bmpbits;

int winID,bmpID;

main(){

   MapMessage("GGM_NEWBITMAP","OnGGNewBitmap");

    bmpID = CreateBitmap(768,1024,bmpbits);

    SuspendEvent("OnDraw");

    winID = CreateWindow("新建窗口",1);

    ResumeEvent("OnDraw");

    DrawBitmap(winID,bmpID);

    UpdateView(winID);

}

event OnDraw(int id){

    if(id == winID){

       DrawBitmap(winID,bmpID);

    }

}

event OnGGNewBitmap(int id,STRING msg){

    SuspendEvent("OnDraw");

    if( 0 != bmpID ){

       DeleteBitmap(bmpID);

    }

    bmpID = CreateBitmap(768,1024,bmpbits);

    ResumeEvent("OnDraw");

    DrawBitmap(id,bmpID);

}

程序清单9.42是程序清单9.41调用的过程代码。extern bmpbits声明一个外部变量,main函数开始MapMessage("GGM_NEWBITMAP","OnGGNewBitmap");一行进行消息映射,表明当收到GGM_NEWBITMAP消息时由函数OnGGNewBitmap响应。接下来使用CreateBitmap函数将全局变量bmpbitsMaster机传来的数据)创建一个图像并加以显示。

当收到主机GGM_NEWBITMAP消息时OnGGNewBitmap函数启动。该函数删除原来的位图对象,用全局变量bmpbits重新创建一个位图对象并显示。由于bmpbits已经由Master进行过更新,所以这次显示了一个与原来不同的图像。

9.10本章小结

(暂时无内容)

 

下载地址:http://download.csdn.net/detail/gordon3000/7922555 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值