Monday, January 05, 2009
Home
.Net
Return of the Repeater
Complete image control & app
Part 2 - Starting the control
Part 3 - Loading images
Part 4 - Custom exceptions
Part 5 - Fax/Multipage tifs
Part 6 - Custom events
Part 7 - Selecting fax pages
Part 8 - Rotating displays
Part 9 - The power of GDI+
Part 10 - Zooming
Part 11 - .Net's fatal exception
Part 12 - Fix the squishies
Part 13 - Zoom to fit
Part 14 - You beat the pros
Part 15 - Cropping
Part 16 - Bonus: StickyMouse
Part 17 - Finishing touches?
Part 18 - Make the application
Part 19 - Adding viewports
Part 20 - A better toolbar
Part 21 - Connect the toolbar
Part 22 - Adding ImageEditors
Part 23 - Toolbar ZoomCombo
Part 24 - VB3 style ease
Part 25 - Saving images to files
Part 26 - Integer-only textBox
Part 27 - Passing save settings
Part 28 - The last exception
Part 29 - Menus offer more
Part 30 - Book, app & source
Couple of CS Snippets
XML processing quickies
File extension extensions
McAfee.Not
cs IntBox
Floating Holidays
Code snippets
Flexible sorts
Converted deserializations
Autodeploy not found
Autodeploy just stops
VB must be killed
Media file attributes
Fastest file searches
Webservicing custom objects
Aspect correct resize
Funky thumbnails
Wide interval timing
VB2005 or bust?
Recurring events
Single instances
Proper proper casing
Simple address formating
Combo filling reminder
Easy gradient forms
Grrrr on interops?
Winform memory usage
Windows service your ISP
Pretty up VS code printing
Remote configs with no BS
GDI+ printing cd cases
Your own flat combo
Where's the splitter?
Full autoemail
ReessppoonncceeWwrriittee
Kill the back cache
Color to hex
Source to web
Recode without recompile
Prop snippet for VS2008
Database tricks

Your own image control

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.


11) Handling the unhandleable exception

Even though the message calls this an "unhandled exception", the truth is that it's unhandle-able. If you step through the code you'll easily track down where it happens (in UpdateZoomDisplay right after you change the picturebox size) but you'll also see that the current catch-all exception handler is getting the message so adding a catch for System.ComponentModel.Win32Exception also won't work. Plus, you have a catch-all down the stack in the testHarness's button click event and it's not recognizing the exception either.

To quickly see the stack dump, play end-user on a debug build. Rebuild the solution and run the exe outside of Visual Studio then zoom till lightning strikes:

Looks like it's pretty far down the food chain in the low windows messages, pretty far out of the control of a .Net language. I've asked a lot of people about this over the years and while there are rule-of-thumb API explanations no one from Redmond has publicly given an official statement. When that happens you may as well call it "A Bug".

I wish I could tell you that there's a way to watch the settings and prevent it from happening just-in-time on all machines with a simple algorithm with pure managed code but I can't. I have come up with a list of magic numbers that can be applied against WMI Video display information but while such a tactic can work WMI is only available on certain operating systems and checking all the possible settings isn't fast. There simply isn't a happy solution to this one, so we'll have to punt.


The maximum width/height has nothing to do with your images, and isn't even an issue specific to a picturebox. All painted controls including buttons, listboxes trees and on and on all will crash your app at the same large sizes and the old "bug that is by design" of GUI control size properties is not protection against fatal exceptions.

Have you ever noticed that GUI controls (buttons, panels, pictureboxes, etc.) have a Size property of Width and Height Shorts (Int16)? The documentation calls them Integers (Int32) so by definition they should allow you to set the values up to 2147483647, but when you do the runtime immediately sets them to 32767 again (Short.MaxValue). It doesn't raise an exception because according to the documentation they're 32bit integers, but under the hood they're being mapped to 16bit and the runtime will automatically perform a fixed-value narrowing (actually a truncation) on any larger settings. You can test this quickly without code by playing with a control's size properties right in the IDE property sheet.

