Entwickler-Ecke

Grafische Benutzeroberflächen (VCL & FireMonkey) - Selektierte Zelle im Stringgrid/Drawgrid färben


Deviljho452 - Di 24.04.12 16:27
Titel: Selektierte Zelle im Stringgrid/Drawgrid färben
Hallo liebe Community,

ich sitze seit tagen verzweifelt an der Komponente des Stringgrids bzw. an dem Drawgrid, weil ich gerne das Game of Life programmieren möchte.
Man kann wunderbar mit der Maus eine Zelle auswählen, jedoch mit welchem event (ondrawcell,onclick....) und mit welchen Befehlen im Event kann ich die selektierte Zelle färben?


Danke für jeden Gedankenstrom :)


Delphi-Laie - Di 24.04.12 16:57

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
und mit welchen Befehlen im Event kann ich die selektierte Zelle färben?


Die Farbe der Schrift jedenfalls färbe ich im OnDrawCell so:


Delphi-Quelltext
1:
StringGrid1.canvas.Font.Color:=Farbe[ACol,ARow]                    


, wobei "Farbe" ein zweidimensionales Array ist, das die Farben (im Hintergrund) speichert. Wie man den Hintergrund des Stringgrids, also dessen Zellen, färbt, weiß ich momentan nicht, vermutlich aber so ähnlich, evtl. mit Wertzuweisung an StringGrind.canvas.Pen.Color.


Narses - Di 24.04.12 17:23

Moin und :welcome: in der EE!

Schonmal in die Suche geschaut? :arrow: Suche in: Delphi-Forum, Delphi-Library STRINGGRID HINTERGRUND :les: :think: ;)

cu
Narses


Deviljho452 - Di 24.04.12 17:28

Danke Narses :)

in der SuFu hab ich natürlich schon gestöbert ;), jedoch liegt mein problem beim Klicken, also beim Färben einer Zelle via Mausklick und die SuFu liefert mir nur Quelltext und Lösungsansätze, bei denen beim Öffnen des Programms bestimmte Zellen gefärbt werden.

Ich suche einfach nur etwas wie stringgrid1.cells[acol,arow].color:=clred :P, natürlich geht es so nicht

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Narses - Di 24.04.12 19:12

Moin!

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
jedoch liegt mein problem beim Klicken, also beim Färben einer Zelle via Mausklick
Nein, dein Problem liegt im Verständnis der Funktionsweise einer ereignisorientierten Anwendung bzw. VCL-Komponente. ;)

Man kann keine Zelle "via Mausklick" färben, weil das String-/Draw-Grid so nicht arbeitet. Es gibt das Ereignis OnDrawCell, in dem du die Gelegenheit bekommst, das aussehen dieser speziellen Zelle selbst festzulegen. Also musst du dir in einem (zusätzlichen) Speicherbereich, der idealerweise genau so groß ist, wie das Grid selbst, merken, welche z.B. in deinem Fall (Hintergrund-)Farbe diese Zelle bekommen soll. Diese Information verwendest du dann im OnDrawCell dazu, entsprechendes zu zeichnen. :idea:

Im OnClick des Grids musst du also in dem von user profile iconDelphi-Laie bereits vorgeschlagenen 2D-Array lediglich die Farbe setzen (und ggfs. ein Neuzeichnen auslösen), das ist alles. :)


cu
Narses


Delphi-Laie - Mi 25.04.12 11:24

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
Ich suche einfach nur etwas wie stringgrid1.cells[acol,arow].color:=clred :P, natürlich geht es so nicht


Ich gebe Dir grundsätzlich recht. Das Einfärben der Zellenhintergründe und/oder Schriftfarben in Stringgrids ist m.E. verunglückt, verkompliziert, nichtintuitiv, ich weiß allerdings nicht, ob man das besser hätte lösen können. Diesbezügliche Anfragen sind in den Foren mehr oder weniger ein Dauerbrenner, das allein spricht doch schon eine deutliche Sprache. Auch ich suchte stundenlang (!) nach einer Lösung.

