Tutorial 19: Tree View Control
이 번장에서는, 트리뷰와 이미지 리스트의 사용법과, 트리뷰에서의 드래그앤드롭에 대해서 설명한다.
소스 | 리소스 | 비트 맵 | 실행 결과 |
※ 컴파일이 되지 않는다면, 자신의 windows.inc 파일을 살펴봐서, TVITEMA 구조체로 선언된 멤버변수가 _mask면 소스파일의 imask 를 _mask 로 변경하길 바란다.
|
트리뷰컨트롤은 특수한 형태의 윈도우로서, 오브젝트를 계층형태로 표시한다.간단히 탐색기의 좌측 부분을 생각하면 된다. 트리뷰 컨트롤은 오브젝트의 상하관계를 표현할 때 자주 사용된다.
트리뷰컨트롤은 CreateWindowEx 함수의 클래스명에"SysTreeView32"를 지정해서 호출하든가, 다이얼로그 박스에서 사용할 수 있다.이 경우, InitCommonControls 함수를 반드시 호출해야하는 것을 잊지 말기 바란다.
트리뷰컨트롤은 자신만의 독특한 스타일이 몇가지 존재하므로, 이 스타일을 설명한다.
- TVS_HASBUTTONS
[+](확장) 와 [-](축소) 버튼이 있어서, 유저는 버튼을 눌러서 , 하위 아이템이 있다면 그것을 확장하거나 축소 할 수 있다. 트리뷰의 루트아이템(최상위 아이템)을 하위아이템의 추가 버튼으로 만들고 싶다면, TVS_LINESATROOT 도 같이 지정해주어야 한다.- TVS_HASLINES
상위 오브젝트와 하위 오브젝트의 관계를 점선으로 표시해준다.- TVS_LINESATROOT
트리뷰컨트롤의 루트아이템과 하위아이템 사이를 선으로 묶어서 표현할 때 사용한다. 이것은, TVS_HASLINES속성이 지정되어야만 효력이 있다.트리뷰컨트롤은 다른 공통컨트롤과 같이, 부모윈도우와 메세지를 교환하는 방식으로 통신하게 된다. 부모윈도우는 다양한 메세지를 트리뷰에 전송할 수 있고 트리뷰는 부모윈도우에게 「통지」메세지를 전송할 수 있다. 이런 점에서 알수 있듯이, 트리뷰컨트롤도 다른 공통컨트롤과 같은것을 알수 있다.
컨트롤에 변화가 발생하면 다음과 같은 정보를 설정해서 WM_NOTIFY메세지를 부모윈도우로 전송한다.
WM_NOTIFY
- wParam = 컨트롤 ID
이 값 유일하다는 보장을 할 수 없기 때문에 사용하지 않는다. 대신에, lParam이 NMHDR 구조체의 포인터이고, 그 멤버인 hwndFrom와 IDFrom를 사용 한다.- lParam = NMHDR 구조체의 포인터
몇가지의 컨트롤에서는 큰 구조체의 포인터가 존재 할지도 모르지만, 처음의 멤버로서 NMHDR 구조체를 가져야만 한다. 그렇기 때문에 lParam은 최소한 NMHDR 구조체의 포인터라는 것은 확실하다.그렇다면, NWHDR 구조체에 대해서 살펴보자.
NMHDR struct DWORD hwndFrom DWORD ? idFrom DWORD ? code DWORD ? NMHDR endshwndFrom는WM_NOTIFY 메세지를 전송하는 컨트롤의 윈도우 핸들이고, idFrom은 컨트롤의 ID이다. code는 부모윈도우에 전송하고 싶은 메세지다. 트리뷰 통지메세지는 「TVN_」, 트리뷰메세지는 「TVM_」이란 접두어가 된다. 트리뷰컨트롤은 NMHDR 구조체의 멤버 변수인 code에 TVN_xxxx를 설정하고, 부모윈도우는 컨트롤에게 TVM_xxxx 형식의 메세지를 전송해서 통신 하게된다.
●트리뷰컨트롤에 아이템추가
트리뷰컨트롤을 생성하면, TVM_INSERTITEM메세지를 전송해서 아이템을 추가할 수 있게 된다.
TVM_INSERTITEM
- wParam = 0
- lParam = 멤버변수의 값을 미리 설정한 상태의 TV_ITEM 구조체의 포인터
트리뷰컨트롤에서 아이템간의 관계를 나타내는 용어에 대해서 알아야 한다. 아이템은, 동시에 상위오브젝트 혹은 하위오브젝트 어디에도 속할 수 있다.상위아이템은 관련된 하위아이템을 가지는 아이템으로서, 또한 그 아이템은 동시에, 다른 아이템의 하위아이템도 될 수 있다. 부모아이템이 없는 아이템을 루트 아이템 이라 부른다. 트리뷰컨트롤에서, 루트 아이템은 한개이상도 존재할 수 있다. 그렇다면, TV_INSERTSTRUCT 구조체에 대해서 알아보자.
TV_INSERTSTRUCT STRUCT DWORD hParent DWORD ? hInsertAfter DWORD ? ITEMTYPE <> TV_INSERTSTRUCT ENDS
- hParent
상위아이템의 핸들. 이것이 TVI_ROOT, 또는 NULL이면, 아이템은 트리뷰컨트롤의 최상위에 있다는 의미이다.- hInsertAfter
새로 추가된 아이템의 핸들, 또는 다음과 같은 값을 가진다.
- TVI_FIRST
리스트에서 가장 먼저 아이템이 추가한다- TVI_LAST
리스트에서 가장 마지막에 아이템을 추가한다- TVI_SORT
알파벳순서로 리스트에 아이템을 추가한다- ITEMTYPE
공용체로서 다음과 같다.
ITEMTYPE UNION itemex TVITEMEX <> item TVITEM <> ITEMTYPE ENDSTVITEM 은 여기에서만 사용되기 때문에, TVITEM의 설명을 한다.
※ 아래의 설명은, TV_ITEM 구조체에 대한 설명이지만, 그것은 windows.inc 파일의 TVITEMA 구조체를 설명을 하는 것이라고 생각하면 된다.
TV_ITEM STRUCT DWORD imask DWORD ? hItem DWORD ? state DWORD ? stateMask DWORD ? pszText DWORD ? cchTextMax DWORD ? iImage DWORD ? iSelectedImage DWORD ? cChildren DWORD ? lParam DWORD ? TV_ITEM ENDS이 구조체는 메세지에 따라 다른 트리뷰아이템에 대해서 정보를 받거나 전송하거나 할 경우에 사용한다. 예를 들어,TVM_INSERTITEM메세지인 경우, 트리뷰컨트롤에 추가하는 아이템의 속성 정보이고, TVM_GETITEM메세지인 경우는 선택된 트리뷰아이템에 대한 정보가 설정된다.
- imask는 TV_ITEM 구조체에서 유효한 멤버가 어떤 것인지를 지정한다. 예를 들면,TVIF_TEXT라면, 멤버 pszText만 유효하게 처리된다. 이 값은 복수지정이 가능하다.
- hItem은 트리뷰아이템의 핸들이다. 각각의 아이템은, 윈도우 핸들과 같이 독자적인 핸들을 가지고 있어서 아이템을 조작하고 싶다면, 이 핸들을 사용해서 해당 아이템을 구별한다.
- pszText는 트리뷰아이템의 레이블명
- cchTextMax는 트리뷰아이템의 레이블명을 사용할 경우에만 사용된다. pszText에 버퍼의 포인터를 저장하지만, Windows는 버퍼의 크기를 모르기 때문에, 버퍼의 크기를 여기서 지정한다.
- iImage 와 iSelectedImage는 이미지 리스트의 인덱스로서, iImage는 아이템이 선택되지 않음을 나타내고, iSelectedImage는 선택되었다는 표시를 위해서 사용되는 이미지를 지정하게 된다.탐색기를 살펴보면 알 수 있을것이다. 2가지 이미지가 있다는 것을...
●트리뷰컨트롤에 이미지추가
트리뷰의 아이템 레이블의 좌측에 이미지를 추가하고 싶다면, 이미지 리스트를 생성하고, 트리뷰컨트롤과 연결시키면 된다. ImageList_Create함수를 호출해서 이미지리스트를 생성 할 수 있다.
ImageList_Create PROTO cx:DWORD, cy:DWORD, flags:DWORD, cInitial:DWORD, cGrow:DWORD생성하게 되면, 빈상태의 이미지리스트의 핸들이 반환된다.
- cx
이미지 리스트내에서 이미지의 폭(픽셀단위)- cy
이미지 리스트내에서 이미지의 높이(픽셀단위)로서, 이미지 리스트내의 이미지는 같은 크기여야만 한다. 만약에 커다란 비트맵을 사용하게되면, Windows는 cx와 cy의 사이즈에 「일렬로 배열된」것으로 간주 한다. 그래서, 같은크기로 분할된 한장의 이미지를 준비해야한다- flags
색상에 관한 플래그로서 흑백이미지 인지, 몇비트로 되어있는지 등을 지정하는 플래그로서 자세한 것은 Win32API 레퍼런스를 참고하기바란다.- cInitial
초기 상태로 몇개의 이미지가 있는지를 지정한다. Windows는 이 변수의 값을 기초로 해서 메모리를 할당한다.- cGrow
새로운 이미지를 위한 공간을 확보하기 위해 이미지 리스트의 크기를 확장할 때, 몇장의 이미지를 확보할 지를 지정한다.(초기값이후의 값)이미지 리스트는 윈도우가 아니다! 단지 다른 윈도우에서 사용되는 이미지를 위한 공간에 대한포인터이다. 이미지 리스트가 생성되면,ImageList_Add함수로 이미지를 추가할 수 있다.
ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD이 함수가 실패하면 -1 값이 반환된다.
- himl
이미지를 추가할려는 이미지 리스트의 핸들로서,ImageList_Create함수에서 반환된 값.- hbmImage
이미지 리스트에 추가하고 싶은 비트맵의 핸들. 대부분의 경우, 리소스에 비트맵을 저장하고,LoadBitmap 함수를 호출해서 로드한다. 이경우 비트맵에 몇개의 이미지가 있는지를 설명하지 않아도 된다. 왜냐하면,ImageList_Create함수의 인수인 cx와 cy로부터 추측할 수 있기 때문이다.- hbmMask
마스크가 포함된 비트맵의 핸들로서, 이미지 리스트에 마스크가 포함되지 않는다면 의미가 없다.보통, 트리뷰컨트롤을 사용할 때, 이미지 리스트에 2개의 이미지만 추가해서 사용 한다. 하나는, 트리뷰아이템이 선택될 때의 표시용이고, 다른 하나는 선택되지 않은 상태의 표시용 이미지이다..
이미지 리스트가 준비되면,TVM_SETIMAGELIST메세지를 트리뷰컨트롤에 전송해서, 이미지 리스트와 트리뷰컨트롤을 서로 연결한다.
TVM_SETIMAGELIST
- wParam = 이미지 리스트의 타입
다음과 같이 2가지가 있다.
- TVSIL_NORMAL
보통의 이미지 리스트를 의미한다. 트리뷰아이템은 선택된 이미지와 선택되지 않은 상태의 이미지가 있다- TVSIL_STATE
상태 포함 이미지 리스트를 의미한다. 프로그래머가 사용자정의한 상태로 표시되는 트리뷰아이템의 이미지가 있다.- lParam = 이미지 리스트의 핸들
●트리뷰아이템에 대한 정보 취득
TVM_GETITEM 메세지를 전송함으로써, 트리뷰아이템에 대한 정보를 얻을 수 있다.
TVM_GETITEM
- wParam = 0
- lParam = 미리 값이 설정된 TV_ITEM 구조체의 포인터
이 메세지를 전송하기 전에, TV_ITEM의 멤버를 Windows에서 설정하게 위해, imask 플래그를 설정해야 한다. 그리고, 당연하겠지만,hItem도 구하고 싶은 아이템의 핸들로 설정해 주어야 한다. 그런데 여기에는 약간의 문제가 있다. 어떻게 원하는 아이템의 핸들을 알 수 있을까? 하는 문제이다. 트리뷰의 모든 핸들을 어디엔가 저장해 둘 필요가 있다는 의미일까?
그런데 , 해결 방법은 의외로 아주 간단하다. 트리뷰컨트롤에 TVM_GETNEXTITEM 메세지를 전송함으로써, 지정한 속성을 가진 트리뷰아이템의 핸들을 얻을수 있다. 여기서 속성이라는 것은, 최초의 하위아이템, 루트 아이템, 선택된 아이템을 의미한다.
TVM_GETNEXTITEM
- wParam = 플래그
- lParam = (플래그와 일치하는) 트리뷰아이템의 핸들
wParam의 값은 아주 중요하므로, 모든 항목을 설명한다.
- TVGN_CARET
현재 선택된 아이템을 얻는다- TVGN_CHILD
hitem에 지정된 아이템의 최초의 하위아이템을 얻는다- TVGN_DROPHILITE
드래그 앤 드롭 작업의 대상이 되는 아이템을 얻는다- TVGN_FIRSTVISIBLE
가장 먼저 표시되는 아이템을 얻는다- TVGN_NEXT
다음의 동일레벨의 아이템을 얻는다- TVGN_NEXTVISIBLE
지정된 아이템 다음에 표시되는 아이템을 얻는다. 지정된 아이템은 표시되어있는 것이라야 한다. TVM_GETITEMRECT 메세지를 전송해서, 아이템이 표시상태인지를 확인할 수 있다.- TVGN_PARENT
지정된 아이템의 상위아이템을 얻는다- TVGN_PREVIOUS
한개 이전단계의 동일레벨 관계에 있는 아이템을 얻는다- TVGN_PREVIOUSVISIBLE
지정된 아이템의 앞에 있는 가장 먼저 표시되는 아이템을 얻는다. 지정된 아이템은 표시되어 있는 것이라야 한다.- TVGN_ROOT
트리뷰컨트롤의 가장 상위레벨에 있는 아이템을 얻는다.이런 플래그를 지정해서, 얻고싶은 아이템의 핸들을 구할수 있다. SendMessage함수의 가 성공적으로 수행되면 트리뷰아이템의 핸들을 반환한다. 그리고,TVM_GETITEM메세지를 사용하기 위해서, 반환된 핸들을,TV_ITEM구조체의 멤버인 hItem에 설정한다.
●트리뷰컨트롤의 드래그앤드롭 처리
이 부분이 이번장의 핵심이다. Win32API 레퍼런스에 있는 예제를 사용할려고 했지만,중요한 정보가 빈약했기 때문에 실제적인 예를 들어서 설명하겠다. 똑같은 전철을 밟지 않기 위해서, 다음과 같이 드래그 앤 드롭의 설명을 한다.
- 유저가 아이템을 드래그 하면, 트리뷰컨트롤은 TVN_BEGINDRAG 통지신호를 부모윈도우로 전송한다. 이 때, 드래그중에 표시되는 드래그 표시용 이미지를 생성한다.
트리뷰컨트롤에 TVM_CREATEDRAGIMAGE 를 전송해서, 현재 드래그중인 아이템에 표시되는 이미지를 생성했다는 통지 신호를 보낸다.
트리뷰컨트롤은 한개의 드래그 이미지에 대해서 이미지리스트를 생성하고, 이미지 리스트의 핸들을 반환한다.- 드래그 이미지를 생성하면, ImageList_BeginDrag 함수를 호출해서 이미지의 핫스팟(Windows는 이 좌표를 이미지의 좌표로서 인식한다)을 설정한다.
ImageList_BeginDrag PROTO himlTrack:DWORD, iTrack:DWORD, dxHotspot:DWORD, dyHotspot:DWORD
- himlTrack은 드래그 이미지를 가진 이미지 리스트의 핸들
- iTrack은 드래그 이미지로 표시되는 이미지 리스트의 인덱스
- dxHotspot는 드래그 이미지의 핫스팟의 수평좌표값으로 좌상단을 원점으로 지정해서,이미지가 마우스 커서 대신에 사용되어 지므로, 이미지의 어떤 부분이 핫스팟 되었는지를 지정해야 한다.
- dyHotspot는 dxHotspot과 같이, 수직좌표값을 지정한다. 트리뷰컨트롤에 드래그 이미지를 직접 생성했다면 보통 iTrack의 값은 0 이다.
또한 , 드래그 이미지의 좌상단을 핫스팟 좌표로 하고 싶다면, dxHotspot 과 dyHotspot를 0 으로 설정하면 된다.- 드래그 이미지를 표시할 준비가 되면, ImageList_DragEnter 함수를 호출해서, 윈도우에 드래그 이미지를 표시한다.
ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
- hwndLock은 드래그 이미지를 표시하는 윈도우의 핸들로서, 드래그 이미지는 윈도우 밖으로는 이동할 수 없다.
- x 와 y는 드래그 이미지가 처음으로 표시되는 장소의 X, Y좌표를 나타낸다. 윈도우의 좌상단이 원점이 되고, 작업영역이 기준점이 아니기 때문에 주의하기 바란다
- 이제 드래그 이미지가 윈도우에 표시되지만, 트리뷰컨트롤의 드래그 처리를 해주어야 한다. 이것은 어려운 것은 아니다. WM_MOUSEMOVE메세지로 드래그 되는 경로와 WM_LBUTTONUP 메세지로서 드롭 되는 좌표를 감시하면 된다.
이는 SetCapture 함수를 사용해서 마우스 입력을 감시해서 사용 하면 된다. 이 함수를 호출하면, 마우스 커서가 어디에 있는지 등의 정보를 알 수있다.- WM_MOUSEMOVE 메세지를 처리하는 중에, ImageList_DragMove 함수를 호출해서 드래그 경로를 업데이트 할 수있다. 이 함수로 인해서 드래그앤드롭 처리중에 드래그 되는 이미지를 이동할 수 있다.
또한 드래그 이미지가 아이템과 겹쳐졌을 경우, 이를 체크하기 위해서 TVM_HITTEST 메세지를 전송함으로써,해당 아이템을 하이라이트 시킬 수가 있다.
다만, TVM_SELECTITEM 메세지를 전송하기 전에, 먼저 드래그 이미지를 표시하지 않아야 드래그 이미지의 잔상이 남지 않는것을 주의하기 바란다.
이는, ImageList_DragShowNolock 함수를 호출해서 드래그 이미지를 숨길수 있고, 하이라이트 처리가 끝나면, ImageList_DragShowNolock 함수를 다시 호출해서 드래그 이미지를 표시할 수 있게 된다.- 유저가 드래그중에 왼쪽 마우스 버튼을 놓는다면,처리해야할 일이 몇가지 발생 한다. 만약에 아이템을 하이라이트 시키고 있었다면, TVM_SELECTITEM메세지를 TVGN_DROPHILITE 플래그를 설정해서 다시 전송해주어서 하이라이트 상태를 비활성화 시켜야 한다.이 때, lParam 은 0 을 설정해 두는것을 잊지말기 바란다. 이렇게 하지 않는다면 다른 아이템을 선택했을 때, 아이템은 사각형태로 선택상태가 되기는 하지만, 하이라이트 되어 있던 아이템이 그대로 하이라이트 한 상태로 되어 있게 되는 것이다.
다음으로, ImageList_DragLeave함수와 ImageList_EndDrag 함수의 순서로 호출한 후,ReleaseCapture함수를 사용해서 마우스 감시를 중지시킨다.
이미지 리스트를 생성하면, ImageList_Destroy함수를 호출함으로써 이미지 리스트를 삭제한다.
이 후, 드래그 앤 드롭 처리가 끝나게 되고, 일반적인 처리를 하면 된다.
Example:
.386 .model flat, stdcall option casemap:none include \masm32\include\windows.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc include \masm32\include\comctl32.inc include \masm32\include\gdi32.inc includelib \masm32\lib\gdi32.lib includelib \masm32\lib\comctl32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD .const IDB_TREE equ 4006 ; ID of the bitmap resource .data ClassName db "TreeViewWinClass", 0 AppName db "Tree View Demo", 0 TreeViewClass db "SysTreeView32", 0 Parent db "Parent Item", 0 Child1 db "child1", 0 Child2 db "child2", 0 DragMode dd FALSE ; a flag to determine if we are in drag mode .data? hInstance HINSTANCE ? hwndTreeView dd ? ; handle of the tree view control hParent dd ? ; handle of the root tree view item hImageList dd ? ; handle of the image list used in the tree view control hDragImageList dd ? ; handle of the image list used to store the drag image .code start: invoke GetModuleHandle, NULL mov hInstance, eax invoke WinMain, hInstance, NULL, NULL, SW_SHOWDEFAULT invoke ExitProcess, eax invoke InitCommonControls WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize, SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra, NULL mov wc.cbWndExtra, NULL push hInst pop wc.hInstance mov wc.hbrBackground, COLOR_APPWORKSPACE mov wc.lpszMenuName, NULL mov wc.lpszClassName, OFFSET ClassName invoke LoadIcon, NULL, IDI_APPLICATION mov wc.hIcon, eax mov wc.hIconSm, eax invoke LoadCursor, NULL, IDC_ARROW mov wc.hCursor, eax invoke RegisterClassEx, addr wc invoke CreateWindowEx, WS_EX_CLIENTEDGE, ADDR ClassName, ADDR AppName,\ WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE, CW_USEDEFAULT,\ CW_USEDEFAULT, 200,400, NULL, NULL,\ hInst, NULL mov hwnd, eax .while TRUE invoke GetMessage, ADDR msg, NULL, 0,0 .BREAK .IF (! eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw mov eax, msg.wParam ret WinMain endp WndProc proc uses edi hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM LOCAL tvinsert:TV_INSERTSTRUCT LOCAL hBitmap:DWORD LOCAL tvhit:TV_HITTESTINFO .if uMsg==WM_CREATE invoke CreateWindowEx, NULL, ADDR TreeViewClass, NULL,\ WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT, 0,\ 0,200,400, hWnd, NULL,\ hInstance, NULL ; Create the tree view control mov hwndTreeView, eax invoke ImageList_Create, 16,16, ILC_COLOR16, 2,10 ; create the associated image list mov hImageList, eax invoke LoadBitmap, hInstance, IDB_TREE ; load the bitmap from the resource mov hBitmap, eax invoke ImageList_Add, hImageList, hBitmap, NULL ; Add the bitmap into the image list invoke DeleteObject, hBitmap ; always delete the bitmap resource invoke SendMessage, hwndTreeView, TVM_SETIMAGELIST, 0, hImageList mov tvinsert.hParent, NULL mov tvinsert.hInsertAfter, TVI_ROOT mov tvinsert.item.imask, TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE mov tvinsert.item.pszText, offset Parent mov tvinsert.item.iImage, 0 mov tvinsert.item.iSelectedImage, 1 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert mov hParent, eax mov tvinsert.hParent, eax mov tvinsert.hInsertAfter, TVI_LAST mov tvinsert.item.pszText, offset Child1 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert mov tvinsert.item.pszText, offset Child2 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert .elseif uMsg==WM_MOUSEMOVE .if DragMode==TRUE mov eax, lParam and eax, 0ffffh mov ecx, lParam shr ecx, 16 mov tvhit.pt.x, eax mov tvhit.pt.y, ecx invoke ImageList_DragMove, eax, ecx invoke ImageList_DragShowNolock, FALSE invoke SendMessage, hwndTreeView, TVM_HITTEST, NULL, addr tvhit .if eax! =NULL invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, eax .endif invoke ImageList_DragShowNolock, TRUE .endif .elseif uMsg==WM_LBUTTONUP .if DragMode==TRUE invoke ImageList_DragLeave, hwndTreeView invoke ImageList_EndDrag invoke ImageList_Destroy, hDragImageList invoke SendMessage, hwndTreeView, TVM_GETNEXTITEM, TVGN_DROPHILITE, 0 invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_CARET, eax invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, 0 invoke ReleaseCapture mov DragMode, FALSE .endif .elseif uMsg==WM_NOTIFY mov edi, lParam assume edi:ptr NM_TREEVIEW .if [edi]. hdr.code==TVN_BEGINDRAG invoke SendMessage, hwndTreeView, TVM_CREATEDRAGIMAGE, 0,[edi]. itemNew.hItem mov hDragImageList, eax invoke ImageList_BeginDrag, hDragImageList, 0,0,0 invoke ImageList_DragEnter, hwndTreeView,[edi]. ptDrag.x,[edi]. ptDrag.y invoke SetCapture, hWnd mov DragMode, TRUE .endif assume edi:nothing .elseif uMsg==WM_DESTROY invoke PostQuitMessage, NULL .else invoke DefWindowProc, hWnd, uMsg, wParam, lParam ret .endif xor eax, eax ret WndProc endp end start
|
WM_CREATE 이벤트 핸들러에서, 트리뷰컨트롤을 생성한다.
invoke CreateWindowEx, NULL, ADDR TreeViewClass, NULL,\ WS_CHILD+WS_VISIBLE+TVS_HASLINES+TVS_HASBUTTONS+TVS_LINESATROOT, 0,\ 0,200,400, hWnd, NULL, hInstance, NULL스타일을 주목하기 바란다. TVS_xxxx 라는 접두어는 트리뷰에서 사용되는 독특한 전용 스타일이다.
invoke ImageList_Create, 16,16, ILC_COLOR16, 2,10 mov hImageList, eax invoke LoadBitmap, hInstance, IDB_TREE mov hBitmap, eax invoke ImageList_Add, hImageList, hBitmap, NULL invoke DeleteObject, hBitmap invoke SendMessage, hwndTreeView, TVM_SETIMAGELIST, 0, hImageList다음으로, 16 x 16 픽셀 크기로 된, 16 비트 색상의 이미지 리스트를 생성한다. 예제에서는 2개의 이미지를 리스트 하고 있지만, 필요 하다면 10개까지도 확장 가능하다. 리소스로부터 비트맵을 읽은 후, 이미지 리스트에 추가한다.이 상태에서 비트맵의 핸들은 필요없기 때문에 해제한다. 이미지 리스트의 세팅이 끝나면, TVM_SETIMAGELIST 메세지를 트리뷰컨트롤에 전송해서, 이미지 리스트와 트리뷰를 연결한다.
mov tvinsert.hParent, NULL mov tvinsert.hInsertAfter, TVI_ROOT mov tvinsert.u.item.imask, TVIF_TEXT+TVIF_IMAGE+TVIF_SELECTEDIMAGE mov tvinsert.u.item.pszText, offset Parent mov tvinsert.u.item.iImage, 0 mov tvinsert.u.item.iSelectedImage, 1 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert트리뷰컨트롤에 최상위아이템부터 아이템을 하나씩 추가한다. 루트아이템이므로, hParent 멤버는 당연히 NULL이며, hInsertAfter는 TVI_ROOT가 된다. imask는 TV_ITEM 구조체의 멤버인 pszText와 iImage, iSelectedImage가 유효함을 나타내므로 각각 해당된 값을 설정한다. pszText는 루트아이템의 이름이고, iImage는 아이템 선택되지 않은 상태를 나타내는 이미지 리스트의 몇번째 이미지를 사용할 지를 지정하고, iSelectedImage는 선택되었음을 표시하는 이미지리스트내의 인덱스를 지정한다.멤버 설정을 마치면, TVB_INSERTITEM 메세지를 트리뷰컨트롤에 전송해서, 루트 아이템을 추가한다.
mov hParent, eax mov tvinsert.hParent, eax mov tvinsert.hInsertAfter, TVI_LAST mov tvinsert.u.item.pszText, offset Child1 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert mov tvinsert.u.item.pszText, offset Child2 invoke SendMessage, hwndTreeView, TVM_INSERTITEM, 0, addr tvinsert루트아이템을 추가 뒤, 하위아이템을 생성한다. hParent 멤버는 부모아이템의 핸들을 설정하고, 이미지는 같은것을 사용하므로, iImage 와 iSelectedImage 는 그대로 사용한다.
.elseif uMsg==WM_NOTIFY mov edi, lParam assume edi:ptr NM_TREEVIEW .if [edi]. hdr.code==TVN_BEGINDRAG invoke SendMessage, hwndTreeView, TVM_CREATEDRAGIMAGE, 0,[edi]. itemNew.hItem mov hDragImageList, eax invoke ImageList_BeginDrag, hDragImageList, 0,0,0 invoke ImageList_DragEnter, hwndTreeView,[edi]. ptDrag.x,[edi]. ptDrag.y invoke SetCapture, hWnd mov DragMode, TRUE .endif assume edi:nothing이번에는, 유저가 아이템을 드래그 할때, 트리뷰컨트롤은, TVN_BEGINDRAG 와 WM_NOTIFY 메세지를 전송한다. 이 때, lParam은 반드시 필요한 것은 NM_TREEVIEW 구조체의 포인터이며, 그 포인터의 값을 edi 레지스터에 설정하고, 이후부터는, edi 레지스터의 내용을 포인터 대신에 사용한다. assume edi:ptr NM_TREEVIEW 라는 코드는, NM_TREEVIEW 구조체의 포인터로 edi 레지스터를 사용한다는 것을 의미한다. 이번에는, 트리뷰컨트롤에 TVM_CREATEDRAGIMAGE 메세지를 전송해서, 드래그중에 표시되는 이미지를 생성한다. 이로 인해서, 드래그 이미지가 포함된 이미지 리스트의 핸들을 얻게되고, 그 핸들을 인수로 해서, ImageList_BeginDrag 함수를 호출해서, 드래그 이미지의 핫스팟을 생성한다. 그리고나서, ImageList_DragEnter 함수를 호출해서 드래그 처리를 하게된다. 이 함수는 지정된 윈도우와 윈도우의 좌표에 드래그 이미지를 표시하는 역활을 수행한다. NM_TREEVIEW 구조체의 멤버인 ptDrag 구조체를 기본적인 드래그 이미지를 표시하는 좌표값으로서 사용하게 된다. 이 후, 마우스 입력을 감시해서 현재 드래그중인지를 체크해서 해당 플래그를 설정해준다.
.elseif uMsg==WM_MOUSEMOVE .if DragMode==TRUE mov eax, lParam and eax, 0ffffh mov ecx, lParam shr ecx, 16 mov tvhit.pt.x, eax mov tvhit.pt.y, ecx invoke ImageList_DragMove, eax, ecx invoke ImageList_DragShowNolock, FALSE invoke SendMessage, hwndTreeView, TVM_HITTEST, NULL, addr tvhit .if eax! =NULL invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, eax .endif invoke ImageList_DragShowNolock, TRUE .endif이 부분은, WM_MOUSEMOVE에 대해서 설명한다. 유저가 드래그 이미지와 함께를 드래그 처리를 할 때, 부모윈도우에는 WM_MOUSEMOVE 메세지가 전송된다. 이런 메세지에 대해서, ImageList_DragMove 함수를 사용해서, 드래그 이미지의 좌표를 업데이트한다. 드래그 이미지가 이동중에, 기존의 아이템과 겹쳐진 상황을 체크하기 위해, 트리뷰컨트롤에 TVM_HITTEST 메세지를 전송함으로서 체크를 하게 된다. 만약 겹쳐졌다면, 겹쳐친 아이템을 하이라이트 하기 위해서, TVM_SELECTITEM 메세지를 TVGN_DROPHILITE 플래그와 함께 전송한다. 하이라이트중에는 보기가 좋지 않으므로, 드래그 이미지를 숨긴다.
.elseif uMsg==WM_LBUTTONUP .if DragMode==TRUE invoke ImageList_DragLeave, hwndTreeView invoke ImageList_EndDrag invoke ImageList_Destroy, hDragImageList invoke SendMessage, hwndTreeView, TVM_GETNEXTITEM, TVGN_DROPHILITE, 0 invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_CARET, eax invoke SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, 0 invoke ReleaseCapture mov DragMode, FALSE .endif유저가 마우스의 왼쪽 버튼을 놓으면, 드래그 처리가 종료됨을 의미하므로, ImageList_DragLeave 함수, ImageList_EndDrag 함수, ImageList_Destroy 함수를 호출해서, 드래그 처리를 종료한다. 트리뷰아이템을 보기좋게 만들려면 , 마지막으로 하이라이트 한 아이템을 체크해서, 선택된 것을 해제해 주는것이 좋다.이런 처리를 하지 않게 되면 다른 아이템을 선택할 경우 하이라이트가 되지 않는다. 이제 마지막으로, 마우스의 감시기능을 중지한다.