Rozdział XIX - Kontrolka widoku drzewa


Na tej lekcji nauczymy się używać kontrolki widoku drzewa (tree view control). Co więcej poznamy sposoby przeciągania i upuszczania elementów w kontrolce widoku drzewa oraz zastosowanie z niż listy obrazków.

 

 

Pobierz plik z przykładem {z tego archiwum}.

 

Teoria

Kontrolka widoku drzewa jest specjalnym rodzajem okna, które pokazuje obiekty w porządku hierarchicznym. Przykładem może być lewy panel Explorera systemu Windows. Możesz użyć tej kontrolki do pokazania stosunków pomiędzy obiektami.

Kontrolkę widoku drzewa możesz utworzyć za pomocą wywołania funkcji CreateWindowEx przekazując jej nazwę klasy "SysTreeView32" albo możesz zawrzeć ją w oknie dialogowym. Nie zapomnij o umieszczeniu wywołania InitCommonControls w swoim kodzie.

Istnieje kilka specyficznych styli dla kontrolki widoku drzewa. Poniżej zebrałem te najczęściej stosowane:

Kontrolka widoku drzewa, podobnie jak inne kontrolki typowe, porozumiewa się z oknem nadrzędnym za pośrednictwem wiadomości. Okno nadrzędne może wysyłać do niej różne wiadomości, a kontrolka widoku drzewa może przesyłać wiadomości "powiadomień" do swojego okna nadrzędnego. W tym względzie kontrolka widoku drzewa nie różni się od innych okien.

Gdy dzieje się z nią coś interesującego, wysyła ona wiadomość WM_NOTIFY do okna nadrzędnego z towarzyszącą informacją.

 

WM_NOTIFY
wParam Numer identyfikacyjny ID kontrolki. Nie jest zagwarantowane, iż wartość ta będzie unikalna, zatem nie korzystamy z niej. Zamiast tego wykorzystujemy pola hwndFrom lub IDForm struktury NMHDR wskazywanej przez lParam.
lParam Wskazanie do struktury NMHDR. Niektóre z kontrolek mogą przekazywać wskazanie do większej struktury, jednakże w każdym przypadku pola struktury NMHDR znajdują się na jej początku w tej samej kolejności. Zatem mając lParam możesz być pewny, iż wskazuje on co najmniej strukturę NMHDR.

 

Teraz zbadajmy strukturę NMHDR.

 

NMHDR STRUCT DWORD
    hwndFrom DWORD ?
    idFrom   DWORD ?
    code     DWORD ?
NMHDR ENDS

Powiadomienia z kontrolki widoku drzewa są stałymi z przedrostkami TVN_. Wiadomości kontrolki widoku drzewa mają przedrostki TVM_, jak TVM_CREATEDRAGIMAGE. Kontrolka widoku drzewa wysyła powiadomienia TVN_xxxx w polu code struktury NMHDR. Okno nadrzędne może wysyłać wiadomości TVM_xxxx do sterowania nią.

Dodawanie elementu do kontrolki widoku drzewa

Po utworzeniu kontrolki widoku drzewa możesz dodawać do niej elementy. Można to wykonać przez wysyłanie do niej wiadomości TVM_INSERTITEM

 

TVM_INSERTITEM
wParam równe 0
lParam wskazanie struktury TV_INSERTSTRUCT

 

W tym momencie powinieneś poznać nieco terminologii odnoszącej się do zależności elementów w kontrolce widoku drzewa.

Element może być nadrzędny (parent), potomny (child) lub jednocześnie nadrzędny i potomny.

Element nadrzędny jest elementem, który posiada powiązane z sobą jakieś inne elementy. Jednocześnie ten element nadrzędny może być potomnym jakiegoś innego elementu. Element nie posiadający elementu nadrzędnego jest nazywany elementem głównym (root element). W kontrolce widoku drzewa może być wiele elementów głównych. Teraz zbadajmy strukturę TV_INSERTSTRUCT:

 

