“ShowDialog” method is not working properly (when

2019-08-31 03:47发布

I'm trying to write a Form that should draw a rectangle over the screen to select an screen region, the question and the solution(s) can be seen in other StackOverflow post, here. I've adapted the marked solution of @ZeroWorks to my needs, but the problem now is that I can't find the way to use the Form like I require.

This is how I would like to use it:

    Dim RegionRect As Rectangle = Rectangle.Empty

    Using Selector As New RegionSelector
        Selector.ShowDialog()
        RegionRect = Selector.SelectedRegion
    End Using

I just would like to use the ShowDialog method to show the Form and stop the execution until the region is selected by the user 'cause on the OnMouseUp event I close the form, but If I try to use the ShowDialog method I'm not able to draw/see the rectangle, by the way if I use the Show method it works properly but like I've said I need to use instead the ShowDialog method to wait a "response" from the Form to proceed with the next instructions, I don't understand why happens this problem, what I'm missing?, how I could fix this?.

This is the (full) code of the custom region selector:

''' <summary>
''' Selects a region on the Screen.
''' </summary>
Public Class RegionSelector : Inherits Form

#Region " Properties "

    ''' <summary>
    ''' Gets or sets the border size of the region selector.
    ''' </summary>
    ''' <value>The size of the border.</value>
    Public Property BorderSize As Integer = 2

    ''' <summary>
    ''' Gets or sets the border color of the region selector.
    ''' </summary>
    ''' <value>The color of the border.</value>
    Public Property BorderColor As Color = Color.Red

    ''' <summary>
    ''' Gets the rectangle that contains the selected region.
    ''' </summary>
    Public ReadOnly Property SelectedRegion As Rectangle
        Get
            Return Me.DrawRect
        End Get
    End Property

#End Region

#Region " Objects "

    ''' <summary>
    ''' Indicates the initial location when the mouse left button is clicked.
    ''' </summary>
    Private InitialLocation As Point = Point.Empty

    ''' <summary>
    ''' The rectangle where to draw the region.
    ''' </summary>
    Public DrawRect As Rectangle = Rectangle.Empty

    ''' <summary>
    ''' The Graphics object to draw on the screen.
    ''' </summary>
    Private ScreenGraphic As Graphics = Graphics.FromHwnd(IntPtr.Zero)

    Public IsDrawing As Boolean = False

    Dim DrawSize As Size

    Dim DrawForm As Form

#End Region

#Region " Constructors "

    ''' <summary>
    ''' Initializes a new instance of the <see cref="RegionSelector"/> class.
    ''' </summary>
    Public Sub New()
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the <see cref="RegionSelector" /> class.
    ''' </summary>
    ''' <param name="BorderSize">Indicates the border size of the region selector.</param>
    ''' <param name="BorderColor">Indicates the border color of the region selector.</param>
    Public Sub New(ByVal BorderSize As Integer,
                   ByVal BorderColor As Color)

        Me.BorderSize = BorderSize
        Me.BorderColor = BorderColor

    End Sub

#End Region

#Region " Event Handlers "

    ''' <summary>
    ''' Handles the Load event of the RegionSelector.
    ''' </summary>
    ''' <param name="sender">The source of the event.</param>
    ''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
    Private Sub RegionSelector_Load(sender As Object, e As EventArgs) Handles Me.Load

        Me.SuspendLayout()

        Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None
        Me.BackColor = System.Drawing.Color.Black
        Me.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None
        Me.CausesValidation = False
        Me.ClientSize = New System.Drawing.Size(0, 0)
        Me.ControlBox = False
        Me.Cursor = System.Windows.Forms.Cursors.Cross
        Me.DoubleBuffered = True
        Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
        Me.MaximizeBox = False
        Me.MinimizeBox = False
        Me.Name = "RegionSelector"
        Me.Opacity = 0.15R
        Me.ShowIcon = False
        Me.ShowInTaskbar = False
        Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide
        Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
        Me.TopMost = False
        Me.WindowState = System.Windows.Forms.FormWindowState.Maximized

        Me.ResumeLayout(True)

        Me.DrawForm = New DrawingRegionClass(Me)
        With DrawForm
            .BackColor = Color.Tomato
            .TopLevel = True
            .TransparencyKey = Color.Tomato
            .TopMost = False
            .FormBorderStyle = Windows.Forms.FormBorderStyle.None
            .ControlBox = False
            .WindowState = FormWindowState.Maximized
        End With

        Me.AddOwnedForm(Me.DrawForm)
        Me.DrawForm.Show()

    End Sub

    ''' <summary>
    ''' Raises the <see cref="E:System.Windows.Forms.Control.MouseDown" /> event.
    ''' </summary>
    ''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
    Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)

        If e.Button = MouseButtons.Left Then

            Me.InitialLocation = e.Location
            Me.IsDrawing = True

        End If

    End Sub

    ''' <summary>
    ''' Raises the <see cref="E:System.Windows.Forms.Control.MouseUp" /> event.
    ''' </summary>
    ''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
    Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)

        Me.IsDrawing = False
        Me.Close()

    End Sub

    ''' <summary>
    ''' Raises the <see cref="E:System.Windows.Forms.Control.MouseMove" /> event.
    ''' </summary>
    ''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
    Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)

        If Me.IsDrawing Then

            Me.DrawSize = New Size(e.X - Me.InitialLocation.X, e.Y - Me.InitialLocation.Y)
            Me.DrawRect = New Rectangle(Me.InitialLocation, Me.DrawSize)

            If Me.DrawRect.Height < 0 Then
                Me.DrawRect.Height = Math.Abs(Me.DrawRect.Height)
                Me.DrawRect.Y -= Me.DrawRect.Height
            End If

            If Me.DrawRect.Width < 0 Then
                Me.DrawRect.Width = Math.Abs(Me.DrawRect.Width)
                Me.DrawRect.X -= Me.DrawRect.Width
            End If

            Me.DrawForm.Invalidate()

        End If

    End Sub

