smithvoice.com
 Y'herd thisun? 

“The main requirement is for commerce to replace politics as the driving force behind space policy”

from Spaceflight Revolution by David Ashford

Your own image control and App part 15

TaggedCoding, VB, Imaging

Originally published December 2002 on DoItIn.net using VB7.0/2002. Updated for VB7.1 February 2005

Links for compiled demo versions, all required resources and source code are included at the end of this article.

Plus, get the complete eBook in Adobe Acrobat 7 format ... all here.


 

16) Bonus Feature: StickyMouse

As you've played with the control's testHarness you've probably come up with some usability tweaks to make your control more real-world useful. I've done the same and we'll get to some of the stragglers but right off the bat, coming off of the cropping, the most top of mind consideration is that users might not want to crop every single image so we really should put in a switch allowing the dev-user to turn that on and off.

I say we go one better and make it an numeric flag rather than a boolean or more specifically an Enumerated switch setting the mouse mode to "Normal", "Crop" and "AutoScroll", with that last one letting the user hold down the mouse at any visible point on the picturebox to drag the image smoothly around without having to scroll the view using the horizontal and vertical bars - as if they had a "sticky mouse".

You're not going to believe how easy this is, it just builds off of the mouse event code that we've already written.

First, create that enumeration and for maintenance sake, add it to the svTypes.vb file, above the definition of the svImagePage structure. Declare it as Public so that the dev-user can bind to it directly:

Public Enum svMouseMode
Normal = 0
Crop = 1
Drag = 2
End Enum

Now in the UC, add a private variable to hold the current mode. Because this feature is closely related to the Cropping, I've changed the name of the "Cropping Variables" sub-region to "Mouse Variables" and added the support elements as follows:

 

#Region "Mouse Variables"
Private m_CurrentMouseMode As svMouseMode = svMouseMode.Normal
  
'cropping support
Private m_NowCropping As Boolean = False
Private m_CropStartX As Integer = 0
Private m_CropStartY As Integer = 0
Private m_CropEndX As Integer = 0
Private m_CropEndY As Integer = 0
  
'ants:
Private m_CropAntsPosition1 As Point
Private m_CropAntsPosition2 As Point
  
'stickymouse support
Private m_NowMoving As Boolean = False
Private m_ScrollStartMousePosX As Integer = 0
Private m_ScrollStartMousePosY As Integer = 0
Private m_ScrollStartControlPosX As Integer = 0
Private m_ScrollStartControlPosY As Integer = 0
  
#End Region

We could rename and reuse some of the Crop variables but to keep things clear we've made a whole new set.

 

#Region "Mouse Handling"
  
Private Sub pbDisplay_MouseDown(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles pbDisplay.MouseDown
If Not pbDisplay.Image Is Nothing Then
If m_CurrentMouseMode = svMouseMode.Crop Then
m_NowCropping = True
  
'trap the mouse in the picturebox
'so if its displayed small, the user can't crop
'outside of the true image (which will save a big black block of worthless data
Cursor.Clip = pbDisplay.RectangleToScreen(pbDisplay.ClientRectangle)
m_CropStartX = e.X
m_CropStartY = e.Y
  
'ants:
' Save the current mouse position.
m_CropAntsPosition1 = Control.MousePosition
m_CropAntsPosition2 = m_CropAntsPosition1
'end ants
  
ElseIf m_CurrentMouseMode = svMouseMode.AutoScroll Then
m_NowMoving = True
m_ScrollStartControlPosX = Me.AutoScrollPosition.X
m_ScrollStartControlPosY = Me.AutoScrollPosition.Y
m_ScrollStartMousePosX = Control.MousePosition.X
m_ScrollStartMousePosY = Control.MousePosition.Y
End If
  
End If
  
End Sub
  
Private Sub pbDisplay_MouseMove(ByVal sender As Object, _
 ByVal e As System.Windows.Forms.MouseEventArgs) _
 Handles pbDisplay.MouseMove
  
 If Not pbDisplay.Image Is Nothing Then
If m_CurrentMouseMode = svMouseMode.Crop Then
'ants:
' Do nothing if we're not cropping.
If Not m_NowCropping Then Exit Sub
' Erase the previous line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
), _
Me.BackColor, _
FrameStyle.Dashed)
  
' Save the new point.
m_CropAntsPosition2 = Control.MousePosition
' Draw the new line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
), _
Me.BackColor, _
FrameStyle.Dashed)
'end ants
ElseIf m_CurrentMouseMode = svMouseMode.AutoScroll Then
' Do nothing if we're not drawing.
If m_NowMoving Then
Me.AutoScrollPosition = New Point( _
-(m_ScrollStartControlPosX) + (m_ScrollStartMousePosX - Control.MousePosition.X), _
-(m_ScrollStartControlPosY) + (m_ScrollStartMousePosY - Control.MousePosition.Y))
End If
  
