2007. 2. 16. 13:39
DevX Win32 Assembly Tutorial 19: Tree View Control

Tutorial 19: Tree View Control

이 번장에서는, 트리뷰와 이미지 리스트의 사용법과, 트리뷰에서의 드래그앤드롭에 대해서 설명한다.
 소스    리소스    비트 맵    실행 결과  

※  컴파일이 되지 않는다면, 자신의 windows.inc 파일을 살펴봐서, TVITEMA 구조체로 선언된 멤버변수가 _mask면 소스파일의 imask 를 _mask 로 변경하길 바란다.

Theory:

트리뷰컨트롤은 특수한 형태의 윈도우로서, 오브젝트를 계층형태로 표시한다.간단히 탐색기의 좌측 부분을 생각하면 된다. 트리뷰 컨트롤은 오브젝트의 상하관계를 표현할 때 자주 사용된다.

트리뷰컨트롤은 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 ends

hwndFrom는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 ENDS

TVITEM 은 여기에서만 사용되기 때문에, 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메세지인 경우는 선택된 트리뷰아이템에 대한 정보가 설정된다.

  • imaskTV_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 레퍼런스에 있는 예제를 사용할려고 했지만,중요한 정보가 빈약했기 때문에 실제적인 예를 들어서 설명하겠다. 똑같은 전철을 밟지 않기 위해서, 다음과 같이 드래그 앤 드롭의 설명을 한다.

  1. 유저가 아이템을 드래그 하면, 트리뷰컨트롤은 TVN_BEGINDRAG 통지신호를 부모윈도우로 전송한다. 이 때, 드래그중에 표시되는 드래그 표시용 이미지를 생성한다.
    트리뷰컨트롤에 TVM_CREATEDRAGIMAGE 를 전송해서, 현재 드래그중인 아이템에 표시되는 이미지를 생성했다는 통지 신호를 보낸다.
    트리뷰컨트롤은 한개의 드래그 이미지에 대해서 이미지리스트를 생성하고, 이미지 리스트의 핸들을 반환한다.
  2. 드래그 이미지를 생성하면, ImageList_BeginDrag 함수를 호출해서 이미지의 핫스팟(Windows는 이 좌표를 이미지의 좌표로서 인식한다)을 설정한다.

    ImageList_BeginDrag PROTO himlTrack:DWORD, iTrack:DWORD, dxHotspot:DWORD, dyHotspot:DWORD

    • himlTrack은 드래그 이미지를 가진 이미지 리스트의 핸들
    • iTrack은 드래그 이미지로 표시되는 이미지 리스트의 인덱스
    • dxHotspot는 드래그 이미지의 핫스팟의 수평좌표값으로 좌상단을 원점으로 지정해서,이미지가 마우스 커서 대신에 사용되어 지므로, 이미지의 어떤 부분이 핫스팟 되었는지를 지정해야 한다.
    • dyHotspot는 dxHotspot과 같이, 수직좌표값을 지정한다. 트리뷰컨트롤에 드래그 이미지를 직접 생성했다면 보통 iTrack의 값은 0 이다.
      또한 , 드래그 이미지의 좌상단을 핫스팟 좌표로 하고 싶다면, dxHotspot 과 dyHotspot를 0 으로 설정하면 된다.
  3. 드래그 이미지를 표시할 준비가 되면, ImageList_DragEnter 함수를 호출해서, 윈도우에 드래그 이미지를 표시한다.

    ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD

    • hwndLock은 드래그 이미지를 표시하는 윈도우의 핸들로서, 드래그 이미지는 윈도우 밖으로는 이동할 수 없다.
    • x 와 y는 드래그 이미지가 처음으로 표시되는 장소의 X, Y좌표를 나타낸다. 윈도우의 좌상단이 원점이 되고, 작업영역이 기준점이 아니기 때문에 주의하기 바란다
  4. 이제 드래그 이미지가 윈도우에 표시되지만, 트리뷰컨트롤의 드래그 처리를 해주어야 한다. 이것은 어려운 것은 아니다. WM_MOUSEMOVE메세지로 드래그 되는 경로와 WM_LBUTTONUP 메세지로서 드롭 되는 좌표를 감시하면 된다.
    이는 SetCapture 함수를 사용해서 마우스 입력을 감시해서 사용 하면 된다. 이 함수를 호출하면, 마우스 커서가 어디에 있는지 등의 정보를 알 수있다.
  5. WM_MOUSEMOVE 메세지를 처리하는 중에, ImageList_DragMove 함수를 호출해서 드래그 경로를 업데이트 할 수있다. 이 함수로 인해서 드래그앤드롭 처리중에 드래그 되는 이미지를 이동할 수 있다.
    또한 드래그 이미지가 아이템과 겹쳐졌을 경우, 이를 체크하기 위해서 TVM_HITTEST 메세지를 전송함으로써,해당 아이템을 하이라이트 시킬 수가 있다.
    다만, TVM_SELECTITEM 메세지를 전송하기 전에, 먼저 드래그 이미지를 표시하지 않아야 드래그 이미지의 잔상이 남지 않는것을 주의하기 바란다.
    이는, ImageList_DragShowNolock 함수를 호출해서 드래그 이미지를 숨길수 있고, 하이라이트 처리가 끝나면, ImageList_DragShowNolock 함수를 다시 호출해서 드래그 이미지를 표시할 수 있게 된다.
  6. 유저가 드래그중에 왼쪽 마우스 버튼을 놓는다면,처리해야할 일이 몇가지 발생 한다. 만약에 아이템을 하이라이트 시키고 있었다면, 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

Analysis:

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 함수를 호출해서, 드래그 처리를 종료한다. 트리뷰아이템을 보기좋게 만들려면 , 마지막으로 하이라이트 한 아이템을 체크해서, 선택된 것을 해제해 주는것이 좋다.이런 처리를 하지 않게 되면 다른 아이템을 선택할 경우 하이라이트가 되지 않는다. 이제 마지막으로, 마우스의 감시기능을 중지한다.


 

Posted by openserver