TTreeView - wyswietlanie ikonek na drzewku

0

Witam,
W moim projekcie używam TTreeView. Drzewko to składa się z
3 poziomów.
Root 1
|--1 poziom
| |
| --- 2 poziom
| |
| --- 2 poziom
|
|-- 1 poziom
|
Root 2
etc...

Elementy 1 poziomu mają miec wyświetlane ikonki reprezentujące ich nazwe, elementy 2 poziomu mają
wyświetlać obrazek reprezentujący ich stan (zacznaczony, odznaczony).
Elementy poziomu Root nie powinny wyswietlać żadnej ikonki.

Pytam się Was - Co zrobić, by nie wyświetlać ikonki dla poziomu root (a wszedzie indziej tak). U mnie,
pozostaje puste miejsce po ikonce, której co prawda nie ma, ale TTreview rezerwuje na to miejsce i napis jest przesunięty - wygląda to źle.
Proszę o wskazówki.
-Pawel

1

ja bym kombinowal z recznym rysowaniem itemow...

0

Odświeżam temat...
Czy ma ktoś z Was może działającą implementację obsługi drzewka TTreeView - to znaczy taką, która pozwala na dodanie co najmniej 3 poziomów i ich pełną obsługę - to znaczy, jeśli zaznaczymy element poziomu 1 - wszystkie podrzędne (poziom 2 i 3, etc) mają się zaznaczyć. Jeśli mam zaznaczone wszystkie elementy i np. odznaczę element z poziomu 3 - to automatycznie poziom 2 i 1 muszą pokazać ten stan. Mam nadzieję, że wiecie o czym mówię...

Ps: czy ja zadałem pytanie tego wątku 14 lat temu?! Jak ten czas leci!

1

Próbowałeś użyć klasy TVirtualStringTree ?
Ma ma naprawdę dużo możliwości

1

Ja tez bym sugerował obejrzeć przykłady do Virtual-TreeView i użył bym TVirtualStringTree

0

@grzegorz_so Gdzie znajdę oryginalną implemenację? Czy to z pakietu Delphi czy od zewnętrznego podmiotu? W moim Delphi CE tego nie ma.
Tak naprawdę potrzebuję prostej i przede wszystkim szybkiej obsługi drzewka - do wyświetlania elementów (tak jak w instalatorach).

1
Pepe napisał(a):

Czy ma ktoś z Was może działającą implementację obsługi drzewka TTreeView - to znaczy taką, która pozwala na dodanie co najmniej 3 poziomów i ich pełną obsługę - to znaczy, jeśli zaznaczymy element poziomu 1 - wszystkie podrzędne (poziom 2 i 3, etc) mają się zaznaczyć. Jeśli mam zaznaczone wszystkie elementy i np. odznaczę element z poziomu 3 - to automatycznie poziom 2 i 1 muszą pokazać ten stan.

Obsługa TTreeView jest na tyle prosta, że nie potrzeba do tego szczegółowych tutoriali. Biorąc pod uwagę to, że klasa węzła daje dostęp zarówno do osadzonych w nim węzłów, jak i do węzła nadrzędnego (rodzica), skanowanie zawartości drzewka sprowadza się do prostych pętli i rekurencji.

Nie wiem za bardzo co masz na myśli pisząc o zaznaczeniu — chodzi o podświetlenie itemów (multiselect), czy o checkboksy? Choć to nieistotne, bo zaznaczanie i odznaczanie i tak sprowadza się do zmiany stanu jednej flagi węzła (selected, checked itp.).

Ot jedyne co musisz oprogramować to zdarzenie wywoływane podczas (próby) zmiany stanu zaznaczenia itema, a w nim wykonać pętlę i pozaznaczać itemy, zagłębiając się w strukturę drzewa aż do węzłów-liści; a także w górę, aż do węzła-korzenia. To naprawdę nic trudnego — spróbuj, pobaw się.

