!!挑战windows API大师:怎样在控件里实现Subclass和hook的引用?

NiceFeather 2000-10-26 11:23:00
独立控件项目里有一个UserControl,一个Module,UserControl里有一个Control,我要在UserControl里用Windows API利用Subclass技术探测并改变Control的类型,并用HOOK探测Windows消息并重绘它,这所有的一切如果不用UserControl而用普通的控件,那么我已完全搞定,不需别人帮忙!但若将普通的控件换成UserControl,我发现控件无法探测到SetWindowsHookEx()设置的WinProc,剩下的工作无法完成!
例示程序代码如下:
Private Sub UserControl_Initialize()
With App
'
' Create a hook to monitor messages sent to window procedures.
' The system calls fAppHook before passing the message to the
' receiving window procedure.
'
' Then in fAppHook we can modify the controls to our liking.
'
mlHook = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf fAppHook, .hInstance, .ThreadID)
'
' Remove the hook.
Call UnhookWindowsHookEx(mlHook)
End With

OwnerDraw
End Sub

Private Sub OwnerDraw()
' Subclass the Parent of the control
' which will be modified as owner drawn controls. Save the
' address of the original window procedure in the registry.
'
mlWndProc = SetWindowLong(GetParent(ListBox1.hwnd), GWL_WNDPROC, AddressOf fAppWndProc)

ListBox1.ListIndex = 0
End Sub

注:1、fAppHook、fAppWndProc均在Module中,而且代码无误。
2、如果将上述独立控件项目改为EXE项目,UserControl换成form里的普通控件,然后将UserControl_Initialize()中的代码放在Main()中,OwnerDraw()中的代码放在普通控件所在的form中的Form_Load()中,则所有程序将正确执行,毫无问题!
3、有人建议我将UserControl_Initialize()和OwnerDraw()放在控件项目里的Main()中或其他地方,我试过,仍然不行!

请windows API大师指点:我应将UserControl_Initialize()和OwnerDraw()中的代码放于独立控件项目何处才能使SetWindowsHookEx()设置的WinProc生效!
...全文
509 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
Un1 2001-02-17
  • 打赏
  • 举报
回复
http://www.banasoft.com/DownLoad/BNHkLib.exe
shyguy 2000-11-14
  • 打赏
  • 举报
回复
To NiceFeather:
COMBOLBOX是一个COMBOBOX的下拉列表部分。这是为什么你上次能够绘制下拉框而无法绘制COMBOBOX的原因。
你彻底搞定了?
if(TRUE)
佩服佩服。
else
偶自己做一个和你不同思路的SlefDraw-Combo给你看看。
shyguy 2000-11-14
  • 打赏
  • 举报
回复
To NiceFeather:
COMBOLBOX是一个COMBOBOX的下拉列表部分。这是为什么你上次能够绘制下拉框而无法绘制COMBOBOX的原因。
你彻底搞定了?
if(TRUE)
佩服佩服。
else
偶自己做一个和你完全不同思路的SlefDraw-Combo给你看看。
NiceFeather 2000-11-14
  • 打赏
  • 举报
回复
to shyguy:
是的,我已完全搞定,就用“ComboBox”,而且是用我们上次探讨的代码,其实是我们想得过于复杂了,即使moudle里的main()、fAppHook、fSetStyle全都不要,就用moudle里的fAppWndProc就可以了!自绘得很漂亮!(这真是令我也大跌眼镜)
前几天一直想和你谈谈,但一直碰不到你,明天(2000-11-15)晚上(20:00)我在OICQ上等你!
NiceFeather 2000-11-11
  • 打赏
  • 举报
回复
to ShyGuy:
1、你说的动态创建(CreateWindowEx)一个ComboBox控件,我用Spy++看了一下,确实是“ComboBox”,但不管他是什么,如果要自绘和探测鼠标消息并做出响应,一样要采用hook技术!
2、你给我大那篇e文,我随便看了一下。那篇e文中提到,如果是用CreateWindowEx()创建non-OCX的ComboBox控件,则很难捕捉到此控件的焦点,确实是这样!作者提到要用到OLE IOLEInPlaceActiveObject interface技术能解决此类问题(文中提到Mike Gainer, Matt Curland and Bill Storage的代码,你那里有吧?怎不见发过来?:));但我是在User_Control里用CreateWindowEx()创建ComboBox子控件,因为User_Control的CanGetFocus属性可调,所以在User_Control中创建动态ComboBox应该不存在这个问题!
3、你似乎在建议我动态创建“ComboBoxEx32”(就是ImageComboBox控件,用Spy++察看也是“ComboBox”),我发现如在User_Control中这样做,则SetWindowLong()无法进入指定的WinProc fAppWndProc,而创建“ComboBox”(标准ComboBox,用Spy++察看是“ComboBox”),则SetWindowLong()能顺利进入指定的WinProc fAppWndProc………………

