I want to create a custom picturebox in which picture is zoomed in a dedicated form on mouse hover.
Everything works fine except the fact that the user must clic out the zoomed image to close it altough i want to close it on mouse leave?
Here’s my code:
Public Class CtrlNoticePictureBox
Inherits PictureBox
Private frmPicExtended As New Form
Public Sub New()
AddHandler Me.MouseEnter, New System.EventHandler(AddressOf Me.PictureBox_MouseEnter)
AddHandler Me.MouseLeave, New System.EventHandler(AddressOf Me.PictureBox_MouseLeave)
End Sub
Private Sub PictureBox_MouseEnter(ByVal sender As Object, ByVal e As System.EventArgs)
Dim screenCurrent As Screen = Screen.FromControl(Me)
Dim workingArea As Rectangle = screenCurrent.WorkingArea
Dim locationForm As Point = Me.TopLevelControl.Location
frmPicExtended = New Form With {
.ShowInTaskbar = False,
.MaximizeBox = False,
.ControlBox = False,
.Location = New Point(Me.Width + locationForm.X, Me.Height + locationForm.Y),
.StartPosition = FormStartPosition.Manual,
.FormBorderStyle = FormBorderStyle.None
}
Dim picBoxExtended = New PictureBox With {
.Name = "pictureBox",
.Image = Me.Image,
.SizeMode = PictureBoxSizeMode.Zoom,
.Dock = DockStyle.Fill
}
frmPicExtended.Controls.Add(picBoxExtended)
frmPicExtended.BringToFront()
frmPicExtended.Show()
End Sub
Private Sub PictureBox_MouseLeave(ByVal sender As Object, ByVal e As System.EventArgs)
Dim rectPicArea As New Rectangle With {.Location = Me.PointToScreen(Point.Empty), .Size = Me.Size}
If IsNothing(frmPicExtended) = False Then
If Not rectPicArea.Contains(Cursor.Position) Then
frmPicExtended.Dispose()
End If
End If
End Sub
Function PointToCurrentScreen(ByVal self As Control, ByVal location As Point) As Point
Dim screenBounds = Screen.FromControl(self).Bounds
Dim globalCoordinates = self.PointToScreen(location)
Return New Point(globalCoordinates.X - screenBounds.X, globalCoordinates.Y - screenBounds.Y)
End Function
End Class
Here is the result:
5
If you look closely at the gif image, when the mouse moves to the greenish container, it actually leaves the internal picBoxExtended
, not the CtrlNoticePictureBox
instance. The covered area of the main PictureBox – by the modeless Form – does not receive the mouse inputs unless that Form and its children are transparent to the mouse inputs. As a result, the MouseLeave
event does not fire and the zoom Form remains. You also need to implement the MouseLeave
event of the internal PictureBox
to hide the Form.
Here’s a working example with some additions and modifications.
<DesignerCategory("Code")>
Public Class CtrlNoticePictureBox
Inherits PictureBox
Public Enum StartPosition
Relative
Center
Cursor
End Enum
Private FrmZoom As Form
Private PicZoom As PictureBox
<DefaultValue(StartPosition.Center)>
Public Property ZoomFormStartPosition As StartPosition = StartPosition.Center
<DefaultValue(True)>
Public Property KeepZoomFormWithinScreen As Boolean = True
Protected Overrides Sub OnMouseEnter(e As EventArgs)
MyBase.OnMouseEnter(e)
If Image Is Nothing Then Return
If FrmZoom Is Nothing Then CreateZoomForm()
If FrmZoom.Visible Then Return
Dim loc As Point
Select Case ZoomFormStartPosition
Case StartPosition.Relative
loc = PointToScreen(Point.Empty)
Case StartPosition.Center
loc = PointToScreen(Point.Empty)
loc.X += (Width - FrmZoom.Width) 2
loc.Y += (Height - FrmZoom.Height) 2
Case Else
loc = Cursor.Position
End Select
If KeepZoomFormWithinScreen Then
Dim sc = Screen.FromControl(Me).Bounds ' or .WorkingArea
If loc.X + FrmZoom.Width > sc.Width Then
loc.X = sc.Right - FrmZoom.Width
End If
If loc.Y + FrmZoom.Height > sc.Height Then
loc.Y = sc.Bottom - FrmZoom.Height
End If
loc.X = Math.Max(0, loc.X)
loc.Y = Math.Max(0, loc.Y)
End If
FrmZoom.Location = loc
PicZoom.Image = Image
FrmZoom.Show()
End Sub
Protected Overrides Sub OnMouseLeave(e As EventArgs)
MyBase.OnMouseLeave(e)
If Not ClientRectangle.Contains(PointToClient(Cursor.Position)) Then
FrmZoom.Hide()
End If
End Sub
Protected Overrides Sub Dispose(disposing As Boolean)
If disposing Then
FrmZoom?.Dispose()
End If
MyBase.Dispose(disposing)
End Sub
Private Sub CreateZoomForm()
PicZoom = New PictureBox With {
.SizeMode = PictureBoxSizeMode.Zoom,
.Dock = DockStyle.Fill,
}
FrmZoom = New Form With {
.FormBorderStyle = FormBorderStyle.None,
.ControlBox = False,
.ShowInTaskbar = False,
.TopMost = True,
.StartPosition = FormStartPosition.Manual,
.Size = New Size(Width * 2, Height * 2)
}
FrmZoom.Controls.Add(PicZoom)
AddHandler FrmZoom.Disposed, Sub(s, e) PicZoom.Dispose()
AddHandler PicZoom.MouseLeave, Sub(s, e) OnMouseLeave(EventArgs.Empty)
End Sub
End Class
Notes
- In a derived class, override the methods of the base class and do not subscribe to its events. Other controls can subscribe to events if needed.
- No need to keep creating and destroying the zoom Form. Create it on first access and override the owner’s
Dispose
method to dispose of it.