#End Region

End Class

Public Class DrawingRegionClass : Inherits Form

    Private DrawParent As RegionSelector

    Public Sub New(ByVal Parent As Form)

        Me.DrawParent = Parent

    End Sub

    Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)

        Dim Bg As Bitmap
        Dim Canvas As Graphics

        If Me.DrawParent.IsDrawing Then

            Bg = New Bitmap(Width, Height)
            Canvas = Graphics.FromImage(Bg)
            Canvas.Clear(Color.Tomato)
            Canvas.DrawRectangle(Pens.Red, Me.DrawParent.DrawRect)
            Canvas.Dispose()
            e.Graphics.DrawImage(Bg, 0, 0, Width, Height)
            Bg.Dispose()

        Else
            MyBase.OnPaintBackground(e)
        End If

    End Sub

End Class

1条回答
家丑人穷心不美
2楼-- · 2019-08-31 04:05

I wouldn't do it with formdialog method, you will have a lot of troubles to get it work, form dialog blocks execution and waits until its closed. But there's is a solution instead... I would use a Callback. So, first define a Delegate:

Public Delegate Sub RegionSelectedDelegate(Region As Rectangle)

Then in the calling form, in a sub or function, since using will destroy form change it:

Dim RegionRect As Rectangle = Rectangle.Empty
Dim Working As Boolean = False

Public Sub GetRectangle()
    Dim Callback As RegionSelectedDelegate
    Dim Selector As New RegionSelector

    If Working Then Exit Sub 'Only one selection at once!
    Working = True
    Callback = New RegionSelectedDelegate(AddressOf RectangleDrawn)

    With Selector
        .Callback = Callback
        .Show()
    End With
    ' Don't do any stuff here... do it in Rectangle Drawn...
End Sub

...

Public Sub RectangleDrawn(Region as Rectangle)   
    Working = false 'Allow draw again.
    Me.RegionRect=Region
    MessageBox.Show("Do next steps!")
    ' Some stuff Here
End Sub

That delegate will be called from drawing form on onmouseup (when the rectangle is drawn) and will be received by RegionSelector Class. So, will define a new property Callback in RegionSelector Class, and invoke this delegate in onmouseup event handler:

''' <summary>
''' Selects a region on the Screen.
''' </summary>
Public Class RegionSelector : Inherits Form

#Region " Properties "

''' <summary>
''' Callback to be invoked when drawing is done...
''' </summary>
''' <value>Delegate of Region Selected</value>
Public Property Callback As RegionSelectedDelegate = Nothing


''' <summary>
''' Gets or sets the border size of the region selector.
''' </summary>
''' <value>The size of the border.</value>
Public Property BorderSize As Integer = 2

''' <summary>
''' Gets or sets the border color of the region selector.
''' </summary>
''' <value>The color of the border.</value>
Public Property BorderColor As Color = Color.Red

''' <summary>
''' Gets the rectangle that contains the selected region.
''' </summary>
Public ReadOnly Property SelectedRegion As Rectangle
    Get
        Return Me.DrawRect
    End Get
End Property

#End Region

#Region " Objects "

''' <summary>
''' Indicates the initial location when the mouse left button is clicked.
''' </summary>
Private InitialLocation As Point = Point.Empty

''' <summary>
''' The rectangle where to draw the region.
''' </summary>
Public DrawRect As Rectangle = Rectangle.Empty

