source: trunk/ab5.0/ablib/src/Classes/ActiveBasic/Windows/UI/Control.ab@ 615

Last change on this file since 615 was 615, checked in by イグトランス (egtra), 16 years ago

サブクラス化機構(Control.Attach)の整備

File size: 15.8 KB
Line 
1'Classes/ActiveBasic/Windows/UI/Control.ab
2
3#require <Classes/ActiveBasic/Windows/UI/EventArgs.ab>
4#require <Classes/ActiveBasic/COM/ComClassBase.ab>
5
6Namespace ActiveBasic
7Namespace Windows
8Namespace UI
9
10Namespace Detail
11 TypeDef PTrackMouseEvent = *Function(ByRef tme As TRACKMOUSEEVENT) As BOOL
12End Namespace
13
14/*
15@brief Windowsのウィンドウを管理する基底クラス
16@auther Egtra
17*/
18Class Control
19 Inherits WindowHandle
20 Implements ActiveBasic.COM.InterfaceQuerable
21Public
22 /*!
23 @brief Destroyイベントよりも後に呼ばれるDestroyイベントデリゲート
24 @date 2008/07/16
25 */
26 finalDestroy As ActiveBasic.Windows.UI.Handler
27
28 Sub Control()
29 comImpl = New COM.ComClassDelegationImpl(This)
30 End Sub
31
32 Function Handle() As HWND
33 Handle = hwnd
34 End Function
35
36 /*!
37 @brief HWNDからControlインスタンスを取得する。
38 @param[in] hwnd 対象のウィンドウハンドル
39 @return 対応するControlインスタンス。ただし、存在しなければNothing。
40 */
41 Static Function FromHWnd(hwnd As HWND) As Control
42 FromHWnd = Nothing
43 If _System_IsWindow(hwnd) Then
44 FromHWnd = FromHWndCore(hwnd)
45 End If
46 End Function
47
48Private
49 Static Function FromHWndCore(hwnd As HWND) As Control
50 FromHWndCore = _System_PtrObj(GetProp(hwnd, PropertyInstance As ULONG_PTR As PCTSTR) As VoidPtr) As Control
51 End Function
52
53'--------------------------------
54' ウィンドウ作成
55
56Public
57 /*!
58 @brief ウィンドウを作成する(詳細版)。
59 @date 2008/08/02
60 通常はCreateやCreateFormその他を使ってください。
61 */
62 Sub CreateEx(parent As Control, style As DWord, exStyle As DWord, hmenu As HMENU)
63 Dim cs As CREATESTRUCT
64 With cs
65 .dwExStyle = exStyle
66 .lpszClass = (atom As ULONG_PTR) As LPCTSTR
67 .lpszName = 0
68 .style = style
69 .x = 0
70 .y = 0
71 .cx = 0
72 .cy = 0
73 If IsNothing(parent) Then
74 .hwndParent = 0
75 Else
76 .hwndParent = parent As HWND
77 End If
78 .hMenu = hmenu
79 .hInstance = hInstance
80 End With
81 GetCreateStruct(cs)
82 createImpl(cs, parent)
83 End Sub
84
85 /*!
86 @brief ウィンドウを作成する(子ウィンドウ以外)。
87 @date 2008/08/02
88 */
89 Sub CreateForm(style As DWord, exStyle As DWord, owner = Nothing As Control, hmenu = 0 As HMENU)
90 CreateEx(owner, style, exStyle, hmenu)
91 End Sub
92
93 Sub CreateForm()
94 CreateEx(Nothing, 0, 0, 0)
95 End Sub
96
97 /*!
98 @brief 子ウィンドウを作成する。
99 @date 2008/08/02
100 */
101 Sub Create(parent As Control, style = 0 As DWord, exStyle = 0 As DWord, id = 0 As Long)
102 CreateEx(parent, style Or WS_CHILD Or WS_VISIBLE, exStyle, id As ULONG_PTR As HMENU)
103 End Sub
104
105Protected
106 Abstract Sub GetCreateStruct(ByRef cs As CREATESTRUCT)
107
108 Sub createImpl(ByRef cs As CREATESTRUCT, parent As Control)
109 throwIfAlreadyCreated()
110
111 StartWndProc()
112
113 With cs
114 'よそのウィンドウクラスの場合、WndProcFirst内のhwndの代入が行われないため、ここでも行っておく。
115 hwnd = CreateWindowEx(.dwExStyle, .lpszClass, .lpszName, .style,
116 .x, .y, .cx, .cy, .hwndParent, .hMenu, .hInstance, .lpCreateParams)
117 If hwnd = 0 Then
118 ThrowWithLastErrorNT("Control.CreateEx")
119 End If
120
121 If IsNothing(FromHWndCore(hwnd)) <> False Then
122 AssociateHWnd(hwnd)
123 TlsSetValue(tlsIndex, 0)
124 End If
125 End With
126
127 If IsNothing(parent) = False Then
128 RegisterUnassociateHWnd(parent)
129 End If
130 End Sub
131
132Public
133 Sub Attach(hwndNew As HWND)
134 throwIfAlreadyCreated()
135 If hwndNew = 0 Then
136 Throw New System.ArgumentNullException("Control.Attach")
137 End If
138 registerStandardEvent()
139 AssociateHWnd(hwndNew)
140 prevWndProc = SetWindowLongPtr(GWLP_WNDPROC, AddressOf(WndProcFirst) As LONG_PTR) As WNDPROC
141 End Sub
142
143Private
144 Sub throwIfAlreadyCreated()
145 If hwnd <> 0 Then
146 Throw New System.InvalidOperationException("Window already created.")
147 End If
148 End Sub
149
150'--------------------------------
151' ウィンドウプロシージャ
152'Protected
153Public
154 Virtual Function WndProc(msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
155 Dim h = Nothing As MessageHandler
156 Dim b = messageMap.TryGetValue(Hex$(msg), h)
157 If b Then
158 If Not IsNothing(h) Then
159 Dim a = New MessageArgs(hwnd, msg, wp, lp)
160 h(This, a)
161 If a.Handled Then
162 WndProc = a.LResult
163 Exit Function
164 End If
165 End If
166 End If
167 WndProc = DefWndProc(msg, wp, lp)
168 End Function
169
170 Virtual Function DefWndProc(msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
171 If prevWndProc Then
172 DefWndProc = CallWindowProc(prevWndProc, hwnd, msg, wp, lp)
173 Else
174 DefWndProc = DefWindowProc(hwnd, msg, wp, lp)
175 End If
176 End Function
177
178Private
179 Static Function makeKeysFormMsg(e As MessageArgs) As Keys
180 Dim t As DWord
181 t = e.WParam And Keys.KeyCode
182 t Or= (GetKeyState(VK_SHIFT) As Word And &h8000) << 1
183 t Or= (GetKeyState(VK_CONTROL) As Word And &h8000) << 2
184 t Or= (GetKeyState(VK_MENU) As Word And &h8000) << 3
185 makeKeysFormMsg = t As Keys
186 End Function
187
188 Static Function makeMouseEventFromMsg(e As MessageArgs) As MouseArgs
189 Dim wp = e.WParam
190 Dim lp = e.LParam
191 makeMouseEventFromMsg = New MouseArgs(LOWORD(wp) As MouseButtons, 1, GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 0)
192 End Function
193
194Protected
195 /*!
196 @brief 最初にウィンドウプロシージャを使うための前処理を行う関数
197 @date 2008/07/11
198 WndProcFirstを使うときは、この関数を呼んでおく必要がある。
199 */
200 Sub StartWndProc()
201 TlsSetValue(tlsIndex, ObjPtr(This))
202 registerStandardEvent()
203 End Sub
204Private
205 /*!
206 @brief 主なメッセージハンドラの登録を行う関数
207 @date 2008/08/02
208 */
209 Sub registerStandardEvent()
210 AddMessageEvent(WM_ERASEBKGND, AddressOf(OnEraseBackground))
211 Dim md = New MessageHandler(AddressOf(OnMouseDownBase))
212 AddMessageEvent(WM_LBUTTONDOWN, md)
213 AddMessageEvent(WM_RBUTTONDOWN, md)
214 AddMessageEvent(WM_MBUTTONDOWN, md)
215 AddMessageEvent(WM_XBUTTONDOWN, md)
216 Dim mu = New MessageHandler(AddressOf(OnMouseUpBase))
217 AddMessageEvent(WM_LBUTTONUP, mu)
218 AddMessageEvent(WM_RBUTTONUP, mu)
219 AddMessageEvent(WM_MBUTTONUP, mu)
220 AddMessageEvent(WM_XBUTTONUP, mu)
221 Dim mb = New MessageHandler(AddressOf(OnMouseDblClkBase))
222 AddMessageEvent(WM_LBUTTONDBLCLK, mb)
223 AddMessageEvent(WM_RBUTTONDBLCLK, mb)
224 AddMessageEvent(WM_MBUTTONDBLCLK, mb)
225 AddMessageEvent(WM_XBUTTONDBLCLK, mb)
226
227 AddMessageEvent(WM_MOUSEMOVE, AddressOf(OnMouseMoveBase))
228 AddMessageEvent(WM_MOUSELEAVE, AddressOf(OnMouseLeaveBase))
229 AddMessageEvent(WM_MOUSEHOVER, AddressOf(OnMouseHoverBase))
230 AddMessageEvent(WM_PAINT, AddressOf(OnPaintBase))
231 AddMessageEvent(WM_KEYDOWN, AddressOf(OnKeyDownBase))
232 AddMessageEvent(WM_CHAR, AddressOf(OnChar))
233 AddMessageEvent(WM_CREATE, AddressOf(OnCreateBase))
234 AddMessageEvent(WM_SIZE, AddressOf(OnSize))
235 End Sub
236
237 Sub OnEraseBackground(sender As Object, e As MessageArgs)
238 Dim a = New PaintBackgroundArgs(e.WParam, e.LParam)
239 e.Handled = e.Handled And OnPaintBackground(a)
240 e.LResult = a.Painted
241 End Sub
242
243 Sub OnMouseDownBase(sender As Object, e As MessageArgs)
244 e.Handled = e.Handled And OnMouseDown(makeMouseEventFromMsg(e))
245 End Sub
246
247 Sub OnMouseUpBase(sender As Object, e As MessageArgs)
248 Dim me = makeMouseEventFromMsg(e)
249 If doubleClickFired = False Then
250 OnClick(Args.Empty)
251 OnMouseClick(me)
252 Else
253 doubleClickFired = False
254 End If
255 e.Handled = e.Handled And OnMouseUp(me)
256 End Sub
257
258 Sub OnMouseDblClkBase(sender As Object, e As MessageArgs)
259 Dim me = makeMouseEventFromMsg(e)
260 doubleClickFired = True
261 OnMouseDown(me)
262 OnDoubleClick(Args.Empty)
263 e.Handled = e.Handled And OnMouseDoubleClick(me)
264 End Sub
265
266 Sub OnMouseMoveBase(sender As Object, e As MessageArgs)
267 Dim me = makeMouseEventFromMsg(e)
268 If mouseEntered = False Then
269 mouseEntered = True
270 OnMouseEnter(me)
271 trackMouseEvent(TME_LEAVE Or TME_HOVER)
272 End If
273 e.Handled = e.Handled And OnMouseMove(me)
274 End Sub
275
276 Sub OnMouseLeaveBase(sender As Object, e As MessageArgs)
277 e.Handled = e.Handled And OnMouseLeave(Args.Empty)
278 mouseEntered = False
279 End Sub
280
281 Sub OnMouseHoverBase(sender As Object, e As MessageArgs)
282 Dim me = makeMouseEventFromMsg(e)
283 e.Handled = e.Handled And OnMouseHover(me)
284 End Sub
285
286 Sub OnPaintBase(sender As Object, e As MessageArgs)
287 If ActiveBasic.IsNothing(paintDC) Then
288 e.Handled = False
289 Else
290 Dim ps As PAINTSTRUCT
291 BeginPaint(ps)
292 Try
293 OnPaintDC(New PaintDCArgs(ps.hdc, ps.rcPaint))
294 Finally
295 EndPaint(ps)
296 End Try
297 End If
298 End Sub
299
300 Sub OnKeyDownBase(sender As Object, e As MessageArgs)
301 e.Handled = e.Handled And OnKeyDown(New KeyArgs(makeKeysFormMsg(e)))
302 End Sub
303
304 Sub OnKeyUpBase(sender As Object, e As MessageArgs)
305 e.Handled = e.Handled And OnKeyUp(New KeyArgs(makeKeysFormMsg(e)))
306 End Sub
307
308 Sub OnChar(sender As Object, e As MessageArgs)
309 e.Handled = e.Handled And OnKeyPress(New KeyPressArgs(e.WParam As Char))
310 End Sub
311
312 Sub OnCreateBase(sender As Object, e As MessageArgs)
313 e.Handled = e.Handled And OnCreate(New CreateArgs(e.LParam As *CREATESTRUCT))
314 End Sub
315
316 Sub OnSize(sender As Object, e As MessageArgs)
317 e.Handled = e.Handled And OnResize(New ResizeArgs(e.WParam, e.LParam))
318 End Sub
319
320 messageMap As System.Collections.Generic.Dictionary<Object /*DWord*/, MessageHandler>
321
322Public
323 /*!
324 @biref メッセージイベントハンドラを登録する。
325 @date 2007/12/04
326 */
327 Sub AddMessageEvent(message As DWord, h As MessageHandler)
328 If Not IsNothing(h) Then
329 If IsNothing(messageMap) Then
330 messageMap = New System.Collections.Generic.Dictionary<Object, MessageHandler>
331 End If
332 Dim msg = Hex$(message)
333 Dim m = Nothing As MessageHandler
334 If messageMap.TryGetValue(msg, m) Then
335 messageMap.Item[msg] = m + h
336 Else
337 messageMap.Item[msg] = h
338 End If
339 End If
340 End Sub
341
342 /*!
343 @biref メッセージイベントハンドラ登録を解除する。
344 @date 2007/12/04
345 */
346 Sub RemoveMessageEvent(message As DWord, a As MessageHandler)
347 If Not IsNothing(a) Then
348 If Not IsNothing(messageMap) Then
349 Dim msg = Hex$(message)
350 Dim m = messageMap.Item[msg]
351 If Not IsNothing(m) Then
352 messageMap.Item[msg] = m - a
353 End If
354 End If
355 End If
356 End Sub
357
358'--------------------------------
359' ウィンドウメッセージ処理
360
361
362'--------
363'イベント
364
365#include "ControlEvent.sbp"
366
367'--------------------------------
368' インスタンスメンバ変数
369Private
370 /*!
371 @brief サブクラス化前のウィンドウプロシージャ
372 @date 2008/08/23
373 サブクラス化していなければNULL
374 */
375 prevWndProc As WNDPROC
376 /*!
377 @brief マウスカーソルがクライアント領域に入っているかどうかのフラグ
378 外から中に入ってきたときにMouseEnterイベントを発生させるために用意している。
379 */
380 mouseEntered As Boolean
381 /*!
382 @brief ダブルクリックが起こったかどうかのフラグ
383 Click/MouseClickイベントのために用意している。
384 @date 2008/07/12
385 */
386 doubleClickFired As Boolean
387
388'--------------------------------
389' 初期ウィンドウクラス
390Protected
391 Static Function WndProcFirst(hwnd As HWND, msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
392 Imports System.Runtime.InteropServices
393
394 Dim rThis = FromHWndCore(hwnd)
395 If IsNothing(rThis) Then
396 rThis = _System_PtrObj(TlsGetValue(tlsIndex)) As Control
397 TlsSetValue(tlsIndex, 0)
398 If IsNothing(rThis) Then
399 Goto *InstanceIsNotFound
400 End If
401 ' ウィンドウが作られて最初にWndProcFirstが呼ばれたとき
402 rThis.AssociateHWnd(hwnd)
403 End If
404 If msg = WM_NCDESTROY Then
405 rThis.UnassociateHWnd()
406 rThis.hwnd = 0
407 End If
408 If msg = WM_DESTROY And IsNothing(rThis.finalDestroy) = False Then
409 Dim f = rThis.finalDestroy
410 f(rThis, Args.Empty)
411 End If
412 WndProcFirst = rThis.WndProc(msg, wp, lp)
413 Exit Function
414
415 *InstanceIsNotFound
416 Dim err = String.Concat("Control.WndProcFirst: The attached instance is not found. msg = &h",
417 Hex$(msg), Ex"\r\n")
418 OutputDebugString(ToTCStr(err))
419 WndProcFirst = DefWindowProc(hwnd, msg, wp, lp)
420 End Function
421
422 /*!
423 @brief Controlインスタンスとウィンドウハンドルを結び付ける。
424 @param[in] hwndNew 結び付けるウィンドウハンドル
425 @date 2008/07/16
426 これを実行することで、UnassociateHWndされるまでControlがGC対象にならなくなると共に、
427 FromHWndでウィンドウハンドルからControlインスタンスの取得が可能となる。
428 */
429 Sub AssociateHWnd(hwndNew As HWND)
430 hwnd = hwndNew
431 Prop[PropertyInstance] = ObjPtr(This) As HANDLE
432 comImpl.AddRef()
433 End Sub
434
435 /*!
436 @brief オーナーの破棄時にこのControlインスタンスとウィンドウハンドルの結び付けを解除するよう登録する。
437 @param[in] owner 結び付けの解除を連動させるControl
438 @date 2008/07/16
439 ownerのFinalDestoryでこのControlとHWNDとの結び付けを解除する。
440 */
441 Sub RegisterUnassociateHWnd(owner As Control)
442 If IsNothing(owner) = False Then
443 Dim e = New Handler(AddressOf(UnassociateHWndOnEvent))
444 If IsNothing(finalDestroy) Then
445 owner.finalDestroy = e
446 Else
447 owner.finalDestroy += e
448 End If
449 End If
450 End Sub
451
452 Sub UnassociateHWndOnEvent(sender As Object, e As Args)
453 UnassociateHWnd()
454 hwnd = 0
455 End Sub
456
457 Sub UnassociateHWnd()
458 comImpl.Release()
459 End Sub
460
461' Static Const WM_CONTROL_INVOKE = WM_USER + 0 As DWord
462' Static Const WM_CONTROL_BEGININVOKE = WM_USER + 1 As DWord
463
464'--------------------------------
465' インタフェース実装
466
467Public
468 Virtual Function QueryInterfaceImpl(ByRef iid As IID, ByRef pv As VoidPtr) As HRESULT
469 QueryInterfaceImpl = E_NOTIMPL
470 End Function
471
472Private
473 /*!
474 @brief ウィンドウの寿命管理
475 Controlには次のAddRef-Releaseの対がある。
476 @li createImpl - WM_NCDESTROY(ウィンドウプロシージャがWndProcFirstの場合)
477 @li createImpl - UnassociateHWnd←UnassociateHWndOnEvent←RegisterUnassociateHWnd(その他のウィンドウクラスの場合)
478 @li Attach - WM_NCDESTROY(サブクラス化された場合)
479 なお、Control派生クラスをサブクラス化すると、後ろ2つが両方適用される。
480 */
481 comImpl As COM.ComClassDelegationImpl
482
483'--------------------------------
484' その他の補助関数
485Private
486 Function trackMouseEvent(flags As DWord) As BOOL
487 If pTrackMouseEvent <> 0 Then
488 Dim tme As TRACKMOUSEEVENT
489 With tme
490 .cbSize = Len(tme)
491 .dwFlags = flags
492 .hwndTrack = hwnd
493 .dwHoverTime = HOVER_DEFAULT
494 End With
495 trackMouseEvent = pTrackMouseEvent(tme)
496 End If
497 End Function
498
499'--------------------------------
500' 初期化終了関連(特にウィンドウクラス)
501Private
502 'ウィンドウ作成時にウィンドウプロシージャへThisを伝えるためのもの
503 Static tlsIndex As DWord
504
505 Static hInstance As HINSTANCE
506 Static atom As ATOM
507 Static hmodComctl As HMODULE
508 Static pTrackMouseEvent As Detail.PTrackMouseEvent
509
510 Static Const WindowClassName = "ActiveBasic.Windows.UI.Control"
511 Static Const PropertyInstance = 0 As ATOM
512Public
513 Static Sub Initialize(hinst As HINSTANCE)
514 tlsIndex = TlsAlloc()
515 hInstance = hinst
516 hmodComctl = LoadLibrary("comctl32.dll")
517 pTrackMouseEvent = GetProcAddress(hmodComctl, ToMBStr("_TrackMouseEvent")) As Detail.PTrackMouseEvent
518
519 Dim PropertyInstanceString = WindowClassName + " " + Hex$(GetCurrentProcessId())
520 PropertyInstance = GlobalAddAtom(ToTCStr(PropertyInstanceString))
521
522 Dim wcx As WNDCLASSEX
523 With wcx
524 .cbSize = Len (wcx)
525 .style = CS_HREDRAW Or CS_VREDRAW Or CS_DBLCLKS
526 .lpfnWndProc = AddressOf (WndProcFirst)
527 .cbClsExtra = 0
528 .cbWndExtra = 0
529 .hInstance = hinst
530 .hIcon = 0
531 .hCursor = LoadImage(0, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE Or LR_SHARED) As HCURSOR
532 .hbrBackground = 0
533 .lpszMenuName = 0
534 .lpszClassName = ToTCStr(WindowClassName)
535 .hIconSm = 0
536 End With
537 atom = RegisterClassEx(wcx)
538 If atom = 0 Then
539 Dim buf[1023] As TCHAR
540 wsprintf(buf, Ex"ActiveBasic.Windows.UI.Control.Control: RegisterClasseEx failed. Error code: &h%08X\r\n", GetLastError())
541 OutputDebugString(buf)
542 Debug
543 ExitThread(0)
544 End If
545 End Sub
546
547 Static Sub Uninitialize()
548 If atom <> 0 Then
549 UnregisterClass(atom As ULONG_PTR As PCSTR, hInstance)
550 End If
551 If tlsIndex <> 0 And tlsIndex <> &hffffffff Then
552 TlsFree(tlsIndex)
553 End If
554 If hmodComctl <> 0 Then
555 FreeLibrary(hmodComctl)
556 End If
557 If PropertyInstance <> 0 Then
558 GlobalDeleteAtom(PropertyInstance)
559 End If
560 End Sub
561End Class
562
563End Namespace 'UI
564End Namespace 'Widnows
565End Namespace 'ActiveBasic
Note: See TracBrowser for help on using the repository browser.