Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - ListBox: waagerechte Scrollbars


Chris1308 - Do 11.07.02 20:42
Titel: ListBox: waagerechte Scrollbars
Hi @all!
Wie kann ich einer Listbox eine waagerechte Scrollbar zuweisen?

Chris1308


DaDoc - Fr 12.07.02 08:26

z.B. mit

Quelltext
1:
SendMessage(Listbox.Handle, LB_SetHorizontalExtent, BreiteInPixel, LongInt(0));                    

Sobald die Listbox kleiner als "BreiteInPixel" ist, wird eine horizontale ScrollBar angezeigt, der so ausgelegt ist, daß man damit den Bereich von "BreiteInPixel" überschauen kann. Dazu muß man aber natürlich erstmal wissen, wie breit der "breiteste" Eintrag in Pixel ist (mit aktuellen Font) ... :wink:

Sven


Chatfix - Mo 15.07.02 15:06

man sollte vieleicht doch die such-funktion des forums nutzen.. ichhatte das gleiche problem..

die lösung findest du hier:

http://www.delphi-forum.de/viewtopic.php?t=207


EDIT: Link aktualisiert, war noch auq.de


Prof. Dachs - Do 21.04.05 13:51

Hallo,
ich habe gerade die Funktion SendMessage ausprobiert. Das hat aber nicht funktioniert, weil LB_SetHorizontal ein "undeclared identifier" ist. Ich habe es auch mit Listbox1.Perform ausprobiert, selber Fehler. Kann das daran liegen dass ich mit Delphi 7 Personal arbeite? Oder an was sonst?
Danke schon im Voraus,
Markus


DaDoc - Do 21.04.05 14:25

Die Deklaration von "LB_SETHORIZONTALEXTENT" befindet sich in der Datei "Messages".
Btw.: Schau mal nach, ob deine Listbox die Eigenschaft "ScrollWidth" im Objektinspektor hat. Damit läst sich auch eine horizontale ScrollBar einblenden, wenn die Breite der Einträge (in Pixel) größer als die Breite der Listbox ist.

Sven


Prof. Dachs - Do 21.04.05 14:38

Mit der Einbindung der Unit Messages hat SendMessage jetzt funktioniert. thx@DaDoc.
Mit Scrollwidth hab ich des erst nich so ganz gepeilt, aber jetzt(nachdem ichs nochmal ausprobiert hab), funktioniert des auch). Also thx nochmal.


DaDoc - Do 21.04.05 14:51

Laut QuellCode von TListBox wird beim Setzen von "ScrollWidth" wird auch nichts anderes gemacht, als ein "SendMessage(Handle, LB_SETHORIZONTALEXTENT, Value, 0)". Bin ich zuerst auch nicht darüber gestolpert... :D

Sven


Dibelius - Fr 03.04.09 21:03

*Uralt-Thread ausgrab*