TV_INSERTSTRUCT STRUCT DWORD 
    hParent      DWORD ? 
    hInsertAfter DWORD ? 
    ITEMTYPE     <> 
TV_INSERTSTRUCT ENDS
ITEMTYPE UNION 
    itemex TVITEMEX <> 
    item   TVITEM   <> 
ITEMTYPE ENDS

 

Tutaj użyjemy tylko struktury TVITEM.

 

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

 

W zależności od typu wiadomości struktura ta jest używana do wysyłania lub odbierania informacji na temat elementu widoku drzewa. Na przykład z wiadomością TVM_INSERTITEM stosuje się ją do określania atrybutu elementu, który ma zostać wstawiony do kontrolki widoku drzewa. Po wysłaniu wiadomości TVM_GETITEM zostanie ona wypełniona informacjami na temat wybranego elementu kontrolki widoku drzewa.

Aby wprowadzić element do kontrolki widoku drzewa, musisz przynajmniej wypełnić pola hParent, hInsertAfter oraz również imask i pszText.

 

Dodawanie obrazków do kontrolki widoku drzewa

Jeśli chcesz umieścić obrazek po lewej stronie etykiety elementu widoku drzewa, musisz utworzyć listę obrazków (image list) i skojarzyć ją z kontrolką widoku drzewa. Listę obrazków możesz utworzyć przez wywołanie ImageList_Create.

 

ImageList_Create PROTO cx:       DWORD,\
                       cy:       DWORD,\
                       flags:    DWORD,\
                       cInitial: DWORD,\
                       cGrow:    DWORD

 

Jeśli wywołanie się powiedzie, to funkcja zwróci uchwyt do pustej listy obrazków.

Lista obrazków nie jest oknem. Stanowi ona jedynie przechowalnię dla obrazków, które mogą być wykorzystywane przez inne okna.

Po utworzeniu listy obrazków możesz dodawać do niej obrazki wywołując ImageList_Add.

 

ImageList_Add PROTO himl:DWORD, hbmImage:DWORD, hbmMask:DWORD

 

Jeśli operacja się nie powiedzie, to funkcja ta zwraca wartość -1.

Zwykle będziemy dodawać jedynie dwa obrazki na tej liście przy zastosowaniu jej w kontrolce widoku drzewa: jeden używany w sytuacji, gdy element widoku drzewa nie jest wybrany oraz drugi, gdy ten element jest wybrany.

Gdy lista obrazków jest gotowa, wiążesz ją z kontrolką widoku drzewa wysyłając do niej wiadomość TVM_SETIMAGELIST

 

TVM_SETIMAGELIST
wParam rodzaj listy obrazków do ustawienia. Istnieją dwie możliwości:
  • TVSIL_NORMAL - ustawienie normalnej listy obrazków, która zawiera obrazki dla elementu wybranego i nie wybranego
  • TVSIL_STATE - ustawia listę obrazków, która zawiera obrazki dla elementów widoku drzewa będących w stanach zdefiniowanych przez użytkownika.
lParam uchwyt do listy obrazków

 

Pobieranie informacji o elemencie widoku drzewa

Możesz otrzymać informację o elemencie widoku drzewa wysyłając do kontrolki wiadomość TVM_GETITEM

 

TVM_GETITEM
wParam równe 0
lParam wskazanie struktury TV_ITEM, która zostanie wypełniona pożądaną informacją

 

Zanim wyślesz tę wiadomość, powinieneś wypełnić pole imask znacznikami, które określają pola struktury TV_ITEM do wypełnienia przez system Windows. A najważniejszym jest umieszczenie w polu hItem uchwytu elementu, na temat którego chcesz otrzymać informację. I to stwarza problem. Skąd masz znać uchwyt elementu, o którym chcesz uzyskać informację? Czy musisz zachować wszystkie uchwyty widoku drzewa?

