注册 登录
编程论坛 VFP论坛

容器控件拦截容器内控件的方法

sam_jiang 发布于 2022-11-16 22:11, 2077 次点击
记得上次发帖求助grid控件的gridhittest事件无法获得正确结果,后来有兄弟用bindevent的方法解决了。但我总记得有简单的方法来传递控件事件给容器控件的,以前在网上有看到过,当时一直没想起来。

最近终于想起来了,分享给大家。今天百度了一下,这个方法叫冒泡事件。。。

想必大家都有用过container,pageframe,commandgroup,grid等容器控件,当点击容器内控件时,首先接收消息的是容器内的最内层的控件,而容器是接收不到的。就像上次我的gridhittest,在grid上点击事件被传递给cell,或者header了,导致gridhittest无法正常工作。而我们编程的过程中,肯定有希望让容器控件来接收用户的鼠标或键盘事件,而不是容器里的控件。举个例子,一个form上有很多控件,用户需要用鼠标动态调整控件的大小,而这些控件是联动的,这就很麻烦,因为要涉及到mousemove事件,当鼠标移动到控件上时,触发的是控件的mousemove事件,移走就触发form的mousemove事件了。。。

最近在写一个prg的文件管理程序,终于实现了!废话不多说,上代码吧。。。

程序代码:

PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN


    **************************************************
