Do prezentacji stacji radiowych użyjemy komponentu VirtualTreeView. Jego główną zaletą jest szybkość. Dodanie miliona węzłów (nodów) zajmuje mniej niż jedną sekundę. W naszym przypadku to aż nadto bo nie przewidujemy takiej ilości danych. Nie będziemy mieli jednak problemu z szybką aktualizacją informacji podczas edycji lub wyszukiwania stacji radiowych.
VirtualTreeView ma ogromne możliwości, o których przeczytasz na stronie soft-gems.net. My skorzystamy tylko z podstawowych funkcji potrzebnych do wyświetlenia Grida z wierszami podzielonymi na kilka kolumn.
Opis instalacji komponentu VirtualTreeView znajdziesz tu.
Konfiguracja
Aby zwiększyć przejrzystość, instancję klasy VirtualStringTree utworzymy w sposób dynamiczny.
TMainForm = class(TForm) private procedure CreateVstStationList; public VstStationList: TVirtualStringTree; end; implementation procedure TMainForm.CreateVstStationList; begin VstStationList := TVirtualStringTree.Create(Self); VstStationList.Name := 'StationList'; VstStationList.Parent := MainPanel; VstStationList.Align := alCustom; VstStationList.DefaultNodeHeight := 20; VstStationList.SelectionCurveRadius := 0; VstStationList.TabOrder := 0; VstStationList.BorderStyle := bsNone; // Events VstStationList.OnBeforeItemErase := @VstStationListBeforeItemErase; VstStationList.OnHeaderClick := @VstStationListHeaderClick; VstStationList.OnFreeNode := @VstStationListFreeNode; VstStationList.OnCompareNodes := @VstStationListCompareNodes; VstStationList.OnGetText := @VstStationListGetText; VstStationList.OnDblClick := @VstStationListDblClick; VstStationList.OnKeyPress := @VstStationListKeyPress; VstStationList.OnFocusChanged := @VstStationListFocusChanged; VstStationList.OnGetNodeDataSize := @VstStationListGetNodeDataSize; VstStationList.OnAfterColumnWidthTracking := @VstStationListAfterColumnWidthTracking; VstStationList.OnHeaderDrawQueryElements := @VstStationListHeaderDrawQueryElements; VstStationList.OnAdvancedHeaderDraw := @VstStationListAdvancedHeaderDraw; // Columns VstStationList.Header.Columns.Add.Text := 'Station Name'; VstStationList.Header.Columns[0].Width := 240; VstStationList.Header.Columns.Add.Text := 'Genre'; VstStationList.Header.Columns[1].Width := 120; VstStationList.Header.Columns.Add.Text := 'Country'; VstStationList.Header.Columns[2].Width := 90; // Visibility of the header columns VstStationList.Header.Options := [hoVisible, hoColumnResize, hoHotTrack, hoOwnerDraw, hoShowHint, hoShowImages, hoShowSortGlyphs]; VstStationList.Header.Height := 20; // Options VstStationList.TreeOptions.AnimationOptions := [toAnimatedToggle]; VstStationList.TreeOptions.AutoOptions := [toAutoDropExpand, toAutoScroll, toAutoScrollOnExpand, toAutoTristateTracking, toAutoDeleteMovedNodes]; VstStationList.TreeOptions.MiscOptions := [toCheckSupport, toFullRepaintOnResize, toGridExtensions, toInitOnSave, toWheelPanning]; VstStationList.TreeOptions.PaintOptions := [toHideFocusRect, toThemeAware, toUseBlendedImages]; VstStationList.TreeOptions.SelectionOptions := [toDisableDrawSelection, toExtendedFocus, toFullRowSelect, toMiddleClickSelect, toRightClickSelect]; VstStationList.TreeOptions.StringOptions := [toSaveCaptions]; // Sort direction VstStationList.Header.SortDirection := TSortDirection.sdAscending; VstStationList.Header.SortColumn := 0; // Scroll bars VstStationList.ScrollBarOptions.ScrollBars := ssVertical; end;
Dodajemy trzy kolumny oraz ustawiamy wysokość wierszy, domyślne sortowanie i widoczność pasków przewijania. Podpinamy również zdarzenia opisane poniżej. Do konfiguracji nagłówków dodajemy możliwość zmiany szerokości hoColumnResize oraz ikonę kierunku sortowania hoShowSortGlyphs. Z opcji zaznaczania SelectionOptions usuwamy toCenterScrollIntoView, który powoduje że cały czas aktywny jest środkowy z wyświetlonych wierszy.
Zdarzenie OnBeforeItemErase
Wołane jest tuż przed wyczyszczeniem wiersza. Używamy do kolorowania co drugiego wiersza oraz do oznaczenia aktualnie odtwarzanej stacji
procedure TMainForm.VstStationListBeforeItemErase(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; const ItemRect: TRect; var ItemColor: TColor; var EraseAction: TItemEraseAction); var Data: PStationNodeRec; begin if (not Sender.Selected[Node]) then begin Data := Sender.GetNodeData(Node); // Colorize line with currently playing station if (Data^.snd.ID = RadioPlayer.CurrentStationId) then begin ItemColor := clRed; EraseAction := eaColor; end else // Coloring every second line if (Odd(Node^.Index)) then begin ItemColor := clGray; EraseAction := eaColor; end; end; end;
PStationNodeRec jest wskaźnikiem na rekord reprezentujący dane danego wiersza, czyli:
type TStationNodeData = class protected FID : string; FName : string; FGenre : string; FCountry : string; public constructor Create(const Id: string; const Name, Genre, Country: string); overload; property ID : string read FID write FID; property Name : string read FName write FName; property Genre : string read FGenre write FGenre; property Country : string read FCountry write FCountry; end; PStationNodeRec = ^TStationNodeRec; TStationNodeRec = record snd : TStationNodeData; end;
Zdarzenie OnHeaderClick
Wyzwalane jest po kliknięciu w nagłówek kolumny. Używamy do sortowania danych.
procedure TMainForm.VstStationListHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); begin // We determine the sort direction but only if click on the same column if (Sender.SortColumn = HitInfo.Column) then begin if Sender.SortDirection = sdAscending then Sender.SortDirection := sdDescending else Sender.SortDirection := sdAscending; end; // show SortGlyph Sender.SortColumn := HitInfo.Column; // sorting Sender.Treeview.SortTree(HitInfo.Column, Sender.SortDirection, True); end;
Zdarzenie OnFreeNode
Wyzwalane jest tuż przed zwolnieniem danego węzła (noda).
procedure TMainForm.VstStationListFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); var Data: PStationNodeRec; begin Data := Sender.GetNodeData(Node); if not Assigned(Data) then Exit; if Data^.snd <> nil then Data^.snd.Free; Finalize(Data^); end;
Zdarzenie OnCompareNodes
Wyzwalane jest przy sortowaniu i przeszukiwaniu węzłów (nodów). Zmienna Column wskazuje na kolumnę, według której porównujemy węzły.
procedure TMainForm.VstStationListCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer); var Data1: PStationNodeRec; Data2: PStationNodeRec; begin Data1 := Sender.GetNodeData(Node1); Data2 := Sender.GetNodeData(Node2); if (not Assigned(Data1) or (Data1^.snd = nil)) or (not Assigned(Data2) or (Data2^.snd = nil)) then Result := 0 else begin case Column of 0: Result := CompareText(Data1^.snd.Name, Data2^.snd.Name); 1: Result := CompareText(Data1^.snd.Genre, Data2^.snd.Genre); 2: Result := CompareText(Data1^.snd.Country, Data2^.snd.Country); end; end; end;
Zdarzenie OnGetText
Wyzwalane jest każdorazowo przy pobieraniu tekstu dla danego węzła i kolumny. Czyli właśnie w tym zdarzeniu wskazujemy co ma zostać wyświetlone w kolumnach.
procedure TMainForm.VstStationListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String); var Data: PStationNodeRec; begin Node^.States := Node^.States +[vsOnFreeNodeCallRequired]; // A handler for the OnGetText event is always needed as it provides // the tree with the string data to display. // Note that we are always using WideString Data := Sender.GetNodeData(Node); if Assigned(Data) and (Data^.snd <> nil) then begin case Column of 0: CellText := Data^.snd.Name; 1: CellText := Data^.snd.Genre; 2: CellText := Data^.snd.Country; end; end; end;
Zdarzenie OnDblClick
Wyzwalane po podwójnym kliknięciu w wiersz.
procedure TMainForm.VstStationListDblClick(Sender: TObject); var node: PVirtualNode; data: PStationNodeRec; begin node := VstStationList.GetFirstSelected; if node <> nil then begin data := VstStationList.GetNodeData(node); RadioPlayer.PlayStation(data^.snd.ID); end; end;
Zdarzenie OnKeyPress
Wyzwalane po naciśnięciu klawisza.
procedure TMainForm.VstStationListKeyPress(Sender: TObject; var Key: char); begin if (Key = Char(VK_RETURN)) then begin // set #0 to eliminate Ding sound Key := #0; end end;
Zdarzenie OnFocusChanged
Wyzwalane, gdy fokus węzła ma się zmienić. Może się przydać np. do zmiany wysokości aktualnie zaznaczonego węzła.
procedure TMainForm.VstStationListFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); begin with TVirtualStringTree(Sender) do begin if OldNode <> nil then NodeHeight[OldNode] := DefaultNodeHeight; if Node <> nil then begin NodeHeight[Node] := round(DefaultNodeHeight * 1.5); OldNode := Node; end; end; end;
Zdarzenie OnGetNodeDataSize
Wyzwalane, gdy dostęp do danych węzła następuje po raz pierwszy, ale rozmiar danych nie jest jeszcze ustawiony.
procedure TMainForm.VstStationListGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); begin NodeDataSize := SizeOf(TStationNodeRec); end;
Zdarzenie OnAfterColumnWidthTracking
Wyzwalane przy zmianie szerokości kolumny. Możemy użyć do zapisu ustawień.
procedure TMainForm.VstStationListAfterColumnWidthTracking(Sender: TVTHeader; Column: TColumnIndex); begin case Column of 0: TTRPSettings.SetValue('StationList.ColumnWidth.StationName', Sender.Columns[Column].Width); 1: TTRPSettings.SetValue('StationList.ColumnWidth.Genre', Sender.Columns[Column].Width); 2: TTRPSettings.SetValue('StationList.ColumnWidth.Country', Sender.Columns[Column].Width); end; end;
Zdarzenie OnHeaderDrawQueryElements
Wskazujemy, że tło nagłówków kolumn będziemy rysować osobiście. Powiązane z kolejnym zdarzeniem OnAdvancedHeaderDraw.
procedure TMainForm.VstStationListHeaderDrawQueryElements(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); begin Elements := [hpeBackground]; end;
Zdarzenie OnAdvancedHeaderDraw
Rysujemy tło nagłówków kolumn
procedure TMainForm.VstStationListAdvancedHeaderDraw(Sender: TVTHeader; var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); begin if hpeBackground in Elements then begin PaintInfo.TargetCanvas.Brush.Color := TSkins.GetColorItem('StationList.Header.BackgroundColor'); PaintInfo.TargetCanvas.FillRect(PaintInfo.PaintRectangle); end; end;
Konfigurację mamy gotową więc w kolejnym wpisie przejdziemy do dodawania, edycji i usuwania danych.
Kod aplikacji dostępny jest na GitHubie.
- Tiny Radio Player #01 – Wprowadzenie
- Tiny Radio Player #02 – Instalacja komponentów
- Tiny Radio Player #03 – Roboczy interfejs aplikacji
- Tiny Radio Player #04 – Budujemy silnik
- Tiny Radio Player #05 – Zapis ustawień aplikacji
- Tiny Radio Player #06 – Zmiana języka aplikacji
- Tiny Radio Player #07 – Logowanie błędów
- Tiny Radio Player #08 – Baza danych
- Tiny Radio Player #09 – Zarządzanie bazą SQLite
- Jesteś tu => Tiny Radio Player #10 – Lista stacji radiowych, konfiguracja VirtualTreeView