Odpowiedź jest całkiem prosta: nie musisz tego robić. Możesz wysłać wiadomość TVM_GETNEXTITEM do kontrolki widoku drzewa, aby otrzymać uchwyt do elementu posiadającego określone przez ciebie atrybuty. Na przykład możesz zażądać uchwytu pierwszego elementu potomnego, elementu głównego, elementu wybranego itd.

 

TVM_GETNEXTITEM
wParam znacznik
lParam uchwyt do elementu widoku drzewa (potrzebny jedynie przy niektórych wartościach znacznika)

 

Wartość wParam jest bardzo ważna, zatem poniżej przedstawiam wszystkie wartości znaczników:

Jak widać, z tej wiadomości można otrzymać uchwyt do interesującego nas elementu widoku drzewa. Jeśli wywołanie SendMessage powiedzie się, to zwróci ono uchwyt elementu widoku drzewa. Możesz następnie wpisać zwrócony uchwyt w pole hItem struktury TV_ITEM, aby wykorzystać ją z wiadomością TVM_GETITEM.

 

Operacja przeciągania i upuszczania w kontrolce widoku drzewa

Ta część jest powodem napisania tego rozdziału. Gdy starałem się śledzić przykład w pomocy dla funkcji bibliotecznych API Win32 (plik win32.hlp z firmy Borland), byłem bardzo sfrustrowany, ponieważ brakowało tam najistotniejszych informacji. W końcu metodą prób i błędów odkryłem, w jaki sposób należy implementować operację przeciągania i upuszczania (drag-and-drop) w kontrolce widoku drzewa i nie chcę, aby inni musieli podążać tą samą ciernistą drogą, co ja.

Poniżej wymieniam kolejne kroki implementacji operacji przeciągania i upuszczania w kontrolce widoku drzewa:

  1. Gdy użytkownik próbuje przeciągać jakiś element, kontrolka widoku drzewa wysyła powiadomienie TVN_BEGINDRAG do swojego okna nadrzędnego. Możesz wykorzystać tę okazję do utworzenia obrazu, który zostanie wykorzystany do przedstawienia elementu przy przeciąganiu. Możesz również wysłać wiadomość TVM_CREATEDRAGIMAGE do kontrolki widoku drzewa, aby nakazać jej utworzyć standardowy obrazek przeciągania z bieżąco używanego obrazka przez przeciągany element. Kontrolka utworzy listę obrazków z tylko jednym obrazkiem przeciągania i zwróci ci jej uchwyt.
  2. Po utworzeniu obrazka przeciągania określasz jego gorący punkt (hotspot) wywołując ImageList_BeginDrag
ImageList_BeginDrag PROTO himlTrack: DWORD,\ 
                          iTrack:    DWORD,\ 
                          dxHotspot: DWORD,\ 
                          dyHotspot: DWORD

Zwykle parametr iTrack będzie wynosił 0, jeśli nakażesz kontrolce widoku drzewa utworzyć obrazek przeciągania, a dxHotspot i dyHotspot mogą mieć wartość 0, jeśli chcesz, aby gorący punkt znajdował się w lewym górnym narożniku obrazka przeciągania.

  1. Gdy obrazek przeciągania jest gotowy do wyświetlenia, wywołujemy ImageList_DragEnter w celu wyświetlenia go w oknie kontrolki.
