Jak narysować ikonkę w VirtualStringTree?

0

Cześć,

szukałem rozwiązania w googlach i nie udało mi się znaleźć rozwiązania dlatego prośba o pomoc.

W bazie danych mam pole Blob w którym przechowuję plik BMP z obrazkiem.
Obrazek ten ma być wyświetlane przy głównym więźle tzn NodeLevel = 0

W jaki sposób namalować bitmapę obok więzła?
W załączniku screen aktualnego drzewa.

0

Najprościej:

  1. załadować pod - TImage.
  2. Obsłużyć OnGetIndex
    Bardziej skomplikowane, BeforePaint lub AfterPaint
0

OK spróbuję tak jak opisałeś

EDIT:
No nie daję rady, nie podstawia obrazka do Image1...

Wczytywanie z bazy danych

procedure TfrmRepository.BuildTree(Tree: TVirtualStringTree; ID_U, ID_S: integer);
var
  Dataset: TIBDataset;
  MS: TMemoryStream;
  Bmp: TBitmap;
  Blob: TBlobField;
begin

  Tree.Clear;

  try
    Dataset := TIBDataset.Create(nil);
    Dataset.Database := frmdm.IBDatabase1;
    Dataset.Transaction := frmdm.IBTransaction1;
    Dataset.BufferChunks := 10;
    Dataset.SelectSQL.Text := 'select * from repository where id_u = :id_u and id_s = :id_s';
    Dataset.ParamByName('id_u').AsInteger := ID_U;
    Dataset.ParamByName('id_s').AsInteger := ID_S;
    Dataset.DisableControls;
    Dataset.Open;

    while not Dataset.Eof do
    begin
      MS := TMemoryStream.Create();
      Bmp := TBitmap.Create();
      Bmp.Width := 16;
      Bmp.Height := 16;
      (Dataset.FieldByName('icon') as TBlobField).SaveToStream(MS);
      MS.Position := 0;
      Bmp.LoadFromStream(MS);
      
      // Jeśli zrobię to tutaj to obrazek jest widoczny
      //Image1.Picture.Bitmap.Handle :=  Bmp.Handle;
      // Niestety wszystko musi się dziać w funkcji AddItem (ostatni parametr)

      AddItem(Tree,
        Dataset.FieldByName('ID_Rep').AsInteger,
        Dataset.FieldByName('process_name').AsString,
        Dataset.FieldByName('window_class').AsString,
        Dataset.FieldByName('window_title').AsString,
        Dataset.FieldByName('control_class').AsString,
        Dataset.FieldByName('control_name').AsString,
        Dataset.FieldByName('control_text').AsString,
        Bmp.Handle); // hicon

      Bmp.Free;
      MS.Free;
      Dataset.Next;
    end;

    Dataset.Close;
    Dataset.EnableControls;

    Tree.ClearSelection;
    Tree.FullCollapse();
    Tree.FocusedNode := nil;

    FreeAndNil(Dataset);
  except
    on e:exception do
    begin
      frmMenu.ShowMessageDlg('Błąd wczytywania repozytorium!#13#13<b>' + e.Message + '</b>', 'err');
      FreeAndNil(Dataset);
    end;
  end;
end;
Funkcja AddNode
function TfrmRepository.AddItem(Tree: TVirtualStringTree; ID_Rep: integer; ProcessName, WindowClass, WindowTitle,
  ControlClass, ControlName, ControlText: string; hIcon: hIcon): integer;
var
  Data: PRepository;

  ms: TMemoryStream;
  bmp: TBitmap;
begin
  Tree.BeginUpdate;

    ProcessNode := Tree.AddChild(nil);
    Data := Tree.GetNodeData(ProcessNode);
    Data.ProcessIcon := hIcon; // przypisuję hIcon do daty

  end;
end;

a tak wygląda wskaźnik z danymi:

type // Lista instrukcji
  PRepository = ^TRepository;
  TRepository = record
    ProcessIcon: hIcon;
  end;