''' <summary>
''' The Graphics object to draw on the screen.
''' </summary>
Private ScreenGraphic As Graphics = Graphics.FromHwnd(IntPtr.Zero)

Public IsDrawing As Boolean = False

Dim DrawSize As Size

Dim DrawForm As Form

#End Region

#Region " Constructors "

''' <summary>
''' Initializes a new instance of the <see cref="RegionSelector"/> class.
''' </summary>
Public Sub New()
End Sub

''' <summary>
''' Initializes a new instance of the <see cref="RegionSelector" /> class.
''' </summary>
''' <param name="BorderSize">Indicates the border size of the region selector.</param>
''' <param name="BorderColor">Indicates the border color of the region selector.</param>
Public Sub New(ByVal BorderSize As Integer,
               ByVal BorderColor As Color)

    Me.BorderSize = BorderSize
    Me.BorderColor = BorderColor

End Sub

#End Region

#Region " Event Handlers "

''' <summary>
''' Handles the Load event of the RegionSelector.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
Private Sub RegionSelector_Load(sender As Object, e As EventArgs) Handles Me.Load

    Me.SuspendLayout()

    Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None
    Me.BackColor = System.Drawing.Color.Black
    Me.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None
    Me.CausesValidation = False
    Me.ClientSize = New System.Drawing.Size(0, 0)
    Me.ControlBox = False
    Me.Cursor = System.Windows.Forms.Cursors.Cross
    Me.DoubleBuffered = True
    Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None
    Me.MaximizeBox = False
    Me.MinimizeBox = False
    Me.Name = "RegionSelector"
    Me.Opacity = 0.15R
    Me.ShowIcon = False
    Me.ShowInTaskbar = False
    Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide
    Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
    Me.TopMost = False
    Me.WindowState = System.Windows.Forms.FormWindowState.Maximized

    Me.ResumeLayout(True)

    Me.DrawForm = New DrawingRegionClass(Me)
    With DrawForm
        .BackColor = Color.Tomato
        .TopLevel = True
        .TransparencyKey = Color.Tomato
        .TopMost = False
        .FormBorderStyle = Windows.Forms.FormBorderStyle.None
        .ControlBox = False
        .WindowState = FormWindowState.Maximized
    End With

    Me.AddOwnedForm(Me.DrawForm)
    Me.DrawForm.Show()

End Sub

''' <summary>
''' Raises the <see cref="E:System.Windows.Forms.Control.MouseDown" /> event.
''' </summary>
''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)

    If e.Button = MouseButtons.Left Then

        Me.InitialLocation = e.Location
        Me.IsDrawing = True

    End If

End Sub

''' <summary>
''' Raises the <see cref="E:System.Windows.Forms.Control.MouseUp" /> event.
''' </summary>
''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
Protected Overrides Sub OnMouseUp(ByVal e As MouseEventArgs)
    'Do the callback here!
    Me.IsDrawing = False
    Callback.Invoke(SelectedRegion)
    Me.Close() 'Must be called last.
End Sub

''' <summary>
''' Raises the <see cref="E:System.Windows.Forms.Control.MouseMove" /> event.
''' </summary>
''' <param name="e">A <see cref="T:System.Windows.Forms.MouseEventArgs" /> that contains the event data.</param>
Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)

    If Me.IsDrawing Then

        Me.DrawSize = New Size(e.X - Me.InitialLocation.X, e.Y - Me.InitialLocation.Y)
        Me.DrawRect = New Rectangle(Me.InitialLocation, Me.DrawSize)

        If Me.DrawRect.Height < 0 Then
            Me.DrawRect.Height = Math.Abs(Me.DrawRect.Height)
            Me.DrawRect.Y -= Me.DrawRect.Height
        End If

        If Me.DrawRect.Width < 0 Then
            Me.DrawRect.Width = Math.Abs(Me.DrawRect.Width)
            Me.DrawRect.X -= Me.DrawRect.Width
        End If

        Me.DrawForm.Invalidate()

    End If

End Sub

#End Region

End Class

Public Class DrawingRegionClass : Inherits Form

Private DrawParent As RegionSelector

Public Sub New(ByVal Parent As Form)

    Me.DrawParent = Parent

End Sub

Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)

    Dim Bg As Bitmap
    Dim Canvas As Graphics

    If Me.DrawParent.IsDrawing Then

        Bg = New Bitmap(Width, Height)
        Canvas = Graphics.FromImage(Bg)
        Canvas.Clear(Color.Tomato)
        Canvas.DrawRectangle(Pens.Red, Me.DrawParent.DrawRect)
        Canvas.Dispose()
        e.Graphics.DrawImage(Bg, 0, 0, Width, Height)
        Bg.Dispose()

    Else
        MyBase.OnPaintBackground(e)
    End If

End Sub

End Class

And that's all, no blocking modal forms are shown, the flow stills its logic and it works. Hope it helps.

查看更多
登录 后发表回答