ImageList_DragEnter PROTO hwndLock:DWORD, x:DWORD, y:DWORD
  1. Teraz, gdy obrazek jest już wyświetlany w oknie, będziesz musiał wspomagać operację przeciągania w kontrolce widoku drzewa. Jednakże nie stanowi to problemu. Musimy monitorować ścieżkę przeciągania przy pomocy wiadomości WM_MOUSEMOVE, a pozycję upuszczenia przy pomocy WM_LBUTTONUP. Jednakże, jeśli obrazek przeciągania znajdzie się nad jakimiś innymi oknami potomnymi, to okno nadrzędne nigdy nie odbierze jakiejkolwiek wiadomości z myszki. Rozwiązaniem jest przechwycenie danych z myszki przy pomocy SetCapture. Za pomocą tego wywołania wiadomości od myszki będą kierowane do określonego okna bez względu na pozycję kursora.
  2. Wewnątrz procedury obsługi wiadomości WM_MOUSEMOVE uaktualniasz ścieżkę przeciągania za pomocą wywołania ImageList_DragMove. Funkcja ta przemieszcza przeciągany obrazek podczas operacji przeciągania i upuszczania. Co więcej, jeśli sobie życzysz, możesz podświetlać element, nad którym w danej chwili znajduje się gorący punkt obrazka przeciągania wysyłając wiadomość TVM_HITEST, która sprawdzi ten przypadek. Jeśli obrazek przeciągania znajduje się nad jakimś elementem listy, to możesz wysłać wiadomość TVM_SELECTITEM ze znacznikiem TVGN_DROPHILITE, aby podświetlić ten element. Zwróć uwagę, iż przed wysłaniem wiadomości TVM_SELECTITEM musisz najpierw ukryć obrazek przeciągania, w przeciwnym razie pozostawi on na ekranie niezbyt ładne ślady. Obrazek przeciągania możesz ukryć wywołując ImageList_DragShowNolock, a gdy operacja zostanie zakończona wywołaj jeszcze raz ImageList_DragShowNolock, aby obrazek ten znów wyświetlić na ekranie.
  3. Gdy użytkownik zwolni lewy przycisk myszki, musisz wykonać kilka rzeczy. Jeśli podświetlałeś element na liście, musisz usunąć to podświetlenie wysyłając ponownie wiadomość TVM_SELECTITEM ze znacznikiem TVGN_DROPHILITE, lecz tym razem lParam MUSI mieć wartość zero. Jeśli tego nie zrobisz, to kontrolka widoku drzewa zacznie się dziwnie zachowywać, gdy będziesz przeciągał i upuszczał następny element. W następnej kolejności musisz wywołać kolejno ImageList_DragLeave oraz ImageList_EndDrag. Musisz uwolnić myszkę wywołując ReleaseCapture. Jeśli tworzyłeś listę obrazków, musisz ją usunąć wywołując ImageList_Destroy. Po tych czynnościach możesz przejść do operacji, które twój program ma wykonać na zakończenie przeciągania i upuszczania.

Przykład

Plik TREVIEW.ASM

 

.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

.DATA

ClassName     DB "TreeViewWinClass", 0
AppName       DB "Widok Drzewa", 0
TreeViewClass DB "SysTreeView32", 0
Parent        DB "Element nadrzędny", 0
Child1        DB "Potomek1", 0
Child2        DB "Potomek2", 0
DragMode      DD FALSE

.DATA?

hInstance      HINSTANCE ?
hwndTreeView   DD ?
hParent        DD ?
hImageList     DD ?
hDragImageList DD ?

.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, 200, 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
        mov    hwndTreeView, eax
        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
        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
            movzx  eax, WORD PTR lParam
            movzx  ecx, WORD PTR lParam + 2
            mov    tvhit.pt.x, eax ;eax pozioma pozycja przeciąganego obrazka
            mov    tvhit.pt.y, ecx ;ecx pozycja pionowa
            INVOKE ImageList_DragMove, eax, ecx
            INVOKE ImageList_DragShowNolock, FALSE
            INVOKE SendMessage, hwndTreeView,TVM_HITTEST, NULL, ADDR tvhit
                                ; sprawdź, czy element jest kliknięty
            .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
                                ;Pobierz obecnie wybrany element
            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

 

Plik TREEVIEW.RC

 

#define IDB_TREE 4006

IDB_TREE BITMAP list.bmp

Analiza