Zrobiłem dla tesów na OnClick nody podstawianie obrazka do Image na podstawie hIcon handle

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  Data: PRepository;
begin
  Data := VST.GetNodeData(HitInfo.HitNode);

  Image1.Picture.Bitmap.Handle := Data.ProcessIcon;

end;

no i niestety ale obraz nie jest wyświetlany... o co chodzi? Dodam, że jak debuguję zdarzenie OnClick to w zmiennej Data.ProcessIcon mam wartość.

2

Bo nie do TImage tylko do TImageList który ustawiasz w Object Inspector jako Images i w zdarzeniu OnGetImageIndex podajesz Indeks obrazka dla danego Node (z tego image list).

0

Ok, nie do TImage, ale możecie mi wytłumaczyć dlaczego powyższy kod nie działa? Podstawianie działa tylko w przypadku, gdy robię to zaraz pod wczytaniem ze streamu, po przekazaniu do PRepository i zaczytaniu z nody, nie wyświetla...

0

Bo Icon to nie jest Bitmapa.

var
  MyIcon:TIcon;
  icoHandle: HIcon;
begin
  MyIcon:=TIcon.Create;
  try
   icoHandle := ExtractIcon(application.handle,'c:\windows\explorer.exe', 0) ;
   MyIcon.Handle:=icoHandle;
   Image1.Picture.Icon:=MyIcon;
  finally
   MyIcon.free;
  end;
end;
0

Robię:

var
  Data: PRepository;
begin
Data.ProcessIcon := hIcon; // hIcon = TBitmap;
 Image1.Picture.Bitmap.Assign(Data.ProcessIcon);
end;

I obrazek się pojawia, ale w OnNodeClick, gdy wykonuję:

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  Data: PRepository;
begin
  Data := VST.GetNodeData(HitInfo.HitNode);


  Image1.Picture.Bitmap.Assign(Data.ProcessIcon);

end;

To obrazka nie ma, dodatkowo uchwyt tej bitmapy = 0... możecie pomóc mi to rozwiązać?

0
type 
  PRepository = ^TRepository;
  TRepository = record
    ProcessIcon: hIcon;
  end;
procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  Data: PRepository;
  ico: TIcon;
begin
  Data := VST.GetNodeData(HitInfo.HitNode);

  Ico := TIcon.Create();
  Ico.Handle := Data.ProcessIcon;
  Image1.Picture.Icon := Ico;
 // Image1.Picture.Bitmap.Assign(Data.ProcessIcon);

end;

Nie działa. Ta odpowiedź nie bardzo chce działać...

0

Czy ProcessIcon zawiera właściwą ikonę na 100%? Bo taki kod, jak poniżej działa.

var
  Ico : TIcon;
begin
  Ico := TIcon.Create;
  Ico.Assign(Application.Icon);
  Image1.Picture.Icon.Handle := Ico.Handle;
  Ico.Free;
end;

Tak samo jak taki jego wariant:

var
  Ico : TIcon;
begin
  Ico := TIcon.Create;
  Ico.Handle := Application.Icon.Handle;
  Image1.Picture.Icon.Handle := Ico.Handle;
  Ico.Free;
end;
0

Nie wiem jak dlaczego miałby posiadać nie własciwą ikonkę, przekazuję dokładnie te same parametry, tak jak by odczyt z VirtualStringTree nie był w pełni kompatybilny... nie wiem o co chodzi, męczę to od rana...

Bezpośrednio po zapisie do wskaźnika i linijka po przypisaniu odczyt obrazka działa, ale jeśli robię odczyt w OnNodeClick to obrazek nie jest wczytywany...

1

To nie wiem. Ja na ogół wszelkie Data zawarte w ListItemach, ale głownie TListView rzutuje do zmiennej typu obiektu, typem obiektowym, a nie wskaźnikowym. I nie miałem raczej problemów. Może ktoś jeszcze coś Ci tutaj doradzi, bo ja tego komponentu co używasz nie znam. I nie musiałem do tej pory poznać.

