精品主页 | 软件下载 | 系统下载 | 精品导航| 精彩图片 | 转帖工具 | 版主申请 | 影视下载
发新话题
打印

感悟 Visual Basic (六)

感悟 Visual Basic (六)

  


                  第八话父与子

在开始这一话之前,不知各位读者有没有使用过MDIForm呢?看看图1,这是一个标准的MDIForm和其中一个子窗体在标准和最大化情况下的外观。不过别误会,我不是想讲MDI,你再看看图2,我只是想让你区别图2的窗体不是MDIForm。图2的两个窗体都是一般的窗体,从最大化的外观就可以看出区别了。是不是觉得很有意思?其实也没有什么秘密。

我说过Windows中多数东西都是一种窗口,比如按钮。一般情况下我们看到的按钮都是在一个窗体的里面,这是因为窗体和按钮有一种父与子的关系。当一个窗口成为另一个窗口的子窗口(Child),那么它的位置的变化就只发生在另一个窗口里,另一个窗口就是这个窗口的父窗口(Parent)。平时我们建立的窗体都是相互独立的,与其他的窗体没有关系,但我们可以通过API使它们建立起父与子的关系。这要用到SetParent:

PrivateDeclareFunctionSetParentLib"user32"(ByValhWndChild



AsLong,ByValhWndNewParentAsLong)AsLong

SetParent接收两个参数,第一个是将成为子窗口的窗口句柄,第二个是将成为父窗口的窗口句柄。它的使用很简单,比如想把Form2作为Form1的子窗口,只需这样使用:



SetParentForm2.hWnd,Form1.hWnd

Windows会自动把Form2在新的父窗口中的位置调整为原父窗口的位置(即使是桌面,也是一个父窗口)。即是说,假如原来在桌面的Form2,位置为10,10,则它在新的父窗口中的位置也为10,10。但这个新的10,10是以新父窗口为参照物的,无论怎么变化,都是在新父窗口中。

不过应该注意,并不是所有东西都适合当父窗口。因为每一种窗口都有为自己设计的行为,比如当画面重画时要画什么,如果我们为它添加了新的子窗口,那么它们将可能产生冲突,因为父窗口在设计时并没有考虑出现意外的子窗口的情况。为了说明这个问题,我做了一个示例,参照图3。当我把按钮作为ListBox的子窗口时,你会看到由于ListBox在选择项目时进行了画面的重画,导致按钮显示变得不正常,但当我按了一下按钮时,又因为按钮的重画,显示又正常了。

值得一提的是,当我们把Form1中的一个子窗口(比如按钮)放置到Form2中,而我们又在Form1中为这个子窗口的某个事件写了执行代码,那么它还会被执行吗?Form2又需不需要为这个新的子窗口做特别处理呢?假如我的处理代码都是写在Form1中的,而所有控件都被我放到Form2中时(如图4),它们的点击事件的代码仍然能被执行。由于无法得知实际上VB内部是如何处理控件的消息循环的,所以我也无法对此中秘密进行解释,特别是一个应该注意的问题——当你把按钮(这里以按钮为例,但其实其他东西也一样)放到Form2中后,如果这个按钮在Form2中获得了焦点,那么你就无法从Form2切换回Form1,除非这时你可以让Form1中某个控件重新获得焦点——比如通过使某个控件从Form2中成为Form1的子窗口,或者使用SetFocus让Form1的某个控件获得焦点。所以,实际应用中应该避免这种情况的发生。如果新的父窗口不是由VB所建立的窗体,那么这种事就不会发生,不过这已不是本话的内容了。


  


                  




在我写的示例源程序里,还有一个GetParent的API这里没有讲到,我用它判断当前的子窗口是哪个窗体的子窗口。它的作用是返回指定子窗口的父窗口的句柄。




第九话寻找子窗口



这里又是一个特别的例子,各位读者先看看图5,虽说 图像处理我还会两下,不过这可不是处理来的,而是真实的抓图。我把开始按钮移到这里来了。再看看图6,怎么样?有意思吧?

这里我要介绍几个API:

PrivateDeclareFunctionFindWindowLib"user32"Alias

"FindWindowA"(ByVallpClassNameAsString,ByVallpWindowNameAs

String)AsLong



PrivateDeclareFunctionGetWindowLib"user32"(ByValhwndAs

Long,ByValwCmdAsLong)AsLong



PrivateDeclareFunctionGetClassNameLib"user32"Alias