*-- Form:         form1 (d:\documents\visual foxpro 项目\bubbledevent.scx)
*-- ParentClass:  form
*-- BaseClass:    form
*-- Time Stamp:   11/16/22 09:52:11 PM
*
DEFINE CLASS form1 AS form


    Height = 750
    Width = 1057
    DoCreate = .T.
    AutoCenter = .T.
    Caption = "Form1"
    WindowState = 0
    Name = "Form1"


    ADD OBJECT olecontrol1 AS olecontrol WITH ;
        Top = 35, ;
        Left = 1, ;
        Height = 24, ;
        Width = 280, ;
        Anchor = 0, ;&&当form调整大小时,控件也随之调整
        Name = "Olecontrol1"


    ADD OBJECT olecontrol3 AS olecontrol WITH ;
        Top = 61, ;
        Left = 1, ;
        Height = 688, ;
        Width = 280, ;
        Anchor = (64+2+1), ;&&当form调整大小时,控件也随之调整
        Name = "Olecontrol3"


    ADD OBJECT edit1 AS editbox WITH ;
        Anchor = (64+8+2+1), ; &&当form调整大小时,控件也随之调整
        Height = 714, ;
        Left = 284, ;
        Top = 35, ;
        Width = 771, ;
        Name = "Edit1"


    ADD OBJECT commandgroup1 AS commandgroup WITH ;
        ButtonCount = 6, ;
        Value = 1, ;
        Height = 30, ;
        Left = 1, ;
        Top = 2, ;
        Width = 154, ;
        Name = "Commandgroup1", ;
        Command1.Top = 2, ;
        Command1.Left = 3, ;
        Command1.Height = 25, ;
        Command1.Width = 25, ;
        Command1.Picture = "graphics\bitmaps\offctlbr\small\color\new.bmp", ;
        Command1.Caption = "", ;
        Command1.StatusBarText = "新建一个文件!", ;
        Command1.Alignment = 2, ;
        Command1.Name = "Command1", ;
        Command2.Top = 2, ;
        Command2.Left = 28, ;
        Command2.Height = 25, ;
        Command2.Width = 25, ;
        Command2.Picture = "graphics\bitmaps\offctlbr\small\color\open.bmp", ;
        Command2.Caption = "", ;
        Command2.StatusBarText = "打开一个文件!", ;
        Command2.Alignment = 2, ;
        Command2.Name = "Command2", ;
        Command3.Top = 2, ;
        Command3.Left = 53, ;
        Command3.Height = 25, ;
        Command3.Width = 25, ;
        Command3.Picture = "graphics\bitmaps\offctlbr\small\color\save.bmp", ;
        Command3.Caption = "", ;
        Command3.StatusBarText = "保存文件", ;
        Command3.Alignment = 2, ;
        Command3.Name = "Command3", ;
        Command4.Top = 2, ;
        Command4.Left = 78, ;
        Command4.Height = 25, ;
        Command4.Width = 25, ;
        Command4.Picture = "graphics\bitmaps\offctlbr\small\color\copy.bmp", ;
        Command4.Caption = "", ;
        Command4.StatusBarText = "复制选择的文字", ;
        Command4.Alignment = 2, ;
        Command4.Name = "Command4", ;
        Command5.Top = 2, ;
        Command5.Left = 103, ;
        Command5.Height = 25, ;
        Command5.Width = 25, ;
        Command5.Picture = "graphics\bitmaps\offctlbr\small\color\print.bmp", ;
        Command5.Caption = "", ;
        Command5.StatusBarText = "打印", ;
        Command5.Alignment = 2, ;
        Command5.Name = "Command5", ;
        Command6.Top = 2, ;
        Command6.Left = 128, ;
        Command6.Height = 25, ;
        Command6.Width = 25, ;
        Command6.Picture = "graphics\bitmaps\tlbr_w95\delete.bmp", ;
        Command6.Caption = "", ;
        Command6.StatusBarText = "删除当前文件", ;
        Command6.Name = "Command6"


    ADD OBJECT line1 AS line WITH ;
        Anchor = 10, ;
        Height = 0, ;
        Left = 1, ;
        Top = 32, ;
        Width = 1054, ;
        Name = "Line1"


    ADD OBJECT line2 AS line WITH ;
        Anchor = 10, ;
        Height = 0, ;
        Left = 1, ;
        Top = 33, ;
        Width = 1054, ;
        BorderColor = RGB(255,255,255), ;
        Name = "Line2"


    PROCEDURE Resize
        this.olecontrol1.Width=this.olecontrol3.Width
    ENDPROC


    PROCEDURE MouseMove &&当鼠标移动至2个控件之间,鼠标指针变成可调整边界的形状,进而动态调整控件的边界
        LPARAMETERS nButton, nShift, nXCoord, nYCoord
        IF nxcoord<this.edit1.Left+3 AND nxcoord>this.olecontrol3.Left+this.olecontrol3.Width-3
            this.MousePointer= 9
        *!*        IF nbutton=1 AND this.MousePointer=9
        *!*            this.olecontrol3.Width=nxcoord-this.olecontrol3.left
        *!*            this.olecontrol1.Width=this.olecontrol3.width
        *!*            x=this.edit1.left
        *!*            this.edit1.Left=this.olecontrol3.left+this.olecontrol3.width+3
        *!*            this.edit1.Width=this.edit1.Width+x-nxcoord-3
        *!*        ENDIF
        *!*    ELSE
        *!*        this.MousePointer= 0 &&不能加这句,否则边框只能向右移,不能左移
        ENDIF

        IF this.MousePointer=9 AND nbutton=1 &&这两个条件顺序不一样居然效果不一样,我也是醉了。
            this.olecontrol3.Width=nxcoord-this.olecontrol3.left
            this.olecontrol1.Width=this.olecontrol3.width
            x=this.edit1.left
            nwidth=this.edit1.width
            this.edit1.Left=this.olecontrol3.left+this.olecontrol3.width+3
            this.edit1.Width=nwidth+x-nxcoord-3
        ENDIF
    ENDPROC


    PROCEDURE olecontrol1.Refresh
        *** ActiveX 控件方法程序 ***
    ENDPROC


    PROCEDURE olecontrol1.Change
        *** ActiveX 控件事件 ***
    ENDPROC


    PROCEDURE olecontrol1.Init
    ENDPROC


    PROCEDURE olecontrol1.GetFirstVisible
        *** ActiveX 控件方法程序 ***
    ENDPROC


    PROCEDURE olecontrol3.MouseMove
        *** ActiveX 控件事件 ***
        LPARAMETERS button, shift, x, y
        thisform.MouseMove(button,shift,x,y)  &&向容器传递mousemove事件,冒泡。如果容器控件中还有控件,那么继续冒泡
    ENDPROC


    PROCEDURE olecontrol3.DblClick
        *** ActiveX 控件事件 ***
    ENDPROC


    PROCEDURE olecontrol3.Init
        this.Nodes.Add(,,"Root","我的电脑",,)
        DECLARE integer GetLogicalDriveStrings IN WIN32API integer,string@
        cstr=REPLICATE(CHR(0),40)
        n=getlogicaldrivestrings(40,@cstr)
        FOR i=1 TO n STEP 4
            this.nodes.Add(1,4,,SUBSTR(cstr,i,2),,)
        ENDFOR
        this.Nodes.Item(1).Expanded=.t.
    ENDPROC


    PROCEDURE olecontrol3.HitTest
        *** ActiveX 控件方法程序 ***
        LPARAMETERS x, y
    ENDPROC


    PROCEDURE edit1.MouseMove
        LPARAMETERS nButton, nShift, nXCoord, nYCoord

        thisform.MouseMove(nbutton,nshift,nxcoord,nycoord) &&向容器传递mousemove事件,冒泡。如果容器控件中还有控件,那么继续冒泡
    ENDPROC


ENDDEFINE
*
*-- EndDefine: form1
**************************************************


现在就可以动态调整edit控件的大小,以及treeview控件的大小了,所有控件都会随之调整大小。同理如果是grid控件,那么把cell,以及header的事件传递column,再把column的事件传递给grid,如果grid在container里面,或者pageframe里面,那么继续一级一级上传,直到传递给表单,然后我们在表单的事件中统一处理。

