Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A very simple magnifying glass

0.00/5 (No votes)
23 Jan 2012 1  
A VB.NET project showing how to build a simple magnifying glass.

Magn_Glass_frmStart.jpg

Introduction

Finally moving from VB6 to VB.NET, here is one of my first steps you might find interesting. Working on another project, I needed a magnifying glass to inspect parts of the screen more closely. Instead of downloading any of those available on the internet, I made one myself, since that way I would surely learn some new things.

How it works

The main code

The application starts with a frmStart which does only a few things: determine the size of the glass and the magnification factor and switch on the glass. Therefore there isn't much code to talk about.

Public Class frmStart
    
    Private Sub cmdStart_Click(sender As System.Object, _
                e As System.EventArgs) Handles cmdStart.Click  
        Me.Hide()
        Dim MagnSize As Integer = Choose(lstMagnSize.SelectedIndex + 1, 48, 64, 96, 128)
        frmScreenCopy.MagnSize = MagnSize
        frmScreenCopy.OriSize = MagnSize \ Choose(lstMagn.SelectedIndex + 1, 2, 4, 8, 16)
        frmScreenCopy.Show()
    End Sub
    
    Private Sub frmStart_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        lstMagnSize.SelectedIndex = 1
        lstMagn.SelectedIndex = 1
    End Sub
End Class

The magnification factor is the size of the glass divided by the original size (that is the portion that actually gets magnified).

There is some more code in frmScreenCopy, the second and last form of the project. Since the MouseMove event is fired very frequently, the code in there should be kept to a minimum. Therefore at first all declarations are done on form level.

Two Public variables are set by frmStart (see above).

Imports System.Drawing.Drawing2D  ' graphics path needs this

Public Class frmScreenCopy

    Public OriSize As Integer = 16  ' size of the original rectangle
    Public MagnSize As Integer = 64 ' size of the magnification glass
    Dim bmpOriCopy As Bitmap        ' buffer of the original rectangle on screen
    Dim bmpgrOriCopy As Graphics     Dim rctOri As Rectangle         ' original rectangle on screen
    Dim rctOriCopy As Rectangle     ' a copy of the original rectangle on screen
    Dim rctMagn As Rectangle        ' rectangle of the magnification glass
    Dim Desktop As Image            ' a backup of the screen
    Dim picgr As Graphics           ' pic (picturebox) graphics

    Dim gpath As GraphicsPath       ' used to make a round circle shaped glass
    Dim rgn As Region
    Dim pn As Pen = New Pen(Color.Silver, 4)

Each time the form is activated, the objects have to be (re-)set in case the size of the glass or original size has changed. The screenshot capturing has to take place in the same event. Before you put the screenshot into the PictureBox, you have to adjust its bounds. Otherwise the glass would only work in the size the PictureBox has at design time. To see what I mean, just switch line 028 and 029.

Private Sub frmScreenCopy_Activated(sender As Object, e As System.EventArgs) Handles Me.Activated
    ' adjust objects to choosen sizes
    bmpOriCopy = New Bitmap(OriSize, OriSize)
    bmpgrOriCopy = Graphics.FromImage(bmpOriCopy)
    rctOriCopy = New Rectangle(0, 0, OriSize, OriSize)
    rctOri = New Rectangle(0, 0, OriSize, OriSize) 'where on screen is set later
    rctMagn = New Rectangle(0, 0, MagnSize, MagnSize)
 
    Dim SC As New ScreenShot.ScreenCapture
    pic.SetBounds(0, 0, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height)
    pic.Image = SC.CaptureScreen
    Desktop = pic.Image.Clone
    picgr = pic.CreateGraphics
    Cursor.Hide()' in the KeyDown event you can switch the cursor on/off
    Cursor.Tag = "off"
End Sub

When the user stops working with the glass using the Escape-key, you make sure the cursor is back and visible.

Private Sub frmScreenCopy_Deactivate(sender As Object, e As System.EventArgs) Handles Me.Deactivate
    Cursor.Show()
    Cursor.Tag = "on"
End Sub

I know to avoid memory leaks, one should carefully dispose off all objects made. But I do not know how far you should go in this. For instance, in this case, when the form is disposed off, aren't all objects too? To be on the safe side, I did dispose them all one after the other here.

Private Sub frmScreenCopy_Disposed(sender As Object, e As System.EventArgs) Handles Me.Disposed
    rgn.Dispose()
    gpath.Dispose()
    bmpgrOriCopy.Dispose()
    bmpOriCopy.Dispose()
    Desktop.Dispose()
    picgr.Dispose()
