Rubberbox zeichnen

Hallo,

ich habe auf ein Formular, eine Picturebox. In diese wird ein Bild geladen und kann dann verändert werden. Prinzipiell habe ich es so gelöst

 Protected Friend WriteOnly Property Picture As Image
 Set(ByVal value As Image)
 OrginalPic = CType(value.Clone, Image)
 MyPic = value
 picBild.Invalidate()
 End Set
 End Property

 Private OrginalPic As Image = Nothing 'Das orginale Bild
 Private MyPic As Image = Nothing 'Das geänderte Bild

Private Sub ÄndereBild
Dim Bmp as new Bitmap(MyPic)
'hier wird nun Bmp verändert
myPic=bmp
picBild.Invalidate() 'PicBild ist die PictureBox
End Sub

 Private Sub picBild\_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picBild.Paint
 If MyPic Is Nothing Then Exit Sub
 e.Graphics.DrawImage(MyPic, picBild.ClientRectangle)
 End Sub

Es sei angemerkt das der Style von der Picturebox auf Strecht gestellt ist!

Nun möchte ich aber einen Bereich des Bildes makieren, mittels einer Rubberbox. Dazu verwende ich folgenden Source

 Private RubberboxVisible As Boolean = False
 Private pRubberBox As Point
 Private sRubberBox As Size

 Private Sub picBild\_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseDown
 RubberboxVisible = True
 pRubberBox = New Point(Control.MousePosition.X, Control.MousePosition.Y)
 sRubberBox = New Size(0, 0)
 ControlPaint.DrawReversibleFrame(New Rectangle(pRubberBox, sRubberBox), Color.White, FrameStyle.Dashed)
 End Sub

 Private Sub picBild\_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseMove
 If e.Button = MouseButtons.Left Then
 If RubberboxVisible Then ControlPaint.DrawReversibleFrame(New Rectangle(pRubberBox, sRubberBox), Color.White, FrameStyle.Dashed)
 sRubberBox = New Size(Control.MousePosition.X - pRubberBox.X, Control.MousePosition.Y - pRubberBox.Y)
 ControlPaint.DrawReversibleFrame(New Rectangle(pRubberBox, sRubberBox), Color.White, FrameStyle.Dashed)
 End If
 End Sub

Das Event MouseUp wurde noch nicht implementiert. Es soll mir halt erst einmal die Rubberbox anzeigen. Er macht dies auch soweit. Ich kann nach belieben, eine Rubberbox aufziehen. Das Flag RubberboxVisible soll mir nur sagen ob eine Rubberbox angezeigt wird oder nicht.
Wenn ich aber nun die Picturebox mit einem anderen Fenster kurz überdecke oder das Formular verschiebe, so das das Paint Ereignis erneut aufgerufen wird, verschwindet nun aber die Rubberbox.

Folgende versuche habe ich bereits unternommen.

*Das Flag RubberboxVisible abgefragt auf True und ggfls. die Rubberbox neu gezeichnet -> Kein Erfolg!

*Das Flag RubberboxVisible auf True abgefragt und die Rubberbox 2 mal neu gezeichnet, da beim überzeichnen sie ja gelöscht wird -> Kein Erfolg

Hat noch jemand eine Idee, was ich noch machen könnte?

MfG Alex

Moin Alex,

aaalso ich habe da (wie du in deiner Nachricht schon angenommen hast) da tatsächlich eine Idee:

Ich stand mal vor dem selben Problem.
Soweit ich mich erinnere habe ich das ganze mit einem Panel gelöst.

