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
