I am writing a WinForms application (.NET 4.8) with which you can load a picture into a PictureBox, move it, zoom it and finally draw on it.
It is planned that the picture will be moved first and then drawn on it. Therefore, the picture is only assigned to the PictureBox when the relevant radio button
has been checked. Unfortunately, the assignment of an image raises the paint event
again and again. There is an infinite loop when triggered once. How can I prevent this? I've already tried to inherit from PictureBox
and set the settings differently.
Public Class PictBoxEx : Inherits PictureBox
Public Sub New()
SetStyle(ControlStyles.Selectable Or ControlStyles.UserMouse, True) ' Important so that the PictureBox does not throw events on its own!
End Sub
End Class
Private Sub PictureBoxEx1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBoxEx1.Paint
If LoadedImage Is Nothing Then
Return
End If
If RadioButton_verschieben.Checked Then
Dim the_rectangle_to_be_zoomed_in_and_drawn As RectangleF = calculate_zoomed_and_or_moved_rect()
e.Graphics.DrawImage(LoadedImage, the_rectangle_to_be_zoomed_in_and_drawn)
ElseIf RadioButton_freihand.Checked Then
PictureBoxEx1.Image = Nothing
GC.Collect()
PictureBoxEx1.Image = LoadedImage
FunctionsToDraw.DrawTheUsersPath(e.Graphics)
End If
End Sub
This is the problematic procedure.↑
In case you need more information, I'll put the source code in here.
Form1.vb
Imports System.Drawing.Drawing2D
Imports Microsoft.VisualBasic.ControlChars
Imports Microsoft.WindowsAPICodePack.Dialogs
Public NotInheritable Class FormMain
'— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
'these variables are used for moving and zooming
Private Shared LoadedImage As System.Drawing.Bitmap
Private Shared current_Zoom_factor As Single = 1.0F
Private mouse_location As PointF = Point.Empty
Private image_location As PointF = Point.Empty
Private image_rect As RectangleF = RectangleF.Empty
Private mouse_up_must_follow As Boolean
Private moving As Boolean
'— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
'these variables are used for drawing
Private Shared _manuallydrawnpathonscreen As New System.Drawing.Drawing2D.GraphicsPath
Private last_mouse_location_on_screen As Point
Public Shared Property Manuallydrawnpathonscreen As GraphicsPath
Get
Return _manuallydrawnpathonscreen
End Get
Set(value As GraphicsPath)
_manuallydrawnpathonscreen = value
End Set
End Property
Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.BackColor = Color.FromArgb(0, 0, 31) ' very dark blue
For Each Bu As Button In Me.Controls.OfType(Of Button)
Bu.BackColor = Color.FromArgb(201, 201, 189)
Next
RadioButton_verschieben.Text = $"verschieben{NewLine}und{NewLine}zoomen"
RadioButton_verschieben.Checked = True
End Sub
Private Sub ButtonStart_Click(sender As Object, e As EventArgs) Handles ButtonStart.Click
Using OFD As New CommonOpenFileDialog
OFD.Title = "Bild zum Öffnen auswählen"
OFD.Filters.Add(New CommonFileDialogFilter("images", ".jpg;.jpeg;.bmp;.png"))
OFD.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
If OFD.ShowDialog() = CommonFileDialogResult.Ok Then
LoadedImage = Nothing
PictureBoxEx1.Image = Nothing
GC.Collect()
LoadedImage = New System.Drawing.Bitmap(OFD.FileName)
current_Zoom_factor = 1.0F
mouse_location = Point.Empty
image_location = Point.Empty
image_rect = RectangleF.Empty
Manuallydrawnpathonscreen.ClearMarkers()
'PictureBoxEx1.Image = LoadedImage
resize_PictureBox()
Else
Return
End If
End Using
End Sub
Private Sub resize_PictureBox()
If LoadedImage Is Nothing Then Return
'Width of the current picture
Dim Aktuelle_Breite As Integer = LoadedImage.Size.Width
'Height of the current picture
Dim Aktuelle_Hoehe As Integer = LoadedImage.Size.Height
' possible width on Form
Dim Moegliche_Breite As Integer = 1762
' possible height on Form
Dim Moegliche_Hoehe As Integer = 1000
If Aktuelle_Breite > Moegliche_Breite OrElse Aktuelle_Hoehe > Moegliche_Hoehe Then
PictureBoxEx1.SizeMode = PictureBoxSizeMode.StretchImage
PictureBoxEx1.Size = If(CInt(Math.Round(Aktuelle_Breite * Moegliche_Hoehe / Aktuelle_Hoehe, 0)) > Moegliche_Breite,
New Size(Moegliche_Breite, CInt(Math.Round(Aktuelle_Hoehe * Moegliche_Breite / Aktuelle_Breite, 0))),
New Size(CInt(Math.Round(Aktuelle_Breite * Moegliche_Hoehe / Aktuelle_Hoehe, 0)), Moegliche_Hoehe))
Else
PictureBoxEx1.SizeMode = PictureBoxSizeMode.Normal
PictureBoxEx1.Size = New Size(Aktuelle_Breite, Aktuelle_Hoehe)
End If
End Sub
Private Sub PictureBoxEx1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseDown
Select Case e.Button
Case MouseButtons.Left
If RadioButton_verschieben.Checked Then
mouse_location = e.Location
image_location = image_rect.Location ' ist (0|0) wenn Bild frisch geladen
Me.Cursor = Cursors.NoMove2D
mouse_up_must_follow = True
moving = True
PictureBoxEx1.Invalidate()
Return
ElseIf RadioButton_freihand.Checked Then
Manuallydrawnpathonscreen.AddLine(last_mouse_location_on_screen, e.Location)
End If
Case MouseButtons.Right
If RadioButton_freihand.Checked Then
Manuallydrawnpathonscreen.ClearMarkers()
PictureBoxEx1.Invalidate()
End If
Case Else
Exit Select
End Select
End Sub
Private Sub PictureBoxEx1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseMove
If e.Button = MouseButtons.Left AndAlso mouse_up_must_follow Then
If RadioButton_verschieben.Checked Then
'for Zoom
image_rect.Location = New PointF(image_location.X (e.Location.X - mouse_location.X),
image_location.Y (e.Location.Y - mouse_location.Y))
mouse_up_must_follow = True
moving = True
PictureBoxEx1.Invalidate()
Return
' end Zoom region
ElseIf RadioButton_freihand.Checked Then
mouse_up_must_follow = True
moving = True
Manuallydrawnpathonscreen.AddLine(last_mouse_location_on_screen, e.Location)
PictureBoxEx1.Invalidate()
End If
End If
last_mouse_location_on_screen = e.Location
End Sub
Private Sub PictureBoxEx1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseUp
If e.Button = MouseButtons.Left AndAlso mouse_up_must_follow Then
If RadioButton_verschieben.Checked Then
Me.Cursor = Cursors.Default
mouse_up_must_follow = False
moving = False
Return
ElseIf RadioButton_freihand.Checked Then
Manuallydrawnpathonscreen.CloseFigure()
mouse_up_must_follow = False
End If
End If
last_mouse_location_on_screen = e.Location
End Sub
Private Sub PictBox1_MouseWheel(sender As System.Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseWheel
If RadioButton_verschieben.Checked Then
If e.Delta > 0 Then
current_Zoom_factor = (1.0F / 30.0F)
PictureBoxEx1.Invalidate()
Else
If current_Zoom_factor > 0.0F Then
current_Zoom_factor -= (1.0F / 30.0F)
PictureBoxEx1.Invalidate()
End If
End If
End If
End Sub
Private Sub PictureBoxEx1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBoxEx1.Paint
If LoadedImage Is Nothing Then
Return
End If
If RadioButton_verschieben.Checked Then
Dim the_rectangle_to_be_zoomed_in_and_drawn As RectangleF = calculate_zoomed_and_or_moved_rect()
e.Graphics.DrawImage(LoadedImage, the_rectangle_to_be_zoomed_in_and_drawn)
ElseIf RadioButton_freihand.Checked Then
PictureBoxEx1.Image = Nothing
GC.Collect()
PictureBoxEx1.Image = LoadedImage
FunctionsToDraw.DrawTheUsersPath(e.Graphics)
End If
End Sub
Private Function calculate_zoomed_and_or_moved_rect() As RectangleF
image_rect.Width = PictureBoxEx1.Width
image_rect.Height = PictureBoxEx1.Height
Dim newLocation As PointF
If moving Then
newLocation = image_rect.Location
Else
newLocation = New PointF(
PictureBoxEx1.Location.X (PictureBoxEx1.Size.Width / 2.0F) - image_rect.Width * current_Zoom_factor / 2.0F,
PictureBoxEx1.Location.Y (PictureBoxEx1.Size.Height / 2.0F) - image_rect.Height * current_Zoom_factor / 2.0F)
End If
Dim scaled As New RectangleF(newLocation,
New SizeF(image_rect.Width * current_Zoom_factor, image_rect.Height * current_Zoom_factor))
Return scaled
End Function
End Class
FunctionsToDraw.vb
Public NotInheritable Class FunctionsToDraw
Public Shared Sub DrawTheUsersPath(ByVal g As Drawing.Graphics)
If g Is Nothing Then
Return
End If
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear
Using Pen_green As New Pen(Color.FromArgb(0, 210, 0), 2.0F)
g.DrawPath(Pen_green, FormMain.Manuallydrawnpathonscreen)
End Using
End Sub
End Class
CodePudding user response:
I've now solved the problem. You all were right – it was not wise to assign a picture to the Picturebox in the paint event handler, especially since I also want to draw on the zoomed rectangle. I thought I had to get the problem resolved first, but then discovered that this was actually the problem.
I've done it now. I can draw on the zoomed and shifted rectangle.
I created a second form showing the current values of the mouse and the rectangle. In the near future, I will test to calculate where the coordinates are on the original image when I draw a GraphicsPath on the zoomed and shifted image.
I'm putting the entire code online because I have rebuilt a lot. You are welcome to submit suggestions for improvement.
Form1.vb
#Disable Warning CA1707
Imports Microsoft.VisualBasic.ControlChars
Imports Microsoft.WindowsAPICodePack.Dialogs
Public NotInheritable Class FormMain
Private Shared _lF As LoggingForm
Private Shared _deu As New System.Globalization.CultureInfo("de-DE")
'— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
'these variables are used for moving and zooming
Private Shared _loadedImage As System.Drawing.Bitmap
Private Shared current_Zoom_factor As Single = 1.0F
Private mouse_location As PointF = Point.Empty
Private image_location As PointF = Point.Empty
Private image_rect As RectangleF = RectangleF.Empty
Private mouse_up_must_follow As Boolean
Private moving As Boolean
'— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
'these variables are used for drawing
Private Shared _manuallydrawnpathonscreen As New System.Drawing.Drawing2D.GraphicsPath
Private Shared _lastMouseLocationOnScreen As Point = Point.Empty
Public Shared Property Manuallydrawnpathonscreen As Drawing2D.GraphicsPath
Get
Return _manuallydrawnpathonscreen
End Get
Set(value As Drawing2D.GraphicsPath)
_manuallydrawnpathonscreen = value
End Set
End Property
Public Shared Property LoadedImage As Bitmap
Get
Return _loadedImage
End Get
Set(value As Bitmap)
_loadedImage = value
End Set
End Property
Public Shared Property LastMouseLocationOnScreen As Point
Get
Return _lastMouseLocationOnScreen
End Get
Set(value As Point)
_lastMouseLocationOnScreen = value
End Set
End Property
Public Shared Property Deu As System.Globalization.CultureInfo
Get
Return _deu
End Get
Set(value As System.Globalization.CultureInfo)
_deu = value
End Set
End Property
Public Shared Property LF As LoggingForm
Get
Return _lF
End Get
Set(value As LoggingForm)
_lF = value
End Set
End Property
'— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.BackColor = Color.FromArgb(0, 0, 31) ' very dark blue
For Each Bu As Button In Me.Controls.OfType(Of Button)
Bu.BackColor = Color.FromArgb(201, 201, 189)
Next
RadioButton_verschieben.Text = $"verschieben{NewLine}und{NewLine}zoomen"
RadioButton_verschieben.Checked = True
'––––––––––––––––––––––––––––––––––––––––––––––
' Find a second screen if possible.
Dim allScreens As Screen() = Screen.AllScreens
If allScreens.Length = 2 Then
Me.Location = New Point(allScreens(1).Bounds.X, allScreens(1).Bounds.Y)
End If
'––––––––––––––––––––––––––––––––––––––––––––––
End Sub
Private Sub FormMain_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
LF = New LoggingForm
LF.Show()
End Sub
Private Sub ButtonStart_Click(sender As Object, e As EventArgs) Handles ButtonStart.Click
Using OFD As New CommonOpenFileDialog
OFD.Title = "Bild zum Öffnen auswählen"
OFD.Filters.Add(New CommonFileDialogFilter("images", ".jpg;.jpeg;.bmp;.png"))
OFD.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
If OFD.ShowDialog() = CommonFileDialogResult.Ok Then
LoadedImage = Nothing
PictureBoxEx1.Image = Nothing
LoadedImage = New System.Drawing.Bitmap(OFD.FileName)
current_Zoom_factor = 1.0F
mouse_location = Point.Empty
image_location = Point.Empty
image_rect = RectangleF.Empty
PictureBoxEx1.Image = LoadedImage ' must be assigned once so that the PictureBox assumes the correct dimensions with ‘resize_PictureBox()’. This is also important later for the rectangle.
resize_PictureBox()
PictureBoxEx1.Image = Nothing
GC.Collect()
Else
Return
End If
End Using
End Sub
Private Sub resize_PictureBox()
If LoadedImage Is Nothing Then Return
'Width of the current picture
Dim Aktuelle_Breite As Integer = LoadedImage.Size.Width
'Height of the current picture
Dim Aktuelle_Hoehe As Integer = LoadedImage.Size.Height
' possible width on Form
Dim Moegliche_Breite As Integer = 1762
' possible height on Form
Dim Moegliche_Hoehe As Integer = 1000
If Aktuelle_Breite > Moegliche_Breite OrElse Aktuelle_Hoehe > Moegliche_Hoehe Then
PictureBoxEx1.SizeMode = PictureBoxSizeMode.StretchImage
PictureBoxEx1.Size = If(CInt(Math.Round(Aktuelle_Breite * Moegliche_Hoehe / Aktuelle_Hoehe, 0)) > Moegliche_Breite,
New Size(Moegliche_Breite, CInt(Math.Round(Aktuelle_Hoehe * Moegliche_Breite / Aktuelle_Breite, 0))),
New Size(CInt(Math.Round(Aktuelle_Breite * Moegliche_Hoehe / Aktuelle_Hoehe, 0)), Moegliche_Hoehe))
Else
PictureBoxEx1.SizeMode = PictureBoxSizeMode.Normal
PictureBoxEx1.Size = New Size(Aktuelle_Breite, Aktuelle_Hoehe)
End If
End Sub
Private Sub PictureBoxEx1_MouseDown(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseDown
Select Case e.Button
Case MouseButtons.Left
If RadioButton_verschieben.Checked Then
mouse_location = e.Location
image_location = image_rect.Location ' is (0 | 0) if the picture is freshly loaded
Me.Cursor = Cursors.NoMove2D
mouse_up_must_follow = True
moving = True
Return
ElseIf RadioButton_freihand.Checked Then
mouse_up_must_follow = True
Manuallydrawnpathonscreen.AddLine(LastMouseLocationOnScreen, e.Location)
PictureBoxEx1.Invalidate()
End If
Case MouseButtons.Right
If RadioButton_freihand.Checked Then
Manuallydrawnpathonscreen = New Drawing2D.GraphicsPath
PictureBoxEx1.Invalidate()
End If
Case Else
Exit Select
End Select
End Sub
Private Sub PictureBoxEx1_MouseMove(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseMove
If e.Button = MouseButtons.Left AndAlso mouse_up_must_follow Then
If RadioButton_verschieben.Checked Then
'for Zoom
image_rect.Location = New PointF(image_location.X (e.Location.X - mouse_location.X),
image_location.Y (e.Location.Y - mouse_location.Y))
mouse_up_must_follow = True
moving = True
PictureBoxEx1.Invalidate()
Return
' end Zoom region
ElseIf RadioButton_freihand.Checked Then
mouse_up_must_follow = True
moving = True
Manuallydrawnpathonscreen.AddLine(LastMouseLocationOnScreen, e.Location)
PictureBoxEx1.Invalidate()
End If
End If
LastMouseLocationOnScreen = e.Location
End Sub
Private Sub PictureBoxEx1_MouseUp(sender As Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseUp
If e.Button = MouseButtons.Left AndAlso mouse_up_must_follow Then
If RadioButton_verschieben.Checked Then
Me.Cursor = Cursors.Default
mouse_up_must_follow = False
moving = False
Return
ElseIf RadioButton_freihand.Checked Then
Manuallydrawnpathonscreen.CloseFigure()
mouse_up_must_follow = False
End If
End If
LastMouseLocationOnScreen = e.Location
End Sub
Private Sub PictBox1_MouseWheel(sender As System.Object, e As MouseEventArgs) Handles PictureBoxEx1.MouseWheel
If RadioButton_verschieben.Checked Then
If e.Delta > 0 Then
current_Zoom_factor = (1.0F / 30.0F)
PictureBoxEx1.Invalidate()
Else
If current_Zoom_factor > 0.0F Then
current_Zoom_factor -= (1.0F / 30.0F)
PictureBoxEx1.Invalidate()
End If
End If
End If
End Sub
Private Sub PictureBoxEx1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBoxEx1.Paint
If LoadedImage Is Nothing Then
Return
End If
If RadioButton_verschieben.Checked Then
Dim the_rectangle_to_be_zoomed_in_and_drawn As RectangleF = calculate_zoomed_and_or_moved_rect()
e.Graphics.DrawImage(LoadedImage, the_rectangle_to_be_zoomed_in_and_drawn)
LF.updateListBox1(the_rectangle_to_be_zoomed_in_and_drawn.X.ToString(Deu),
the_rectangle_to_be_zoomed_in_and_drawn.Y.ToString(Deu),
the_rectangle_to_be_zoomed_in_and_drawn.Width.ToString(Deu),
the_rectangle_to_be_zoomed_in_and_drawn.Height.ToString(Deu),
current_Zoom_factor.ToString(Deu))
ElseIf RadioButton_freihand.Checked Then
Dim the_rectangle_to_be_zoomed_in_and_drawn As RectangleF = calculate_zoomed_and_or_moved_rect()
FunctionsToDraw.DrawTheUsersPath(e.Graphics, the_rectangle_to_be_zoomed_in_and_drawn)
End If
End Sub
Private Function calculate_zoomed_and_or_moved_rect() As RectangleF
image_rect.Width = PictureBoxEx1.Width
image_rect.Height = PictureBoxEx1.Height
Dim newLocation As PointF
If moving Then
newLocation = image_rect.Location
Else
newLocation = New PointF(
PictureBoxEx1.Location.X (PictureBoxEx1.Size.Width / 2.0F) - image_rect.Width * current_Zoom_factor / 2.0F,
PictureBoxEx1.Location.Y (PictureBoxEx1.Size.Height / 2.0F) - image_rect.Height * current_Zoom_factor / 2.0F)
End If
Dim scaled As New RectangleF(newLocation,
New SizeF(image_rect.Width * current_Zoom_factor, image_rect.Height * current_Zoom_factor))
Return scaled
End Function
End Class
#Enable Warning CA1707
FunctionsToDraw.vb
Public NotInheritable Class FunctionsToDraw
Public Shared Sub DrawTheUsersPath(g As Graphics, ByVal r As RectangleF)
If g Is Nothing OrElse r.Width = 0.0F Then
Return
End If
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear
Using Pen_green As New Pen(Color.FromArgb(0, 210, 0), 2.0F)
g.DrawImage(FormMain.LoadedImage, r)
g.DrawPath(Pen_green, FormMain.Manuallydrawnpathonscreen)
FormMain.LF.updateListBox2(FormMain.LastMouseLocationOnScreen.X.ToString(FormMain.Deu),
FormMain.LastMouseLocationOnScreen.Y.ToString(FormMain.Deu))
End Using
End Sub
End Class
PictureBoxEx.vb
Public NotInheritable Class PictureBoxEx : Inherits PictureBox
Public Sub New()
SetStyle(ControlStyles.Selectable Or ControlStyles.UserMouse, True)
End Sub
End Class