End If
  
End If
  
 End Sub
  
 Private Sub pbDisplay_MouseUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.MouseEventArgs) _
Handles pbDisplay.MouseUp
  
'source coordinate/size values need high enough
'precision to avoid rounding issues
'during zoom calculations
Dim currentRatio As Single
Dim srcX As Single
Dim srcY As Single
Dim srcWidth As Single
Dim srcHeight As Single
If Not pbDisplay.Image Is Nothing Then
If m_CurrentMouseMode = svMouseMode.Crop Then
Try
If m_NowCropping Then
'clear the clip so mouse can exit the control
Cursor.Clip = Nothing
'a quick click with no position change
'shouldn't be calculated
If m_CropStartX = e.X Or m_CropStartY = e.Y Then
m_NowCropping = False
Exit Sub
End If
  
'account for dragging from any corner
If m_CropStartX > e.X Then
m_CropEndX = m_CropStartX
m_CropStartX = e.X
Else
m_CropEndX = e.X
End If
If m_CropStartY > e.Y Then
m_CropEndY = m_CropStartY
m_CropStartY = e.Y
Else
m_CropEndY = e.Y
End If
  
'account for clipping to picturebox affecting the
'right or bottom end points
'the e.X value in this case will round to 3 pixels
'less than the actual edge
'yep, it's a hack but it's the lesser evil
If m_CropEndX >= pbDisplay.Width - 3 Then
m_CropEndX = pbDisplay.Width
End If
If m_CropEndY >= pbDisplay.Height - 3 Then
m_CropEndY = pbDisplay.Height
End If
  
'currentRatio = pbDisplay.Width / objImage.Width
'same as:
currentRatio = CSng(m_ImagePages(m_CurrentPageNumber).CurrentZoom / 100)
'watch the type of division
'"\" gives a rounded int, "/" gives the precision you need
If currentRatio <> 0 Then
srcX = m_CropStartX / currentRatio
srcY = m_CropStartY / currentRatio
srcWidth = (m_CropEndX - m_CropStartX) / currentRatio
srcHeight = (m_CropEndY - m_CropStartY) / currentRatio
Else
srcX = m_CropStartX
srcY = m_CropStartY
srcWidth = m_CropEndX - m_CropStartX
srcHeight = m_CropEndY - m_CropStartY
End If
'ants:
' Erase the previous line.
ControlPaint.DrawReversibleFrame( _
New Rectangle( _
m_CropAntsPosition1.X, _
m_CropAntsPosition1.Y, _
-(m_CropAntsPosition1.X - m_CropAntsPosition2.X), _
-(m_CropAntsPosition1.Y - m_CropAntsPosition2.Y) _
 ), _
 Me.BackColor, _
 FrameStyle.Dashed _
)
'end ants
  
Dim croppedImage As Image = CropImage(pbDisplay.Image, _
 srcX, srcY, srcWidth, srcHeight)
RaiseEvent ImageCropped(Me, New svImageCroppedEventArgs(croppedImage))
End If
  
Catch ex As Exception
Throw ex
'todo: remove after testing
  
Finally
m_NowCropping = False
  
End Try
  
ElseIf m_CurrentMouseMode = svMouseMode.AutoScroll Then
If m_NowMoving Then
m_NowMoving = False
  
End If
End If
  
End If
  
End Sub
  
#End Region

 

As you see this feature requires very little code compared its sister, in fact the MouseUp which had so much complexity for Cropping only has a flip of the m_NowMoving switch (which technically could be reduced to just forcing the variable to False without even checking its original value, but being explicit helps debugging).

The real meat is in the MouseMove event. Here we take the inverse of the Scrollbar X/Y coordinates saved in MouseDown and add to the difference between the original mouse position and the current mouse position (the positions are relative to the UC, not the screen). Happily, if the result is less than 0/0 or greater than ControlWidth-ScrollbarGrabberWidth/ControlHeight-ScrollbarGrabberHeight no exception is thrown and the scrollbars just stop moving.

To make it work we'll need to have a property to switch mousemodes and to help the dev-user make a responsive GUI any change should raise an event. In svEventArguments.vb, add the new eventargs class definition:

 

Public Class svMouseModeChangedEventArgs
Inherits EventArgs
  
Private m_NewMouseMode As svMouseMode
  
Public Sub New(ByVal newMode As svMouseMode)
MyBase.New()
m_NewMouseMode = newMode
  
End Sub
  
Public ReadOnly Property NewMouseMode() As svMouseMode
Get
 Return m_NewMouseMode
End Get
  
End Property
  
End Class

And in the UC, declare the event...

