Gdiplus Grafik wie auf eine PaintBox übertragen?
-
Hallo,
ich versuche, mit gdiplus etwas zu zeichnen, und sobald die Mouse losgelassen wird, soll die Zeichnung in die PaintBox geschrieben werden:
In PaintBox1MouseUp-Event steht also so etwas:Gdiplus::Graphics zeichenflaeche(PaintBox1->Canvas->Handle); //Handle zur Zeichenfläche setzen PaintBox1->Canvas->Draw(0, 0, zeichenflaeche); //?????
Wie muss die ?????-Zeile richtig aussehen?
Danke!
-
Das was du im MouseUp zeichnen willst, musst dir irgendwo merken und dann im Paint Event ausführen.
-
Habe die PaintBox-Events so erstellt (Linie zeichnen funktioniert einwandfrei, aber nur temporär). Wie muss jetzt der temporär gezeichnete Inhalt der Paintbox fest übergeben werden?
void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { IsDrawing = True; //sofort bei MouseDown einen kleinen Punkt zeichnen/radieren: Gdiplus::Pen Stift(Gdiplus::Color(255, 0, 0, 255), StrichMiddle); Gdiplus::Graphics zeichenflaeche(PaintBox1->Canvas->Handle); //Handle zur Zeichenfläche setzen zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); zeichenflaeche.DrawLine(&Stift, X, Y, X+1, Y); //Linienstartposition zwischenspeichern zum Zeichnen einer Linie bei MouseMove: LinienstartX = X; LinienstartY = Y; } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (IsDrawing) { Gdiplus::Pen Stift(Gdiplus::Color(255, 0, 0, 255), StrichMiddle); Gdiplus::Graphics zeichenflaeche(PaintBox1->Canvas->Handle); //Handle zur Zeichenfläche setzen zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); zeichenflaeche.DrawLine(&Stift, LinienstartX, LinienstartY, X, Y); LinienstartX = X; LinienstartY = Y; } } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if( IsDrawing ) { Gdiplus::Graphics zeichenflaeche(PaintBox1->Canvas->Handle); //Handle zur Zeichenfläche setzen PaintBox1->Canvas->Draw(0, 0, zeichenflaeche); //???? <-- hier die noch fehlerhafte Zeile für die Übergabe if (eraser->Down) PaintBox1->Refresh(); //damit Löschung angezeigt wird IsDrawing = False; } } // ---------------------------------------------------------------------------
-
So wie ich geschrieben habe. Du musst OnPaint überschreiben und dadrin zeichnen. Entweder zeichnest du in den Mouse Event Handlern in ein Offscreen Image (TBitmap oder so) und zeichnest das im Paint Event in die Paint Box. Oder du speicherst dir die einzelnen Objekte, die du zeichnen willst, in irgendwelchen Datenstrukturen ab, und zeichnest die dann.
-
und zeichnest das im Paint Event in die Paint Box
bedeutet im OnPaint-Event der PaintBox?
so:void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { PaintBox1->Canvas->Draw(0, 0, DrawingBoard.get()); }
wobei DrawingBoard mein unsichtbares Bitmap ist (
DrawingBoard.reset(new Graphics::TBitmap());
) auf das ich zeichne per:
void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { IsDrawing = True; //sofort bei MouseDown einen kleinen Punkt zeichnen/radieren: Gdiplus::Pen Stift(Gdiplus::Color(255, 0, 0, 255), StrichMiddle); Gdiplus::Graphics zeichenflaeche(DrawingBoard->Canvas->Handle); //Handle zur Zeichenfläche setzen zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); zeichenflaeche.DrawLine(&Stift, X, Y, X+1, Y); //Linienstartposition zwischenspeichern zum Zeichnen einer Linie bei MouseMove: LinienstartX = X; LinienstartY = Y; }
ansonsten wie vorher die Events, nur mit
Gdiplus::Graphics zeichenflaeche(DrawingBoard->Canvas->Handle)
Problem: ich sehe nun nicht mehr, was ich zeichne!?
-
williman schrieb:
Problem: ich sehe nun nicht mehr, was ich zeichne!?
Ok, aber beim Neuzeichnen des Fensters siehst du das dann? Dann musst du, wenn du etwas verändert hast, Repaint aufrufen.
-
Er zeigt mir nichts an, weil mein "DrawingBoard" per
DrawingBoard->Transparent = true;
transparent gemacht ist. Ansonsten würde es gehen. Aber die Transparenz soll so sein, damit ein dahinter liegender Hintergrund sichtbar ist. Idee?
-
Die Transparenz sollte nicht stören, der Stift mit dem du zeichnest ist ja nicht transparent.
-
Trotzdem sehe ich keine Linie. Mache ich etwas falsch beim Erstellen des Drawingboard:
std::unique_ptr<Graphics::TBitmap> DrawingBoard; DrawingBoard.reset(new Graphics::TBitmap()); . . . DrawingBoard->SetSize(PaintBox1->Width, PaintBox1->Height); //Board genauso groß wie PaintBox DrawingBoard->Transparent = true; DrawingBoard->TransparentColor = clFuchsia; // Bitmap löschen und transparent machen: DrawingBoard->Canvas->Brush->Color = clFuchsia; DrawingBoard->Canvas->FillRect(Rect(0, 0, DrawingBoard->Width, DrawingBoard->Height));
-
Ich bin kein VCL Experte. Ich hab früher Delphi programmiert, ist aber schon sehr lange her. Was C++ angeht, sagen mir der C++ Builder und VCL überhaupt nicht zu.
Die Initialisierung vom unique_ptr würde ich zumindest in einer Zeile schreiben - RAII. Die Deklaration und die Initialisierung zu trennen macht wenig Sinn. Völlig unwichtig, wollts aber mal ewähnen, wo ichs grad sehe.Der Code schaut soweit nicht falsch aus. Poste nochmal den kompletten (relevanten) Code mit der Initialisierung und den Event Handlern.
-
Unit1.h: auszugsweise
private: // Benutzer-Deklarationen std::unique_ptr<Graphics::TBitmap> DrawingBoard; bool IsDrawing; int maxWidth; int maxHeight; int LinienstartX; int LinienstartY; DWORD pdwGdiStartup; Gdiplus::GdiplusStartupInput GdiStartupInp;
Unit1.cpp:
//--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { //Initialisierung: // Initialize GDI+. GdiplusStartup(&pdwGdiStartup, &GdiStartupInp, NULL); DrawingBoard.reset(new Graphics::TBitmap()); // Textur mit der Farbe rot erstellen, Syntax von Color: (alpha, rot, grün, blau) // Stift mit schwarzer Farbe und der dicke von StrichMiddle Pixel erstellen Gdiplus::Pen Stift(Gdiplus::Color(255, 0, 0, 255), StrichMiddle); //Zeichenflächengröße an Hauptfenster (Bildschirm) anpassen: //max. Zeichenfenstergröße bestimmen: Form1->Show(); //Formular anzeigen (nötig zum Einstellen der max. Fenstergröße) maxWidth = Form1->ClientWidth; maxHeight = Form1->ClientHeight - ToolBar1->Height; ScrollBox1->SetBounds(0,0,maxWidth,maxHeight); hintergrundbild->SetBounds(0,0,maxWidth-zeichenflaechenrand,maxHeight-zeichenflaechenrand); PaintBox1->SetBounds(0,0,hintergrundbild->Width,hintergrundbild->Height); //Paintbox so groß wie hintergrundbild DrawingBoard->SetSize(PaintBox1->Width, PaintBox1->Height); //Board genauso groß wie PaintBox DrawingBoard->Transparent = true; DrawingBoard->TransparentColor = clFuchsia; // Bitmap löschen und transparent machen: DrawingBoard->Canvas->Brush->Color = clFuchsia; DrawingBoard->Canvas->FillRect(Rect(0, 0, DrawingBoard->Width, DrawingBoard->Height)); } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1Paint(TObject *Sender) { PaintBox1->Canvas->Draw(0, 0, DrawingBoard.get()); } //--------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { IsDrawing = True; //sofort bei MouseDown einen kleinen Punkt zeichnen/radieren: Gdiplus::Pen Stift(Gdiplus::Color::Red, StrichMiddle); Gdiplus::Graphics zeichenflaeche(DrawingBoard->Canvas->Handle); //Handle zur Zeichenfläche setzen zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); zeichenflaeche.DrawLine(&Stift, X, Y, X+1, Y); //Linienstartposition zwischenspeichern zum Zeichnen einer Linie bei MouseMove: LinienstartX = X; LinienstartY = Y; } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseMove(TObject *Sender, TShiftState Shift, int X, int Y) { if (IsDrawing) { Gdiplus::Pen Stift(Gdiplus::Color::Red, StrichMiddle); Gdiplus::Graphics zeichenflaeche(DrawingBoard->Canvas->Handle); //Handle zur Zeichenfläche setzen zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias); zeichenflaeche.DrawLine(&Stift, LinienstartX, LinienstartY, X, Y); LinienstartX = X; LinienstartY = Y; } } // --------------------------------------------------------------------------- void __fastcall TForm1::PaintBox1MouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { if( IsDrawing ) { Gdiplus::Graphics zeichenflaeche(DrawingBoard->Canvas->Handle); //Handle zur Zeichenfläche setzen PaintBox1->Canvas->Draw(0, 0, DrawingBoard.get()); if (eraser->Down) PaintBox1->Refresh(); //damit Löschung angezeigt wird IsDrawing = False; } } // ---------------------------------------------------------------------------
Noch eine Verständnisfrage:
Ich schreibe in jedem Handler "Gdiplus::Pen Stift(Gdiplus::Color::Red, StrichMiddle);". Damit wird doch jedesmal eine neue Instanz erzeugt, oder? Wo müsste ich was schreiben, damit der "Stift" einmal erstellt wird u. in jedem Handler verwendet werden kann?
In MouseMove hatte ich ein PaintBox1->Repaint() eingefügt, was allerdings dazu führt, dass das Bild bei jeder Mousebewegung dann flackert.
-
williman schrieb:
Noch eine Verständnisfrage:
Ich schreibe in jedem Handler "Gdiplus::Pen Stift(Gdiplus::Color::Red, StrichMiddle);". Damit wird doch jedesmal eine neue Instanz erzeugt, oder? Wo müsste ich was schreiben, damit der "Stift" einmal erstellt wird u. in jedem Handler verwendet werden kann?Etwas seltsame Frage. Genauso wie du DrawingBoard als Klassemember angelegt hast, genauso könntest du auch alles andere was du brauchst als Klassenmember anlegen. Dann im Konstruktor initialisieren und in deinen Handlern verwenden. Ich seh hier auch nicht, warum du überhaupt ein unique_ptr brauchst, du kannst einfach ein Objekt benutzen (einfach Graphics::TBitmap DrawingBoard;).
Den Fehler seh ich so direkt aber leider nicht. Wenn das nur an dem Transparent = true liegt und das ohne funktioniert, dann könnte ich mir vorstellen, dass sich das mit GDI+ in die Quere kommt. GDI+ kann echte Transparenzen, dafür gibts den Alpha Kanal. Bei VCL oder vielleicht auch schon in GDI selbst (ich weiß es nicht mehr, aber ich kann mich grad an keine solchen Konstrukte in GDI erinnern) war das mehr so ein Hack, man hat eine transparente Farbe angegeben. Kann sein, dass das irgendwie stört.
Wenn das ohne funktioniert, dann lass das einfach weg und mach das später mit GDI+, wenn du das brauchst.
-
Das mit dem unique_ptr wurde mir hier vorgeschlagen:
https://www.c-plusplus.net/forum/335786-10?highlight=graphics+tbitmap+drawingboardAuf diese Art und Weise wollte ich natürlich auch Gdiplus::Pen Stift deklarieren, was allerdings immer wieder zu Fehlermeldungen geführt hat. Was ist hier falsch:
.h-Datei:private: // Benutzer-Deklarationen Gdiplus::Pen Stift(); Gdiplus::Graphics zeichenflaeche();
.cpp-Datei:
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { ... Gdiplus::Pen* Stift = new Gdiplus::Pen(Gdiplus::Color(255, 0, 0, 255), StrichMiddle); Gdiplus::Graphics* zeichenflaeche = new Gdiplus::Graphics(DrawingBoard->Canvas->Handle);
Bei einem Aufruf von z.B.
zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
kommt dann allerdings der Fehler "Auf linker Seite der Struktur ist . oder .* erforderlich"
-
Was sollen die Klammern bei der Deklaration?
-
So besser?:
private: // Benutzer-Deklarationen Gdiplus::Pen* Stift; Gdiplus::Graphics* zeichenflaeche;
Allerdings kommt dann "[bcc32 Fehler] E2294 Auf linker Seite der Struktur ist . oder .* erforderlich" bei z.B.
zeichenflaeche.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
???
-
Das sind aber alles elementare C++ Grundkenntnisse, dir dir zu fehlen scheinen. Und diese sollten sitzen, wenn man mit externen Libs (wie der VCL) arbeitet.
Da 'zeichenflaeche' einen Zeiger darstellt, mußt du beim Zugriff dereferenzieren, d.h. (*). oder ->.
-
Ist schon klar, aber:
mitzeichenflaeche->SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
kommt bei der Programmausführung eine Zugriffsverletzung!
-
Ok, das sind wirklich absolute Grundlagen, die dir hier noch fehlen. Ich würde dir ehrlich gesagt nicht raten, an der Stelle weiterzumachen. Lern zuerst richtig C++. Wenn du schon sowas nicht verstehst, dann fehlt dir noch viel viel mehr. Du kannst die Probleme jetzt zwar irgendwie lösen, aber es wird auf die Weise keinen Spass machen. Das war auch der Punkt, wo ich früher das Lernen von C++ aufgegeben habe, habs mir erst viele Jahre später wieder angeschaut.
In dem anderen Beitrag steht nichts dabei, warum der smart pointer empfohlen wurde. Ich vermute, weil du eh schon ein new hattest und audacity davon ausging, dass du einen rohen Zeiger verwendest. Da ist ein smart pointer natürlich besser. Aber ich glaub, in dem Fall spricht nichts gegen ein Objekt.
-
Das Problem liegt wohl kaum jetzt beim Smartpointer.
Wenn das nun an den Basics liegt, könnte man in drei Zeilen ja die Lösung schreiben, oder?Jetzt eine andere Frage am Rande: Macht es überhaupt Sinn, PaintBox zusammen mit Gdiplus zu benutzen? Oder sollte man besser ein Bitmap erzeugen u. dieses für das Zeichnen benutzen?
-
Nein, das Problem liegt nicht am smart pointer. Aber du verstehst nicht, was du machst. Du bringst wild ". und ->" durcheinander und verstehst nicht, was das genau bedeutet und was dann genau passiert oder eben nicht passiert und was du machen müsstest. Bevor dir das nicht ganz genau klar ist, macht es keinen Sinn, weiterzugehen. Und es gibt noch um einiges mehr an Grundlagen, die man sicher beherrschen muss, bevor man sich sehr spezielle (und ich würde sagen exotische) Bibliotheken anschaut.
Und dein eigentliches Problem ist eben nicht trivial. Das hat auch nichts mit C++ an sich zu tun, da muss man sich mit diesen konkreten Bibliotheken und deren möglichen Quirks auskennen. Aber am besten erst, wenn man die Grundlagen beherrscht.Die Paint Box an sich stört nicht. Um ein Bitmap oder was auch immer auszugeben, brauchst du ein Handle. Das kann eine PaintBox sein, oder einfach ein Fenster. Man kann GDI und GDI+ mischen, habe ich früher auch gemacht. Nur hatte ich nichts mit Transparenzen gemacht, das könnte hier wie gesagt zu Problemen führen.