W sekcji obsługi wiadomości WM_CREATE tworzysz kontrolkę widoku drzewa

 

    INVOKE CreateWindowEx, NULL, ADDR TreeViewClass, NULL,\
                           WS_CHILD + WS_VISIBLE +\
                           TVS_HASLINES + TVS_HASBUTTONS +\
                           TVS_LINESATROOT, 0,\
                           0, 200, 400, hWnd, NULL,\
                           hInstance, NULL

 

Zwróć uwagę na style: TVS_xxxx są stylami specyficznymi dla widoku drzewa.

 

    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

 

Następnie tworzysz pustą listę obrazków, która będzie przechowywała obrazki o rozmiarze 16 x 16 pikseli w 16 kolorach i początkowo będzie zawierała 2 obrazki, lecz może być rozszerzona do 10, jeśli potrzeby wzrosną. Dalej ładujemy grafikę rastrową z zasobu i dodajemy ją do właśnie utworzonej listy obrazków. Po tej operacji usuwamy uchwyt do grafiki rastrowej, ponieważ nie będzie nam już potrzebny. Gdy lista obrazków jest już w całości gotowa, kojarzymy ją z kontrolką widoku drzewa wysyłając do niej wiadomość TVM_SETIMAGELIST.

 

        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

 

Wstawiamy elementy do kontrolki widoku drzewa rozpoczynając od elementu głównego. Ponieważ będzie to element główny, pole hParent ma wartość NULL a hInsertAfter ma wartość TVI_ROOT. Pole imask określa, iż dane zawarte są w polach pszText, iImage oraz iSelectedImage w strukturze TV_ITEM. Pola te wypełniamy odpowiednimi wartościami:

Gdy zostaną wypełnione wszystkie odpowiednie pola, wysyłamy wiadomość TVM_INSERTITEM do kontrolki widoku drzewa, aby dodać do niej element główny.

 

    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

 

Po dodaniu elementu głównego możemy dołączyć do niego elementy potomne. Pole hParent zostaje wypełnione uchwytem elementu głównego. Ponieważ będziemy używać identycznych obrazków z listy obrazków, zatem nie zmieniamy pól iImage i 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

 

Teraz gdy użytkownik próbuje przeciągać jakiś element, kontrolka widoku drzewa wysyła wiadomość WM_NOTIFY z kodem TVN_BEGINDRAG. lParam jest wskazaniem struktury NM_TREEVIEW, która zawiera kilka informacji nam potrzebnych, zatem w rejestrze edi umieszczamy tę wartość i używamy go jako wskazania struktury NM_TREEVIEW. ASSUME edi:PTR NM_TREEVIEW jest sposobem nakazania asemblerowi MASM potraktowania rejestru edi jako wskazania do struktury NM_TREEVIEW. Następnie tworzymy obrazek przeciągania wysyłając wiadomość TVM_CREATEDRAGIMAGE do kontrolki widoku drzewa. Zwraca ona uchwyt do nowo utworzonej listy obrazków z umieszczonym wewnątrz niej obrazkiem przeciągania. Wywołujemy ImageList_BeginDrag do ustawienie gorącego punktu na obrazku przeciągania. Następnie wchodzimy do operacji przeciągania wywołując ImageList_DragEnter. Funkcja ta wyświetla obrazek przeciągania na podanej pozycji w podanym oknie. Wykorzystujemy strukturę ptDrag będącą polem struktury NM_TREEVIEW jako punkt, gdzie początkowo powinien zostać wyświetlony obrazek przeciągania. Struktura NM_TREEVIEW jest następująca:

 

NM_TREEVIEW STRUCT
    hdr     NMHDR  <>
    action  DWORD  ?
    itemOld TVITEM <>
    itemNew TVITEM <>
    ptDrag  POINT  <>
NM_TREEVIEW ENDS

