Home > front end >  PictureBox shouldn't fire events on its own
PictureBox shouldn't fire events on its own

Time:10-08

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
  • Related