ich hab nach diesem Tutorial [http://www.delphi-treff.de/tipps/komponenten/wiki/Listbox%20mit%20horizontaler%20Scrollbar] für eine Listbox eine horizontale Scrollbar eingefügt. Funktioniert auch alles wunderbar... bis auf die Tatsache, dass sich gleiches Prinzip nicht auf eine zweite Listbox anwenden lässt, die ebenfalls in Form1 ist. Ich hab es sowohl mit gleichen als auch mit unterschiedlichen Variablen für die Scrollbar-Breite versucht. Klappte bisher alles nicht.

Kann mir da jemand eienn heißen Tipp geben?


delphi10 - Fr 03.04.09 21:11

user profile iconDibelius hat folgendes geschrieben Zum zitierten Posting springen:
*Uralt-Thread ausgrab*

Kann mir da jemand eienn heißen Tipp geben?


Versuchs mal damit:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
Procedure TFormX.SetHorizontalScrollBar(lb : TListBox);
var
  j, MaxWidth,tmp: integer;
begin
 MaxWidth := 0; tmp := 0;
 for j := 0 to lb.Items.Count - 1 do
 if MaxWidth < lb.Canvas.TextWidth(lb.Items[j]) then
 begin
   MaxWidth := lb.Canvas.TextWidth(lb.Items[j]);
   If MaxWidth > tmp then tmp := MaxWidth;
 end;
 SendMessage(lb.Handle,LB_SETHORIZONTALEXTENT,MaxWidth+300,0);
end;


Gruß delphi10
Edit. Ach ja, es kommt auf die Übergabe der Variable TListBox (Zeile1) an. Alles andere ist eigentlich genau so wie schon oben geschrieben.
SetHorizontalScrollBar(ListBox1);
SetHorizontalScrollBar(ListBox2); und so weiter...


Dibelius - Sa 04.04.09 13:49

uh, danke erstmal.
Aber irgendwie hab ich mit der Prozedur mehr Probleme. Erstmal haut das auch wieder nur für eine Listbox hin, obwohl ich als Parameter natürlich die jeweils richtige Listbox übergebe. Zudem seh ich die Items in der Listbox nun gar nicht mehr erst und es kommt zu unschönen Darstellungsfehlern beim Scrollen. Ist nicht so das Wahre :/

/EDIT:
Ich hab die Prozedur aus dem Tut nochmal abgekapselt von der ListBox.OnDrawItem Methode


Delphi-Quelltext
1:
2:
3:
4:
5:
  private
    { Private-Deklarationen }
    flbHorzScrollWidth, flbHorzScrollWidth2: Integer;
    procedure AutoSizeListBox(TargetBox: TListBox; Index: integer; Rect: TRect;
      ScrollWidth: integer);


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure TForm1.AutoSizeListBox(TargetBox: TListBox; Index: integer; Rect: TRect;
  ScrollWidth: integer);
var
  Len: integer;
  NewText: string;
begin
  NewText := TargetBox.Items[Index];

  with TargetBox.Canvas do
  begin
    FillRect(Rect);
    TextOut(Rect.Left + 1, Rect.Top, NewText);
    Len := TextWidth(NewText) + Rect.Left + 3;
    if Len > ScrollWidth then
    begin
      ScrollWidth := Len;
      TargetBox.Perform(LB_SETHORIZONTALEXTENT, ScrollWidth, 0 );
    end;
  end;
end;


Und dann beiden Listboxes im OI unter Events > OnDrawItem das der zweiten zugewiesen.

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.ListBox2DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  case (Control as TListBox).Tag of
    1: AutoSizeListBox(ListBox1, Index, Rect, flbHorzScrollWidth);
    2: AutoSizeListBox(ListBox2, Index, Rect, flbHorzScrollWidth2);
  end;
end;

Leider funktioniert auch das wieder nur für eine Listbox (die mit Tag = 2), unabhängig davon, ob ich den Parameter "flbHorzScrollWidth" nur für eine oder für beide Listboxes verwende. :(

Das Problem liegt wohl woanders. Und zwar hab ich gerade mal den Code für die erste Listbox angepasst und ein showMessage eingefügt (in beiden Listboxes ist bei Programmstart zurzeit schon was eingetragen, was die Breite jeweils überschreitet).

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
  case (Control as TListBox).Tag of
    1: showMessage('Test');
    2: AutoSizeListBox(ListBox2, Index, Rect, flbHorzScrollWidth2);
  end;
end;

Die showMessage bleibt aus... bei der 2. klappt es und ich versteh einfach nicht, warum...


JayEff - Sa 04.04.09 16:17

:shock: OnDrawItem? Meinst du nicht dass das ein winziges bisschen zu häufig ist? :rofl: Ändere die Größe des Scrollbalkens doch beim Einfügen oder Löschen von Einträgen einmalig ;)

Edit:
Delphi-Hilfe zu OnDrawItem:
Mit einer Ereignisbehandlungsroutine für OnDrawItem können Sie die Einträge von Listenfeldern zeichnen, deren Eigenschaft Style den Wert lbOwnerDrawFixed, lbOwnerDrawVariable oder lbVirtualOwnerDraw hat.

OnDrawItem tritt nur auf, wenn du Listbox.Style auf eine der OwnerDraw-Optionen gesetzt hast.


Dibelius - Sa 04.04.09 17:41

Würde ich gern so machen, wenn ich wüsste, was ich als "Rect" und "Index" an die AutoSizeListBox-Prozedur übergeben müsste...


JayEff - Sa 04.04.09 18:00

Andere Frage: Hat deine Listbox eine Eigenschaft ScrollWidth? Das ist nämlich das gleiche, nur gekapselt in einer simplen Zuweisung (Meine unter Delphi7 hat sie.) Falls nicht, kannst du die markierte Stelle mit TargetBox.Perform(LB_SETHORIZONTALEXTENT, ScrollWidth, 0 ); ersetzen.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TForm1.AutoSizeListBox(TargetBox: TListBox);
var
  Len, i: integer;
  NewText: string;
begin
  Len := TargetBox.Canvas.TextWidth(TargetBox.Items[0]);
  for i := 0 to TargetBox.Items.Count - 1 do
  begin
    NewText := TargetBox.Items[i];
    if TargetBox.Canvas.TextWidth(NewText) > Len then
      Len := TargetBox.Canvas.TextWidth(NewText);
  end;

  TargetBox.ScrollWidth := Len + 10//diese Zeile mit der darunter ersetzen
  //TargetBox.Perform(LB_SETHORIZONTALEXTENT, Len + 10, 0 );
end;

Aufruf: AutosizeListbox(Listbox1);
Problem: Etwas langsam. Spürt man aber nicht wirklich bei 2000 Einträgen, denke ich. Einfach immer dann aufrufen, wenn ein Item gelöscht oder hinzugefügt wird. Wenn man's schneller haben will, kann man zunächst TargetBox.Items in eine TStringList schreiben und mit dieser arbeiten, ich hab das mal ausprobiert, kann durchaus schneller gehen, dadurch.

Wegen "Nicht in onDrawItem aufrufen": Da hab ich den Code falsch betrachtet, der ist für onDrawItem gedacht, definitiv. :oops:
Wenn der alte Code nicht funktioniert, schau mal ob du beide Listboxes auf Style = dsOwnerDraw[irgendwas] gestellt hast, wenn nicht, wird das onDrawItem Ereignis garnicht ausgelöst


delphi10 - Sa 04.04.09 18:23

user profile iconDibelius hat folgendes geschrieben Zum zitierten Posting springen:
Würde ich gern so machen, wenn ich wüsste, was ich als "Rect" und "Index" an die AutoSizeListBox-Prozedur übergeben müsste...



Delphi-Quelltext
1:
Rect := ItemRect(x);                    

Zitat:
Die showMessage bleibt aus... bei der 2. klappt es und ich versteh einfach nicht, warum...

Hat denn eine ListBox den Tag= 1?

Wenn Du wirklich im Inspector für die ListBox den Style auf lbOwnerDrawVariable gestellt hast, musst du gar nichts übergeben und auch nicht explizit aufrufen. Das geht mit der Ausgabe eines Items ganz automatisch. Ist denn das Ereignis "OnDrawItem" richtig gesetzt mit "ListBox2DrawItem"? Schau Dir mal http://www.delphipraxis.net/topic137508_verschiedene+farben+im+listboxstring.html an.


Dibelius - Sa 04.04.09 18:39

user profile iconJayEff hat folgendes geschrieben Zum zitierten Posting springen:
Wenn der alte Code nicht funktioniert, schau mal ob du beide Listboxes auf Style = dsOwnerDraw[irgendwas] gestellt hast, wenn nicht, wird das onDrawItem Ereignis garnicht ausgelöst

argh, ich hatte wohl Tomaten auf den Augen. Danke, genau das war es, warum onDrawItem bei der einen Box nicht aufgerufen wurde. Kleiner Fehler, große Wirkung ^^

achso, eine Sache noch (nur zum Verständnis):

Wo wird überhaupt der Parameter "ScrollWidth" initialisiert? (s. meinen Code oben)
Ich deklariere es da zwar unter private, aber dann vergleicht die Prozedur plötzlich den Wert mit "Len" und erst bei Erfolg wird ein Wert zugewiesen. Welchen Wert hat "ScrollWidth" vor dem Vergleich?


JayEff - Sa 04.04.09 18:56

user profile iconDibelius hat folgendes geschrieben Zum zitierten Posting springen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure TForm1.AutoSizeListBox(TargetBox: TListBox; Index: integer; Rect: TRect;
  ScrollWidth: integer);
var
  Len: integer;
  NewText: string;
begin
  NewText := TargetBox.Items[Index];

  with TargetBox.Canvas do
  begin
    FillRect(Rect);
    TextOut(Rect.Left + 1, Rect.Top, NewText);
    Len := TextWidth(NewText) + Rect.Left + 3;
    if Len > ScrollWidth then
    begin
      ScrollWidth := Len;
      TargetBox.Perform(LB_SETHORIZONTALEXTENT, ScrollWidth, 0 );
    end;
  end;
end;



Delphi-Quelltext
1:
AutoSizeListBox(ListBox1, Index, Rect, flbHorzScrollWidth);                    
Die markierten Stellen sollten das erklären (auch wenn ich mich etwas wundere, da ScrollWith kein var-Parameter ist): Die globale Variable flbHorzScrollWidth speichert die ScrollWidth der entsprechenden Listbox wie sie vor dem Aufruf der Autosize-Prozedur ist. Dieser Wert wird der Autosize übergeben. In dieser wird für das gerade gezeichnete Item der Listbox ermittelt, wie breit das Item im Endeffekt ist. Ist in flbHorzScrollWidth bereits ein größerer Wert als der gerade ermittelte, muss keine Änderung der ScrollWidth durchgeführt werden. Falls doch, so setzen wir ScrollWidth auf den ermittelten Wert und ändern die größe des horizontalen Scrollbalkens. (Die Perform-Methode tut das). Ausserdem übernimmt die Autosize-Prozedur das Zeichen des Items selber, was auch nötig ist, da die Listbox ja auf OwnerDraw steht. Das geschieht über FillRect und TextOut (Rechteck mit Farbe füllen, dann Text draufschreiben).
Funktioniert die Listbox dann noch so wie gewohnt? Ich meine, ist das markierte Item blau hinterlegt? Ich sehe nämlich nirgends im Code, dass im onDrawItem der Parameter State auf "Selected" überprüft wird :nixweiss:

Edit: Globale Integer-Variablen werden normalerweise mit 0 initialisiert, worauf man sich aber nie verlassen sollte :mahn: In diesem Fall vermute ich, dass beim allerersten Aufruf der Autosize-Prozedur 0 in der Variable steht, was im Prinzip genau das ist, was wir hier wollen.


Dibelius - Sa 04.04.09 19:12

Ok, danke für die Aufklärung. Ich wusste nicht, dass globale Variablen mit 0 initialisiert werden. Ich hab das bisher immer eigenhändig getan und werde wohl auch dabei bleiben.

user profile iconJayEff hat folgendes geschrieben Zum zitierten Posting springen:
Funktioniert die Listbox dann noch so wie gewohnt? Ich meine, ist das markierte Item blau hinterlegt? Ich sehe nämlich nirgends im Code, dass im onDrawItem der Parameter State auf "Selected" überprüft wird :nixweiss:

Ja, funktioniert alles. Ich stelle da kein Unterschied zur "normalen" Listbox fest, bis auf die Textausgabe, die man m.H. von Canvas relativ frei gestalten könnte, wenn man das will. Selected sieht man aber.

btw: in deiner Prozedur fehlt die Textausgabe noch.


JayEff - Sa 04.04.09 19:26

user profile iconDibelius hat folgendes geschrieben Zum zitierten Posting springen:
btw: in deiner Prozedur fehlt die Textausgabe noch.
Die ist auch nicht nötig, weil meine Prozedur ohne onDrawItem arbeitet und darum die Listbox auf lbStandard gestellt werden kann.

user profile iconDibelius hat folgendes geschrieben Zum zitierten Posting springen:
Ich wusste nicht, dass globale Variablen mit 0 initialisiert werden. Ich hab das bisher immer eigenhändig getan und werde wohl auch dabei bleiben.
Ist auch gut so, wie ich schon sagte:
user profile iconJayEff hat folgendes geschrieben Zum zitierten Posting springen:
[...]worauf man sich aber nie verlassen sollte :mahn:
Selbst initialisieren ist schon für die Übersicht am besten, da weiß man immer genau, was drin steht, ohne zuerst zu überlegen. :zustimm: