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
RevLine 
[473]1'Classes/ActiveBasic/Windows/UI/Control.ab
2
[545]3#require <Classes/ActiveBasic/Windows/UI/EventArgs.ab>
[575]4#require <Classes/ActiveBasic/COM/ComClassBase.ab>
[473]5
6Namespace ActiveBasic
7Namespace Windows
8Namespace UI
9
[564]10Namespace Detail
11 TypeDef PTrackMouseEvent = *Function(ByRef tme As TRACKMOUSEEVENT) As BOOL
12End Namespace
[542]13
[615]14/*
15@brief Windowsのウィンドウを管理する基底クラス
16@auther Egtra
17*/
[473]18Class Control
[547]19 Inherits WindowHandle
[575]20 Implements ActiveBasic.COM.InterfaceQuerable
[473]21Public
[551]22 /*!
23 @brief Destroyイベントよりも後に呼ばれるDestroyイベントデリゲート
24 @date 2008/07/16
25 */
26 finalDestroy As ActiveBasic.Windows.UI.Handler
[547]27
[473]28 Sub Control()
[575]29 comImpl = New COM.ComClassDelegationImpl(This)
[473]30 End Sub
31
[542]32 Function Handle() As HWND
33 Handle = hwnd
[473]34 End Function
35
[575]36 /*!
37 @brief HWNDからControlインスタンスを取得する。
38 @param[in] hwnd 対象のウィンドウハンドル
39 @return 対応するControlインスタンス。ただし、存在しなければNothing。
40 */
[473]41 Static Function FromHWnd(hwnd As HWND) As Control
42 FromHWnd = Nothing
[547]43 If _System_IsWindow(hwnd) Then
[473]44 FromHWnd = FromHWndCore(hwnd)
45 End If
46 End Function
47
48Private
49 Static Function FromHWndCore(hwnd As HWND) As Control
[575]50 FromHWndCore = _System_PtrObj(GetProp(hwnd, PropertyInstance As ULONG_PTR As PCTSTR) As VoidPtr) As Control
[473]51 End Function
52
53'--------------------------------
[544]54' ウィンドウ作成
[542]55
[473]56Public
[575]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)
[473]63 Dim cs As CREATESTRUCT
[551]64 With cs
65 .dwExStyle = exStyle
66 .lpszClass = (atom As ULONG_PTR) As LPCTSTR
67 .lpszName = 0
[575]68 .style = style
[561]69 .x = 0
70 .y = 0
71 .cx = 0
72 .cy = 0
[551]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
[473]81 GetCreateStruct(cs)
[551]82 createImpl(cs, parent)
[547]83 End Sub
[473]84
[575]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)
[551]91 End Sub
[575]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
[615]104
[473]105Protected
106 Abstract Sub GetCreateStruct(ByRef cs As CREATESTRUCT)
107
[551]108 Sub createImpl(ByRef cs As CREATESTRUCT, parent As Control)
[615]109 throwIfAlreadyCreated()
[559]110
[542]111 StartWndProc()
112
[473]113 With cs
[561]114 'よそのウィンドウクラスの場合、WndProcFirst内のhwndの代入が行われないため、ここでも行っておく。
[551]115 hwnd = CreateWindowEx(.dwExStyle, .lpszClass, .lpszName, .style,
[473]116 .x, .y, .cx, .cy, .hwndParent, .hMenu, .hInstance, .lpCreateParams)
[547]117 If hwnd = 0 Then
[615]118 ThrowWithLastErrorNT("Control.CreateEx")
[547]119 End If
[551]120
121 If IsNothing(FromHWndCore(hwnd)) <> False Then
[575]122 AssociateHWnd(hwnd)
[551]123 TlsSetValue(tlsIndex, 0)
124 End If
[473]125 End With
[551]126
127 If IsNothing(parent) = False Then
128 RegisterUnassociateHWnd(parent)
129 End If
[547]130 End Sub
[473]131
[615]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
[473]150'--------------------------------
151' ウィンドウプロシージャ
152'Protected
153Public
154 Virtual Function WndProc(msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
[551]155 Dim h = Nothing As MessageHandler
[542]156 Dim b = messageMap.TryGetValue(Hex$(msg), h)
157 If b Then
158 If Not IsNothing(h) Then
[551]159 Dim a = New MessageArgs(hwnd, msg, wp, lp)
[542]160 h(This, a)
[615]161 If a.Handled Then
162 WndProc = a.LResult
163 Exit Function
164 End If
[542]165 End If
166 End If
167 WndProc = DefWndProc(msg, wp, lp)
[473]168 End Function
169
170 Virtual Function DefWndProc(msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
[615]171 If prevWndProc Then
172 DefWndProc = CallWindowProc(prevWndProc, hwnd, msg, wp, lp)
173 Else
174 DefWndProc = DefWindowProc(hwnd, msg, wp, lp)
175 End If
[473]176 End Function
177
178Private
[551]179 Static Function makeKeysFormMsg(e As MessageArgs) As Keys
[473]180 Dim t As DWord
[544]181 t = e.WParam And Keys.KeyCode
[473]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
[544]185 makeKeysFormMsg = t As Keys
[473]186 End Function
187
[551]188 Static Function makeMouseEventFromMsg(e As MessageArgs) As MouseArgs
[544]189 Dim wp = e.WParam
190 Dim lp = e.LParam
[551]191 makeMouseEventFromMsg = New MouseArgs(LOWORD(wp) As MouseButtons, 1, GET_X_LPARAM(lp), GET_Y_LPARAM(lp), 0)
[542]192 End Function
[473]193
[575]194Protected
[542]195 /*!
[575]196 @brief 最初にウィンドウプロシージャを使うための前処理を行う関数
[542]197 @date 2008/07/11
[575]198 WndProcFirstを使うときは、この関数を呼んでおく必要がある。
[542]199 */
200 Sub StartWndProc()
[575]201 TlsSetValue(tlsIndex, ObjPtr(This))
202 registerStandardEvent()
203 End Sub
204Private
205 /*!
206 @brief 主なメッセージハンドラの登録を行う関数
207 @date 2008/08/02
208 */
209 Sub registerStandardEvent()
[547]210 AddMessageEvent(WM_ERASEBKGND, AddressOf(OnEraseBackground))
[551]211 Dim md = New MessageHandler(AddressOf(OnMouseDownBase))
[542]212 AddMessageEvent(WM_LBUTTONDOWN, md)
213 AddMessageEvent(WM_RBUTTONDOWN, md)
214 AddMessageEvent(WM_MBUTTONDOWN, md)
215 AddMessageEvent(WM_XBUTTONDOWN, md)
[551]216 Dim mu = New MessageHandler(AddressOf(OnMouseUpBase))
[542]217 AddMessageEvent(WM_LBUTTONUP, mu)
218 AddMessageEvent(WM_RBUTTONUP, mu)
219 AddMessageEvent(WM_MBUTTONUP, mu)
220 AddMessageEvent(WM_XBUTTONUP, mu)
[551]221 Dim mb = New MessageHandler(AddressOf(OnMouseDblClkBase))
[564]222 AddMessageEvent(WM_LBUTTONDBLCLK, mb)
223 AddMessageEvent(WM_RBUTTONDBLCLK, mb)
224 AddMessageEvent(WM_MBUTTONDBLCLK, mb)
225 AddMessageEvent(WM_XBUTTONDBLCLK, mb)
[544]226
[547]227 AddMessageEvent(WM_MOUSEMOVE, AddressOf(OnMouseMoveBase))
228 AddMessageEvent(WM_MOUSELEAVE, AddressOf(OnMouseLeaveBase))
[564]229 AddMessageEvent(WM_MOUSEHOVER, AddressOf(OnMouseHoverBase))
[547]230 AddMessageEvent(WM_PAINT, AddressOf(OnPaintBase))
231 AddMessageEvent(WM_KEYDOWN, AddressOf(OnKeyDownBase))
[551]232 AddMessageEvent(WM_CHAR, AddressOf(OnChar))
[547]233 AddMessageEvent(WM_CREATE, AddressOf(OnCreateBase))
[561]234 AddMessageEvent(WM_SIZE, AddressOf(OnSize))
[542]235 End Sub
[473]236
[551]237 Sub OnEraseBackground(sender As Object, e As MessageArgs)
[615]238 Dim a = New PaintBackgroundArgs(e.WParam, e.LParam)
239 e.Handled = e.Handled And OnPaintBackground(a)
240 e.LResult = a.Painted
[542]241 End Sub
[473]242
[551]243 Sub OnMouseDownBase(sender As Object, e As MessageArgs)
[615]244 e.Handled = e.Handled And OnMouseDown(makeMouseEventFromMsg(e))
[542]245 End Sub
246
[551]247 Sub OnMouseUpBase(sender As Object, e As MessageArgs)
[544]248 Dim me = makeMouseEventFromMsg(e)
[542]249 If doubleClickFired = False Then
[561]250 OnClick(Args.Empty)
[542]251 OnMouseClick(me)
[575]252 Else
[542]253 doubleClickFired = False
254 End If
[615]255 e.Handled = e.Handled And OnMouseUp(me)
[542]256 End Sub
257
[551]258 Sub OnMouseDblClkBase(sender As Object, e As MessageArgs)
[544]259 Dim me = makeMouseEventFromMsg(e)
[542]260 doubleClickFired = True
261 OnMouseDown(me)
[561]262 OnDoubleClick(Args.Empty)
[615]263 e.Handled = e.Handled And OnMouseDoubleClick(me)
[542]264 End Sub
265
[551]266 Sub OnMouseMoveBase(sender As Object, e As MessageArgs)
[544]267 Dim me = makeMouseEventFromMsg(e)
[575]268 If mouseEntered = False Then
[542]269 mouseEntered = True
270 OnMouseEnter(me)
[564]271 trackMouseEvent(TME_LEAVE Or TME_HOVER)
[542]272 End If
[615]273 e.Handled = e.Handled And OnMouseMove(me)
[542]274 End Sub
275
[551]276 Sub OnMouseLeaveBase(sender As Object, e As MessageArgs)
[615]277 e.Handled = e.Handled And OnMouseLeave(Args.Empty)
[544]278 mouseEntered = False
279 End Sub
280
[564]281 Sub OnMouseHoverBase(sender As Object, e As MessageArgs)
282 Dim me = makeMouseEventFromMsg(e)
[615]283 e.Handled = e.Handled And OnMouseHover(me)
[564]284 End Sub
285
[551]286 Sub OnPaintBase(sender As Object, e As MessageArgs)
[615]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
[542]298 End Sub
299
[551]300 Sub OnKeyDownBase(sender As Object, e As MessageArgs)
[615]301 e.Handled = e.Handled And OnKeyDown(New KeyArgs(makeKeysFormMsg(e)))
[544]302 End Sub
[542]303
[551]304 Sub OnKeyUpBase(sender As Object, e As MessageArgs)
[615]305 e.Handled = e.Handled And OnKeyUp(New KeyArgs(makeKeysFormMsg(e)))
[544]306 End Sub
307
[551]308 Sub OnChar(sender As Object, e As MessageArgs)
[615]309 e.Handled = e.Handled And OnKeyPress(New KeyPressArgs(e.WParam As Char))
[551]310 End Sub
[544]311
[551]312 Sub OnCreateBase(sender As Object, e As MessageArgs)
[615]313 e.Handled = e.Handled And OnCreate(New CreateArgs(e.LParam As *CREATESTRUCT))
[544]314 End Sub
315
[561]316 Sub OnSize(sender As Object, e As MessageArgs)
[615]317 e.Handled = e.Handled And OnResize(New ResizeArgs(e.WParam, e.LParam))
[561]318 End Sub
319
[551]320 messageMap As System.Collections.Generic.Dictionary<Object /*DWord*/, MessageHandler>
[542]321
322Public
[473]323 /*!
[542]324 @biref メッセージイベントハンドラを登録する。
[473]325 @date 2007/12/04
326 */
[551]327 Sub AddMessageEvent(message As DWord, h As MessageHandler)
[542]328 If Not IsNothing(h) Then
329 If IsNothing(messageMap) Then
[551]330 messageMap = New System.Collections.Generic.Dictionary<Object, MessageHandler>
[542]331 End If
332 Dim msg = Hex$(message)
[551]333 Dim m = Nothing As MessageHandler
[542]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
[473]340 End Sub
341
[542]342 /*!
343 @biref メッセージイベントハンドラ登録を解除する。
344 @date 2007/12/04
345 */
[551]346 Sub RemoveMessageEvent(message As DWord, a As MessageHandler)
[542]347 If Not IsNothing(a) Then
348 If Not IsNothing(messageMap) Then
[545]349 Dim msg = Hex$(message)
[542]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
[473]362'--------
363'イベント
364
365#include "ControlEvent.sbp"
366
367'--------------------------------
[544]368' インスタンスメンバ変数
[473]369Private
[542]370 /*!
[615]371 @brief サブクラス化前のウィンドウプロシージャ
372 @date 2008/08/23
373 サブクラス化していなければNULL
374 */
375 prevWndProc As WNDPROC
376 /*!
[542]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
[473]387
388'--------------------------------
[544]389' 初期ウィンドウクラス
[575]390Protected
[473]391 Static Function WndProcFirst(hwnd As HWND, msg As DWord, wp As WPARAM, lp As LPARAM) As LRESULT
392 Imports System.Runtime.InteropServices
393
[547]394 Dim rThis = FromHWndCore(hwnd)
[473]395 If IsNothing(rThis) Then
[575]396 rThis = _System_PtrObj(TlsGetValue(tlsIndex)) As Control
[473]397 TlsSetValue(tlsIndex, 0)
[575]398 If IsNothing(rThis) Then
[473]399 Goto *InstanceIsNotFound
400 End If
401 ' ウィンドウが作られて最初にWndProcFirstが呼ばれたとき
[575]402 rThis.AssociateHWnd(hwnd)
[473]403 End If
404 If msg = WM_NCDESTROY Then
[551]405 rThis.UnassociateHWnd()
[615]406 rThis.hwnd = 0
[473]407 End If
[551]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)
[473]413 Exit Function
414
415 *InstanceIsNotFound
[615]416 Dim err = String.Concat("Control.WndProcFirst: The attached instance is not found. msg = &h",
417 Hex$(msg), Ex"\r\n")
[559]418 OutputDebugString(ToTCStr(err))
[473]419 WndProcFirst = DefWindowProc(hwnd, msg, wp, lp)
420 End Function
[542]421
[551]422 /*!
423 @brief Controlインスタンスとウィンドウハンドルを結び付ける。
[615]424 @param[in] hwndNew 結び付けるウィンドウハンドル
[551]425 @date 2008/07/16
[575]426 これを実行することで、UnassociateHWndされるまでControlがGC対象にならなくなると共に、
427 FromHWndでウィンドウハンドルからControlインスタンスの取得が可能となる。
[551]428 */
[615]429 Sub AssociateHWnd(hwndNew As HWND)
430 hwnd = hwndNew
431 Prop[PropertyInstance] = ObjPtr(This) As HANDLE
[575]432 comImpl.AddRef()
[559]433 End Sub
[551]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()
[615]454 hwnd = 0
[551]455 End Sub
456
457 Sub UnassociateHWnd()
[575]458 comImpl.Release()
[551]459 End Sub
460
[473]461' Static Const WM_CONTROL_INVOKE = WM_USER + 0 As DWord
462' Static Const WM_CONTROL_BEGININVOKE = WM_USER + 1 As DWord
463
464'--------------------------------
[575]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
[615]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 */
[575]481 comImpl As COM.ComClassDelegationImpl
[615]482
[575]483'--------------------------------
[542]484' その他の補助関数
485Private
[564]486 Function trackMouseEvent(flags As DWord) As BOOL
487 If pTrackMouseEvent <> 0 Then
[542]488 Dim tme As TRACKMOUSEEVENT
489 With tme
490 .cbSize = Len(tme)
[564]491 .dwFlags = flags
492 .hwndTrack = hwnd
[542]493 .dwHoverTime = HOVER_DEFAULT
494 End With
[564]495 trackMouseEvent = pTrackMouseEvent(tme)
[542]496 End If
[564]497 End Function
[542]498
499'--------------------------------
[544]500' 初期化終了関連(特にウィンドウクラス)
[542]501Private
[473]502 'ウィンドウ作成時にウィンドウプロシージャへThisを伝えるためのもの
503 Static tlsIndex As DWord
504
505 Static hInstance As HINSTANCE
506 Static atom As ATOM
[542]507 Static hmodComctl As HMODULE
[564]508 Static pTrackMouseEvent As Detail.PTrackMouseEvent
[473]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
[564]516 hmodComctl = LoadLibrary("comctl32.dll")
517 pTrackMouseEvent = GetProcAddress(hmodComctl, ToMBStr("_TrackMouseEvent")) As Detail.PTrackMouseEvent
[473]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()
[542]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
[564]554 If hmodComctl <> 0 Then
555 FreeLibrary(hmodComctl)
556 End If
[542]557 If PropertyInstance <> 0 Then
558 GlobalDeleteAtom(PropertyInstance)
559 End If
[473]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.