Ich hab da ein eben zusammengeflicktes Codesnippet für dich:

 private bool down = false;
 private Point startP;
 private Point endP;
 Bitmap back = Get\_your\_inverted\_image();

 private void panel1\_Paint(object sender, PaintEventArgs e)
 {
 RefreshPicPanel(e.Graphics);
 }

 private void panel1\_MouseDown(object sender, MouseEventArgs e)
 {
 down = true;
 startP = e.Location;
 }

 private void panel1\_MouseUp(object sender, MouseEventArgs e)
 {
 down = false;
 }

 private void RefreshPicPanel(Graphics g)
 {
 Pen pen = new Pen(Brushes.Fuchsia);
 if (back != null)
 g.DrawImage(back, new Point(0, 0));
 else
 g.Clear(panel1.BackColor);
 g.DrawRectangle(pen, startP.X, startP.Y, endP.X - startP.X, endP.Y - startP.Y);
 }

 private void panel1\_MouseMove(object sender, MouseEventArgs e)
 {
 if (down)
 {
 endP = e.Location;
 RefreshPicPanel(panel1.CreateGraphics());
 }
 }

Beim MouseOver hast du ein leichtes Flackern im gezeichneten Rechteck. Falls ich mich richtig erinnere habe ich das so gelöst in dem ich zwei Panels übereinander gelegt habe. Das untere hat das Bild angezeigt, das obere hatte BackgroundColor = Colors.Transparent und hat das Rectangle gezeichnet.

Du musst dann noch den Zoomfaktor selber berechnen. Aber das sollte relativ einfach machbar sein. Außerdem bringt es dir den vorteil, dass du die Seitenlängen auf die tatsächlichen Bildmaße umrechnen kannst, wenn du sie für weitere Bearbeitungen benötigst.

Bei Fragen wie immer: Einfach Fragen :smile:

MfG
Salbei

Hallo!

Die übliche Vorgehensweise ist, die Logik (wann soll eine wie große Rubberbox zu sehen sein?) von der Darstellung (wenn die Picturebox neu gezeichnet wird, wie soll es dann aussehen?) zu trennen.

Du hast das Paint-Ereignis der PicBox eh’ schon überschrieben (Deine picBild_Paint Methode), wenn Du also das Zeichnen der Rubberbox dort hinein verlagerst und in den Mouse-Events nur die Größe/Position der Box anpasst und anschließend ein Invalidate() für die PicBox aufrufst, wird neu gezeichnet und das Bild samt Rubberbox dargestellt. Das passiert immer, wenn die PicBox neu gezeichnet werden soll, also auch, wenn sie von einem anderen Fenster überlagert war und dann wieder sichtbar wird.

Gruß,
Martin

Hallo Martin,

auch dir erst einmal ein grosses Danke das Du Dich meinem Problem annimmst.

Es folgendes passieren.

Irgendwo in einem Formular, werden Datensätze angezeigt. Diese werden aus einer DB /Datatable ausgelesen. Dabei wird auch ein Bild ausgelesen und angezeigt. Nun kann der User einen Doppelklick darauf machen und es öffnet sich ein neues Formular, wo das Bild zur Bearbeitung angezeigt wird.
In der neuen Form ist eine private Variable vom Typ Bitmap declariert. Auch hat diese Formular eine Eigenschaft Picture. Sobald ich ihr ein Bild zuweise, dann wird die der Variablen ds Picture zugewiesen und danach die Invalidate Methode aufgerufen. Im Paint Ereignis wird dann das Bild gezeichnet.

In diesem Formular nun kann der User beliebige Änderungen am Bild vornehmen (Invertieren /Drehen /Kontrast etc). Dabei erstelle ich immer ein neues Bitmap und zeichne dies dann. Danach weisse ich der privaten Variable das neu erstellte Bild zu und rufe die Invalidate Methode auf. Klappt soweit auch super.

Nun möchte ich aber folgendes realisieren.

Der User kann eine Rubberbox auf das Image ziehen. Sobald er die Maustaste loslaesst, soll diese stehen bleiben und ein Menu wird freigeschaltet, wo er dann spezielle Functionen zu dem ausgewählten Ausschnitt bekommt.