Some people consider this silent alteration to be an obnoxious way to run a development platform. I'd be happy with it if it kept things from crashing, but it doesn't; If you try to set a control to 32767x32767 you're still going to crash.

As I see it from testing, the actual maximum width/height values depend on the current color depth (8bit 256 color, 16bit "high color" or 32bit "true color" as specified in your display settings and also the video controller's native resolution (standard 4:3 or the new widescreen native modes on laptops). Note that the native resolution of the controller is what is important because even if you set a widescreen display to a standard screen resolution such as 1024x768 you're only interpolating the native pixels for the display, not altering the controller's native resolution.

Making thing even more difficult, the limit of one dimension (such as the width) changes as you increase or decrease the other dimension and the combined value is not a constant. For example, on a 4:3 native controller set to 32bit color depth, you can set a button or picturebox to be a square at 16095x16095 and all is well but using 16096x16096 you'll crash. That implies a limit is 16095+16095=32190. Thing is you can set the control to the equivalent of 16096+16096=32192 by using 16228x15964 without the fatal exception ... and to add to the weirdness if you try to set the size to 16227x15965 you'll crash. The values move in a less-than-logical way, and are completely different at different color depths and different controller native resolutions.

In case you're wondering, the betas of .Net2 add nothing to address this OS issue.

Sticking to RAD options we have two primary contenders: (1) Use DrawImage's ability to blit sections of a source image to a new bitmap with scaling; (2) Go for a common denominator size limit that seems to work on all testable displays even if that limit is far less than what any particular user's controller settings can handle.

Using DrawImage is one to try if lowballing doesn't make users happy. It makes programming more complex in that you lose the helpful AutoScroll scrollbars and will have to duplicate their abilities. This duplication will mean on the fly creation of bitmaps beyond the viewable area and even with doublebuffering you'll probably not ever reach the smoothness of AutoScroll. We've all used systems that do this kind of trickery, such as web-based map sites that make you click big side arrows and wait for the redraws rather than providing a nice smooth scrolling. (You can make things faster by dropping the un-accelerated GDI+ for GDI but then you're back to the pre-.Net TIF problems.)

Looks like a common denominator will be the fastest way to market, all we have to do is come up with the values. For that you'll have to do testing and because of the fatal exception the tests will have to be done manually (even having a controller exe shell a controlled exe with commandlines and a flag file won't work as an automated test since the exception will bail the controlled exe before it can unset the flag). I'll save you the time and give you values that I've come up with: For 16bit 18000 and for 32bit 13000, the safe limits being slightly higher for 4:3 native controllers but these values accommodate widescreen controllers that are the latest fad in notebooks.

Ints or Shorts? Does it matter?

With all the details we now know about image sizes, the question of value types is worth pondering. We know that our source images can't exceed 32767 (Short.MaxValue) in either dimension, and that means that the ZoomPercent can't exceed 32767 either because if you have a source image at 32767x5 the width already limits any greater zooming. So should we change out ZoomPercent to a 16bit integer (Int16/Short)? If we don't we're making things just as confusing as Microsoft does with Visual Studio's control size properties, and if we do are we asking for a performance hit when .Net internally converts the Shorts to Ints to best work with a 32bit operating system?

This is an interesting and often debated question. If your experience goes back to Win3x and/or DOS then you remember worrying over every bit of memory and taking great pains to use the smallest safe types possible, but then with Win32 and cheaper RAM you probably did so many API calls that you just got in the habit of using Int32s (called "Longs" in VB3-6) all the time. Because of the theoretical portability of .Net, some folks say that the clue to best practice is found in the max value of the "Integer" alias, with the thinking that for best performance "Integer" would convert to Int32 on 32bit Windows and Int64 on 64bit OSes, but now that 64bit Windows is here and 64bit .Net2 is in beta, that old logic isn't holding up. As I read it in the MSIL discussions, "Integer" is still an Int32 even when compiling with the 64bit switches (at least with the current .Net2 beta as of March 2005). Further reading says that technically there are no Int32s, Int16s or Int64s in the IL and instead all there are Pointers to memory addresses and there have been deductions on more than one occasion to the effect of there being no tangible performance benefit to defaulting numeric variables to Int32s just because we're using a 32bit OS; These experts are of the impression that all Ints perform essentially at the same level (in fact I remember one post where the dev stated he was using all Doubles internally and only converting them as needed for method/API calls).