(……NiceFeather终于忍不住这痛苦的煎熬,冲进盥洗室,狂吐了一阵血,ShyGuy赶忙讲其扶住,劝道:“兄弟,条条大路通罗马,何必跟ComboBox这厮过不去呢!”,NiceFeather心有不甘,甫一站定,咬牙切齿地仰天骂道:“狗日的ComboBox,我跟你没完……”……)

欲知后事如何,且听下回分解…………
shyguy 2000-11-09
  • 打赏
  • 举报
回复
To NiceFeather:
Try This
Select Case sClass
'Case "ComboLBox"
Case "ThunderComboBox"
mlSetStyle = GetWindowLong(CWP.hwnd, GWL_STYLE)
If bCombo Then
mlSetStyle = mlSetStyle Or CBS_OWNERDRAWFIXED 'Or LBS_USETABSTOPS Or CBS_AUTOHSCROLL
Else
mlSetStyle = mlSetStyle Or CBS_OWNERDRAWFIXED
bCombo = True
End If
End Select
shyguy 2000-11-09
  • 打赏
  • 举报
回复
我想,VB中的COMBOBOX根本不是正宗的COMBOBOX,用SPY++可以发现是ThunderR6ComboBox.而我用vc做了一个测试程序,用spy++看,发现里边的ComboBox是ComboBox.
所以我想实现的方法就是:自己做个一comboEx控件,用createwindowEx来创建!不要再用hook,但是这样的话焦点问题就会出现(详见昨天给你的E文).
shyguy 2000-11-09
  • 打赏
  • 举报
回复
To NiceFeather: I'm going to resolve the mishooking problem...
Try This!!!!!!!!!!!!!!! it's ok now(in some extend:-)
Select Case sClass
'Case "ComboLBox"
'Case "ThunderComboBox"
Case "ThunderRT6ComboBox"
mlSetStyle = GetWindowLong(CWP.hwnd, GWL_STYLE)
If bCombo Then
mlSetStyle = mlSetStyle Or CBS_OWNERDRAWFIXED 'Or LBS_USETABSTOPS Or CBS_AUTOHSCROLL
Else
mlSetStyle = mlSetStyle Or CBS_OWNERDRAWFIXED
bCombo = True
End If
End Select
NiceFeather 2000-11-07
  • 打赏
  • 举报
回复
哦,对了,我的OICQ号:11488994
NiceFeather 2000-11-07
  • 打赏
  • 举报
回复
哦,ShyGuy,对不起,前一段时间我有些“不务正业”,现在该言归正传了!

过一会再谈你说的GetProp/SetProp/RemoveProp,我先说一有趣的现象,当我在UserControl上放一个(或以上)的DriveListBox或DirListBox(注意他们都是自绘型ListBox)时,无需加载任何代码,加载在其他控件(指我要subclass和hook的控件)的代码会自动转嫁到他们身上,DriveListBox subclass和hook得很成功,自绘得很漂亮,这时不论DriveListBox的个数和tab次序都如此,而我要hook的控件(普通的combobox)未发生任何变化,这使我考虑到可能是这个combobox的style转换成CBS_OWNERDRAWFIXED未成功,而DriveListBox本身为CBS_OWNERDRAWFIXED,所以成功。对combobox的style转换工作我已作了,并且是用GetClassName来进行对控件类型的探测和转换的,不能对普通combobox进行class定位可能使问题的关键,看这段设置控件style的代码(注意看注解):
Public Function fAppHook(ByVal lHookID As Long, _
ByVal wParam As Long, ByVal lParam As Long) As Long