0

odczyt.png
po przypisaniu.png
Po odczycie ze wskaźnika TIcon ma całkiem inny uchwyt, inne rozmiary wychodzące poza integer zamiast 16x16 - bo taki obrazek tam przechowuję. HOWARD WHY????

0

Może dlatego, że dodajesz do własności przechowującej Pointery obiekt jako Pointer. Albo tak też się odwołujesz. Na przykład taki kod jak poniżej, wyświetli ikonę Aplikacji, ale dopiero po naciśnięciu Button2. Po tym gdy klikniemy na Button1, teoretycznie nic się nie dzieje w kwestii wyświetlania i Handle też będzie inne niż pokazuje się w zerowej kolumnie ListView. Analogicznie dzięję się z wszelkimi komponentami przechowującymi Dane jako Pointery. W tym pewnie i ten, którego używasz u siebie w kodzie.

//...
type
  TObj = class(TObject)
    AnIcon : HICON;
  end;
  PObj = ^TObj;

procedure TForm1.FormCreate(Sender : TObject);
var
  Obj : TObj;
  LI : TListItem;
begin
  LI := ListView1.Items.Add;
  Obj := TObj.Create;
  Obj.AnIcon := Application.Icon.Handle;
  LI.Caption := IntToStr(Obj.AnIcon);
  LI.Data := Obj;
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  ObjPtr : PObj;
  LI : TListItem;
begin
  LI := ListView1.Items.Item[0];
  ObjPtr := LI.Data;
  Image1.Picture.Icon.Handle := ObjPtr.AnIcon;
  Caption := IntToStr(ObjPtr.AnIcon);
end;

procedure TForm1.Button2Click(Sender : TObject);
var
  Obj : TObj;
  LI : TListItem;
begin
  LI := ListView1.Items.Item[0];
  Obj := LI.Data;
  Image1.Picture.Icon.Handle := Obj.AnIcon;
  Caption := IntToStr(Obj.AnIcon);
end;
//...

EDIT: teraz widzę. Trzymasz to jako rekord. Nie znam się, z tym zawsze miałem problemy. Lepiej przechowywać to co siedzi w Data, jako obiekt i już. Nie ma też wtedy problemu przekazywania go jako Pointer dla wątków w kodzie korzystającym z WinAPI.

EDIT #2: dla obsługi typu rekordowego, jeśli się przy nim upierasz. Musisz "pokombinować" mniej więcej tak, jak pokazuje poniżej:

//..
type
  TObj = record
    AnIcon : HICON;
  end;

procedure TForm1.FormCreate(Sender : TObject);
var
  Obj : TObj;
  ObjPtr : ^TObj;
  LI : TListItem;
begin
  LI := ListView1.Items.Add;
  Obj.AnIcon := Application.Icon.Handle;
  ObjPtr := @Obj;
  LI.Caption := IntToStr(Obj.AnIcon);
  LI.Data := Pointer(Obj);
end;

procedure TForm1.Button1Click(Sender : TObject);
var
  ObjPtr : ^TObj;
  LI : TListItem;
begin
  LI := ListView1.Items.Item[0];
  ObjPtr := @LI.Data;
  Image1.Picture.Icon.Handle := ObjPtr.AnIcon;
  Caption := IntToStr(ObjPtr.AnIcon);
end;
//...
0

O matko :P spróbuję jutro i dam znać, dzisiaj już nie mam siły na to... Dzięki Olesio

0

@olesio w tym rekordzie lepiej trzymać hIcon czy może całą ikonkę TIcon? Będzie jakaś różnica?

0

Przecież już dostałeś odpowiedź na to pytanie, HICON który dostałeś od systemu może zostać zwolniony przez system w nieoczekiwanym momencie.

0

Porażka...
Odczyt

var
  Data: PRepository;

  ObjPtr : ^TRepository;