Po tych działaniach przechwytujemy dane z myszki i ustawiamy znacznik, który wskazuje, iż weszliśmy do trybu obsługi przeciągania.

 

    .ELSEIF uMsg==WM_MOUSEMOVE
        .IF DragMode==TRUE
            movzx  eax, WORD PTR lParam
            movzx  ecx, WORD PTR lParam + 2
            mov    tvhit.pt.x, eax ;eax pozioma pozycja
            mov    tvhit.pt.y, ecx ;ecx pozycja pionowa
            INVOKE ImageList_DragMove, eax, ecx
            INVOKE ImageList_DragShowNolock, FALSE
            INVOKE SendMessage, hwndTreeView, TVM_HITTEST, NULL, ADDR tvhit
                                ; sprawdź, czy element jest kliknięty
            .IF eax!=NULL
                INVOKE SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, eax
            .ENDIF
            INVOKE ImageList_DragShowNolock, TRUE
        .ENDIF

 

Teraz skoncentrujemy się na WM_MOUSEMOVE. Gdy użytkownik przeciąga obrazek, nasze okno nadrzędne otrzymuje wiadomości WM_MOUSEMOVE. W odpowiedzi na te wiadomości uaktualniamy pozycję obrazka przeciągania przy pomocy wywołania funkcji ImageList_DragMove. Po tej operacji sprawdzamy, czy obrazek przeciągania znajduje się ponad jakimś elementem. Informację tę uzyskamy wysyłając wiadomość TVM_HITTEST do kontrolki widoku drzewa z punktem do sprawdzenia. Jeśli obrazek przeciągania znajduje się ponad jakimś elementem, to podświetlamy ten element wysyłając do kontrolki widoku drzewa wiadomość TVM_SELECTITEM ze znacznikiem TVGN_DROPHILITE. Podczas trwania operacji podświetlania ukrywamy obrazek przeciągania, aby nie pozostawił na ekranie niepożądanych śladów.

 

    .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
                                ;Pobierz obecnie wybrany element
            INVOKE SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_CARET, eax
            INVOKE SendMessage, hwndTreeView, TVM_SELECTITEM, TVGN_DROPHILITE, 0
            INVOKE ReleaseCapture
            mov    DragMode, FALSE
        .ENDIF

 

Gdy użytkownik zwolni lewy przycisk myszki, operacja przeciągania jest kończona. Opuszczamy tryb przeciągania wywołując kolejno ImageList_DragLeave i ImageList_EndDrag oraz ImageList_Destroy. Aby elementy widoku drzewa wyglądały poprawnie, musimy również zaznaczyć ostatnio podświetlony element i wybrać go. Należy także usunąć podświetlenie, w przeciwnym wypadku inne elementy nie byłyby podświetlane w momencie wybierania ich. A na koniec zwalniamy przechwytywanie myszki.

 

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program TreeView;

uses Windows;

const
  IDB_TREE      = 4006;
  ClassName     = 'TreeViewWinClass';
  AppName       = 'Widok Drzewa';
  TreeViewClass = 'SysTreeView32';
  Parent        = 'Element nadrzędny';
  Child1        = 'Potomek1';
  Child2        = 'Potomek2';

var
  DragMode       : boolean;
  hInstance      : HINST;
  hwndTreeView   : longword;
  hParent        : longword;
  hImageList     : longword;
  hDragImageList : longword;

