Eine Frage zu StringGrid-Geheimnissen [gelöst]

Rund um die LCL und andere Komponenten
Antworten
aldicek
Beiträge: 37
Registriert: Do 6. Mär 2008, 12:48
OS, Lazarus, FPC: WinXP/Mint13KDE (Lazarus 1.0.8 FPC 2.6.2)
CPU-Target: 64 Bit
Wohnort: Halle (Saale)

Eine Frage zu StringGrid-Geheimnissen [gelöst]

Beitrag von aldicek »

Hallo liebe Forumsgemeinde!

Es gibt ja im StringGrid bereits eine Menge vorgefertigte Funktionalität, beispielsweise die Möglichkeit, die erste fixierte Spalte automatisch nummerieren zu lassen (goFixedRowNumbering). Lässt man nach dem Befüllen des StringGrids dann jedoch die Spalten nach dem "längsten" Eintrag automatisch in der Breite optimieren, indem man alle Einträge auf ihre Weite prüft und sich die größte Weite merkt, kommt leider die Erkenntnis, dass in diesem Fall in Spalte 0 an der Stelle StringGrid.Cells[0, n]. nicht wirklich etwas steht, was sich mit StringGrid.Canvas.TextWidth(tringGrid.Cells[0, n]) auslesen lässt. Wo zum Teufel versteckt StringGrid diese doch sichtbaren Zeichen und wie kann ich deren Weite feststellen? (Mein Workaraound ist, dass ich momentan goFixedRowNumbering auf False lasse und beim Befüllen die fixierte Spalte einfach mit einem Zähler überschreibe, aber das kann's doch nicht auf Dauer sein!)
Also: hat jemand damit (goFixedRowNumbering=True & ColumnWidthResize per Code) bereits Erfahrungen gesammelt? (Möglichst erfolgreicher als ich?)

Zur Demo hänge ich mal mein Testprojekt an (Unit1...

Code: Alles auswählen

unit Unit1;
 
{$mode objfpc}{$H+}
 
interface
 
uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, Grids,
  StdCtrls;
 
type
 
  { TForm1 }
 
  TForm1 = class(TForm)
    Btn: TButton;
    Grid: TStringGrid;
    procedure BtnClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { private declarations }
    procedure AutoSizeStringGridCol(sg: TStringGrid; column: integer);
  public
    { public declarations }
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormCreate(Sender: TObject);
var
  i: integer;
  aStr: string;
begin
  AutoSizeStringGridCol(Grid, 0);
  for i := 1 to Grid.RowCount - 1 do
  begin
    aStr := Grid.Cells[0, i];
    Grid.Cells[1, i] := aStr;
  end;
end;
 
procedure TForm1.BtnClick(Sender: TObject);
begin
  Grid.Options := Grid.Options - [goFixedRowNumbering];
  Update;
  AutoSizeStringGridCol(Grid, 0);
end;
 
procedure TForm1.AutoSizeStringGridCol(sg: TStringGrid; column: integer);
var
  j, W, WMax: integer;
begin
  WMax := 0;
  for j := 0 to (sg.RowCount - 1) do
  begin
    W := sg.Canvas.TextWidth(sg.Cells[column, j]);
    if W > WMax then
      WMax := W;
  end;
  sg.ColWidths[column] := WMax + 7;
end;
 
end.
 
und den Quelltext des Formulars):

Code: Alles auswählen

object Form1: TForm1
  Left = 481
  Height = 240
  Top = 462
  Width = 327
  Caption = 'Form1'
  ClientHeight = 240
  ClientWidth = 327
  OnCreate = FormCreate
  LCLVersion = '1.0.8.0'
  object Btn: TButton
    Left = 0
    Height = 25
    Top = 215
    Width = 327
    Align = alBottom
    Caption = 'goFixedRowNumbering aus und AutoSize Col 0'
    OnClick = BtnClick
    TabOrder = 0
  end
  object Grid: TStringGrid
    Left = 0
    Height = 215
    Top = 0
    Width = 327
    Align = alClient
    Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goRowSelect, goThumbTracking, goDblClickAutoSize, goSmoothScroll, goFixedRowNumbering, goHeaderHotTracking, goHeaderPushedLook, goFixedColSizing]
    TabOrder = 1
  end
end
 
Ich freue mich über jede zweckdienliche Antwort!

Aldicek
Zuletzt geändert von aldicek am Di 6. Aug 2013, 08:34, insgesamt 1-mal geändert.

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2822
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von m.fuchs »