Zuerst dachte ich auch daran, es so zu machen wie du es mir vorgeschlagen hast. Also einfach die Punkte festlegen, einer Variablen dann den Wert zuweisen das sie gezeichnet ist, danach die Invalidate Methode aufrufen und im Paint Ereignis dann diese Variable abzufragen und dann die Rubberbox zu zeichnen. Aber irgendwie zeigt er mir diese dann nicht an :frowning: Dazu verwende ich folgenden Source

 Private RubberboxVisible As Boolean = False
 Private pRubberBox As Point
 Private sRubberBox As Size

 Private Sub picBild\_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picBild.Paint
 If MyPic Is Nothing Then Exit Sub
 e.Graphics.DrawImage(MyPic, picBild.ClientRectangle)
 If RubberboxVisible Then ControlPaint.DrawReversibleFrame(New Rectangle(pRubberBox, sRubberBox), Color.White, FrameStyle.Dashed)
 End Sub

 Private Sub picBild\_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseMove
 If e.Button = MouseButtons.Left Then
 sRubberBox = New Size(Control.MousePosition.X - pRubberBox.X, Control.MousePosition.Y - pRubberBox.Y)
 picBild.Invalidate()
 End If
 End Sub

 Private Sub picBild\_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseDown
 If e.Button = Windows.Forms.MouseButtons.Left Then
 RubberboxVisible = True
 pRubberBox = New Point(Control.MousePosition.X, Control.MousePosition.Y)
 sRubberBox = New Size(0, 0)
 picBild.Invalidate()
 ElseIf e.Button = Windows.Forms.MouseButtons.Right Then
 'Rechte Maustaste -\> Menu anzeigen ...
 End If
 End Sub

Das Resultat ist aber folgendes

Solange ich die Maus bewegen mit gedrückter Maustaste wird die Rubberbox brav angezeigt. Sobald ich sie aber loslasse, verschwindet sie! Wenn ich das Formular verdecke und dann wieder sichtbar mache, erscheint sie wieder!
Wenn ich das Formular verschiebe, verschiebt es die Rubberbox nicht mit :frowning: Das selbe passiert beim ändern der Grösse :frowning:

Weisst du woran das nun wiederrum liegt?
Ich werde mal noch einiges probieren :smile: Hab ja heute unmengen an Zeit :smile:

MfG Alex

Hallo Salbei,

aaalso ich habe da (wie du in deiner Nachricht schon
angenommen hast) da tatsächlich eine Idee:

Super, dann bin ich mal gespannt :smile:

Ich stand mal vor dem selben Problem.

Soweit ich mich erinnere habe ich das ganze mit einem Panel
gelöst.

Ach herje :frowning:Also das Formular wollte ich nun nicht gerade bindend umbauen. Aber zur Not, sollte dies auch kein Problem sein.
Ich möchte aber noch dazu sagen, das folgendes Szenario bei mir gegeben ist. Auf dem Formular ist nur die Picturebox drauf, die über Dock.Fill angezeigt wird. Die SizeMode Eigenschaft der Picturebox ist auf StretchImage gesetzt. Wenn der User nun das Formular in der grösse ändert, so ändert sich auch die grösse der Picturebox und somit die grösse des Bildes! So kann der User ganz bequem das Bild zoomen ohne das ich einen Aufwand habe :smile:

Ich hab da ein eben zusammengeflicktes Codesnippet für dich:

private bool down = false;

private Point startP;

private Point endP;

Bitmap back = Get_your_inverted_image();

private void panel1_Paint(object sender,
PaintEventArgs e)

{

RefreshPicPanel(e.Graphics);

}

private void panel1_MouseDown(object sender,
MouseEventArgs e)

{

down = true;

startP = e.Location;

}

private void panel1_MouseUp(object sender,
MouseEventArgs e)

{

down = false;

}

private void RefreshPicPanel(Graphics g)

{

Pen pen = new Pen(Brushes.Fuchsia);

if (back != null)

g.DrawImage(back, new Point(0, 0));

else

g.Clear(panel1.BackColor);

g.DrawRectangle(pen, startP.X, startP.Y, endP.X -
startP.X, endP.Y - startP.Y);

}

private void panel1_MouseMove(object sender,
MouseEventArgs e)

{

if (down)

{

endP = e.Location;

RefreshPicPanel(panel1.CreateGraphics());

}

}