Narses, ich bestreite, daß diese Anfrage zu der Verallgemeinerung berechtigt, einem ein gewisses Verständnis für Ereignisorientierung abzusprechen. Gewiß wollte Deviljho452 den (Ein-)Färbbefehl in irgendeine Ereignisbehandlungsroutine oder einen Unitanfang packen. Quellcode an Stellen zu plazieren, die keinem Ereignis zugrundeliegen, ist sogar ziemlich schwierig.

Fatalerweise findet die automatische Codevervollständigung ("Code insight") - jedenfalls in meinem Delphi 4 - nur Stringgrid1.Canvas.BrushCopy, doch die Suchfunktion hier im Forum liefert schon im zweiten Falle die Lösung für die Zellen(hintergrund)färbung: StringGrid1.Canvas.Brush.Color:=Farbe[ACol,ARow]. Klappt jedenfalls bei mir.

Ergänzung: Daß Stringgrid und individuelle (Ein-)Färbbarkeit irgendwie auf Kriegsfuß stehen, zeigt sich auch daran, wenn man mal versucht, Schrift und Zellenhintergrund - natürlich mit verschiedenen - Farben zu versehen. Die Ergebnisse sind i.d.R. inkonsistent.

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Deviljho452 - Mi 25.04.12 16:51

Hi Laie,

ich hab mich gestern Nacht noch etwas mit dem Problem beschäftigt und jetzt via OnMouseDown bei Mausklick schon die Kennzeichnung des Feldes mit 'X' hinbekommen.
Daraufhin wollte ich einfach eine Schleife durchlaufen lassen und bei jeder Zelle, deren Caption X ist, soll die Hintergrundfarbe clred werden. Ich habe dabei deinen Befehl eingebaut, jedoch verlangt er bei [ARow,ACol] einen Array Typen o.O . Da bin ich ratlos und weiß nicht was der Debugger von mir verlangt

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Narses - Mi 25.04.12 17:48

Moin!

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
Daraufhin wollte ich einfach eine Schleife durchlaufen lassen und bei8 jeder Zelle, deren Caption X ist, soll die Hintergrundfarbe clred werden.
user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
dein Problem liegt im Verständnis der Funktionsweise einer ereignisorientierten Anwendung bzw. VCL-Komponente.
An dieser Tatsache hat sich leider noch nichts geändert. :nixweiss:

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
Ich habe dabei deinen Befehl eingebaut, jedoch verlangt er bei [ARow,ACol] einen Array Typen o.O . Da bin ich ratlos und weiß nicht was der Debugger von mir verlangt
Eine Array-Referenz, die du natürlich auch selbst anlegen und verwalten musst. :idea:

Da wohl doch leider noch zu viele Grundlagen und zu viel Verständnis für ereignisorientiertes Programmieren fehlen (wobei es völlig egal ist, ob das nun einfach oder nicht sei, es ist halt so, wie es ist), hier mal ein Beispiel für ein StringGrid:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
  Rect: TRect; State: TGridDrawState);
begin
  if (Sender as TStringGrid).Cells[ACol, ARow] <> '' then begin
    (Sender as TStringGrid).Canvas.Brush.Color := clRed;
    (Sender as TStringGrid).Canvas.FillRect(Rect);
  end;
end;
Damit wird jede Zelle, deren Inhalt nicht-leer ist, mit rotem Hintergrund gezeichnet (ohne Inhalt). Wenn du in den Optionen des StringGrids goEditing auf TRUE setzt, kann man das schön zur Laufzeit ausprobieren.

Der Schlüssel zum Verständnis ist: es gibt keine irgendwie geartete Anweisung, mit der man den Hintergrund einer Zelle färben kann, sondern man muss beim Zeichnen des Grids eingreifen und die gewünschten/betroffenen Zellen anders (selbst) malen. Warum? Weil du (=dein Programm) nicht wissen kannst, wann das Grid gezeichnet wird, das passiert ausserhalb deines Einflussbereiches. Deshalb musst du "von hinten durch die Brust ins Auge" und darauf reagieren, dass etwas gezeichnet werden soll.

cu
Narses


Delphi-Laie - Mi 25.04.12 22:04

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
jedoch verlangt er bei [ARow,ACol] einen Array Typen o.O . Da bin ich ratlos und weiß nicht was der Debugger von mir verlangt