如果你们觉得这个方法有用,点个赞吧。。。


[此贴子已经被作者于2022-11-16 22:13编辑过]

25 回复
#2
sam_jiang2022-11-16 22:31
刚才贴的代码有个小bug,form的mousemove方法要加一句话,代码如下:
程序代码:

    PROCEDURE MouseMove &&当鼠标移动至2个控件之间,鼠标指针变成可调整边界的形状,进而动态调整控件的边界
        LPARAMETERS nButton, nShift, nXCoord, nYCoord
        IF nxcoord<this.edit1.Left+3 AND nxcoord>this.olecontrol3.Left+this.olecontrol3.Width-3
            this.MousePointer= 9
        ENDIF

        IF this.MousePointer=9 AND nbutton=1 &&这两个条件顺序不一样居然效果不一样,我也是醉了。
            this.olecontrol3.Width=nxcoord-this.olecontrol3.left
            this.olecontrol1.Width=this.olecontrol3.width
            x=this.edit1.left
            nwidth=this.edit1.width
            this.edit1.Left=this.olecontrol3.left+this.olecontrol3.width+3
            this.edit1.Width=nwidth+x-nxcoord-3
        ELSE
            this.mousepointer =0
        ENDIF
    ENDPROC
#3
schtg2022-11-17 05:50
谢谢分享!
#4
cssnet2022-11-17 09:04
这个例子相当于“分割条控件”。
若能将它通用化,做成一个“分割条控件类”,那就用处大大的!
当然,那样一来,还需保存或传递一对“左边那一个、右边那一个,俩邻居对象控件”的参数或者表单自定义属性(后者可能更好些),以记录两个相邻的控件对象引用。
此外,还需考虑增加另一种情况:
不仅仅是左右拖动,上下拖动也应该能同样地处理——对吧?

感谢分享!学习了。
#5
厨师王德榜2022-11-17 09:11
好帖.我也思考过类似的问题,但没有你想得那么透彻.
#6
cssnet2022-11-17 10:52
以下是引用cssnet在2022-11-17 09:04:35的发言:
若能将它通用化,做成一个“分割条控件类”,那就用处大大的!


发完上一帖后,我才想起来,楼主“一路冒泡往上传递”的目标,最终指向了表单事件。这个恐怕有些不易实现子类化。
先前我们的思路僵化,一心只想着如何用一个可见的图象控件去实现“分割条”,从没想过用表单MouseMove事件去统一调度协调处理邻居控件的位置和大小。确实是个大失误!
这帖子确实有着“一语点醒梦中人”的意义!

#7
sam_jiang2022-11-17 11:36
回复 4楼 cssnet
上下拖动也是一样的,道理一样!
#8
sam_jiang2022-11-17 11:44
回复 6楼 cssnet
应该可以做成类,设置2个参数,分别为2个相邻的object! 并不一定是由form来处理,看使用者需求,也可以由某个container控件来统一处理。
#9
sam_jiang2022-11-17 11:50
不光是mousemove事件可以这样冒泡处理,click事件也经常有需求从控件传递给容器控件,比如commandgroup,只需在command控件的click事件中加一句,this.parent.click(),然后在commandgroup控件中,根据它的value值,对每个不同的command编写click事件。
#10
吹水佬2022-11-17 11:55
有点象消息反弹
在表单定义响应反弹的方法,用这个方法来接收反弹对象的数据(包括反弹对象等等),这样表单就可以明确是什么对象弹过来的,之后再作其他处理。
#11
cssnet2022-11-17 12:01
以下是引用sam_jiang在2022-11-17 11:44:12的发言:
应该可以做成类,设置2个参数,分别为2个相邻的object! 并不一定是由form来处理,看使用者需求,也可以由某个container控件来统一处理。


你可能低估了Container控件跟表单的区别。
Container控件是做不到表单一模一样效果的。
至于深层原因,我觉得可能在于:
表单有句柄(oForm.hWnd),而Container控件无句柄。
Form是正经的、标准的Windows窗口,而Container只是VFP“随手”画出来的,或许也可以大略地理解为一个可视图片而已。
而OLE控件,跟Form处在同一级别,都是司令长官;
它们跟Container控件在官阶方面皆不对等,后者大致相当于副官或者上士一等兵。
副官或者上士一等兵,是没有足够的权利与资格,去管司令长官很多事件的。
#12
sam_jiang2022-11-17 12:25
事实上,mousemove事件经常有会有小状况,不按套路走,我发上来的另一个目的是希望大家试用,看看有没有什么bug,以便反馈给我。