With the geeks not able to see a clear benefit to defaulting to Int32s on a 32bit OS, there seems to be merit in exposing the ZoomPercent and holding its related variables as Shorts. But before you start changing code ...

All of a sudden we see why Microsoft just made everything Integers whether it technically made sense or not for a specific situation. If we start being more logical with Shorts for zooming, shouldn't we also change the variables and properties related to PageNumbers? In the real world I doubt if anyone will ever be processing a file containing over 32767 pages ... or even 256 for that matter, which means a Byte type would probably suffice. In this case I'd say stick with the Int32 because under the hood we'd just end up converting every time we ping the array ... and now from a big picture view our special zoom Short is sticking out like a sore thumb. Right or wrong defaulting to Int32s is the standard that dev-users expect most everything to be and worrying over memory isn't a big deal in a RAD tool (the Runtime takes care of memory, that's part of the contract and the definition of a RAD tool, after all).

Decision made, decision justified: "Integers" will be our default numeric types BUT unlike Visual Studio we'll protect our code from the fatal exception.

Add a new sub-region called "Constants" to the Declarations region of the UC and enter those limits plus specify a value for the minimum zoom issue we mentioned earlier. In the Private Variables sub-region, add an Int to hold the current color depth:

#Region "Declarations"

#Region "Constants"
    Private Const MAX_CONTROL_DIMENSION_16BIT As Integer = 18000
    Private Const MAX_CONTROL_DIMENSION_32BIT As Integer = 13000
    Private Const MIN_ZOOM_PIXELS As Integer = 2

#End Region

#Region "Private Variables"
    Private m_ImagePages() As svImagePage
    Private m_CurrentPageNumber As Integer = 0

'default to the safest limit
    Private m_MaxControlDimension As Integer = MAX_CONTROL_DIMENSION_32BIT

#End Region

#Region "Events"
    
...

We don't have to deal with both color depths, just limiting to the 32bit max would take care of everything lower. I chose to go the extra mile because in my experience corporate users run less powerful machines than developers and usually are stuck with 16bit, and in the case that's an advantage in that they can zoom in farther. Since corporate end users will likely be the people using this control most let's give them the added boost if we can.

The most logical time to check the color depth is when the control is loaded. This is the first time we've done any initialization code in the UC but it won't be the last so we'll plan for the future by creating a new sub in the "Private Methods" region named InitColorDepth and calling it from the UC's Load event (get the Load stub using the IDE combos):

#Region "Private Methods"

Private Sub svImageEditor_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load

        InitColorDepth()

    End Sub

    Private Sub InitColorDepth()
        Try
            Select Case ColorDepth()
                Case 32
                    m_MaxControlDimension = MAX_CONTROL_DIMENSION_32BIT
                Case Else
                    'takes care of 16 and lower,
                    'less than 16 shouldn't come up often enough
                    'to need special code for it
                    m_MaxControlDimension = MAX_CONTROL_DIMENSION_16BIT
            End Select

        Catch ex As Exception
            'Todo: remove after testing
            Throw ex
        End Try
    End Sub

    
    Private Function SourceFilePageCount(ByVal sourceImage As Image) As Integer

...

Unfortunately .Net doesn't give us the color depth information so we will have to use P/Invoke to get it. Add a new module to the UC project, name it "Win32.vb" and paste in the following API code:

Module Win32

#Region "Declarations"
    Private Const PLANES As Integer = 14

    Private Const BITSPIXEL As Integer = 12

    Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Integer, _
       ByVal nIndex As Integer) As Integer

    Private Declare Function GetDC Lib "user32" (ByVal hWnd As Integer) As Integer

    Private Declare Function ReleaseDC Lib "user32" (ByVal hWnd As Integer, _
       ByVal hdc As Integer) As Integer