array of array of TColor deklarieren (definieren) und dieses Array vor dem ersten Zugriff mit setlength([Arrayvariable],xmax-1,ymax-1) ausreichend groß dimensionieren. Ich nannte mein Array bzw. konkreter die Arrayvariable einfach "Farbe", s.o. .

Oder, wenn die Größe für immer feststeht, gleich statisch definieren: array[0..xmax-1,0..ymax-1of TColor (nicht: array[1..xmax,1..ymax], weil das Stringgrid bei den Zeilen und Spalten auch die Indizes 0 besitzt).

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Narses - Mi 25.04.12 23:59

Moin!

user profile iconDeviljho452 hat folgendes geschrieben Zum zitierten Posting springen:
Da bin ich ratlos und weiß nicht was
Ich denke mittlerweile, ein StringGrid (oder sonstiges Grid) ist einfach die falsche Komponente für dich und diesen Zweck. :nixweiss:

Warum probierst du nicht mal Shapes aus? Die lassen sich prima imperativ "befingern" und sind auch nicht langsamer oder schneller, als ein Grid. :idea:

Wenn man die Shapes dynamisch auf das Formular packt, dann ist das auch relativ leicht zu handeln:

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:
46:
uses
  ..., ExtCtrls; // für die Shapes

const
  MAX_X = 5// Anzahl Felder waagerecht
  MAX_Y = 5// Anzahl Felder senkrecht
  FIELDSIZE = 32// Feldgröße in Pixeln

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure ShapeMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);

implementation

procedure TForm1.FormCreate(Sender: TObject);
  var
    i, j: Integer;
    ThisShape: TShape;
begin
  Self.ClientWidth := (MAX_X +2) *FIELDSIZE;
  Self.ClientHeight := (MAX_Y +2) *FIELDSIZE;
  for i := 1 to MAX_Y do
    for j := 1 to MAX_X do begin
      ThisShape := TShape.Create(Self); // erzeugen, Eigentümer: das Formular
      ThisShape.Parent := Self; // sichtbar machen
      ThisShape.Left := j*FIELDSIZE; // Position setzen
      ThisShape.Top := i*FIELDSIZE;
      ThisShape.Width := FIELDSIZE; // Größe setzen
      ThisShape.Height := FIELDSIZE;
      // Referenz-Nummer: daraus kann man ggfs. wieder die Koordinaten berechnen
      ThisShape.Tag := i*MAX_Y +j;
      // Ereignis zuweisen
      ThisShape.OnMouseDown := ShapeMouseDown;
    end;
end;

procedure TForm1.ShapeMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if (Sender as TShape).Brush.Color = clWhite then
    (Sender as TShape).Brush.Color := clBlack
  else
    (Sender as TShape).Brush.Color := clWhite;
end;
Im Anhang das entsprechende Demo-Projekt, einfach mal ausprobieren. :les: Fragen? :gruebel: Fragen! :think:

cu
Narses


DelphiJogi - So 01.10.23 22:24

Ich experimentiere seit einiger Zeit Zellen eines StringGrids zu färben. Es will mir einfach nicht gelingen! Habe schon so einige Codebeispiele ausprobiert, ohne Erfolg. Es kann doch nicht so schwierig sein !!!
Sogar dieser einfache Code macht nicht da was er soll...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm5.StatistikListeSparDrawCell(Sender: TObject; ACol, ARow: Integer;Rect: TRect; State: TGridDrawState);
begin
 for i := 1 to 10 do
  begin
     If StatistikListeSpar.Cells[2, i] <> '' then begin
        StatistikListeSpar.RePaint;
        StatistikListeSpar.Canvas.Brush.Color := clRed;
        StatistikListeSpar.Canvas.FillRect(Rect);
     end;
  end;
end;


Moderiert von user profile iconNarses: Full-Quote entfernt und Delphi-Tags hinzugefügt


mandras - Mo 02.10.23 00:48

Versuche es einmal so:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm5.StatistikListeSparDrawCell(Sender: TObject; ACol, ARow: Integer;Rect: TRect; State: TGridDrawState);
begin
     If StatistikListeSpar.Cells[2, i] <> '' then begin
        StatistikListeSpar.Canvas.Brush.Color := clRed;
        StatistikListeSpar.Canvas.FillRect(Rect);
     end;
