Entwickler-Ecke

Multimedia / Grafik - Punkte in eine PaintBox zeichnen


Aerin - Mo 06.11.06 00:18
Titel: Punkte in eine PaintBox zeichnen
Ich habe eine PaintBox erstellt und möchte nun per Mausklick an der entsprechenden Stelle einen Punkt einzeichnen. Ich habe mir gedacht ich könnte das mit nem OnClick Event machen bei dem folgendes ausgeführt wird:


Delphi-Quelltext
1:
PaintBox.Canvas.Pixels[x,y] := clBlack                    


Das Problem ist, dass ich nicht weiß wie ich die aktuelle Zeigerposition auslese. Sry wenns ne dumme Frage is aber ich hab wirklich noch sehr wenig Erfahrung mit Delphi ;).
Also wie müsste das richtig aussehen?

Und die nächste Frage: Wie ändere ich die Hintergrundfarbe der PaintBox?

Moderiert von user profile iconChristian S.: Delphi-Tags hinzugefügt
Moderiert von user profile iconChristian S.: Topic aus Delphi Language (Object-Pascal) / CLX verschoben am So 12.11.2006 um 18:41


Ironwulf - Mo 06.11.06 00:46

onmousedown verwenden, nicht onclick
das erste liefert noch die koordinaten mit x,y mit


Kroko - Mo 06.11.06 06:18

oder in OnMouseMove die Koordinaten merken

Hintergrund: TBrush + F1 , FillRect + F1


Lannes - Mo 06.11.06 09:12

Hallo,

oder in OnClick die Maus-Koordinaten ermitteln, z.B:

Delphi-Quelltext
1:
2:
3:
4:
5:
var aPoint : TPoint;
begin
  GetCursorPos(aPoint);// >> Bildschirmkoordinaten
  aPoint := PaintBox.ScreenToClient(aPoint);// >> Koordinaten in PaintBox
  PaintBox.Canvas.Pixels[aPoint.x,aPoint.y] := clBlack;