我今天发现偶尔会失灵,明明没有按下鼠标左键,系统会认为我按下了左键(不知道是不是我的鼠标问题),也就是,没有按下鼠标左键,居然有时候可以调整控件的大小。
#13
cssnet2022-11-17 12:32
以下是引用sam_jiang在2022-11-17 12:25:52的发言:
事实上,mousemove事件经常有会有小状况,不按套路走,我发上来的另一个目的是希望大家试用,看看有没有什么bug,以便反馈给我。
我今天发现偶尔会失灵,明明没有按下鼠标左键,系统会认为我按下了左键(不知道是不是我的鼠标问题),也就是,没有按下鼠标左键,居然有时候可以调整控件的大小。


这是小问题。你给表单添个属性StartDrag,平时是.f.,需要调大小时赋值.t.不就结了。
#14
sam_jiang2022-11-17 12:35
回复 11楼 cssnet
其实每个控件都有句柄,只是控件的句柄被隐藏了。我的意思让container控件来处理,意思是,可能不需要上升到form的程度。比如grid控件,我们把cell,header,column的mousemove事件传递给grid,这样就可以使用grid的gridhittest方法了,这里就没有需求将mousemove事件冒泡到form上。
#15
cssnet2022-11-17 12:44
以下是引用sam_jiang在2022-11-17 12:35:38的发言:

其实每个控件都有句柄,只是控件的句柄被隐藏了。我的意思让container控件来处理,意思是,可能不需要上升到form的程度。比如grid控件,我们把cell,header,column的mousemove事件传递给grid,这样就可以使用grid的gridhittest方法了,这里就没有需求将mousemove事件冒泡到form上。


上次之所以我建议一位狐友,用Form.Label去粗暴压住下边Form的OLE控件,就是因为,普通的VFP内部控件——包括container控件——都不能够如人所愿地显示到OLE控件上方,无论你如何调object.ZOrder(0),都会被OLE控件压住,这会导致一些奇怪的、出人意料的后果。
#16
sam_jiang2022-11-17 12:45
回复 10楼 吹水佬
我这样理解对不对,假设给form添加一个respond方法,参数为evalue,oobject,然后给控件的某个事件中写入以下代码:

thisform.respond(evalue,this)

这好像也是个好办法!
#17
sam_jiang2022-11-17 12:50
回复 15楼 cssnet
就是那个K歌软件的字幕压制吗?成功了?
#18
sam_jiang2022-11-17 12:52
回复 13楼 cssnet
呵呵,也是个好办法

刚试了以下,不行。因为我们要在鼠标左键按下的情况下,移动鼠标,才开始调节控件位置。没有按下鼠标的左键,移动鼠标,则不产生任何操作。按理我们松开鼠标,nbutton应该就会返回0,可事实上它的值居然还是1。。。所以系统误认为我们还在调整控件位置。

[此贴子已经被作者于2022-11-17 13:18编辑过]

#19
cssnet2022-11-17 12:52
以下是引用sam_jiang在2022-11-17 12:50:36的发言:

就是那个K歌软件的字幕压制吗?成功了?


后来他具体怎么做法我不清楚,不过他似乎自己采用其他手段做到了吧?
#20
吹水佬2022-11-17 13:06
回复 16楼 sam_jiang
微软的VC类库事件就是这样用消息反弹实现
#21
吹水佬2022-11-17 13:13
最好能有同步或异步调动,能用异步时尽量用异步,这样效率高些,还能避免出现假死(无响应)
#22
sam_jiang2022-11-17 14:53
回复 21楼 吹水佬
这个mousemove事件返回的nbutton值不对,能帮忙看看哪里出问题了吗?
#23
吹水佬2022-11-17 16:06
以下是引用sam_jiang在2022-11-17 14:53:26的发言:

这个mousemove事件返回的nbutton值不对,能帮忙看看哪里出问题了吗?

还没看明白,如在textbox.mousemove当成form.mousemove来执行,是什么机制,会不会有冲突。
textbox.mousemove 与 form.mousemove 各有其独立消息机制(事件),触发时机是各自的。
#24
sam_jiang2022-11-17 20:11
回复 23楼 吹水佬
这个事件冒泡的本质,其实是将控件的mousemove里的几个参数,传递给form的mousemove,由于nxcoord,和nycoord2个参数是以form为参照系的,所以当控件把几个参数传递给form时,form的mousemove就当其他控件是透明,从而实现模拟操作。

现在的问题是,子控件传递的nbutton参数出现了问题,当我们松开鼠标时,按理应该传递nbutton=0给form,不知道为什么,这个nbutton居然依然是1。。。

#25
吹水佬2022-11-17 20:22
回复 24楼 sam_jiang
直接用事件当方法调用有风险,尤其是有参数,接收到的入口参数有时未必如你所愿。
windows用的是消息机制,消息有优先级、同步、异步。至于form的事件是系统触发的还是程序主动调动的就好难说谁先谁后。
#26
吹水佬2022-11-17 20:26
最好自定义一个form方法来处理,就可以想点就点。
1