End Sub

In the KeyDown event, there is more code than shown here, but it is simple and obvious code that doesn't need any explanation.

Private Sub frmScreenCopy_KeyDown(sender As Object, _
            e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
    Select e.KeyCode
        Case Keys.Escape
            Me.Close()
            frmStart.Show()
        Case ...
    End Select
End Sub

Finally, here you have the code that makes the magnifying glass. First, the rctOri rectangle is set to the position of the mouse. The rectangle is positioned in the middle of the mouse. Then a copy is made of that portion of the screen in the buffering bitmap bmpOriCopy. Why I explain later.

Private Sub pic_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs) Handles pic.MouseMove
    ' copy part of screen under mouse original size
    rctOri.X = e.X - OriSize / 2
    rctOri.Y = e.Y - OriSize / 2
    bmpgrOriCopy.DrawImage(pic.Image, rctOriCopy, rctOri, GraphicsUnit.Pixel)

Before you can draw a new magnifying glass, you have to remove the previous one from the screen. That's the function of the Desktop image object: it is buffering the screen for restoration purposes.

'restore background first before putting new magn glass
picgr.DrawImage(Desktop, rctMagn, rctMagn, GraphicsUnit.Pixel)

Now we finally are ready to draw a new magnification glass.

068           ' put new magn glass
069           rctMagn.X = e.X - MagnSize / 2
070           rctMagn.Y = e.Y - MagnSize / 2
071           gpath = New GraphicsPath
072           gpath.AddEllipse(rctMagn)
073           rgn = New Region(gpath)
074           picgr.Clip = rgn
075   
076           picgr.DrawImage(bmpOriCopy, rctMagn, rctOriCopy, GraphicsUnit.Pixel)
077           picgr.DrawEllipse(pn, rctMagn)
078   
079       End Sub
080   End Class

Comment on the code

The shortest summary of what is done sounds like this:

  • The screen is captured and put in a screen-sized PictureBox.
  • In the MouseMove event of the PictureBox, a portion under the mouse button is enlarged in a circular formed shape.

Screen capturing

Capturing the whole desktop screen in VB.NET is not so different from VB6: there are only some special API functions one should know, such as:

  • User32.GetDesktopWindow();
  • User32.GetWindowDC();
  • GDI32.CreateCompatibleDC();
  • GDI32.CreateCompatibleBitmap()...