Static bCombo As Boolean
Dim CWP As CWPSTRUCT
Dim k As Long
Dim sClass As String
'
' This hook procedure is a callback function invoked by the
' SetWindowsHookEx API issued in Sub Main. The system calls
' this function whenever a message is sent to this application.
' Before passing the message to the destination window procedure,
' the system passes the message to this procedure. This procedure
' can examine the message but cannot modify it.
'
' The lParam parameter is the address of a CWPSTRUCT structure
' that contains details about the message that was sent. Using
' the address, the message data is copied into a local copy.
'
Call CopyMemory(CWP, ByVal lParam, Len(CWP))
'
' Now that we have the message structure, see what message was sent.
'
Select Case CWP.message
'
' We are only interested in the Create message. When this
' message is sent we want to modify how the control is
' created.
'
Case WM_CREATE
mlSetStyle = 0
'
' Get the name of the class the window belongs to.
'
sClass = Space$(128)
k = GetClassName(CWP.hwnd, ByVal sClass, 128)
sClass = Left$(sClass, k)
'
' See if the class matches that for one of our
' controls. The best way to determine the class
' name is by uncommenting this debug command or
' by using Spy++ that comes with VB enterprise.
'
' NOTE:
' To use the VB6 version of Microsoft Common Controls,
' change the class names to those indicated below.
'
'Debug.Print sClass
Select Case sClass
Case "ComboLBox"
mlSetStyle = GetWindowLong(CWP.hwnd, GWL_STYLE)
If bCombo Then
mlSetStyle = mlSetStyle Or LBS_USETABSTOPS Or CBS_OWNERDRAWFIXED
Else
mlSetStyle = mlSetStyle Or CBS_OWNERDRAWFIXED
bCombo = True
End If
End Select
'
' Subclass the control by setting a new address for
' its associated window procedure. Now when a message is
' sent to the control, our callback procedure will be called.
'
' The SetWindowLong returns the address of the original
' window procedure.
'
If mlSetStyle Then
mlHookWndProc = SetWindowLong(CWP.hwnd, GWL_WNDPROC, AddressOf fSetStyle)
End If
End Select
'
' Pass the message information to the original window procedure.
'
fAppHook = CallNextHookEx(mlHook, lHookID, wParam, ByVal lParam)
End Function

关于上面fSetStyle很简单,是这样的:
Public Function fSetStyle(ByVal hwnd As Long, _
ByVal Msg As Long, ByVal wParam As Long, _
ByVal lParam As Long) As Long

Dim C As CREATESTRUCT
'
' This callback routine is called by Windows whenever a message
' is sent to the control indicated by hwnd. We are only interested
' in the create message.
'
Select Case Msg
Case WM_CREATE
'
' When a create message is sent, the lParam parameter has the
' address of a CreateStruct structure containing style
' information for the control being created. This structure
' is copied locally, modified and then copied back so that the
' control is created with our desired style.
'
Call CopyMemory(C, ByVal lParam, Len(C))
C.style = mlSetStyle
Call CopyMemory(ByVal lParam, C, Len(C))
'
' Set the new style.
'
Call SetWindowLong(hwnd, GWL_STYLE, C.style)
'
' Unsub-class the control by assigning it the address
' of its original window procedure.
'
Call SetWindowLong(hwnd, GWL_WNDPROC, mlHookWndProc)
End Select
'
' Call the original window procedure.
'
fSetStyle = CallWindowProc(mlHookWndProc, hwnd, Msg, wParam, lParam)
End Function

看完这段代码,我认为问题就在于那个“ComboLBox”上,我不知什么叫“ComboLBox”,但你说他不正确吧,这段代码在EXE项目中对普通ComboBox进行hook完全没问题!

再来谈谈你说的GetProp/SetProp/RemoveProp,没错,假如在一个SubClass()过程中GetProp/SetProp,在UnSubclass()中GetProp/RemoveProp,然后在Form_Load()中SubClass(),在Form_Unload()中UnSubclass()会区分每一个要hook的控件WinProc,
但在独立控件项目中是否可行,我还未证实。然而现在最紧迫的是怎样对控件的起码一个普通ComboBox能hook成功,所以怎样区分多个控件的WinProc的问题那是后话!所以关于这个问题我以后可能还要和你探讨!

我觉得你Win API水平不俗,能告诉我你的OICQ号吗?希望我们以后能经常探讨此类问题!

欢迎所有其他Win API高手来此探讨此类问题,我将有更多高分相送!我有的是分数哦!!
为了表示我的诚意,我现在就将这个问题的分数加到300分!
shyguy 2000-11-06
  • 打赏
  • 举报