Hallo,
aldicek hat geschrieben:Es gibt ja im StringGrid bereits eine Menge vorgefertigte Funktionalität, beispielsweise die Möglichkeit, die erste fixierte Spalte automatisch nummerieren zu lassen (goFixedRowNumbering). Lässt man nach dem Befüllen des StringGrids dann jedoch die Spalten nach dem "längsten" Eintrag automatisch in der Breite optimieren, indem man alle Einträge auf ihre Weite prüft und sich die größte Weite merkt, kommt leider die Erkenntnis, dass in diesem Fall in Spalte 0 an der Stelle StringGrid.Cells[0, n]. nicht wirklich etwas steht, was sich mit StringGrid.Canvas.TextWidth(tringGrid.Cells[0, n]) auslesen lässt. Wo zum Teufel versteckt StringGrid diese doch sichtbaren Zeichen und wie kann ich deren Weite feststellen?
die automatische Nummerierung wird nicht in die Cells vorgenommen, sondern bei Zeichnen der Zellen ausgeführt. Und auch nur, wenn in der Zelle nix steht:

Code: Alles auswählen

procedure TCustomStringGrid.DrawCellAutonumbering(aCol, aRow: Integer;  aRect: TRect; const aValue: string);
begin
  if Cells[aCol,aRow]='' then
    inherited DrawCellAutoNumbering(aCol,aRow,aRect,aValue);
end;
Also bleibt dir wohl nichts anderes übrig, als die Nummerierung selber in den Cells abzulegen.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

Benutzeravatar
m.fuchs
Lazarusforum e. V.
Beiträge: 2822
Registriert: Fr 22. Sep 2006, 19:32
OS, Lazarus, FPC: Winux (Lazarus 2.0.10, FPC 3.2.0)
CPU-Target: x86, x64, arm
Wohnort: Berlin
Kontaktdaten:

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von m.fuchs »

Nachtrag: Dein automatisches Optimieren auf den längsten Beitrag kannst du übrigens auch so lösen:

Code: Alles auswählen

for i := 0 to StringGrid1.ColCount - 1 do
  StringGrid1.AutoSizeColumn(i);      
Das berücksichtigt allerdings die Nummerierung auch nicht.
Software, Bibliotheken, Vorträge und mehr: https://www.ypa-software.de

wp_xyz
Beiträge: 5160
Registriert: Fr 8. Apr 2011, 09:01

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von wp_xyz »

Die automatische Breitenanpassung der 1. Spalte könnte man - bei eingeschalteter Auto-Nummerierung - so bewirken:

Code: Alles auswählen

 
procedure AutoFixedColNumberWidth(AGrid: TCustomStringGrid);
var
  s: String;
  L: Integer;
begin
  with AGrid do begin
    L := Canvas.TextWidth(IntToStr(RowCount-1));
    ColWidths[0] := L + 2*constCellPadding + 2;
  end;
end; 
 

aldicek
Beiträge: 37
Registriert: Do 6. Mär 2008, 12:48
OS, Lazarus, FPC: WinXP/Mint13KDE (Lazarus 1.0.8 FPC 2.6.2)
CPU-Target: 64 Bit
Wohnort: Halle (Saale)

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von aldicek »

Hallo!

Zunächst mal vielen Dank für die schnellen und sachdienlichen Hinweise. Wie man sieht, lernt man nie aus. Ich habe beide Tipps in meinen Code übernommen. Clever find ich ja auch die Idee von wp_xyz:
wp_xyz hat geschrieben:Die automatische Breitenanpassung der 1. Spalte könnte man - bei eingeschalteter Auto-Nummerierung - so bewirken:

Code: Alles auswählen

 
procedure AutoFixedColNumberWidth(AGrid: TCustomStringGrid);
var
  s: String;
  L: Integer;
begin
  with AGrid do begin
    L := Canvas.TextWidth(IntToStr(RowCount-1));
    ColWidths[0] := L + 2*constCellPadding + 2;
  end;
end; 
 
spart schon mal einige Laufzeit (bei den von mir geplanten Grids geht es um RowCounts > 10^4 bis in die halben Millionen) und zeugt davon, dass einige Menschen beim Programmieren tatsächlich ihren Kopf benutzen und auch ihre Werkzeuge kennen.
Dank auch an Michael Fuchs für den Tipp StringGrid.AutoSizeColumn(i), wozu auch etwas neu erfinden, was es schon gibt. (Und der Code wird auch leichter lesbar, wenn weniger drin steht.)

In diesem Sinne betrachte ich die Angelegenheit als abgeschlossen - Fragen beantwortet, danke!
Aldicek

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von Socke »

aldicek hat geschrieben:bei den von mir geplanten Grids geht es um RowCounts > 10^4 bis in die halben Millionen
Abgesehen von der Laufzeit sparst du auch jede Menge an Arbeitsspeicher. Für jeden String im Stringgrid wird abgelegt:
  • Codepage (2 Byte)
  • Größe eines Zeichens (2 Byte)
  • Referenzzähler (Größe eines Zeigers)
  • Länge (Größe eines Zeigers)
  • Nutzdaten (Ziffern; Anzahl Zeichen + 1)
