-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSUBCLASS.BAS
More file actions
276 lines (246 loc) · 11.1 KB
/
Copy pathSUBCLASS.BAS
File metadata and controls
276 lines (246 loc) · 11.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
Attribute VB_Name = "MSubclass"
Option Explicit
' declares:
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetProp Lib "user32" Alias "GetPropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function SetProp Lib "user32" Alias "SetPropA" (ByVal hwnd As Long, ByVal lpString As String, ByVal hData As Long) As Long
Private Declare Function RemoveProp Lib "user32" Alias "RemovePropA" (ByVal hwnd As Long, ByVal lpString As String) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
'Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
Private Declare Function GetCurrentProcessId Lib "kernel32" () As Long
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
lpvDest As Any, lpvSource As Any, ByVal cbCopy As Long)
Private Const GWL_WNDPROC = (-4)
' SubTimer is independent of VBCore, so it hard codes error handling
Public Enum EErrorWindowProc
eeBaseWindowProc = 13080 ' WindowProc
eeCantSubclass ' Can't subclass window
eeAlreadyAttached ' Message already handled by another class
eeInvalidWindow ' Invalid window
eeNoExternalWindow ' Can't modify external window
End Enum
Private m_iCurrentMessage As Long
Private m_iProcOld As Long
Public Property Get CurrentMessage() As Long
CurrentMessage = m_iCurrentMessage
End Property
Private Sub ErrRaise(e As Long)
Dim sText As String, sSource As String
If e > 1000 Then
sSource = App.EXEName & ".WindowProc"
Select Case e
Case eeCantSubclass
sText = "Can't subclass window"
Case eeAlreadyAttached
sText = "Message already handled by another class"
Case eeInvalidWindow
sText = "Invalid window"
Case eeNoExternalWindow
sText = "Can't modify external window"
End Select
Err.Raise e Or vbObjectError, sSource, sText
Else
' Raise standard Visual Basic error
Err.Raise e, sSource
End If
End Sub
Sub AttachMessage(iwp As ISubclass, ByVal hwnd As Long, _
ByVal iMsg As Long)
Dim procOld As Long, f As Long, c As Long
Dim iC As Long, bFail As Boolean
' Validate window
If IsWindow(hwnd) = False Then ErrRaise eeInvalidWindow
If IsWindowLocal(hwnd) = False Then ErrRaise eeNoExternalWindow
' Get the message count
c = GetProp(hwnd, "C" & hwnd)
If c = 0 Then
' Subclass window by installing window procecure
procOld = SetWindowLong(hwnd, GWL_WNDPROC, AddressOf WindowProc)
If procOld = 0 Then ErrRaise eeCantSubclass
' Associate old procedure with handle
f = SetProp(hwnd, hwnd, procOld)
'Debug.Assert f <> 0
' Count this message
c = 1
f = SetProp(hwnd, "C" & hwnd, c)
Else
' Count this message
c = c + 1
f = SetProp(hwnd, "C" & hwnd, c)
End If
'Debug.Assert f <> 0
' SPM - in this version I am allowing more than one class to
' make a subclass to the same hWnd and Msg. Why am I doing
' this? Well say the class in question is a control, and it
' wants to subclass its container. In this case, we want
' all instances of the control on the form to receive the
' form notification message.
c = GetProp(hwnd, hwnd & "#" & iMsg & "C")
If (c > 0) Then
For iC = 1 To c
If (GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC) = ObjPtr(iwp)) Then
ErrRaise eeAlreadyAttached
bFail = True
Exit For
End If
Next iC
End If
If Not (bFail) Then
c = c + 1
' Increase count for hWnd/Msg:
f = SetProp(hwnd, hwnd & "#" & iMsg & "C", c)
'Debug.Assert f <> 0
' Associate object with message at the count:
f = SetProp(hwnd, hwnd & "#" & iMsg & "#" & c, ObjPtr(iwp))
'Debug.Assert f <> 0
End If
End Sub
Sub DetachMessage(iwp As ISubclass, ByVal hwnd As Long, _
ByVal iMsg As Long)
Dim procOld As Long, f As Long, c As Long
Dim iC As Long, iP As Long, lPtr As Long
' Get the message count
c = GetProp(hwnd, "C" & hwnd)
If c = 1 Then
' This is the last message, so unsubclass
procOld = GetProp(hwnd, hwnd)
'Debug.Assert procOld <> 0
' Unsubclass by reassigning old window procedure
Call SetWindowLong(hwnd, GWL_WNDPROC, procOld)
' Remove unneeded handle (oldProc)
RemoveProp hwnd, hwnd
' Remove unneeded count
RemoveProp hwnd, "C" & hwnd
Else
' Uncount this message
c = GetProp(hwnd, "C" & hwnd)
c = c - 1
f = SetProp(hwnd, "C" & hwnd, c)
End If
' SPM - in this version I am allowing more than one class to
' make a subclass to the same hWnd and Msg. Why am I doing
' this? Well say the class in question is a control, and it
' wants to subclass its container. In this case, we want
' all instances of the control on the form to receive the
' form notification message.
' How many instances attached to this hwnd/msg?
c = GetProp(hwnd, hwnd & "#" & iMsg & "C")
If (c > 0) Then
' Find this iwp object amongst the items:
For iC = 1 To c
If (GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC) = ObjPtr(iwp)) Then
iP = iC
Exit For
End If
Next iC
If (iP <> 0) Then
' Remove this item:
For iC = iP + 1 To c
lPtr = GetProp(hwnd, hwnd & "#" & iMsg & "#" & iC)
SetProp hwnd, hwnd & "#" & iMsg & "#" & (iC - 1), lPtr
Next iC
End If
' Decrement the count
RemoveProp hwnd, hwnd & "#" & iMsg & "#" & c
c = c - 1
SetProp hwnd, hwnd & "#" & iMsg & "C", c
End If
End Sub
Private Function WindowProc(ByVal hwnd As Long, ByVal iMsg As Long, _
ByVal wParam As Long, ByVal lParam As Long) _
As Long
Dim procOld As Long, pSubclass As Long, f As Long
Dim iwp As ISubclass, iwpT As ISubclass
Dim iPC As Long, iP As Long, bNoProcess As Long
Dim bCalled As Boolean
' Get the old procedure from the window
procOld = GetProp(hwnd, hwnd)
'Debug.Assert procOld <> 0
' SPM - in this version I am allowing more than one class to
' make a subclass to the same hWnd and Msg. Why am I doing
' this? Well say the class in question is a control, and it
' wants to subclass its container. In this case, we want
' all instances of the control on the form to receive the
' form notification message.
' Get the number of instances for this msg/hwnd:
bCalled = False
iPC = GetProp(hwnd, hwnd & "#" & iMsg & "C")
If (iPC > 0) Then
' For each instance attached to this msg/hwnd, call the subclass:
For iP = 1 To iPC
bNoProcess = False
' Get the object pointer from the message
pSubclass = GetProp(hwnd, hwnd & "#" & iMsg & "#" & iP)
If pSubclass = 0 Then
' This message not handled, so pass on to old procedure
WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
wParam, ByVal lParam)
bNoProcess = True
End If
If Not (bNoProcess) Then
' Turn the pointer into an illegal, uncounted interface
CopyMemory iwpT, pSubclass, 4
' Do NOT hit the End button here! You will crash!
' Assign to legal reference
Set iwp = iwpT
' Still do NOT hit the End button here! You will still crash!
' Destroy the illegal reference
CopyMemory iwpT, 0&, 4
' OK, hit the End button if you must--you'll probably still crash,
' but it will be because of the subclass, not the uncounted reference
' Store the current message, so the client can check it:
m_iCurrentMessage = iMsg
m_iProcOld = procOld
' Use the interface to call back to the class
With iwp
' Preprocess (only check this the first time around):
If (iP = 1) Then
If .MsgResponse = emrPreprocess Then
If Not (bCalled) Then
WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
wParam, ByVal lParam)
bCalled = True
End If
End If
End If
' Consume (this message is always passed to all control
' instances regardless of whether any single one of them
' requests to consume it):
WindowProc = .WindowProc(hwnd, iMsg, wParam, ByVal lParam)
' PostProcess (only check this the last time around):
If (iP = iPC) Then
If .MsgResponse = emrPostProcess Then
If Not (bCalled) Then
WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
wParam, ByVal lParam)
bCalled = True
End If
End If
End If
End With
End If
Next iP
Else
' This message not handled, so pass on to old procedure
WindowProc = CallWindowProc(procOld, hwnd, iMsg, _
wParam, ByVal lParam)
End If
End Function
Public Function CallOldWindowProc( _
ByVal hwnd As Long, _
ByVal iMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long _
) As Long
CallOldWindowProc = CallWindowProc(m_iProcOld, hwnd, iMsg, wParam, lParam)
End Function
' Cheat! Cut and paste from MWinTool rather than reusing
' file because reusing file would cause many unneeded dependencies
Function IsWindowLocal(ByVal hwnd As Long) As Boolean
Dim idWnd As Long
Call GetWindowThreadProcessId(hwnd, idWnd)
IsWindowLocal = (idWnd = GetCurrentProcessId())
End Function
'