"GetClassNameA"(ByValhwndAsLong,ByVallpClassNameAsString,ByValnMaxCountAsLong)AsLong

首先是FindWindow。FindWindow可以根据所给的条件,从桌面上寻找一个窗口,lpClassName是窗口的类名,而lpWindowName是窗口的标题。我们可以传递lpClassName,让它找符合的类名的窗口,或传递lpWindowName,让它找符合的标题的窗口,如果我们不需要两个条件都符合,则另一个参数可以传递vbNullString,让它忽略。它的返回值就是找到的窗口的句柄。那么什么是类名?避开C  的相关术语来说,其实Windows的窗口都是某种类中的一种,这个“类”可以是Textbox、Combobox,也可以是由用户来定义的,这个窗口是属于哪一类的,它的类名就是什么。GetWindow也可以用来寻找某个窗口并返回其句柄,但它只限于在某个窗口中寻找子窗口,因此它需要传递hWnd以表示在哪个窗口里寻找。而wCmd用来描述要找的子窗口与父窗口的关系。它的值如下:

GW_CHILD:寻找第一个子窗口GW_HWNDFIRST:寻找第一个同级窗口,或寻找

第一个顶级窗口GW_HWNDLAST:寻找最后一个同级窗口,或寻

找最后一个顶级窗口GW_HWNDNEXT:寻找下一个同级窗口GW_HWNDPREV:寻找前一个同级窗口GW_OWNER:寻找窗口的所有者(即父窗口)

我们先来理解什么是同级窗口和顶级窗口。打个比方,如果一个窗口有三个子窗口,则这三个窗口都是同一级的,互为同级窗口。如果我们从没寻找过一个子窗口,那么API不知道我们要找的是和哪个窗口同级,那么此时它找的是顶级窗口,顶级窗口即是子窗口,但这个子的关系是直接的,而不会是子窗口的子窗口(即孙子,别笑,这里的术语不是我自己造的)。最后一个GetClassName和以前讲过的几个字符串相关的API用法差不多,hWnd是窗口句柄,lpClassName是用来接收窗口类名的缓冲区,nMaxCount则是说明缓冲区的大小。

那么接下来我是如何用它们的呢?看这里:

DimhTaskbarAsLong,hStartbuttonAsLong

DimsClassAsString*250



hTaskbar=FindWindow("Shell_traywnd",vbNullString)

hStartbutton=GetWindow(hTaskbar,GW_CHILD)



Do

GetClassNamehStartbutton,sClass,250

IfLCase(Left$(sClass,6))="button"ThenExitDo

hStartbutton=GetWindow(hStartbutton,GW_HWNDNEXT)Loop


  


                  






我使用FindWindow从桌面上找到了一个类名为

“Shell_traywnd”的窗口,它就是任务栏(不要问我是怎么知道它的类名的)。然后我又用GetWindow函数,从任务栏找到第一个子窗口。接下来,我用一个Do⋯Loop结构的循环为上一次找到的子窗口检查其类名,如果类名是button,则说明是个按钮,一般来说,任务栏上只有一个是button类的,所以一找到,它势必就是“开始”按钮了。如果没找到,则仍使用GetWindow,但这次和第一次不同,我传递的不是任务栏的句柄,而是上一次找到的子窗口的句柄,为的是找下一个同级窗口,就这样一次次循环直到找到开始按钮。

那么,开始按钮就被我这么找到了,然后我就可以像对待其他窗口一样对待它:比如将它移动。不要忘了上一期所讲的内容,SetWindowPos将在这里产生作用,你可以移动它,或者为最后一个参数组合上SWP_HIDEWINDOW,让开始按钮变得不可见,或者组合SWP_SHOWWINDOW重新显示⋯⋯

接下来轮到任务栏了,你从图6中可以看到在开始按钮的位置有另一个“厉害”的按钮取代它,这是上一话的内容:SetParent。我用SetParent为原本在Form1上的按钮指定了新的父窗口——任务栏。如果你查看我的示例源程序,你会发现在此按钮的GotFocus事件中,我把焦点转移给了另一个按钮,原因在上一话已经说了。

在示例源程序中,我还演示了隐藏和显示任务栏,仍然是SetWindowPos的功劳,提醒一下,为了不改变窗口的一些属性,要在最后一个参数组合上合适的值。

好了,这一期的内容就这么多,我想这一次你应该好好研究我的源程序,里面的东西涉及到上一期和本期的内容,把它消化下去吧。

TOP

发新话题