end;

DrawCell wird für jede sichtbare Zelle automatisch einmal aufgerufen,
insofern sind die for-Schleife und das Repaint nicht sinnvoll.

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


Th69 - Mo 02.10.23 09:46

Auch das ist noch nicht ganz richtig, sondern eher so

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm5.StatistikListeSparDrawCell(Sender: TObject; ACol, ARow: Integer;Rect: TRect; State: TGridDrawState);
begin
     if ACol = 2 and StatistikListeSpar.Cells[ACol, ARow] <> '' then begin
        StatistikListeSpar.Canvas.Brush.Color := clRed;
        StatistikListeSpar.Canvas.FillRect(Rect);
     end;
end;


DelphiJogi - Mo 02.10.23 19:04

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Auch das ist noch nicht ganz richtig, sondern eher so

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure TForm5.StatistikListeSparDrawCell(Sender: TObject; ACol, ARow: Integer;Rect: TRect; State: TGridDrawState);
begin
     if ACol = 2 and StatistikListeSpar.Cells[ACol, ARow] <> '' then begin
        StatistikListeSpar.Canvas.Brush.Color := clRed;
        StatistikListeSpar.Canvas.FillRect(Rect);
     end;
end;


Danke, aber so komme ich nicht weiter! Also mache ich mal einen Versuchsaufbau.
Ich lege ein StringGrid mit den StandardOptionen auf meine Form.

Erzeuge folgende DrawCell-Procedure ...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm5.StringGrid1DrawCell(Sender: TObject; ACol,ARow: Integer; Rect: TRect; State: TGridDrawState);
begin 
   if not Odd(ARow) and not (gdFixed in State) then
   with StringGrid1 do
   begin
     Canvas.Brush.Color := clRed;
     Canvas.FillRect(Rect);
     Canvas.TextOut(Rect.Left+2, Rect.Top+2, Cells[ACol, ARow]);

   end;
end;

Dieser Code soll jede zweite Zeile rot färben.
Der Aufruf der Procedure ....

Delphi-Quelltext
1:
StringGrid1DrawCell(Sender, Acol, Arow, Rect, State);                    

Ist das richtig? Was ist mit Acol und Arow? Müssen da nicht Parameter übergeben werden?

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


mandras - Mo 02.10.23 19:33

Kann es sein, daß Du Deine DrawCell-Methode manuell angelegt hast und nicht über die Ereignisspalte im Formulardesigner?

Normalerweise würde man wie folgt vorgehen, um jede 2. Zeile mit einer bestimmten Farbe anzuzeigen:

Das Grid im Formular anwählen, links im Objektinspektor auf die rechte Spalte ("Ereignisse") wechseln und dann einen Doppelklick auf das weiße Eingabefeld neben "OnDrawCell" machen.

Delphi erstellt dann das "Drumherum" (Deklaration der Prozedur am Anfang des Quellcodes, erstellen des Prozedurrumpfs, die Prozedur heißt dann automatisch zB StringGrid1DrawCell).

Dann ergänzt Du den Prozedurrumpf mit Deinem Code (der ist übrigens korrekt, bei mir färbt er den Hintergrund jeder 2. Zeile).

Nebenbei hat Delphi die Prozedur StringGrid1DrawCell als Ereignis des Stringgrids eingetragen.
Das bedeutet, daß StringGrid1DrawCell nun automatisch für jede Zelle des Grids aufgerufen wird, die gezeichnet werden muß.

Ich hoffe, daß ich damit Dein Problem richtig erfaßt habe.

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


DelphiJogi - Mo 02.10.23 20:15

Vielen Dank Mandras,
das war mein Fehler. OnDrawCell im EreignisHandler habe ich total ignoriert.

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt


DelphiJogi - Mo 02.10.23 21:07

Bleibt noch eine Frage ...
Wie ist "FocusColor" des Grids zu ermitteln? Und ist die Focusfarbe auch zu ändern?


Th69 - Di 03.10.23 09:48