#End Region

#Region "Methods"
    Public Function ColorDepth() As Integer
        'code by "Richard from the UK" 
        'originally posted at: http://tinyurl.com/69gz2

        Dim nPlanes As Integer
        Dim BitsPerPixel As Integer
        Dim dc As Integer
        Try
            dc = GetDC(0)
            nPlanes = GetDeviceCaps(dc, PLANES)
            BitsPerPixel = GetDeviceCaps(dc, BITSPIXEL)
            ReleaseDC(0, dc)
            Return nPlanes * BitsPerPixel

        Catch ex As Exception
            'Todo: remove after testing

            'Richard chose to set to a "safe" result
            'nPlanes = 1
            'BitsPerPixel = 8
            'Return nPlanes * BitsPerPixel
            'after testing, decide if that makes sense
            
            'in our case where we're limiting based on the result, 
            'we'd best err on the most restrictive
            Return 32

        End Try

    End Function

#End Region

End Module

We'll need to test our zoomed dimension values before applying them to the picturebox and if the largest source dimension exceeds the magic number then we'll force the zoom to a limited percentage. We'll take care of hitting the minimum zoom value in the same function. Click into the UC's "Private Methods" region and paste in that routine:

Private Sub SetZoomValues(ByVal value As Integer)
        'test each dimension to see if the zoom 
        'exceeds the Max limit for the color depth and/or
        'the minimum display size from MIN_ZOOM_PIXELS

        Dim finalZoom As Integer
        Dim hitMin, hitMax As Boolean

        'pull out the current imagepage, rather than pinging the array every time
        Dim tmpImagePage As svImagePage = m_ImagePages(m_CurrentPageNumber)

        'as long as we're fussing over dots, hold a ref to the image
        Dim tmpWorkingImage As Image
        tmpWorkingImage = tmpImagePage.WorkingImage

        Dim tmpZoom As Integer = tmpImagePage.CurrentZoom + value

        'the larger dimension sets the pace
        If tmpWorkingImage.Width > tmpWorkingImage.Height Then
            If (tmpWorkingImage.Width * tmpZoom \ 100) > m_MaxControlDimension Then
                'the picturebox width will be set to the limit
                'calculate what the percentage would be
                finalZoom = CInt((m_MaxControlDimension / tmpWorkingImage.Width) * 100)
                hitMax = True

            Else
                If (tmpWorkingImage.Width * tmpZoom \ 100) < MIN_ZOOM_PIXELS Then
                    'can't be smaller than MIN_ZOOM_PIXELS
                    'calculate the percent zoom of MIN_ZOOM_PIXELS
                    finalZoom = CInt((MIN_ZOOM_PIXELS / tmpWorkingImage.Width) * 1000)
                    hitMin = True

                Else
                    finalZoom = tmpImagePage.CurrentZoom + value

                End If
            End If
        Else
            'this is duplication of the above code but against the Height
            'be careful to keep it in synch and to get all of the 
            '"Width"s changed to "Height"s
            If (tmpWorkingImage.Height * tmpZoom \ 100) > m_MaxControlDimension Then
                'the picturebox height will be set to the limit
                'calculate what the percentage would be
                finalZoom = CInt((m_MaxControlDimension / tmpWorkingImage.Height) * 100)
                hitMax = True

            Else
                If (tmpWorkingImage.Height * tmpZoom \ 100) < MIN_ZOOM_PIXELS Then
                    'can't be smaller than MIN_ZOOM_PIXELS
                    'calculate the percent zoom of MIN_ZOOM_PIXELS
                    finalZoom = CInt((MIN_ZOOM_PIXELS / tmpWorkingImage.Height) * 1000)
                    hitMin = True

                Else
                    finalZoom = tmpImagePage.CurrentZoom + value

                End If

            End If

        End If

        tmpImagePage.CurrentZoom = finalZoom
        tmpImagePage.MinDimensionReached = hitMin
        tmpImagePage.MaxDimensionReached = hitMax
        'shoot the temp imagepage into the slot
        m_ImagePages(m_CurrentPageNumber) = tmpImagePage

    End Sub