But for the screen capturing part of the project, I thankfully borrowed from gigemboy on vbforums.com, his ScreenCapture class and I left it unchanged (some functions haven't been used in my Magnifying Glass project and could be deleted). Using that class (line 27-29), capturing the screen is very easy.

Drawing a transparent circle shape

After putting a copy of the desktop into a PictureBox, you can detect the position of the mouse (line 59) and then project an enlarged version of the portion of the screen underneath the mouse.

Basically the enlargement is done using rectangle shapes, with DrawImage. But a magnifying glass should be circular shaped.

In VB6, I would have made the circular shape using the API functions bitblt or stretchblt and a mask (black and white) picture. With those API functions, it would go something like that:

bitblt picResult.hdc, x, y, W, H, picMask.hdc,0,0,SRCINVERT
        bitblt picResult.hdc, x, y, W, H, picGrad.hdc,0,0,SRCAND
        bitblt picResult.hdc, x, y, W, H, picMask.hdc,0,0,SRCINVERT

mask_demo.jpg

In VB.NET, I could do the same. But then I would learn nothing. Still, I was not sure if I would find another way, a way implemented in .NET that would be as fast as the APIs bitblt, stretchblt. But I gave it a chance, and found out there are indeed ways in VB.NET, and they work fast enough.

You can first draw something into a GraphicsPath (lines 71-72), put this path into a Region object (line 73), and then use this region to clip (line 74) the following graphic actions (lines 76-77). A clipping region works as a mask. Paths can be anything you like. This way you can produce all kinds of transparent irregular shapes. In my project, I draw a circle (~ellipse) (line 72) I use to clip the enlarged rectangle (line 76).

Probably, behind the scenes, this clipping with a path in a region is still using the raster operations mentioned above. I think they first draw in white on a black surface to create a mask, and then use the mask to draw transparently.

It has to work smoothly

The most difficult part of the project was to make things work as smoothly as possible. There are many different ways to implement a working magnifying glass, but they do not always work that smoothly. The code in the MouseMove event gets repeated very fast. The less code there, the better. It is for instance very important to place Dims and News outside if possible. Here, Path and Region have to be renewed each time the mouse moves. But all the rest is declared outside.

There are also things I cannot explain. Often less is more. But not in this case. In the alternative code beneath, I use no buffer for the original size rectangle image and I draw from the Desktop-image directly instead. I also don't use a PictureBox to work with. Instead I use Me.BackgroundImage. The result is less code, and less actions to do for the computer. But in this version, there is a certain flickering. Why is it not clear? It should work better.

000   Imports System.Drawing.Drawing2D
001   
002   Public Class frmScreenCopy
003   
004       Public OriSize As Integer = 16  ' size of the original rectangle
005       Public MagnSize As Integer = 64 ' size of the magnification glass
006       Dim rctOri As Rectangle         ' original rectangle on screen
007       Dim rctMagn As Rectangle        ' rectangle of the magnification glass
008       Dim Desktop As Image
009       Dim picgr As Graphics
010   
011       Dim gpath As GraphicsPath       ' used to make a round circle shaped glass
012       Dim rgn As Region
013       Dim pn As Pen = New Pen(Color.Silver, 4)
014   
015       Private Sub frmScreenCopy_Activated(sender As Object, e _
                      As System.EventArgs) Handles Me.Activated
016           ' adjust objects to choosen sizes
017           rctOri = New Rectangle(0, 0, OriSize, OriSize)     ' where on screen is set later
018           rctMagn = New Rectangle(0, 0, MagnSize, MagnSize)
019   
020           Dim SC As New ScreenShot.ScreenCapture
021           Me.SetBounds(0, 0, Screen.PrimaryScreen.Bounds.Width, _
                           Screen.PrimaryScreen.Bounds.Height)
022           Me.BackgroundImage = SC.CaptureScreen
023           Desktop = Me.BackgroundImage.Clone
024           picgr = Me.CreateGraphics
025   
026           Cursor.Hide()
027           Cursor.Tag = "off"
028       End Sub
029   
030       Private Sub frmScreenCopy_Deactivate(sender As Object, _
                      e As System.EventArgs) Handles Me.Deactivate
031           Cursor.Show()
032           Cursor.Tag = "on"
033       End Sub
034   
035       Private Sub frmScreenCopy_Disposed(sender As Object, _
                      e As System.EventArgs) Handles Me.Disposed
036           rgn.Dispose()
037           gpath.Dispose()
038           Desktop.Dispose()
039           picgr.Dispose()
040       End Sub
041   
042       Private Sub frmScreenCopy_KeyDown(sender As Object, _
                  e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
043           Select e.KeyCode
044               Case Keys.Escape
...
047               Case Keys.F2
...
061               Case Keys.F4
070           End Select
071       End Sub
072   
073       Private Sub frmScreenCopy_MouseMove(sender As Object, _
                  e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
074           'restore background first before putting new magn glass
075           picgr.DrawImage(Desktop, rctMagn, rctMagn, GraphicsUnit.Pixel)
076   
077           ' position original and magnifying rectangle around the mouse
078           rctOri.X = e.X - OriSize / 2
079           rctOri.Y = e.Y - OriSize / 2
080           rctMagn.X = e.X - MagnSize / 2
081           rctMagn.Y = e.Y - MagnSize / 2
082   
083           gpath = New GraphicsPath
084           gpath.AddEllipse(rctMagn)
085           rgn = New Region(gpath)
086           picgr.Clip = rgn
087   
088           picgr.DrawImage(Desktop, rctMagn, rctOri, GraphicsUnit.Pixel)
089           picgr.DrawEllipse(pn, rctMagn)
090       End Sub
091   End Class

If you want to see what I mean by flickering, you'll have try out this code. The flickering is subtle but real and not nice.

History

This is a working first draft. I probably will code new versions of it with more functions. For instance I would like to be able to easily select parts of the screen and save them. But then maybe it won't be simple anymore and would be better separated in another article?

Update: January 23, 2012

I've had a very useful comment from trembru who suggested to replace the capture class that uses API functions, with the following code:

Private Function CaptureScreen() As Image
     Dim Img As New Bitmap(Screen.PrimaryScreen.WorkingArea.Width, _
                           Screen.PrimaryScreen.WorkingArea.Height)
     Dim g As Graphics = Graphics.FromImage(Img)
     g.CopyFromScreen(0, 0, 0, 0, Img.Size)
     g.Dispose()
 
     Return Img
End Function

Of course, I changed my code since it makes it better, shorter, and simpler again. I also changed the application icon, because the previous one looked terrible. The result is here:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here