function WndProc(hWnd:HANDLE;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
var
  tvinsert : TV_INSERTSTRUCT;
  tvhit    : TV_HITTESTINFO;
  hBitmap  : longword;
  x        : longint;
  tvp      : ^NM_TREEVIEW;

begin
  Result := 0;
  case uMsg of
    WM_CREATE:
    begin
      hwndTreeView := CreateWindowEx(0,TreeViewClass,0,
                                     WS_CHILD + WS_VISIBLE + TVS_HASLINES +
                                     TVS_HASBUTTONS + TVS_LINESATROOT,0,0,200,400,
                                     hWnd,0,hInstance,0);
      hImageList := ImageList_Create(16,16,ILC_COLOR16,2,10);
      hBitmap    := LoadBitmap(hInstance,IDB_TREE);
      ImageList_Add(hImageList,hBitmap,0);
      DeleteObject(hBitmap);
      SendMessage(hwndTreeView,TVM_SETIMAGELIST,0,hImageList);
      tvinsert.hParent      := 0;
      tvinsert.hInsertAfter := TVI_ROOT;
      tvinsert.item.mask    := TVIF_TEXT + TVIF_IMAGE + TVIF_SELECTEDIMAGE;
      tvinsert.item.pszText := Parent;
      tvinsert.item.iImage  := 0;
      tvinsert.item.iSelectedImage := 1;
      hParent := SendMessage(hwndTreeView,TVM_INSERTITEM,0,longint(@tvinsert));
      tvinsert.hParent := HTREEITEM(hParent);
      tvinsert.hInsertAfter := TVI_LAST;
      tvinsert.item.pszText := Child1;
      SendMessage(hwndTreeView,TVM_INSERTITEM,0,longint(@tvinsert));
      tvinsert.item.pszText := Child2;
      SendMessage(hwndTreeView,TVM_INSERTITEM,0,longint(@tvinsert));
    end;
    WM_MOUSEMOVE:
      if DragMode then
      begin
        tvhit.pt.x := lParam and $ffff;
        tvhit.pt.y := lParam shr 16;
        ImageList_DragMove(tvhit.pt.x,tvhit.pt.y);
        ImageList_DragShowNolock(false);
        x := SendMessage(hwndTreeView,TVM_HITTEST,0,longint(@tvhit));
        if x <> 0 then
          SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,x);
        ImageList_DragShowNolock(true);
      end;
    WM_LBUTTONUP:
      if DragMode then
      begin
        ImageList_DragLeave(hwndTreeView);
        ImageList_EndDrag;
        ImageList_Destroy(hDragImageList);
        SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_CARET,
          SendMessage(hwndTreeView,TVM_GETNEXTITEM,TVGN_DROPHILITE,0));
        SendMessage(hwndTreeView,TVM_SELECTITEM,TVGN_DROPHILITE,0);
        ReleaseCapture;
        DragMode := false;
      end;
    WM_NOTIFY:
    begin
      tvp := POINTER(lParam);
      if tvp^.hdr.code = TVN_BEGINDRAG then
      begin
        hDragImageList := SendMessage(hwndTreeView,TVM_CREATEDRAGIMAGE,0,
                          longint(tvp^.itemNew.hItem));
        ImageList_BeginDrag(hDragImageList,0,0,0);
        ImageList_DragEnter(hwndTreeView,tvp^.ptDrag.x,tvp^.ptDrag.y);
        SetCapture(hWnd);
        DragMode := true;
      end;
    end;
    WM_DESTROY: PostQuitMessage(0);
    else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
  end;
end;

function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD) : longint;
var
  wc   : WNDCLASSEX;
  msg  : MSG;
  hwnd : HANDLE;

begin
  with wc do
  begin
    cbSize        := sizeof(WNDCLASSEX);
    style         := CS_HREDRAW or CS_VREDRAW;
    lpfnWndProc   := @WndProc;
    cbClsExtra    := 0;
    cbWndExtra    := 0;
    hInstance     := hInst;
    hbrBackground := COLOR_APPWORKSPACE;
    lpszMenuName  := 0;
    lpszClassName := ClassName;
    hIcon         := LoadIcon(0,IDI_APPLICATION);
    hIconSm       := hIcon;
    hCursor       := LoadCursor(0,IDC_ARROW);
  end;
  RegisterClassEx(wc);
  hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
                         WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_MINIMIZEBOX +
                         WS_MAXIMIZEBOX + WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
                         200,200,0,0,hInst,0);
   while GetMessage(msg,0,0,0) do
   begin
     TranslateMessage(msg);
     DispatchMessage(msg);
   end;
   Result := msg.wParam;
end;

begin
  InitCommonControls;
  DragMode  := false;
  hInstance := GetModuleHandle(0);
  ExitProcess(WinMain(hInstance,0,0,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.