Ps.: Herzlich willkommen in der Entwickler-Ecke (EE) :wave:
Zur Formatierung von Delphi-Code kannst du die BBCodes [Delphi]Dein Code[/Delphi] verwenden. Weitere Infos dazu Link [http://www.delphi-forum.de/help_schreiben_bbcodes_source.html&sub=,19,27,32&popup=1]


Aerin - Mo 06.11.06 21:17

user profile iconLannes hat folgendes geschrieben:
Hallo,

oder in OnClick die Maus-Koordinaten ermitteln, z.B:

Delphi-Quelltext
1:
2:
3:
4:
5:
var aPoint : TPoint;
begin
  GetCursorPos(aPoint);// >> Bildschirmkoordinaten
  aPoint := PaintBox.ScreenToClient(aPoint);// >> Koordinaten in PaintBox
  PaintBox.Canvas.Pixels[aPoint.x,aPoint.y] := clBlack;


Ps.: Herzlich willkommen in der Entwickler-Ecke (EE) :wave:
Zur Formatierung von Delphi-Code kannst du die BBCodes [Delphi]Dein Code[/Delphi] verwenden. Weitere Infos dazu [url=http://www.delphi-forum.de/help_schreiben_bbcodes_source.html&sub=,19,27,32&popup=1]Link[/url]


Das funktioniert genau so wie soll, danke.

Aber wie ich die Hintergrundfarbe ändere habe ich noch nicht ganz verstanden.
Ich hab die Eigenschaft Color auf clWhite geändert und dann als Test ob das so funktioniert folgendes in meine Clickprocedure eingefügt.

Delphi-Quelltext
1:
with PB_Oberflaeche.Canvas do FillRect(ClipRect);                    


damit bekomm ich den Hintergrund weiß, allerdings wird beim nächsten Punkt der alte auch wieder gelöscht, ist ja klar, denn jedes mal wird alles wieder mit Weiß gefüllt.
Also wo muss das hin damit der Hintergrund gleich beim Programmstart weiß ist? Oder gibts da vll ne bessere Lösung für das Problem?

Vielen Dank schon mal für euere Hilfe


GTA-Place - Mo 06.11.06 21:44

Schreib das in OnCreate des Formulars.


Aerin - Mo 06.11.06 23:38

user profile iconGTA-Place hat folgendes geschrieben:
Schreib das in OnCreate des Formulars.


Habs ausprobiert und so funktionierts leider nicht. Hab folgende procedure für OnCreate eingetragen:


Delphi-Quelltext
1:
2:
3:
4:
procedure TF_1.FormCreate(Sender: TObject);
begin
with PB_Oberflaeche.Canvas do FillRect(ClipRect);
end;


Allerdings wird da die Farbe zum füllen der PaintBox genommen die unter Form.Color eingestellt ist und nicht die PaintBox.Color. Außerdem wird die PaintBox auch wieder gelöscht sobald ich auf ein anderes Fenster wechsele und zurück, da in dem Fall jedes mal wieder die OnCreate procedure ausgeführt wird.


Lannes - Di 07.11.06 01:09

Hallo,

such mal nach Suche in der Entwickler-Ecke OFFSCREENBITMAP


GTA-Place - Di 07.11.06 07:46


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure TF_1.FormCreate(Sender: TObject);  
begin  
  with PB_Oberflaeche.Canvas do
  begin
    Brush.Color := clWhite;
    FillRect(ClipRect); 
  end;
end;

OnCreate wird nur 1x aufgerufen. Dein Problem hat einen anderen Grund. Der Tipp von Lannes wird da helfen.


Aerin - Sa 11.11.06 17:56

hab zu OFFSCREENBITMAP das hier gefunden:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
var
Hintergrund : TBitmap;
begin
   //Bitmap erzeugen
  Hintergrund := TBitmap.Create;
   //Hintergrundfarbe
  Hintergrund.Canvas.Brush.Color := clWhite;
   //Größe festlegen
  Hintergrund.Width  := PB_Oberflaeche.Width;
  Hintergrund.Height := PB_Oberflaeche.Height;
end;


allerdings hilft mir das auch nich viel weiter, da die Farbe in der das Form eingefärbt wird wieder nicht das angegebene weiß ist....


Lannes - Sa 11.11.06 18:12

Hallo,

aus Deinem Code ist nicht ersichtlich wo er ausgeführt wird.
Wird denn auch dein Bitmap "Hintergrund" mit BitBlt der PaintBox zugewiesen?

Wenn Du nur die Form färben willst brauchst Du nur Color der Form zu setzen.

Was hast Du denn zu OffScreenBitmap gefunden?
falls Du das (Link [http://www.michael-puff.de/Developer/Artikel/2003_2005/WieWindowsFunktioniert.shtml]) noch nicht kennst, solltest Du es dir durchlesen(und verstehen :wink: ).


Aerin - Sa 11.11.06 20:32

user profile iconLannes hat folgendes geschrieben:
Hallo,
aus Deinem Code ist nicht ersichtlich wo er ausgeführt wird.
Wird denn auch dein Bitmap "Hintergrund" mit BitBlt der PaintBox zugewiesen?

hab das Ganze wieder im OnCreate des Forms ausführen lassen.
Hast recht, ich hatte die Bitmap gar nicht auf die PaintBox zeichnen lassen.
Hab das mal korrigiert und noch mal dran geschrieben was ich denke, was der jeweilige Befehl machen sollte

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
procedure TF_1.FormCreate(Sender: TObject);
var
Hintergrund : TBitmap;
begin
  Hintergrund := TBitmap.Create; //Bitmap erzeugen
  Hintergrund.Canvas.Brush.Color := clWhite; //Farbe des Pinsels festlegen
  Hintergrund.Canvas.Pen.Color := clWhite;   //Farbe des Stifts festlegen
  Hintergrund.Width  := PB_Oberflaeche.Width; //Breite der Bitmap festlegen
  Hintergrund.Height := PB_Oberflaeche.Height;//Höhe festlegen
  Hintergrund.Canvas.Rectangle(0,0,Hintergrund.Width,Hintergrund.Height);//Rechteck in die Bitmap zeichnen, das eigentlich weiß sein sollte und die komplette Bitmap abdecken sollte
  PB_Oberflaeche.Canvas.Draw(0,0,Hintergrund);//Die weiße Bitmap wird auf die Paintbox gemalt
end;


nach dem was in den einzelnen Schritten stattfindet sollte die Painbox also weiß sein, aber wenn ich F9 drücke ist sie dennoch grau....

Edit: Liegt daran das man das nicht mit OnCreate des Forms amchen kann, hab die selbe procedure grade OnClick bei meiner Paintbox zugewiesen und getestet, auf Click wird die PB weiß.

Also muss ich jetzt nur noch feststellen wo ich die procedure ausführen lassen muss damit dsa Form beim Start weiß wird.

Edit2:
und hier noch mein eigenes rumprobieren, was OnClick jetzt auch funktioniert


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
var
PBRechteck : TRect;
begin
PB_Oberflaeche.Canvas.Brush.Color := clWhite;
PBRechteck := Rect(PB_Oberflaeche.Left,PB_Oberflaeche.Top,PB_Oberflaeche.Left+PB_Oberflaeche.Width,PB_Oberflaeche.Top+PB_Oberflaeche.Height);
PB_Oberflaeche.Canvas.FillRect(PBRechteck);


Edit3: Auch wenn ich nicht genau weiß wann OnPaint ausgelöst wird, es macht genau das was ich will, beim Progstart weiß füllen ;)


Lannes - Sa 11.11.06 23:09

Hallo,
user profile iconAerin hat folgendes geschrieben:

...Auch wenn ich nicht genau weiß wann OnPaint ausgelöst wird...
das ist schade, deshalb hatte ich Dir den Link gegeben.
Und es ist schade das Du den Artikel vermutlich nicht ausführlich genug gelesen hast. Am Ende des Artikels steht auch ein schöner übersichtlicher Code der eigentlich alles gut verdeutlicht.

Lass Dir doch mal in einem zusätzlichen Memo das Auftreten des Ereignisses OnPaint ausgeben.


Aerin - So 12.11.06 02:55

So, hab den Text mal ganz gelesen und jetzt ist mir auch klar wann OnPaint eigentlich ausgelöst werden sollte, einfach beim Zeichnen des Fensterinhaltes(wobei ich mir auch nicht so ganz sicher bin ob bei diesem neuzeichnen des Fensterinhaltes das onpaint der paintbox ausgelöst wird), also jedesmal wenn das Programmfenster angezeigt werden soll wenn es vorher nicht angezeigt wurde, also entweder wenn das Programm gestartet wird oder wenn ein Teil verdeckt gewesen ist und neu gezeichnet werden soll, weshalb es auch zu dem Phänomen kommt das der Teil wo ich mit nem anderen Fenster "drüberwische" wieder weiß wird. Wie ich das Zwischenspeichern des Inhalts genau in mein Programm einbaue beschäftigte ich mich dann morgen ;)
Aber eine Frage wär da noch:
Macht es denn demnach überhaupt Sinn die Hintergrundfarbe durch OnPaint zu ändern? Wenn ich den Text richtig verstanden würde ja dann bei jedem neuzeichnen wieder alles neugezeichnete weiß werden, selbst wenn ich eigentlich etwas einbaue um den Inhalt zwischenzuspeichern.


jaenicke - So 12.11.06 09:05

Nun ja, am sinnvollsten ist es, wenn du alles auf die Hintergrundbitmap zeichnest und diese dann auf die eigentliche Zeichenfläche kopierst.
In dem von dir etwas modifizierten Code, den du gepostet hast, malst du aber nur ein weißes Rechteck und kopierst dieses dann auf die Zeichenfläche. Vor allem machst du das aber in FormCreate mit einer lokalen Bitmap.

Sinnvoll ist das aber nur, wenn du eine globale Hintergrundbitmap benutzt (direkt unter public oben in der Formulardeklaration deklariert)! Diese wird in FormCreate erzugt und mit dem Hintergrund (in dies Fall nur das füllende Rechteck) gefüllt. In OnPaint zeichnest du die dann auf die reale Zeichenfläche.
Alles, was ausgegeben werden soll, zeichnest du auf die Hintergrundbitmap. Entweder zeichnest du kleine Operationen dann auch direkt zusätzlich auf die reale Zeichenfläche oder du rufst einfach Repaint auf, wo ja dann die Hintergrundbitmap inklusive der neuesten Änderung auf die Zeichenfläche übertragen wird.

Das ist auch in dem Link von user profile iconLannes so gemeint! Da du das anscheinend wirklich gelesen, aber nur nicht verstanden hast, habe ich es hier nochmal versucht zu erklären...
Denn ich muss zugeben, dass dies etwas kurz beschrieben ist, es steht aber da:
Zitat:
aber, und das ist die elegantere Methode, wir zeichnen auf ein Bitmap im Speicher und kopieren es bei Bedarf auf unser Fenster.


Aerin - So 12.11.06 19:39

Ich denke ich habs geschafft. Danke für die Hilfe, hier mal der ganze Code:


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:
47:
48:
49:
50:
51:
52:
53:
54:
55:
unit U_Test;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TF_1 = class(TForm)
    PB_Oberflaeche: TPaintBox;
    procedure FormCreate(Sender: TObject);
    procedure PB_OberflaechePaint(Sender: TObject);
    procedure PB_OberflaecheClick(Sender: TObject);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
    Hintergrund : TBitmap;
    end;

var
  F_1: TF_1;

implementation

{$R *.dfm}

procedure TF_1.FormCreate(Sender: TObject);
begin
    Hintergrund := TBitmap.Create; //Bitmap erzeugen
    Hintergrund.Canvas.Brush.Color := clWhite; //Farbe des Pinsels festlegen
    Hintergrund.Canvas.Pen.Color := clWhite;   //Farbe des Stifts festlegen
    Hintergrund.Width  := PB_Oberflaeche.Width; //Breite der Bitmap festlegen
    Hintergrund.Height := PB_Oberflaeche.Height;//Höhe festlegen
    Hintergrund.Canvas.Rectangle(0,0,Hintergrund.Width,Hintergrund.Height);//Rechteck in die Bitmap zeichnen, das eigentlich weiß sein sollte und die komplette Bitmap abdecken sollte
end;

procedure TF_1.PB_OberflaechePaint(Sender: TObject);
begin
PB_Oberflaeche.Canvas.Draw(0,0,Hintergrund);//Die weiße Bitmap wird auf die Paintbox gemalt
end;

procedure TF_1.PB_OberflaecheClick(Sender: TObject);
var
aPoint : TPoint;
begin
GetCursorPos(aPoint);
aPoint := PB_Oberflaeche.ScreenToClient(aPoint);
Hintergrund.Canvas.Pixels[aPoint.x,aPoint.y] := clBlack;
PB_Oberflaeche.Repaint;
end;


end.


das ganze funktioniert, und auch wenn ich auf nen anderes Fenster wechsle und zurück, bleibt der Inhalt erhalten.

Wenn man sonst noch was verbessern kann bin ich natürlich für Vorschläge offen ;)
Vll gibts ja ne einfache Möglichkeit das Flackern beim Repaint zu vermeiden (stört aber nicht so wirklich)


jaenicke - So 12.11.06 19:44

Das Flackern kommt wohl daher, dass du die Draw Methode verwendest...
Versuchs doch mal so wie in dem Link oben.
Michael hat nicht ohne Grund BitBlt benutzt, das hat er ja auch geschrieben...


Lannes - Mo 13.11.06 00:50

Hallo,

nicht vergessen das Bitmap in OnDestroy der Form wieder freizugeben. :mahn:


Aerin - Fr 17.11.06 19:09

OK, hab im OnDestroy Hintergrund.Free; eingetragen.
Und wenn ich das richtig verstanden habe wird doch durch den Befehl einfach der Inhalt der Bitmap wieder aus dem Speicher entfernt und der Speicher wieder freigegeben für andere Programme oder?
Was sollte/kann man noch beim OnDestroy freigeben? Ich verwende auch arrays, müssen die freigegeben werden?


Lannes - Fr 17.11.06 22:20

Hallo,

so Pi mal Daumen,

mit Create(reserviert Speicher) erzeugte Objecte >> freigeben TStringList, Streams ...
Stichworte Owner, Create >> Delphi Hilfe

statische Arrays = nein,
dynamische Arrays = ja, denn mit SetLength reservierst Du Speicher


Aerin - Mo 04.12.06 21:24

OK, aber wie gebe ich ein Array frei?
Habs mit
Array.Free;
versucht, also wie bei Bitmaps, aber da kommt nen Fehler.

Und ich hätte da noch eine andere Frage:
Inzwischen hat mein Prog mehrere Bitmaps.

Delphi-Quelltext
1:
2:
3:
4:
public

    Hintergrund                                  : TBitmap;
    HGPunkte,HGSpannbaum                         : TBitmap;


Die Hintergrundbitmap wird immer noch wird beim starten bemalt.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
procedure TF_1.FormCreate(Sender: TObject);
  begin

  Hintergrund                    := TBitmap.Create;                        //Bitmap erzeugen
  Hintergrund.Canvas.Brush.Color := clWhite;                               //Farbe des Pinsels festlegen
  Hintergrund.Canvas.Pen.Color   := clWhite;                               //Farbe des Stifts festlegen
  Hintergrund.Width              := PB_Oberflaeche.Width;                  //Breite der Bitmap festlegen
  Hintergrund.Height             := PB_Oberflaeche.Height;                 //Höhe festlegen
  Hintergrund.Canvas.Rectangle(0,0,Hintergrund.Width,Hintergrund.Height);  //weißes Rechteck in die Bitmap zeichnen, dasss die komplette Bitmap abdeckt
end;


In OnPaint wird die OffScreenBitmap Hintergrund auf meine PB geladen und auf Click Punkte gemalt.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
procedure TF_1.PB_OberflaechePaint(Sender: TObject);
  begin

  BitBlt(PB_Oberflaeche.Canvas.Handle, 00, Hintergrund.Width, Hintergrund.Height, Hintergrund.Canvas.Handle, 00, SrcCopy);  //PB_Oberflaeche.Canvas.Draw(0,0,Hintergrund); //Die weiße Bitmap wird auf die Paintbox gemalt
 
  end;

procedure TF_1.PB_OberflaecheClick(Sender: TObject);
  var
    Point  : TPoint;
  begin

  GetCursorPos(Point);
  Point := PB_Oberflaeche.ScreenToClient(Point);
  Hintergrund.Canvas.Brush.Color := clBlack;
  Hintergrund.Canvas.Pen.Color   := clBlack;
  Hintergrund.Canvas.Rectangle(Point.x-3,Point.y-3,Point.x+3,Point.y+3); //Hintergrund.Canvas.Pixels[Point.x,Point.y] := clBlack;
  PB_Oberflaeche.Repaint;
 
  end;


In einer weiteren click procedure erstell ich die anderen beide Bitmaps und weise darauf Hintergrund zu, dann zeichne ich in einer Schleife Linien auf die HGSpanbaum Bitmap. Solange bleiben nur die schwarzen Punkte sichbar, denn ich löse ja kein Repaint aus.
Löse ich jetzt in einer anderen Click procedure Repaint aus, wird eine bmp mit Linien angezeigt...
Das versteh ich nicht, ich zeichne meine Linien doch auf HGSpannbaum und lass mir Hintergrund anzeigen, da sollten doch gar keine Linien drauf sein.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TF_1.B_RechneClick(Sender: TObject);
  HGPunkte     := TBitmap.Create;
  HGSpannbaum  := TBitmap.Create;
  HGPunkte     := Hintergrund;
  HGSpannbaum  := Hintergrund;
  for AnzeigeschleifeSB := 0 to PunkteLaufVar - 2 do
    begin
    HGSpannbaum.Canvas.MoveTo(Punkte[Spannbaum[AnzeigeschleifeSB].Von].x,Punkte[Spannbaum[AnzeigeschleifeSB].Von].y);
    HGSpannbaum.Canvas.LineTo(Punkte[Spannbaum[AnzeigeschleifeSB].Nach].x,Punkte[Spannbaum[AnzeigeschleifeSB].Nach].y)
    end;
  end;

procedure TF_1.B_ZeigePunkteClick(Sender: TObject);
  begin

  Hintergrund := HGPunkte;
  PB_Oberflaeche.Repaint;
  {egal ob ich hier nur den Repaint auslöse oder vorher die Zuweisung mach, es kommt ein Bild raus, das die Linien von HGSpannbaum enthält}
  end;