0

@furious programming
Z pewnością tak. Ale...

  • Drzewko musi obsługiwać zaznaczanie/odznaczanie (łącznie z wyszarzeniem) elementów wielu poziomów (musi obsługiwać 4 stany - zaznaczony/odznaczony/zablokowany(nie można zmienić stanu)/wyszarzony (stan jest zależny od potomnych itemów)
  • Drzewko musi mieć możliwość wyświetlania ikonek dla itemów (dla niektórych, a dla niektórych tylko checkbox)
  • Musi to być szybkie i responsywne
  • Musi to dobrze wyglądać (używam też styli VCL) 😛

Nie jestem pewien, ale chyba w zależności od wersji CommCtrll drzewko wygląda inaczej (chodzi mi o wygląd checkboxów oraz przycisków rozwijania/zwijania oraz linii łączących itemy).
Ten nowoczesny, z Delphi 12 jest jakiś taki dziwny - te checkboxy takie wielkie, podświetelnie itema jakby zachodzi na checkbox... dziwne. Podobało mi się klasyczne drzewko, takie jakie jest w instalatorach NSIS.

Tree.png

Można wczytać własne obrazki symulujące checkbox (to robi NSIS zdaje się), te nowoczesne "ptaszki" są schludne, ale moim zdaniem mniej komunikatywne.
Samo zaznaczanie, odznaczanie jest oczywiście do zrobienia... ale - nie jeden już to zrobił i nie uśmiecha mi się nad tym siedzieć. Nigdy nie potrzebowałem obsługi wielu poziomów, chyba 3 to max. Ale fajnie by było mieć możliwość nieoglądania się na numer poziomu - po prostu żeby można było obsłużyć wszystkie, to znaczy jeśli ODznaczę item poziomu 6 to wszystkie inne mają na to zareagować (itemy wyszarzają się), no wiadomo o co chodzi (pętla pętlę pogania 😛 ).
Ale nie to jest problemem - głównie mi chodzi o wygląd. Jest to na tyle podstawowa kontrolka, że z pewnością nie jeden z was ma ją opanowaną do perfekcji.

Wiem, można użyć też alternatywnych rozwiązań, jak sugerowali koledzy. I być może nawet te inne są szybsze i lepsze - ale na pewni nie prostsze w implementacji.
A ja lubię rozwiązania proste i przenośne (w sensie zawarte w środowisku Delphi, nie z zewnątrz).

Zamierzam się tym pobawić, ale jakaś implementacja dużo by zmieniła.
-Pawel

1

@Pepe: Przenośność w Delphi najłatwiej uzyskać nie instalując żadnych komponentów i wszystko robić w standardowych.
Jezeli jakiś dodatkowy komponent jest potrzebny to źródła muszą byc w podkatalogu i używamy bez instalowania

Jak instalujesz komponenty i masz kilka różnych projektów i jeszcze różne wersje komponentów potrzebne to robi się "sajgon"

Programowanie wiąże się z inwestowaniem czasu, albo zainwestujesz w nauke Virtual-TreeView albo musisz sie pogodzić z tym co masz TTreeView :D

1
Pepe napisał(a):
  • Drzewko musi obsługiwać zaznaczanie/odznaczanie (łącznie z wyszarzeniem) elementów wielu poziomów (musi obsługiwać 4 stany - zaznaczony/odznaczony/zablokowany(nie można zmienić stanu)/wyszarzony (stan jest zależny od potomnych itemów)

Da się coś takiego zrobić, nawet na zwyczajnym TTreeView. Nawet jeśli ten komponent nie obsługuje checkboksów (jak w Lazarusie), to i tak da się zrobić wszystko czego potrzebujesz — wystarczy skorzystać z ikonek itemów. A biorąc pod uwagę to, że każdy item może wyświetlać maksymalnie dwie ikonki obok siebie (dla każdego itema niezależnie), drzewko może wyświetlać zarówno obrazek, jak i checkbox (w dowolnej kolejności). Tak zrobiłem w swoich edytorach — checkboksy zrobiłem w formie obrazków, żeby wyglądały tak jak chcę:

screenshot-20241024133912.png

Checkbox może być zaznaczany kliknięciem, spacją lub oczywiście programowo (są dostępne różne zdarzenia i metody, więc to nie problem). Do tego dodałem sobie wsparcie większej liczby ikonek (jw. z ostrzeżeniem o konflikcie) oraz kolorowych etykiet itemów, aby interfejs był czytelny. Tak więc ogranicza tylko wyobraźnia. 😉

Jedyny minus jest taki, że ruszanie kursorem nad checkboksem, nie zmienia jego ikonki. Zwykły TCheckBox obsługuje hover — po najechaniu kursorem, np. podświetla się obramowanie kontrolki (kwadracik z ptaszkiem), a w drzewie tego nie ma. Oczywiście coś takiego też można by dodać — OnMouseMove, pobranie itema pod kursorem, sprawdzenie czy kursor jest nad checkboksem i jesli tak, zmiana indeksu ikonki dla tego itema. Ale nie chciało mi się tego robić, skoro np. TCheckListBox też hovera nad checkboksami nie wspiera.

  • Drzewko musi mieć możliwość wyświetlania ikonek dla itemów (dla niektórych, a dla niektórych tylko checkbox)

To jest wspierane przez tę kontrolkę.

  • Musi to być szybkie i responsywne

To raczej nie jest problem, bo standardowe kontrolki są szybkie (mimo kobylastości VCL/LCL). To co zawsze należy mieć na uwadze to to, aby przed modyfikacją zawartości kontrolki wywołać metodę BeginUpdate, a po modyfikacji wywołać EndUpdate.

  • Musi to dobrze wyglądać (używam też styli VCL) 😛

To co pokazałeś wygląda dobrze — zresztą wszystkie Windowsy wyglądały dobrze (oprócz Win8). Pytanie tylko, czy zależy Ci na tym, aby interfejs zawsze był zgodny z resztą systemu czy nie. Jeśli masz pomysł na własny interfejs, to akurat Delphi ma wsparcie skórek (szkoda, że Lazarus nie ma…), więc możesz stworzyć własną. A jeśli potrzebujesz zgodności, to też na pewno coś da się w tym temacie wymyślić — ale trzeba znać współczesne Delphi, a ja nie znam. 😛

W Lazarusie mógłbym to bez problemu wykonać — ot wystarczy dynamicznie stworzyć kilka bitmap, namalować na nich checkboksy za pomocą ThemeServices i dodać te bitmapki do ImageList, podpiętego do kontrolki drzewa. A żeby móc dostosować wygląd tych ikon w momencie gdy użytkownik zmienił skórkę w systemie, wystarczy złapać komunikat WM_THEMECHANGED i te ikonki namalować od nowa.

Ten nowoczesny, z Delphi 12 jest jakiś taki dziwny - te checkboxy takie wielkie, podświetelnie itema jakby zachodzi na checkbox... dziwne. Podobało mi się klasyczne drzewko, takie jakie jest w instalatorach NSIS.

NSIS trzyma się ikonek z Windows XP — poznaję ten wygląd.

Można wczytać własne obrazki symulujące checkbox (to robi NSIS zdaje się), te nowoczesne "ptaszki" są schludne, ale moim zdaniem mniej komunikatywne.

Te nowoczesne, które Delphi oferuje, są zgodne z wyglądem Windows 11.

Samo zaznaczanie, odznaczanie jest oczywiście do zrobienia... ale - nie jeden już to zrobił i nie uśmiecha mi się nad tym siedzieć. Nigdy nie potrzebowałem obsługi wielu poziomów, chyba 3 to max. Ale fajnie by było mieć możliwość nieoglądania się na numer poziomu - po prostu żeby można było obsłużyć wszystkie, to znaczy jeśli ODznaczę item poziomu 6 to wszystkie inne mają na to zareagować (itemy wyszarzają się), no wiadomo o co chodzi (pętla pętlę pogania 😛 ).

Jak pisałem — to są dwie funkcje rekurencyjne, każda z jedną pętlą.

Aby odznaczyć wszystkie dzieci danego itema, w jednej pętli lecisz po jej dzieciach, odznaczasz je i wywołujesz rekurencję na każdym dziecku. Druga funkcja idzie od bieżącego itema w górę, po itemach-rodzicach. Za każdym razem zmienia zaznaczenie rodzica i rekurencyjnie przechodzi do jego rodzica — i tak aż do korzenia (lub braku rodzica). Zaznaczenie działa podobnie — też trzeba dwóch funkcji, jedna skanuje rodziców, druga dzieci, również rekurencyjnie.

A to jak konkretnie ma się zachowywać takie automatyczne zaznaczanie i odznaczanie, zależy konkretnie od Twoich wymagań. Chodzi np. o to, czy zaznaczenie danego itema (zafajkowanie go) ma automatycznie zafajkować wszystkie dzieciaki czy nie itp.

Ale nie to jest problemem - głównie mi chodzi o wygląd. Jest to na tyle podstawowa kontrola, że z pewnością nie jeden z was ma ją opanowaną do perfekcji.

Tutaj też przydałoby się wiedzieć jaki konkretnie wygląd Cię interesuje — zawsze zgodny z motywem systemowym czy nie (niezmienny); jeśli statyczny to czy ma być zgodny z wyglądem checkboksów z któregoś Windows czy nie; czy chcesz skorzystać z wbudowanych checkboksów czy imitować je ikonkami, a jeśli tak, to czy potrzebujesz hovera itp itd.

Wiem, można użyć też alternatywnych rozwiązań, jak sugerowali koledzy. I być może nawet te inne są szybsze i lepsze - ale na pewni nie prostsze w implementacji.

Własne skórkowanie drzewka nie jest trudne i wcale nie wymaga pisania dużej ilości kodu. Kwestia tylko jak konkretnie ma wyglądać to drzewko (zrób prototyp w Paincie z wyglądem, jaki Cię interesuje) i jak ma się zachowywać (reakcja na hover czy nie). Chodzi mi o kwestie dotyczące samego wyglądu, bo obsługa zaznaczania itemów to osobna rzecz — tu trzeba będzie napisać kilka funkcji.

0

@furious programming
No elegancko! 😀
Ja wiem, że dla Ciebie to pestka! 😀

Ale niektórzy dopiero się uczą, mimo łupania w kościach 😛

Więc, właśnie siedzę i rozkminiam drzewko... Ma ono 3 poziomy (Grupy Aplikacji (1) | Aplikacje(2) | Rozszerzenia (3)).
Na razie więcej nie potrzebuję (co ułatwia sprawę).

Dzięki własności "StateImages" wyświetlam stan itema wszystkich poziomów (zaznaczony, odznaczony lub wyszarzony) - z odpowiedniego zestawu ikon (TImageList).
Dzięki własności "Images" wyświetlam ikonki aplikacji (poziom 2) - z kolejnego odpowiedniego zestawu ikon (TImageList)

Problem -> Jak ukryć puste miejsce dla itemów, w których NIE CHCĘ wyświetlac ikonki?
Na poniższym zrzucie są to Itemy poziomu 1 (np. Internet | Audio | Obrazki) oraz Itemy poziomu 3 (np. .aup, .$c). Poziom 1 ma ikonki, które chcę.
Wygląda to nieładnie, napis (Caption) jest jakby przesunięty w prawo, bo jest puste miejsce... Co się robi w tym przypadku?

Zrzut ekranu 2024-10-24 210934.png

I kolejny problem.
Czy ktoś może pomóc w takiej kwesti...
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?
Edit:
Tu coś piszą (http://jack.r.free.fr/mirror/dart/zen/Articles/TTreeView/TTreeView_eg05.html) - skorzystam z liczby w Data (bo mi to wystarczy).
Ale, proszę o wyjaśnienie tego:

 {The record pointed to by each Node's .data property}

prNodeData = ^rNodeData;
rNodeData = record
sText : string;
end;
Instead of a class declare a record. Also declare a pointer to the record.

OK, to zrozumiałe - deklarujemy rekord i wskaźnik na niego.

  {Allocate memory for the record}

Data := New( prNodeData );
Tutaj - Pytanie - to robię dla każdego itema czy raz (chyba dla każdego, co?)?

  {Set the nodes date time}

prNodeData(Data)^.sText := FormatDateTime( 'hh:nn:ss', now );
First allocate memory for the record. Then set the sText field. Remember to dereferance ( ^ ) the pointer.

Tu rozumiem do daje swoj tekst do "Data" dla każdego itema

  {Free the memory used by the record}

if( tv_eg1.Selected.Data <> nil ) then
Dispose( prNodeData(tv_eg1.Selected.Data) );
Free the memory used by the record.

Tutaj zwalniam pamięć rekordu. Ale tutaj jest pokazane dla jednego itema... rozumiem, że muszę przeiterować po wszystkich itemach, tak?

-Pawel

2

Ja moge tylko pokazac wstepny zarys jak to zrobic w TVirtualStringTree

poprosilem chatgpt , ja dodalem 5%

unit MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VirtualTrees, System.ImageList,
  Vcl.ImgList, VirtualTrees.Types;

type
  TForm8 = class(TForm)
    ImageList1: TImageList;
    procedure FormCreate(Sender: TObject);
  private
    VirtualStringTree1: TVirtualStringTree;

    procedure VirtualStringTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
    TextType: TVSTTextType; var CellText: string)  ;
  procedure VirtualStringTreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
    var Ghosted: Boolean; var ImageIndex: TImageIndex);
var
  public
    { Public declarations }
  end;

var
  Form8: TForm8;

implementation

{$R *.dfm}

type
Tdata = record
  str:string;
  poziom: integer;
end;

PData = ^TData;


procedure TForm8.VirtualStringTreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex;
    var Ghosted: Boolean; var ImageIndex: TImageIndex);
var
 Data: PData;
  begin
   Data := VirtualStringTree1.GetNodeData(Node);

   if Data^.poziom = 0 then
   begin
    ImageIndex := -1;
      exit;
   end;


   if Data^.poziom = 1 then
   begin
     if Kind = ikNormal then
       ImageIndex := 1;
     if Kind = ikSelected then
       ImageIndex := 2;
     if Kind = ikState then
       ImageIndex := 3;
     if Kind = ikOverlay then
       ImageIndex := 4;


      exit;
   end;

   if Data^.poziom = 2 then
   begin
    ImageIndex := 2;
      exit;
   end;



  end;


procedure TForm8.FormCreate(Sender: TObject);
var
  ParentNode, ChildNode, ChildNode2: PVirtualNode;
  Data: PData;
begin
  // Tworzenie instancji TVirtualStringTree
  VirtualStringTree1 := TVirtualStringTree.Create(Self);
  VirtualStringTree1.Parent := Self; // Ustawienie rodzica na formę
  VirtualStringTree1.Align := alClient; // Dopasowanie do rozmiaru formy
  VirtualStringTree1.TreeOptions.MiscOptions := VirtualStringTree1.TreeOptions.MiscOptions + [toCheckSupport];

  VirtualStringTree1.Images := ImageList1;


  VirtualStringTree1.NodeDataSize := SizeOf(TData);

  ParentNode := VirtualStringTree1.AddChild(nil);
  Data := VirtualStringTree1.GetNodeData(ParentNode);
  Data^.str := 'ROOT 1';
  ParentNode.CheckType := ctTriStateCheckBox;


  ParentNode := VirtualStringTree1.AddChild(nil);
  Data := VirtualStringTree1.GetNodeData(ParentNode);
  Data^.str := 'ROOT 2';
  ParentNode.CheckType := ctRadioButton;


  ChildNode := VirtualStringTree1.AddChild(ParentNode);
  Data := VirtualStringTree1.GetNodeData(ChildNode);
  Data^.str := 'Poziom 1 Dziecko 1';
  Data^.poziom := 1;

  ChildNode := VirtualStringTree1.AddChild(ParentNode);
  Data := VirtualStringTree1.GetNodeData(ChildNode);
  Data^.str := 'Poziom 1 Dziecko 2';
  Data^.poziom := 1;

  ChildNode2 := VirtualStringTree1.AddChild(ChildNode);
  Data := VirtualStringTree1.GetNodeData(ChildNode2);
  Data^.str := 'Poziom 2 Dziecko 1';
  Data^.poziom := 2;

  ChildNode2 := VirtualStringTree1.AddChild(ChildNode);
  Data := VirtualStringTree1.GetNodeData(ChildNode2);
  Data^.str := 'Poziom 2 Dziecko 2';
  Data^.poziom := 2;



  ParentNode := VirtualStringTree1.AddChild(nil);
  Data := VirtualStringTree1.GetNodeData(ParentNode);
  Data^.str := 'ROOT 3';


  VirtualStringTree1.OnGetText :=  VirtualStringTreeGetText;
  VirtualStringTree1.OnGetImageIndex := VirtualStringTreeGetImageIndex;
end;

procedure TForm8.VirtualStringTreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex;
    TextType: TVSTTextType; var CellText: string);