begin
  Data := VST.GetNodeData(HitInfo.HitNode);
  ObjPtr := @Data.ProcessIcon;

  Image1.Picture.Icon := ObjPtr.ProcessIcon;

Zapis:

var
  Data: PRepository;
  ObjPtr : ^TRepository;
  Obj : TRepository;
begin

    ProcessNode := Tree.AddChild(nil);
    Data := Tree.GetNodeData(ProcessNode);
    Data.ProcessIcon := Ico;

    // Wersja Olesio
    Obj.ProcessIcon := Ico;
    ObjPtr := @Obj;
end;

Access Violation na odczycie ;/

0

Nie znam tego komponentu, pokazałem Ci jak to się robi z typami rekordowymi dla celów TListView. A czy na pewno dane w Nodzie są przechowywane jako typ Pointer? Poza tym wydaje się mi, że jednak ja dodaje i odczytuje nieco inaczej. Ale kombinuj, jak uważasz.

0

Zrobiłem tak:

Odczyt:

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  ObjPtr : ^TRepository;
begin
  ObjPtr := VST.GetNodeData(HitInfo.HitNode);
  Image1.Picture.Icon:= ObjPtr.ProcessIcon;
end;

Zapis:

    Data := Tree.GetNodeData(ProcessNode);
var
  Ico: TIcon;
  Data: PRepository;

  Obj : TRepository;
  ObjPtr : ^TRepository;
begin
    Ico := Application.Icon;

    Obj.ProcessIcon := Ico;
    ObjPtr := @Obj;
    Data.ProcessIcon := Pointer(Obj.ProcessIcon);

Rekord

type 
  PRepository = ^TRepository;
  TRepository = record
    ProcessIcon: TIcon;
end;

Po kliknięci pierwszy raz na Node podczas odczytu w zmiennej Obj.ProcessIcon mam ikonkę, lecz po przejściu na następną pozycję w drzewku i powrót na poprzednią powoduje, że obiekt znowu ma dziwne dane, dodatkowo, gdy w kodzie przed przypisaniem obiektu wykonam taki kod:

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  ObjPtr : ^TRepository;
begin
  Image1.Picture.Icon := nil; // magiczna linijka
  
  // ObjPtr od razu ma nieprawidłowe wartości nawet przy pierwszym kliknięciu

  ObjPtr := VST.GetNodeData(HitInfo.HitNode);
  Image1.Picture.Icon:= ObjPtr.ProcessIcon;
end;

Jeszcze robię coś źle?

0
user322 napisał(a):

@olesio w tym rekordzie lepiej trzymać hIcon czy może całą ikonkę TIcon? Będzie jakaś różnica?

Skoro już programujesz z wykorzystaniem VCL, to nie baw się w pointery - trzymaj po prostu referencję do instancji klasy ikony typu TIcon; Pamięć dla ikony i tak będzie musiała być zarezerwowana, więc te kilka bajtów więcej nie będzie stanowić wielkiego problemu;

Zyskasz większą wygodę i prostotę użytkowania, bo ominiesz rzutowanie i zabawę wskaźnikami, z którymi coś się motasz;

Image1.Picture.Icon := nil; // magiczna linijka

IMHO tą magiczną linijką powodujesz wyciek pamięci, jeśli takie przypisanie nie jest odpowiednio obsługiwane przez wewnętrzne mechanizmy komponentu (obsługa właściwości nie zwalnia zarezerwowanej dla ikony pamięci); Trzeba by to zwąchać.

0

@furious programming mistrzu poradź jak rozwiązać ten problem! :)

0

Przede wszystkim stwórz sobie zestaw ikon, zawierający ikonki mogące zostać załadowane do instancji klas w jak najprostszy sposób; Rekord który pakujesz do danych itema także może być prostą klasą - ułatwisz sobie pracę na rzecz kilku(nastu) bajtów więcej;