Diese Eigenschaft heißt SelectedColor und zum Selberzeichnen s. ...change the default cell selection color in a TStringGrid? [https://www.swissdelphicenter.ch/en/showcode.php?id=1258]
Und ändern kannst du diese Farbe auch mit z.B. StringGridX.SelectedColor := clBlue;


Sinspin - Fr 13.10.23 16:46

Wäre ich dein Arbeitgeber und Du würdest mir diesen Code abliefern... dann wäre ich die längste Zeit dein Arbeitgeber gewesen.
user profile iconDelphiJogi hat folgendes geschrieben Zum zitierten Posting springen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
procedure TForm5.StringGrid1DrawCell(Sender: TObject; ACol,ARow: Integer; Rect: TRect; State: TGridDrawState);
begin 
   if not Odd(ARow) and not (gdFixed in State) then
   with StringGrid1 do
   begin
     Canvas.Brush.Color := clRed;
     Canvas.FillRect(Rect);
     Canvas.TextOut(Rect.Left+2, Rect.Top+2, Cells[ACol, ARow]);

   end;
end;

Das Verwenden von with führt zu sofortiger Degradierung auf das Level eines Arbeitssuchenden.


DelphiJogi - Mi 18.10.23 17:58

Moderiert von user profile iconNarses: Komplett-Zitat des vorigen Beitrags entfernt.

Dann erkläre doch Mal warum der Code so schlecht ist und wie du diesen schreiben würdest!
Übrigens ist dieser Code ein "Code-Beispiel" der nicht von mir selbst stammt.


mandras - Mi 18.10.23 23:45

with ist eine Anweisung, die böse Fehler verursachen kann, die nur schwer ins Auge stechen, siehe auch with statement considered harmful [https://hallvards.blogspot.com/2004/08/with-statement-considered-harmful.html].

Aus diesem Grund wurde der VCL-Sourcecode überarbeitet, um ohne with auszukommen.

Beim Debugging ist with ein Horror, da der Inspektor damit nicht klarkommt und man selbst die auszuwertenden Ausdrücke innerhalb eines with anpassen muß.
In Deinem Fall zB müßtest Du um bei einem Haltepunkt "Canvas.Brush.Color" auszuwerten immer "Stringgrid1." davorschreiben.

Ansonsten finde ich die Behauptung, "with" würde eine Kündigung rechtfertigen, etwas übertrieben :)

Moderiert von user profile iconTh69: Delphi-Tags hinzugefügt
Moderiert von user profile iconTh69: URL-Titel hinzugefügt.
Moderiert von user profile iconTh69: Beitragsformatierung überarbeitet.


Sinspin - Do 19.10.23 18:21

Ich gehe davon aus dass nur faule und oder rücksichtslose Mitarbeiter with verwenden, somit stellt es keinen Verlust, sondern eine Schadensminimierung dar, sich von denen schnellstmöglich zu trennen.
Ist klar, das ist drastisch, aber auch die Probleme können drastisch ausfallen wenn es um viel Geld oder Menschenleben geht. Zum Glück hat es noch keinen Fall gegeben bei dem jemand zu Schaden gekommen ist.

Um es komplett zu machen:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
// irgendwo weiter oben :
SgdColoredCells: TStringGrid;
 
procedure TForm5.SgdColoredCellsDrawCell(Sender: TObject; ACol,ARow: Integer; Rect: TRect; State: TGridDrawState);
begin 
   if not Odd(ARow) and not (gdFixed in State) then
   begin
     SgdColoredCells.Canvas.Brush.Color := clRed;
     SgdColoredCells.Canvas.FillRect(Rect);
     SgdColoredCells.Canvas.TextOut(Rect.Left+2, Rect.Top+2, SgdColoredCells.Cells[ACol, ARow]);
   end;
end;

PS: Es gibt auch Firmen die die Zwischenablage überwachen um sicherzustellen das nicht Quelltext wild umherkopiert wird. Denn auch das ist eine ganz ordentliche Fehlerquelle bei programmieren.
Zum Beispiel beim ersetzten von Variablen oder Aufrufen. Wo dann was zuviel ersetzt wird oder was falsches mitkopiert wurde. Wenn man das alles selber schreiben muss denkt man besser mit was man macht.