var
  Data: ^string;
begin
  Data := Sender.GetNodeData(Node);
  CellText := Data^;
end;

end.

end.

screenshot-20241025102749.png

aby to "pykło" musisz dodac do sciezki poszukiwań folder source z https://github.com/JAM-Software/Virtual-TreeView
oraz dodac imagelist z obrazkami jako ikony

1
Pepe napisał(a):

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.

0
furious programming napisał(a):
Pepe napisał(a):

Problem -> Jak ukryć puste miejsce dla itemów, w których NIE CHCĘ wyświetlac ikonki?

Ot poprzez ustawienie indeksu ikonki na -1.

Tak, to wydaje się być oczywiste - ale NIE DZIAŁA.
Ustawienie indeksu na -1 nic nie zmienia.

RootNode.ImageIndex := -1;
RootNode.ExpandedImageIndex := -1;
RootNode.OverlayIndex := -1;

Ustawiając takie coś - nic nie zmienia - a logicznie rozumując powinno. Dlaczego!!!?

Resztę rozkminię wieczorem...
Dzięki

0
Marius.Maximus napisał(a):

Ja moge tylko pokazac wstepny zarys jak to zrobic w TVirtualStringTree

poprosilem chatgpt , ja dodalem 5%

Dzięki.
Przeanalizuję to w ramach rozrywki.
A nóż mi się spodoba 😛