#Region "Events"
Public Event ImageLoaded(ByVal sender As Object, ByVal e As svImageLoadEventArgs)
 
Public Event PageChanged(ByVal sender As Object, ByVal e As svImagePageChangeEventArgs)
 
Public Event ZoomChanged(ByVal sender As Object, ByVal e As svImageZoomChangeEventArgs)
  
Public Event ImageCropped(ByVal sender As Object, ByVal e As svImageCroppedEventArgs)
  
Public Event MouseModeChanged(ByVal sender As Object, ByVal e As svMouseModeChangedEventArgs)
 
#End Region

... and add the switch to the public properties:

Public Property MouseMode() As svMouseMode
Get
Return m_CurrentMouseMode
 
End Get
 
Set(ByVal Value As svMouseMode)
If Value.IsDefined(GetType(svMouseMode), Value) Then
m_CurrentMouseMode = Value
'raise an event to help the dev set the gui
RaiseEvent MouseModeChanged(Me, New svMouseModeChangedEventArgs(m_CurrentMouseMode))
Else
Throw New ArgumentOutOfRangeException("MouseMode", _
"Invalid MouseMode.  Acceptable modes are 0=Normal, 1=Crop, 2=AutoScroll")
End If
End Set
 
End Property

You see we've put some added protection into the MouseMode setter. Enums are helpful groupings of related values and with them a good IDE will pop up readable intellisense, but under the hood the compiler is using the numeric value. Even though we defined the property signature with type "svMouseMode", the enum values themselves are of type "Integer" and a careless dev-user won't hit a type exception if they pass in a whole number that fits in an Int32. Without Option Strict being set in their project, m_CurrentMouseMode will accept an invalid value and the control won't function the way we want it to. This hole is fixed by checking the passed value with "IsDefined" before assigning the variable to it.

We'd better take care of the RotateDisplay method too because it exposes the rotation argument as a RotateFlipType:

 

Public Sub RotateDisplay(ByVal rotation As RotateFlipType, _
Optional ByVal rotateAll As Boolean = False)
 
Dim tmpFrameImage As Image
 
If rotation.IsDefined(GetType(RotateFlipType), rotation) Then
If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
...
 
Else
Throw New ArgumentOutOfRangeException("Rotation", "Invalid RotateFlipType.")
 
End If
 
End Sub

 Back in August 2004 in the microsoft.public.dontnet.framework.clr newsgroup, there was a great discussion about low level VB.Net and C# enum values in the thread named"enumerations as parameters, VB, and the value 0"

Rebuild and Save then add a grouped set of radio buttons ("rbMModeNormal" Checked=True, "rbMModeCrop" and "rbMModeSticky") to the testHarness form.

Double click on any of the radiobuttons and adjust the CheckChanged event to handle all of the controls:

 

Private Sub MouseModeRadios_CheckedChanged(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles rbMModeNormal.CheckedChanged, _
rbMModeCrop.CheckedChanged, _
rbMModeSticky.CheckedChanged
 
If sender Is rbMModeCrop Then
SvImageEditor1.MouseMode = svMouseMode.Crop
ElseIf sender Is rbMModeSticky Then
SvImageEditor1.MouseMode = svMouseMode.AutoScroll
Else
SvImageEditor1.MouseMode = svMouseMode.Normal
End If
 
End Sub

 

F5 to give a whirl.

"BAM!" :)

Next: Finishing Touches

Robert Smith
Kirkland, WA

added to smithvoice march 2005


jump to:

  • 1) The spec
  • 2) Setting up the workspace
  • 3) Feature 1: Loading an image
  • 4) Custom Exceptions
  • 5) "Fax images" and Multipage TIFFs
  • 6) Custom events
  • 7) Selecting specific fax pages
  • 8) Feature 2: Rotating image displays
  • 9) The most useful tool in GDI+: DrawImage
  • 10) Feature 3: Zooming
  • 11) Handling the unhandleable exception
  • 12) Fixing the squish
  • 13) Zooming to fit the control
  • 14) You're already beating the Pros
  • 15) Feature 4: Cropping
  • 16) Bonus Feature: StickyMouse
  • 17a) Final Cleanup
  • 17b) Passing the current display image
  • 18) Making the application
  • 19) Source and result viewports
  • 20) A better toolbar
  • 21) Hooking the toolbar to the project
  • 22) Adding ImageEditors
  • 23) The toolbar ZoomCombo
  • 24) The final solution
  • 25) Saving to image files
  • 26) An integer-only textbox
  • 27) Passing save options between forms
  • 28) Dealing with that last exception
  • 29) Offer more options with menus
  • 30 The downloads and ebook


  • home     who is smith    contact smith     rss feed π
    Since 1997 a place for my stuff, and it if helps you too then all the better smithvoice.com