Autor Beitrag
Popov
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Sa 14.02.04 18:48 
Mehrere Spalten in einer TListBox darstellen?

Letztens hab ich Windows Commander (WinCmd, bzw. seit kurzem Totall Commander) geöffnet und plötzlich war es mir klar: der benutzt kein TListView für die Fenster, sondern eine ListBox. Es ist zwar seine eigene Weiterentwicklung, aber es ist eine ListBox. Klar war es eigentlich schon immer (Bug: Texte die sich überzeichneten), aber man hat das im Geiste nicht ausgesprochen. Also dachte ich mir, ich guck ob ich das auch hinkriege. Ich hab mir einen halben Abend Zeit dafür gegeben und war richtig erschrocken, als ich es in zehn Minuten fertig hatte.

Die Prozedur, die das alles korrekt darstellt, ist allgemein gehalten, d.h. man kann sie übernehmen, einsetzten, aus OnDrawItem der eigenen ListBox aufrufen und schon wird alles korrekt dargestellt.

Hier aber einige Grundregeln:

  1. Es wird eine HeaderControl Komponente benötigt (zu finden in der Win32 Komponentensparte). Die Komponente wird über der ListBox abgelegt und regelt die Breite der einzelnen Spalten. Spalten (Sections) die es in HeaderControl nicht gibt werden auch nicht in der ListBox angezeigt.
  2. Die einzelnen Spalten-Werte müssen in den einzelnen Items in der Form von CommaText abgelegt sein (siehe dazu TStrings.CommaText). Es ist wichtig, daß man sich an die CommaText Konventionen hällt, sonnst ist das Ergebnis nicht korrekt. Am besten ist es die Spalten in Anführungszeichen zu setzen und mit dem gültigen Trenner (z.B. Komma ',') zu trennen, z.B. "Name","Vorname","Alter".
  3. Die allgemeine Prozedur muß aus aus der OnDrawItem Prozedur der ListBox aufgerufen werden, sonnst wird nicht gezeichnet.
  4. Die Style Eigenschaft der ListBox muß den Wert lbOwnerDrawFixed haben, sonnst wird nicht gezeichnet.
  5. In der OnSectionResize Ereignisprozedur der HeaderControl muß ein ListBox.Repaint für die betreffende ListBox stehen, sonnst wird nicht akualistiert.

Das wars schon. Hier die Prozedur:

Es werden mehrere Parameter benötigt:
  • Control, Index, Rect, State - Es sind die gleichen Parameter die auch die OnDrawItem Prozedur liefert, d.h. es müssen nur die Namen übernommen werden.
  • HeaderControl - hier den Namen des HeaderControl angeben der für die ListBox die Spaltenbreiten regelt

ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
{ ListBox.Style auf lbOwnerDrawFixed stellen, sonnst geht es nicht}
procedure DrawListBoxHContr(Control: TWinControl; Index: Integer; Rect: TRect;
  State: TOwnerDrawState; HeaderControl: THeaderControl);

  function Rec(R: TRect; L, W: Word): TRect;
  begin
    with R do 
      Result := Classes.Rect(Left + L, Top, Left + L + W, Bottom);
  end;
var
  k, l1, l2: Integer;
  sl: TStringList;
const
  Col: array [Boolean] of TColor = (clInactiveCaptionText, clWindowText);
begin
  with (Control as TListbox) do 
  begin
    if odSelected in State then 
      Canvas.Font.Color := clCaptionText
    else 
      Canvas.Font.Color := Col[(Control as TListBox).Enabled];

    sl := TStringList.Create;
    try
      sl.CommaText := Items[Index];

      while sl.Count < HeaderControl.Sections.Count do 
        sl.Add('');

      l1 := 0;
      for k := 0 to HeaderControl.Sections.Count - 1 do 
      begin
        l2 := HeaderControl.Sections[k].Width;
        Canvas.TextRect(Rec(Rect, l1, l2), Rect.Left + l1, Rect.Top, sl[k]);
        l1 := l1 + HeaderControl.Sections[k].Width;
      end;

      { den Rest mit leerer Fläche zeichen, sonnst Canvas-Darstellungsfehler }
      l2 := HeaderControl.Width - l1;
      Canvas.TextRect(Rec(Rect, l1, l2), Rect.Left + l1, Rect.Top, '');
    finally
      sl.Free;
    end;
  end;
end{Popov}


Wie funktioniert das ganze? Ganz einfach: es wird davon ausgegangen, daß der Item-String bereits einen Text im CommaText Format enthällt, z.B. "Name","Vorname","Alter". Dieser Text wird dann in der DrawListBoxHContr Prozedur in einzelne Teile (Spalten-Felder) zerlegt. In diesem Fall wären es drei Teile. Jedes Teil wird dann in einer eigenen Spalte dargestellt, d.h. "Name" in der ersten, "Vorname" in der zweiten, "Alter" in der dritten. Voraussetztung ist, daß HeaderControl mindestens drei "Sections" hat. Hat HeaderControl nur zwei Sections, dann werden auch nur zwei Spalten angezeigt. In desem Fall wurde dann das "Alter" nicht angezeigt. HeaderControl Sections entscheiden also wieviel Spalten die ListBox hat und wie breit die sind. Hat HeaderControl mehr Sections als die ListBox CommaText Einheiten, dann werden diese Bereiche in der Listbox mit Leertext ausgefüllt. d.h. es können mehr Sections im HeaderControl sein als Felder in der ListBox.


Beispiel:

Wichtig!!!

Stellt die ListBox1 auf Zeichen um. Einzustellen entweder per Code oder im Objektinspektor.
ausblenden Delphi-Quelltext
1:
  ListBox1.Style := lbOwnerDrawFixed;					


Wichtig!!!

Ruft die allg. Prozedur aus OnDrawItem der ListBox1 auf.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  DrawListBoxHContr(Control, Index, Rect, State, HeaderControl1);
end;


Wichtig!!!

Bewirkt, daß nach HeaderControl Spaltenanpassung die ListBox1 neu gezeichnet wird.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.HeaderControl1SectionResize(HeaderControl: THeaderControl;
  Section: THeaderSection);
begin
  ListBox1.Repaint;
end;


Hier noch ein kleines Beispiel. Durch die Ausführung wird HeaderControl1 und ListBox1 mit Beispielwerten gefüllt:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
procedure TForm1.Button1Click(Sender: TObject);
var
  HS: THeaderSection;
begin
  with HeaderControl1 do 
  begin
    HS := HeaderControl1.Sections.Add;
    HS.Text := 'Vorname';
    HS.Width := 100;
    HS := HeaderControl1.Sections.Add;
    HS.Text := 'Name';
    HS.Width := 100;
    HS := HeaderControl1.Sections.Add;
    HS.Text := 'Beruf';
    HS.Width := 100;
  end;

  with ListBox1 do 
  begin
    Items.Add('"Richard","Wagner","Komponist"');
    Items.Add('"Johann","Strauß","Komponist"');
    Items.Add('"Konrad","Adenauer","Politiker"');
    Items.Add('"Clint","Eastwood","Schauspieler"');
  end;
end;

Moderiert von user profile iconjasocul: Anpassungen an den Style-Guide
Moderiert von user profile iconjasocul: Beitrag geprüft am 23.05.2006

_________________
Popov