-Pawel

1
Pepe napisał(a):

Ustawienie indeksu na -1 nic nie zmienia.

RootNode.ImageIndex := -1;
RootNode.ExpandedImageIndex := -1;
RootNode.OverlayIndex := -1;

Ustawiając takie coś - nic nie zmienia - a logicznie rozumując powinno. Dlaczego!!!?

Jeśli wszystkie indeksy ustawiłeś tak jak należy, a mimo to odstępy nadal się pojawiają, to by oznaczało, że ta kontrolka po prostu zawsze rezerwuje miejsce dla ikonek (po podpięciu pod nią ImageList), nie zwracając uwagi na to, że indeks ikonki ustawiony jest na -1. A to by oznaczało, że tego miejsca po prostu nie usuniesz. Sprawdź kod VCL, ten dotyczący renderowania itemów — tam znajdziesz odpowiedź.

Pozostaje co najwyżej skorzystanie z kontrolki innego typu (np. darmowy i otwartoźródłowy VirtualTreeView), albo zmodyfikowanie źródeł tej kontrolki (tu: albo za pomocą subclassingu, albo bezpośrednio w kodzie VCL).


W Lazarusie takiego problemu nie ma, bo jeśli indeks ikonki ustawiony jest na -1, to obszar dla ikonki nie jest rezerwowany. Dlatego bez problemu można — w jednej kontrolce — wyświetlać itemy z jednym lub dwoma obrazkami, ale też i bez żadnego. Czyli tak jak widać na moim poprzednim zrzucie:

screenshot-20241025125053.png

Węzły folderów mają jedną ikonkę (folder), a te plików mają dwie (plik + checkbox). Przy czym ikonki checkboxów u mnie nie posiadają ramki, więc checkbox odznaczony wygląda jak odstęp, mimo że nim nie jest.

0

Dzięki. Choć w jednym Lazarus jest lepszy 😛
Jest kilka opcji. Albo będę musiał z tym żyć, albo pustkę zapełnić po prostu jakąś ikonką, albo zrezygnować z ikonek.

Wiem, że można zmodyfikować źródła TTreeView tworząc klasę dziedziczącą z tą jedną zmianą... ale nie wiem jak się to robi.
-Pawel

1
Pepe napisał(a):

Albo będę musiał z tym żyć, albo pustkę zapełnić po prostu jakąś ikonką, albo zrezygnować z ikonek.

Można też skorzystać z opcji pośredniej, ale to będzie wymagało nieco więcej kodu. Możesz użyć tylko jednego zestawu ikon (czyli po jednej ikonce na każdy węzeł drzewa), ale każda ikonka, zamiast być kwadratową i posiadać jeden obrazek (logo aplikacji albo checkbox), będzie prostokątna i będzie miała namalowane dwie ikonki — logo apliakcji oraz odpowiedni checkbox.