Nutzdaten hast du zwischen 48 Kilobytes (10^4 Zeilen) und 3,4 Megabyte (5*10^5 Zeilen); Die Verwaltungsdaten betragen pro String 12 Bytes auf 32-Bit-Systemen und 24 Bytes auf 64-Bit-Systemen. Damit kann man jetzt rechnen und erhält folgende Werte (Angaben in Bytes).

Code: Alles auswählen

Zeilen   Textbytes  Verwaltungsdaten   Gesamtgröße	
                     32 bit   64 bit    32 bit   64 bit
-------------------------------------------------------
 10000       48876   120000   240000    168876   288876
500000     3388870  6000000 12000000   9388870 15388870
Jetzt stellt sich natürlich die Frage nach den restlichen Daten und inwiefern diese 16 KB bis 15 MB überhaupt ins Gewicht fallen.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

wp_xyz
Beiträge: 5160
Registriert: Fr 8. Apr 2011, 09:01

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von wp_xyz »

bei den von mir geplanten Grids geht es um RowCounts > 10^4 bis in die halben Millionen
Mal davon abgesehen, ob so viele Strings überhaupt angezeigt werden müssen (wer schaut das an?), möchte ich noch darauf hinweisen, dass man bei der Anwendung von StringGrids die Strings oft doppelt gespeichert hat. Ich weiß nicht, wo die Originalinformation steht, die du in dem Grid anzeigst. Vielleicht sind das Zahlen aus einem Array oder einer Matrix. Wenn diese Datenquelle im Programm bestehen bleibt und im Grid angezeigt werden, wird der Speicher zusätzlich zu den Originaldaten auch noch mit den Strings für die Gridzellen belastet. Um diese Doppelspeicherung zu verhindern, solltest du statt einem TStringGrid ein TDrawGrid verwenden. Dieses ist von den Properities eigentlich identisch mit dem StringGrid, jedoch speichert es die angezeigten Texte nicht, sondern malt sie so wie es im OnDrawCell Event programmiert wird. Damit belegen die Strings im Grid keinen Speicher mehr.

Hier ein Beispiel, in dem angenommen ist, dass die Daten in einem zwei-dimenionalen Array Data stehen:

Code: Alles auswählen

 
procedure TForm1.GridDrawCell(Sender: TObject; ACol,ARow: String; ARect: TRect; AState: TGridDrawState);
var
  grid: TCustomDrawGrid;
  style: TTextStyle;
begin
  grid := Sender as TCustomDrawGrid;
  with grid.Canvas do begin
    style := TextStyle;
    style.Alignment := alCenter;   // horizontal zentrieren
    style.Layout := tlCenter;      // vertikal zentrieren
    TextRect(ARect, ARect.Left, ARect.Top, GetCellText(grid, ACol, ARow));
  end;
end;
 
function TForm1.GetCellText(AGrid: TCustomDrawGrid; ACol,ARow: Integer): String;
begin
  Result := '';
  if ARow = 0 then begin
    Result := Format('Spalte %d', [ACol])    // Spalten-Überschrift
  else begin
    Result := FloatToStr(Data[ACol, ARow]);   // Daten für Zelle ACol/ARow in String umwandeln
end;
 

Socke
Lazarusforum e. V.
Beiträge: 3178
Registriert: Di 22. Jul 2008, 19:27
OS, Lazarus, FPC: Lazarus: SVN; FPC: svn; Win 10/Linux/Raspbian/openSUSE
CPU-Target: 32bit x86 armhf
Wohnort: Köln
Kontaktdaten:

Re: Eine Frage zu StringGrid-Geheimnissen

Beitrag von Socke »

wp_xyz hat geschrieben:möchte ich noch darauf hinweisen, dass man bei der Anwendung von StringGrids die Strings oft doppelt gespeichert hat. Ich weiß nicht, wo die Originalinformation steht, die du in dem Grid anzeigst. Vielleicht sind das Zahlen aus einem Array oder einer Matrix. Wenn diese Datenquelle im Programm bestehen bleibt und im Grid angezeigt werden, wird der Speicher zusätzlich zu den Originaldaten auch noch mit den Strings für die Gridzellen belastet.
AnsiStrings werden in Free Pascal mit einem automatischen Referenzzähler versehen (vgl. oben). Damit wird ein String nur einmal im Arbeitsspeicher gehalten, egal wie oft er referenziert wird.
Deine Optimierung bringt also am meisten, wenn die Daten nicht bereits als String vorliegen. Falls sie bereits als String vorliegen, spart man allenfalls Anzahl der Spalten * Anzahl der Zeilen * Größe eines Zeigers an Bytes -- also den Speicher, in dem die Referenzen auf die Strings gehalten werden.
MfG Socke
Ein Gedicht braucht keinen Reim//Ich pack’ hier trotzdem einen rein

Antworten