SetZoomValues is only half as complex as it looks. The code is doubled because the finalZoom value is based on the larger of the two source dimensions. Both halves just drill down to check first if a new zoom value will exceed the Max limit and if so a limiting Zoom percentage is calculated and a boolean flag "hitMax" is set. If the Max isn't hit then the next check is to see if the Min limit is going to be hit and if so then that zoom percentage is calculated and a bool "hitMin" is set. If the zoom is safely between the limits then it is simply applied to the current zoom value. (As the comments state, be careful if you start refactoring to make sure that the Width and Height parts stay in synch.)

At the end of the sub we're popping the temporary svImagePage back into the array and notice that we've got a couple new members to the svImage structure: MinDimensionReached and MaxDimensionReached being set to the hitMin and hitMax bools. These new members, when attached to the svImageZoomChangeEventArgs object, will make GUI development easy for our dev-user; When the ZoomChanged event is raised they can set the .Visible properties of zoom buttons directly to these values and instantly make their forms more user-friendly.

In svTypes.vb, add the new members to the svImagePage structure:

Friend Structure svImagePage
    Dim OriginalImage As Image
    Dim WorkingImage As Image
    Dim CurrentZoom As Integer
Dim MinDimensionReached As Boolean
    Dim MaxDimensionReached As Boolean

End Structure

And update the svImageZoomChangeEventArgs class in svEventArguments.vb:

Public Class svImageZoomChangeEventArgs
    Inherits EventArgs

    Private m_CurrentZoom As Integer = 100
Private m_MinLimitReached As Boolean = False
    Private m_MaxLimitReached As Boolean = False
   
    Public Sub New(ByVal currentZoom As Integer, _
        ByVal reachedMinLimit As Boolean, ByVal reachedMaxLimit As Boolean)
        
        MyBase.New()
        m_CurrentZoom = currentZoom
m_MinLimitReached = reachedMinLimit 
        m_MaxLimitReached  = reachedMaxLimit 
    
    End Sub

    Public ReadOnly Property CurrentZoom() As Integer
        Get
            Return m_CurrentZoom

        End Get

    End Property

Public ReadOnly Property MinLimitReached() As Boolean
        Get
            Return m_MinLimitReached 

        End Get

    End Property

    Public ReadOnly Property MaxLimitReached() As Boolean
        Get
            Return m_MaxLimitReached 

        End Get

    End Property

End Class

Y'know, an alternative route could be to calculate the maximum zoom percentage during the ImageLoad and hold that percentage in a member of the svImagePage. If we did that then SetZoomValues would only need to check if a new zoom percentage exceeds the precalculated limit. While there wouldn't be a noticeable performance difference even on a relatively slow machine, comparing two values is more direct than doing division AND comparing every time the Zoom is changed, and cutting complexity is always a good thing. What's more we wouldn't need the MinDimensionReached and MaxDimensionReached structure variables anymore... instead we could just raise a fast comparison of CurrentZoom to the Max and Min percentages, that means even less complexity and less chances for bugs.

Replace the MinDimensionReached and MaxDimensionReached bools with integer members named "MinZoomPercent" and "MaxZoomPercent" to the svImagePage type:

Friend Structure svImagePage
    Dim OriginalImage As Image
    Dim WorkingImage As Image
    Dim CurrentZoom As Integer
Dim MinZoomPercent As Integer
Dim MaxZoomPercent As Integer

End Structure

Now create a new private function for the up-front calculations.

If you do some quick math you'll see that very large images can have minimum zooms of less than one percent (and obviously greater than zero), and when those results are moved to the Integer structure member they'll be narrowed to zero. That will mean that the images will be too small to see in the picturebox which was the reason for the MIN_ZOOM_PIXELS constant to begin with. We'll do a safety net for this unlikely situation and force a zero result to one, this will make the picturebox display relatively large depending on the exact size of the massive source image but it's better than making the structure members floats/singles just to account for exceptional images.

