Problem -> Jak ukryć puste miejsce dla itemów, w których NIE CHCĘ wyświetlac ikonki?
Ot poprzez ustawienie indeksu ikonki na -1
.
Opiszę Ci to na przykładzie Lazarusa, powinno w nim być tak samo jak w Delphi. Lista TreeView.StateImages
dotyczy ikonki wyświetlanej po lewej stronie (pierwsza ikonka, u Ciebie to checkbox), natomiast TreeView.Images
dotyczy drugiej ikonki (u Ciebie to ikonka aplikacji). To dotyczy komponentu drzewa, do którego masz przypisane dwie listy z ikonkami.
Teraz węzły drzewka. TreeNode.StateIndex
dotyczy ikonki po lewej, czyli checkboksu — jeśli chcesz ukryć checkbox, ustaw tę właściwość na -1
. Obie właściwości TreeNode.ImageIndex
oraz TreeNode.SelectedIndex
dotyczą drugiej ikonki, tej z prawej (u Ciebie to ikonka aplikacji) — TreeNode.ImageIndex
dotyczy ikonki wyświetlanej gdy item nie jest zaznaczony, a TreeNode.SelectedIndex
kiedy item jest zaznaczony. Jeśli więc chcesz ukryć drugą ikonkę, czyli tę z logo aplikacji, ustaw -1
zarówno dla TreeNode.ImageIndex
, jak i dla SelectedIndex
.
W momencie tworzenia drzewa i dodawania do niego węzłów, dynamicznie ustawiaj te indeksy. Dla wszystkich itemów, które mają posiadać tylko jedną ikonkę (checkbox), ustaw -1
dla TreeNode.ImageIndex
oraz TreeNode.SelectedIndex
oraz indeks odpowiedniej ikonki checkboksu w właściwości TreeNode.StateIndex
.
Mam nadzieję, że wszystko jest teraz jasne.
Jak dodać do każdego itema na drzewku dodatkową informację (typu string), której nie widać, ale która jest przechowywana dla każdego itema (właściwość Data).
Jak się to robi? Jak się zwalnia te dane?
Biorąc pod uwagę to, że właściwość TreeNode.Data
jest zwykłym wskaźnikiem, możesz tam przypisać absolutnie cokolwiek. Kontrolka drzewa oraz klasa węzłów sama z siebie nie używa danych, które tam wpiszesz — jedyne co dzieje się z automatu to ustawienie temu wskaźnikowi wartości nil
w destruktorze węzła.
Jedyne na co musisz zwrócić uwagę to to, żeby do tej właściwości nie wpisywać wskaźnika na dane zaalokowane na stosie. Możesz tam bezpośrednio podać adres ciągu znaków (typu String
, bo to pointer na dane znajdujące się na stercie i jest zarządzany przez menedżer pamięci) czy czegokolwiek innego zaalokowanego dynamicznie, np. przez GetMem
(albo instancję klasy).
Nie możesz natomiast wpisywać tam adresu zmiennych lokalnych, bo te znajdują się w ramce stosu. Po wyjściu z metody/funkcji, ramka stosu zostaje usunięta, więc późniejszy dostęp do tych danych albo spowoduje błąd Access Violation (czy tam SIGSEGV), albo program będzie czytał śmieci, a więc straci stabilność.
Jeśli chcesz, możesz do tego TreeNode
bezpośrednio wpisać String
, jeśli jest to długi ciąg znaków — ten jest niejawnym wskaźnikiem na dane znajdujące się na stercie. Możesz zrzutować zmienną na Pointer
, możesz podać adres pierwszego znaku (czyli @MyString[1]
, na jedno wychodzi, bo to ten sam adres). To konieczne, bo Pascale są silnie typowane. Takiego ciągu znaków nie musisz zwalniać ręcznie, bo tym zajmie się menedżer pamięci (długie ciągi znaków są zarządzane). Ale na wszelki wypadek sprawdź to sobie — nie powinno być wycieków.
Jeśli potrzebujesz wepchnąć tam więcej danych niż pojedynczy ciąg znaków lub dane musisz samodzielnie zaalokować, to musisz ręcznie te dane zaalokować (np. za pomocą GetMem
czy stworzyć instancję klasy) i wpisać pointer/referencję do właściwości Data
. Takie dane musisz samodzielnie zwolnić, a jeśli tego nie zrobisz, to nastąpi wyciek pamięci.
Aby móc reagować na usuwanie węzłów i mieć możliwość zwolnienia dynamicznie zaalokowanych danych, które przypisywałeś do TreeNode.Data
, wygeneruj sobie zdarzenie TreeView.OnDeletion
i w nim zwalniaj dane. To zdarzenie wywoływane jest automatycznie, zawsze gdy którykolwiek węzeł drzewa jest niszczony.
Tutaj - Pytanie - to robię dla każdego itema czy raz (chyba dla każdego, co?)?
Jeśli każdy item ma mieć swoje dane (swój własny, dodatkowy String
), to dla każdego itema osobno. Przy czym tak jak pisałem wcześniej, długi ciąg znaków typu String
już jest pointerem, więc nie musisz go opakowywać w rekord, aby jego adres wpisać do TreeNode.Data
.
Tutaj zwalniam pamięć rekordu. Ale tutaj jest pokazane dla jednego itema... rozumiem, że muszę przeiterować po wszystkich itemach, tak?
Możesz ręcznie to robić, ale wygodniej jest mieć jedno zdarzenie (czyli TreeView.OnDeletion
), które będzie wywoływane dla każdego niszczonego itema automatycznie. W tym zdarzeniu otrzymujesz referencję węzła w parametrze, więc jedyne co musisz zrobić to zwolnić dane.
Ale jak pisałem wcześniej, jeśli wpisujesz do TreeNode.Data
sam łańcuch znaków, to jego zwolnieniem zajmie się menedżer pamięci. Zresztą próba zwolnienia takiego ciągu za pomocą FreeMem
najpewniej spowoduje błąd. String
to pointer na pierwszy znak, ale nie na pierwszy bajt bloku danych ciągu — przed pierwszym znakiem są metadane.
Pobaw się tym i wybadaj jak się Delphi zachowuje, bo to wszystko może działać inaczej niż w Lazarusie i co nieco być może trzeba będzie zrobić inaczej. No ale od tego masz debugger i inne narzędzia. Stringi są refcountowane, więc nie powinno być problemów z ich bezepośrednim wpisywaniem do TreeNode.Data
.