回复
keep in the top!
shyguy 2000-11-02
  • 打赏
  • 举报
回复
to nicefeather:
这么多天了,解决了吗?
shyguy 2000-10-29
  • 打赏
  • 举报
回复
TMD 网度太慢了,不玩了。
shyguy 2000-10-29
  • 打赏
  • 举报
回复
你用数组储存address of wndproc是可以的,但是随之而来的问题是,这样就需要严格区分每个控件实例的hwnd,才能吧相应的消息一一传给对应的控件,您说是吗?
建议您吧hwnd储存在窗口自带的数据结构中,大致代码如下:
Declare Function GetProp Lib "user32" Alias "GetPropA" _
(ByVal hwnd As Long, ByVal lpString As String) As Long
Declare Function SetProp Lib "user32" Alias "SetPropA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
Declare Function RemoveProp Lib "user32" Alias "RemovePropA" _
(ByVal hwnd As Long, ByVal lpString As String) As Long

' To set a property called 'NumberOfInstances' to 3 for a form:
SetProp Me.hWnd, "NumberOfInstances", 3

' To get the 'NumberOfInstances' value:
lNumber = GetProp(Me.hWnd, "NumberOfInstances")

' To delete the property when finished with it
' (Windows does this automatically when the application is ended):
RemoveProp Me.hWnd, "NumberOfInstances"
shyguy 2000-10-29
  • 打赏
  • 举报
回复
你用数组储存address of wndproc是可以的,但是随之而来的问题是,这样就需要严格区分每个控件实例的hwnd,才能吧相应的消息一一传给对应的控件,您说是吗?
shyguy 2000-10-29
  • 打赏
  • 举报
回复
我现在没有电脑喽。这种状态至少要维持半个月。惨惨惨。
再乱写一通吧。
0xFFCD 2000-10-28
  • 打赏
  • 举报
回复
nono
thomasz2 2000-10-28
  • 打赏
  • 举报
回复
nn
NiceFeather 2000-10-28
  • 打赏
  • 举报
回复
to shyguy:
你说的没错,addressof所指向的回调函数被放于模块中,而且被需要subclass和hook的控件实例共享,当对每一个控件实例进行setwindowlong()时,都需记录old wndproc函数指针(这个指针在公共模块的全局变量中),以便在CallWindowProc()时进行相应恢复,否则就如你所说,当一个进程中有多个控件实例时,后面的wndproc地址会替代前面的wndproc地址而导致前面的控件实例hook失败,但按道理来说,这种情况一般会发生在多个控件实例时,一个控件实例应该不会有什么问题,然而当我在一个exe项目的form上放置一个UserControl时,我发现程序根本没有进入setwindowlong()所调用的wndproc!但我现在已对这段程序进行了修改,我已将原来单一的mlWndProc指针改成mlWndProc数组指针,并在wndproc中严格区分这些指针并相应CallWindowProc(),我想这样可能会解决old wndproc互相替代的问题,但我发现仍然无效!
谢谢你,欢迎继续参与讨论!
shyguy 2000-10-26
  • 打赏
  • 举报
回复
我以前看过一篇E文,依稀记得一点。希望有用。
首先,在user control中是可以subclass 和 hook的。
如果我没有记错的话,addressof操作符要求相应的回调函数放在一个模块中。该模块被创建的控件实例们(如果你创建多个空间实例的话)共享,这样,当然,模块中的函数和变量对于空间实例也是共享的。
每创建一个孔件实例,都会setwindowlong一次,返回old wndproc函数的指针(好像把它储存在一个在模块级别中申明的全局变量中)。利用这个指针你可以而且必须把不处理的消息传递给old wndproc.
当创建第一个空间实例的时候,该全局变量中储存的是正确的 old proc的地址,当你创建第二个空间实例的时候,由于前面讲到,模块对于所有空间实例是共享的,所以模块中储存old wndproc地址的变量内容会被新的空间实例的old wndproc的地址所覆盖,于是,您的可爱的第一个控件估计就会死翘翘啦。(消息传不过去喽)您说是吗?
考虑一下吧。
由于我在网吧,没有vb,所以随便写了写,如果有误,敬请朋友们指正。

1,451

社区成员

发帖
与我相关
我的任务
社区描述
VB 控件
社区管理员
  • 控件
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