Beim MouseOver hast du ein leichtes Flackern im gezeichneten
Rechteck. Falls ich mich richtig erinnere habe ich das so
gelöst in dem ich zwei Panels übereinander gelegt habe. Das
untere hat das Bild angezeigt, das obere hatte BackgroundColor
= Colors.Transparent und hat das Rectangle gezeichnet.

Ok, das probiere ich dann aber mal in einem neuen Projekt aus :smile:

Aber sehe ich das richtig, das ich ein Panel auf die Form lege und darein direkt das Picture zeichnen soll und dann über das Panel noch ein neues legen soll?

Du musst dann noch den Zoomfaktor selber berechnen. Aber das
sollte relativ einfach machbar sein. Außerdem bringt es dir
den vorteil, dass du die Seitenlängen auf die tatsächlichen
Bildmaße umrechnen kannst, wenn du sie für weitere
Bearbeitungen benötigst.

Du an dem Punkt bin ich noch gar nicht :smile: Dazu kommen wir dann später, wenn denn erst einmal die Ruberbox funktioniert wie gewünscht :smile:

Och ich haette da schon noch einige :smile:
Wie gut kennst Du Dich denn mit Reflection aus?
Sprich wenn ein Projekt zum Bsp. nur aus eigenen Steuerelementen besteht, diese dann als Dll machen und dann diese Dll in ein neues Projekt einbinden und dann die Uercontrol’s via Reflection in das Projekt einbinden? Ok, das habe ich schon hinbekommen, aber wo ich Probleme hatte, war, da dann die Eigenschaften zu setzen :frowning:

MfG Alex

Hallo nochmal!
Die Methoden der ControlPaint-Klasse verwenden jeweils Bildschirmkoordinaten, sind daher für das Zeichnen innerhalb von (verschiebbaren) Controls nur mit Zusatzaufwand geeignet.

Beim Anlegen der Rubberbox hast Du die Position über die Mausposition (in Bildschirmkoordinaten). Verschiebst Du das Fenster, ändert sich die Position der Rubberbox auf dem Bildschirm, relativ zu Deiner PicBox aber nicht!
Für die Umrechnung gibt es die Methoden Control.PointToScreen() bzw. PointToClient().

Zum Menü auf der RMT würde ich der PicBox ein entsprechendes ContextMenu zuweisen, dann musst Du Dich nicht selbst darum kümmern, es anzuzeigen - es geht automatisch bei RMT auf der PicBox.

Gruß,
Martin

Hallo Martin,

Die Methoden der ControlPaint-Klasse verwenden jeweils
Bildschirmkoordinaten, sind daher für das Zeichnen innerhalb
von (verschiebbaren) Controls nur mit Zusatzaufwand geeignet.

Das habe ich mittlerweile auch mitbekommen. Also müsste ich das festlegen der Punkte nochmals überarbeiten!

Beim Anlegen der Rubberbox hast Du die Position über die
Mausposition (in Bildschirmkoordinaten). Verschiebst Du das
Fenster, ändert sich die Position der Rubberbox auf dem
Bildschirm, relativ zu Deiner PicBox aber nicht!

Das habe ich auch schon mitbekommen. Aber da wollte ich eigentlich im Move Event, einfach alles neu berechnen.

Für die Umrechnung gibt es die Methoden
Control.PointToScreen() bzw. PointToClient().

Das schaue ich mir mal an. So schwer sollte das nicht sein :smile:

Zum Menü auf der RMT würde ich der PicBox ein entsprechendes
ContextMenu zuweisen, dann musst Du Dich nicht selbst darum
kümmern, es anzuzeigen - es geht automatisch bei RMT auf der
PicBox.

Was meinst du mit RMT ?
In dem Proggi hat man die Auswahl entweder über das Contex Menu oder das Menu vom Formular, welches dann frei geschalten werden soll. Aber soweit bin ich ja noch nicht :frowning:

Aber scheinbar, reden wir aneinander vorbei Martin :frowning:
Mein Problem ist zur Zeit folgendes. Wenn ich mit dem ziehen der Maus aufhöre, zeigt er mir die Box nicht an :frowning: Genauso wenn das Menu vom Formular die Rubberbox überdeckt :frowning: Das wir die Form vergrössern oder verschieben lassen wir erst einmal aussen vor