Ja nie znam tego komponentu, więc napisz jakiego typu dane zwraca ta instrukcja:

VST.GetNodeData(HitInfo.HitNode);

i co kryje się w typie THitInfo i polu/właściwości HitInfo.HitNode.

0
VST.GetNodeData(HitInfo.HitNode);

zwraca Pointer, nastomiast HitInfo jest klasą zawierającą metody dot. aktualnie zaznaczonej pozycji np. HitNode (aktualnie wybrana noda), HitColumn (Kolumna w której kliknęliśmy), z kolei HitNode jest typem TVirtualNode;

0

No dobra, napisz jeszcze czym jest TRepository i PRepository, oraz dlaczego zmienne deklarujesz jako:

Data: PRepository;
lub

ObjPtr: ^TRepository;

Jeśli PRepository to wskaźnik na TRepository, to raczej nie ma sensu deklarować ich inaczej.

0
Data: PRepository;

nauczyłem się korzystać tak z wielu przykładów i także stąd Virtual Treeview

ObjPtr: ^TRepository;

Jest to kombinowanie w stylu tego co @olesio poradził, jak on to robi z ListView, ale no mi to nie działa na VirtualStringTree, albo źle coś robię.

A TRepository jest to rekord z danymi dla wskaźnika:

type 
  PRepository = ^TRepository;
  TRepository = record
    Nazwa: string;
    Kolumna1: string;
    Kolumna2: string;
    ProcessIcon: TIcon;
  end;
0

Zapisy PRepository i ^TRepository są równoznaczne na poziomie języka, jednak mogą mylić;

Z drugiej strony tak patrzę na ten tutorial z podanego linka i mam wątpliwości... Birąc pod lupę poniższy kod:

var
  Data: PWirtualnyRekord;
begin
  Data := Sender.GetNodeData(Node);
  if Length(Data.Caption) = 0 then
  Data.Caption := 'Wiersz ' + IntToStr(Sender.AbsoluteIndex(Node)+1);  // tu problem...
  CellText := Data.Caption;
end; 

już na pierwszy rzut oka widać błędy; Jeśli PWirtualnyRekord jest wskażnikiem na rekord typu TWirtualnyRekord, to odwołanie do zmiennej Data jako wskaźnika w sposób Data.Caption jest niepoprawne i taki kod powinien walić błędami Access Violation; Prawidłowym zapisem było by Data^.Caption;

Dlatego też jeśli wrzucasz wskaźnik do danych węzła, to po jego pobraniu powinieneś się odwoływać do jego składowych za pomocą operatora ^; @olesio pokazał Ci przykład z wykorzystaniem obiektu, a nie rekordu, więc musisz analizować podawane kody i rozumieć jak działają;</del>
Dobrze jest jednak - zapomniałem że brak operatora ^ w Delphi jest dopuszczalny; Przesiadłem się na FPC, a tam bez daszka kod się nie skompiluje;

Obstawiałbym, że poniższy kod:

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree;
  const HitInfo: THitInfo);
var
  ObjPtr : ^TRepository;
begin
  ObjPtr := VST.GetNodeData(HitInfo.HitNode);
  Image1.Picture.Icon:= ObjPtr.ProcessIcon;
end;

powinien wyglądać coś w ten deseń:

procedure TfrmRepository.VSTNodeClick(Sender: TBaseVirtualTree; const HitInfo: THitInfo);
var
  ptrRep : PRepository;
begin
  ptrRep := PRepository(VST.GetNodeData(HitInfo.THitInfo));
  Image1.Picture.Icon := ptrRep^.ProcessIcon;
end;