Problem jednak w tym, że trzeba będzie takie ikonki malować dynamicznie (w runtime) i na każde unikalne logo programu przypadać będą cztery osobne ikonki w ImageList (jedno logo aplikacji, ale cztery różne obrazki dla czterech stanów checkboksa). Więcej z tym roboty, ale końcowy efekt powinien być taki jakiego oczekujesz. Ikonek z logo aplikacji raczej nie masz zbyt wiele, więc spokojnie możesz coś takiego zrobić — tylko nie pogub się w indeksach.

No chyba że Delphi oblicza jedną szerokość ikonki dla wszystkich węzłów (szerokość najszerszej ikonki) i wszystkim przydziela tyle samo miejsca. W takim przypadku i tak powstanie zbędny pusty obszar w węzłach dla rozszerzeń plików (tam gdzie tylko checkbox ma być wyświetlony), a z tym to już kompletnie nic nie zrobi — nic innego niż modyfikacja kodu kontrolki, albo skorzystanie z innego typu kontrolki drzewa.

Wiem, że można zmodyfikować źródła TTreeView tworząc klasę dziedziczącą z tą jedną zmianą... ale nie wiem jak się to robi.

Chodziło mi o subclassing klasy TCustomTreeView i nadpisanie metod odpowiedzialnych za obliczanie obszarów ikonki i tekstu. W ten sposób zmienisz zachowanie tylko i wyłącznie tych kontrolek drzew, które zadeklarowane są w module z ów subclassingiem (zamiast całego VCL-a). Ale to będzie możliwe tylko jeśli metody wykonujące obliczenia (których zachowanie chcesz zmodyfikować) są chronione lub publiczne.

To musisz sprawdzić sam, bo Delphi nie mam u siebie i raczej nie zamierzam go instalować.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.