Probiere mal bitte wenn du Lust und Zeit hast folgenden Source aus.
Einfach ein Formular und da mal ein Menu reinbasteln. Danach folgenden Source

Public Class Form1

 Private Mypic As Bitmap = Nothing
 Private PB As New PictureBox With {.Dock = DockStyle.Fill, .SizeMode = PictureBoxSizeMode.StretchImage}
 Private RubberboxVisible As Boolean = False
 Private pRubberBox As Point
 Private sRubberBox As Size


 Private Sub Form1\_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 Me.Controls.Add(PB)
 Mypic = Image.FromFile("d:\a.jpg")' 

Starte mal das Proggi, ziehe eine Rubberbox auf und klicke dann mal auf das Menu. Das Menu sollte dabei aber einen Teil der Rubberbox überdecken.

Siehst du was ich meinte? Wie kann ich das denn umgehen? Und wo liegt hier mein Fehler?


MfG Alex

Hallo nochmal!

RMT = Rechte MausTaste :wink:

Ich hab’s mal eben ausprobiert und es hat etwas mit dem DrawReversibleFrame zu tun (wobei ich aber auch noch nicht zu 100% verstehe, warum).
Wenn Du Deine Rubberbox auf Clientkoordinaten umstellst und dann statt dem DrawReversibleFrame() im Paint Event

e.Graphics.DrawRectangle(Pens.Red, New Rectangle(pRubberBox, sRubberBox))

schreibst, bekommst Du einen roten Rahmen, der auch beim Loslassen bleibt. Die Art der Darstellung (gestrichelt bspw.) kannst Du über Eigenschaften des verwendeten Pens anpassen (ich hab’ nur einen einfachen roten genommen)

Gruß,
Martin

Hallo Martin,

RMT = Rechte MausTaste :wink:

Achso, na da soll mal einer drauf kommen. Aber nun weiss ich es ja :smile:

Ich hab’s mal eben ausprobiert und es hat etwas mit dem
DrawReversibleFrame zu tun (wobei ich aber auch noch nicht zu
100% verstehe, warum).

Ich habe mal gelesen, das wenn man einen Rahmen mit DrawReversibleFrame an eine Position x,y zeichnet und ihn dann genau wieder an die selbe Stelle zeichnet, er dann gelöscht wird. Vielleicht hat es ja damit etwas auf sich?

Wenn Du Deine Rubberbox auf Clientkoordinaten umstellst und
dann statt dem DrawReversibleFrame() im Paint Event

e.Graphics.DrawRectangle(Pens.Red, New Rectangle(pRubberBox,
sRubberBox))

schreibst, bekommst Du einen roten Rahmen, der auch beim
Loslassen bleibt. Die Art der Darstellung (gestrichelt bspw.)
kannst Du über Eigenschaften des verwendeten Pens anpassen
(ich hab’ nur einen einfachen roten genommen)

Meine Güte, wieso bin ich da nicht selber drauf gekommen? Manchmal denkt man nicht an die einfachsten Dinge :confused:

Ich habe mal eben was probiert und siehe da das Resultat ist wie gewünscht, dank Deinem Denkanstoß. Er zeichnet nun den Rahmen und beim verschieben bleibt er an der selben stelle. Vergrössert man nun das Bild, so rutscht die Rubberbox automatisch rüber :smile:

Gelöst habe ich es so

 Private RubberboxVisible As Boolean = True
 Private mStartpunkt As Point
 Private mEndpunkt As Point
 Private Pos As Size

 Private Sub picBild\_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles picBild.Paint
 If MyPic Is Nothing Then Exit Sub
 e.Graphics.DrawImage(MyPic, picBild.ClientRectangle)
 If RubberboxVisible Then
 Dim Pen As New Pen(Brushes.White)
 Pen.DashStyle = Drawing2D.DashStyle.Dash
 e.Graphics.DrawRectangle(Pen, New Rectangle(mStartpunkt.X, mStartpunkt.Y, mEndpunkt.X, mEndpunkt.Y))
 End If
 End Sub

 Private Sub picBild\_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseDown
 If e.Button = Windows.Forms.MouseButtons.Left Then
 mStartpunkt.X = e.X
 mStartpunkt.Y = e.Y
 mEndpunkt.X = e.X
 mEndpunkt.Y = e.Y
 Pos = picBild.Size
 End If
 End Sub

 Private Sub picBild\_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles picBild.MouseMove
 If e.Button = Windows.Forms.MouseButtons.Left Then
 RubberboxVisible = True
 mEndpunkt.X = e.X - mStartpunkt.X
 mEndpunkt.Y = e.Y - mStartpunkt.Y
 picBild.Invalidate()
 End If
 End Sub

 Private Sub picBild\_SizeChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles picBild.SizeChanged
 Dim NewPoint As Size = picBild.Size
 Try
 mStartpunkt.X = CInt((((mStartpunkt.X \* 100) / Pos.Width) \* NewPoint.Width) / 100)
 mStartpunkt.Y = CInt((((mStartpunkt.Y \* 100) / Pos.Height) \* NewPoint.Height) / 100)
 mEndpunkt.X = CInt((((mEndpunkt.X \* 100) / Pos.Width) \* NewPoint.Width) / 100)
 mEndpunkt.Y = CInt((((mEndpunkt.Y \* 100) / Pos.Height) \* NewPoint.Height) / 100)
 Pos = NewPoint
 picBild.Invalidate()
 Catch ex As Exception
 Return
 End Try
 End Sub

MfG Alex

Hallo nochmal

Ich habe mal gelesen, das wenn man einen Rahmen mit
DrawReversibleFrame an eine Position x,y zeichnet und ihn dann
genau wieder an die selbe Stelle zeichnet, er dann gelöscht
wird. Vielleicht hat es ja damit etwas auf sich?

Eigentlich wäre das auch die Funktionalität gewesen, die ich mir erwartet hätte. Wenn man die Maus bewegt, klappt es ja auch - man sieht den Rahmen.
Das Entfernen des Rahmens durch erneutes Zeichnen wird aber eh’ nicht gemacht, weil Du im Paint() jeweils zuerst das Bild malst und dann den Rahmen, d.h. der Rahmen invertiert IMMER die Pixel des Originalbildes.
Zum Vergleich: Mit einem einfarbigen Rahmen „schmiert“ der Rahmen ja auch nicht, sondern Du hast genau 1 Rahmen angezeigt, weil vorher immer das Pic neu gezeichnet wird.

Der Rahmen könnte verschwinden, weil er eben an der selben Stelle nicht nur einmal gezeichnet wird, sondern zweimal (bzw. 2n-mal). Da aber immer das Bild gemalt wird, kann ich es mir momenten immer noch nicht erklären.

Was mir noch aufgefallen ist: Wenn man den Rahmen mit Deinem Code nicht nach rechts unten aufzieht, sondern nach links oben, klappt es nicht (weil dann die Breite und Höhe negativ werden). Kannst bei Gelegenheit ja noch korrigieren :smile:

Gruß,
Martin

Wie gut kennst Du Dich denn mit Reflection aus?

Sprich wenn ein Projekt zum Bsp. nur aus eigenen
Steuerelementen besteht, diese dann als Dll machen und dann
diese Dll in ein neues Projekt einbinden und dann die
Uercontrol’s via Reflection in das Projekt einbinden? Ok, das
habe ich schon hinbekommen, aber wo ich Probleme hatte, war,
da dann die Eigenschaften zu setzen :frowning:

Probleme die Eigenschaften zu setzen? Hm, also meine Projektemappen haben meist mindestens ein Steuerelemtprojekte. Allerdings weiß ich nicht, inweit sich das bei C# dann von VB.NET unterscheidet.

Aber generell hatte ich noch keine Probleme, was so etwas angeht.

Also… einfach Fragen :wink:

MfG
Salbei