Napisałem "w ten deseń", bo składniowo było by poprawnie, jednak nie mam jak tego przetestować; No i musisz pamiętać, że jeśli korzystasz ze wskaźników, to rekordy na które wskaźniki wskazują muszą posiadać zaalokowaną pamięć; Bez względu na to czy istnieją one w jakichś zmiennych, polach klasy, macierzach, czy alokowane dynamicznie, za pomocą np. GetMem; Osobiście wybrałbym dynamiczną alokację i trzymanie wskaźników na ich pamięć tylko w danych elementów; Dzięki temu przy tworzeniu nowego elementu drzewa, alokowałoby się pamięć dla nowego rekordu i przypisywało wskaźnik do zaalokowanej pamięci, a przy usuwaniu zwalniałoby się ją;

Jak widzisz korzystanie ze wskaźników daje ciekawe możliwości, ale ich obsługa nie jest taka prosta i łatwo się pogubić, przez co kod może nie działać prawidłowo, a program generować wycieki pamięci; Jeśli jeszcze motasz się ze wskaźnikami, to poćwicz sobie wcześniej na dedykowanym jedynie dla testów programie - mniej będziesz miał później problemów.

0

Twój kod się kompiluje, jednak po klinkięciu otrzymuję komunikat

Stream read error

po kliknięciu Debug przenosi mnie do Unitu System.Classes

```delphi
procedure TStream.ReadBuffer(var Buffer; Count: Longint);
var
  Buf: TBytes;
  LTotalCount,
  LReadCount: Longint;
begin
  SetLength(Buf, Count);
  if Count = 0 then Exit;

  { Perform a read directly. Most of the time this will succeed
    without the need to go into the WHILE loop. }

  LTotalCount := Read(Buf, 0, Count);

  while (LTotalCount < Count) do
  begin
    { Try to read a contiguous block of <Count> size }
    LReadCount := Read(Buf, LTotalCount, (Count - LTotalCount));

    { Check if we read something and decrease the number of bytes left to read }
    if LReadCount <= 0 then
      raise EReadError.CreateRes(@SReadError) // TĄ LINIJKE PODŚWIETLA
    else
      Inc(LTotalCount, LReadCount);
  end;
  Move(Buf[0], Buffer, Count);
end;

Ponadto, przy wyjściu z programu mam accessy :(

1

Witam.
Nie wiem dokładnie co chcesz uzyskać (przyznaję się, nie czytałem całości :(, ale żeby narysować ikonkę w wybranym węźle, zrobiłem to tak:
Do wirtualnego rekordu VST dodałem jeszcze jedno pole, np. 'NumerIkonki : Integer', teraz po kliknięciu w odpowiedniego Noda, do tego pola przypisywana jest jakaś wartość (w zależności jaką chcę ikonkę). Wszystkie ikonki (u mnie *.png) wrzuciłem do 'ImageList1' i obsłużyłem zdarzenie VST OnBeforeCellPaint:

 procedure TViewTransferFrm.VSTBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
           CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
 begin
   Data := Sender.GetNodeData(Node);
   case Data.NumerIkonki of
     0 : ImageList1.Draw(TargetCanvas, CellRect.Left + 39, CellRect.Top, 0);
     1 : ImageList1.Draw(TargetCanvas, CellRect.Left + 11, CellRect.Top, 1);
     2 : ImageList1.Draw(TargetCanvas, CellRect.Left + 24, CellRect.Top, 2);
   end;
 end;

i jak na razie wszystko działa bez problemu :)

0

Nie wiem dokładnie co chcesz uzyskać (przyznaję się, nie czytałem całości
No a szkoda, bo nie o to chodzi.
Chodzi o to, że w bazie mam pole BLOB, które zawiera ikonkę TIcon przypisaną do danej nody i teraz chcę tą ikonkę narysować obok Nody. Problem w tym, że Bezpośrednio po narysowaniu drzewa mam dostęp do ikonki i się wyświetla, natomiast po zastosowaniu w innych miejscach

var
  Ikona: TIcon;
begin
Data := VST.GetNodeData(Node);
Ikona := Data.IkonaProcesu;
end;

Nie dostaję ikony, a rekord ma jakieś śmieciowe wartości.

1 użytkowników online, w tym zalogowanych: 0, gości: 1