Private Sub CalcZoomPercentLimits(ByRef imagePage As svImagePage)

        'unrealistically large images will need fractional minimums
        'we will round up fractionals to a 1 for our integer MinZoomPercent
        Dim tmpMin As Integer = 0

        'the larger dimension sets the pace
        If imagePage.WorkingImage.Width > imagePage.WorkingImage.Height Then
            tmpMin = _
                CInt((MIN_ZOOM_PIXELS / imagePage.WorkingImage.Width) * 1000)
            If tmpMin = 0 Then
                imagePage.MinZoomPercent = 1
            Else
                imagePage.MinZoomPercent = tmpMin
            End If

            imagePage.MaxZoomPercent = _
                CInt((m_MaxControlDimension / imagePage.WorkingImage.Width) * 100)

        Else
            tmpMin = _
                CInt((MIN_ZOOM_PIXELS / imagePage.WorkingImage.Height) * 1000)
            If tmpMin = 0 Then
                imagePage.MinZoomPercent = 1
            Else
                imagePage.MinZoomPercent = tmpMin
            End If

            imagePage.MaxZoomPercent = _
                CInt((m_MaxControlDimension / imagePage.WorkingImage.Height) * 100)

        End If

    End Sub

Next, clean up the LoadImage routine with that up front call:

...
        If pageCount > 1 Then
            For i As Integer = 0 To UBound(m_ImagePages)
                tmpFileImage.SelectActiveFrame(FrameDimension.Page, i)
                tmpFrameImage = DirectCast(tmpFileImage.Clone, Image)
                tmpFrameImage = CopyImage(tmpFrameImage)
                m_ImagePages(i).OriginalImage = tmpFrameImage
                m_ImagePages(i).WorkingImage = DirectCast(tmpFrameImage.Clone, Image)
                m_ImagePages(i).CurrentZoom = 100
CalcZoomPercentLimits(m_ImagePages(i))

            Next

        Else
            m_ImagePages(0).OriginalImage = tmpFileImage
            m_ImagePages(0).WorkingImage = DirectCast(tmpFileImage.Clone, Image)
            m_ImagePages(0).CurrentZoom = 100
CalcZoomPercentLimits(m_ImagePages(0))

        End If
...

And lastly trim down the complexity of SetZoomValues:

Private Sub SetZoomValues(ByVal value As Integer)
        'test each dimension to see if the zoom 
        'exceeds the Max limit for the color depth and/or
        'the minimum display size from MIN_ZOOM_PIXELS

        Dim finalZoom As Integer
     
        'pull out the current imagepage, rather than pinging the array every time
        Dim tmpImagePage As svImagePage = m_ImagePages(m_CurrentPageNumber)

        Dim tmpZoom As Integer = tmpImagePage.CurrentZoom + value

        If tmpZoom >= tmpImagePage.MaxZoomPercent Then
            finalZoom = tmpImagePage.MaxZoomPercent
        ElseIf tmpZoom <= tmpImagePage.MinZoomPercent Then
            finalZoom = tmpImagePage.MinZoomPercent
        Else
            finalZoom = tmpZoom
        End If

        tmpImagePage.CurrentZoom = CInt(finalZoom)
        'shoot the temp imagepage into the slot
        m_ImagePages(m_CurrentPageNumber) = tmpImagePage

    End Sub

Ready for some good news? Right now we have the ZoomChanged event being raised in three places: the ZoomPercent and CurrentPageNumber property sets and ResetImage. That's a maintenance problem we can avoid by ripping it out of all three places and having it come only from UpdateZoomDisplay. Here's that new code:

Private Sub UpdateZoomDisplay()
        Dim tmpWorkingImage As Image
        Try
            tmpWorkingImage = m_ImagePages(m_CurrentPageNumber).WorkingImage

            pbDisplay.Width = tmpWorkingImage.Width * m_ImagePages(m_CurrentPageNumber).CurrentZoom \ 100
            pbDisplay.Height = tmpWorkingImage.Height * m_ImagePages(m_CurrentPageNumber).CurrentZoom \ 100

            pbDisplay.Image = tmpWorkingImage

            RaiseEvent ZoomChanged(Me, New svImageZoomChangeEventArgs( _
                m_ImagePages(m_CurrentPageNumber).CurrentZoom, _
                m_ImagePages(m_CurrentPageNumber).CurrentZoom <= _
                    m_ImagePages(m_CurrentPageNumber).MinZoomPercent, _
                m_ImagePages(m_CurrentPageNumber).CurrentZoom >= _
                    m_ImagePages(m_CurrentPageNumber).MaxZoomPercent) _
                )

        Catch ex As Exception
            'Todo: remove after testing
            Throw ex

        Finally
            If Not tmpWorkingImage Is Nothing Then
                tmpWorkingImage = Nothing
            End If

        End Try

    End Sub

And here are the "final" versions of the related property sets, with the event removed and a more intuitive call to UpdateZoomDisplay inserted:

Public Property CurrentPageNumber() As Integer
        'current page is held internally as zero-based
        'we expose as one-baed
        Get
            If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
                Return m_CurrentPageNumber + 1

            Else
                Return 0

            End If

        End Get

        Set(ByVal Value As Integer)
            If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
                If Value <= TotalPageCount And Value > 0 Then
                    Dim initialPage As Integer = m_CurrentPageNumber + 1
                    m_CurrentPageNumber = Value - 1

UpdateZoomDisplay()

                    'tell the host
                    RaiseEvent PageChanged(Me, _
                        New svImagePageChangeEventArgs(TotalPageCount, initialPage, m_CurrentPageNumber + 1))

                Else
                    Throw New ArgumentOutOfRangeException("Page " & Value.ToString & _
                        " does not exist (file contains " & TotalPageCount.ToString & " pages)")

                End If

            End If

        End Set

    End Property


    Public Property ZoomPercent() As Integer
        Get
            If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
                Return m_ImagePages(m_CurrentPageNumber).CurrentZoom

            Else
                'default for empty control
                Return 100

            End If

        End Get
        Set(ByVal Value As Integer)
            If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
SetZoomValues(Value)
                UpdateZoomDisplay()
             
            End If

        End Set

    End Property

ResetImage is going to have to have an overhaul. Currently it only deals with Rotations by replacing the svImagepage.WorkingImage with a fresh clone of the OriginalImage, but now it makes sense to let it force zooms back to 100%. We could overload it but there'd be no signature difference between a Rotation-only and a Zoom-only version so going with optionals works better:

Public Sub ResetImage(Optional ByVal resetRotation As Boolean = True, _
        Optional ByVal resetZoom As Boolean = True, _
        Optional ByVal resetAll As Boolean = False)

        If Not m_ImagePages Is Nothing AndAlso m_ImagePages.Length > 0 Then
            If resetAll Then
                For i As Integer = 0 To UBound(m_ImagePages)
                    If resetRotation Then
                        m_ImagePages(i).WorkingImage = _
                           DirectCast(m_ImagePages(i).OriginalImage.Clone, Image)

                    End If

                    If resetZoom Then
                        m_ImagePages(i).CurrentZoom = 100

                    End If

                Next

            Else
                If resetRotation Then
                    m_ImagePages(m_CurrentPageNumber).WorkingImage = _
                        DirectCast(m_ImagePages(m_CurrentPageNumber).OriginalImage.Clone, Image)

                End If

                If resetZoom Then
                    m_ImagePages(m_CurrentPageNumber).CurrentZoom = 100
                 
                End If

            End If

            UpdateZoomDisplay()

        End If

    End Sub

Next: Fixing the squish

Robert Smith
Kirkland, WA

 

added to smithvoice march 2005


Print  

pagecomment
  Add Comment



Submit Comment
  View Ratings
50.00%0
40.00%0
30.00%0
20.00%0
10.00%0

Number of Comments 0 , Average of Ratings
  View Comments
No comment.


Privacy Statement  |  Terms Of Use
Copyright 2008 by Robert C. Smith