Click to Rate and Give Feedback
Related Articles

Jeremy Miller continues his discussion of persistence patterns by reviewing the Unit of Work design pattern and examining the issues around persistence ignorance.

Jeremy Miller

MSDN Magazine June 2009

...

Read more!

In this article, we show you how to integrate a Windows Services-based solution with SharePoint. The results enable you to provision, start, stop, and remove service instances through SharePoint 3.0 Central Administration.

Pav Cherny

MSDN Magazine April 2009

...

Read more!

We demonstrate creating a peer-to-peer processing platform where multiple players function together for a common purpose: getting your work done.

Matt Neely

MSDN Magazine June 2009

...

Read more!

Mike Calligaro shows you the basics of using XNA Game Studio 3.0 to write games for Zune.

Mike Calligaro

MSDN Magazine May 2009

...

Read more!

Microsoft Velocity exposes a unified, distributed memory cache for client application consumption. We show you how to add Velocity to your data-driven apps.

Aaron Dunnington

MSDN Magazine June 2009

...

Read more!

Also by this Author

Achieving cross-browser compatibility for events is no easy task. The jQuery event handling API addresses the differences in event handling across browsers, allowing you to write more predictable JavaScript.

Dino Esposito

MSDN Magazine April 2009

...

Read more!

There’s a strong similarity between Web-based Silverlight 2 applications and desktop WPF applications. Enabling easy code reuse between the two is Dino’s focus here.

Dino Esposito

MSDN Magazine October 2008

...

Read more!

Dino Esposito compares the use of AJAX patterns and DOM manipulations to the use of the ASP.NET partial rendering engine.

Dino Esposito

MSDN Magazine August 2008

...

Read more!

This month Dino builds a service layer that authenticates users of Silverlight 2 and ASP.NET AJAX services to prevent illegal access to sensitive back-end services.

Dino Esposito

MSDN Magazine September 2008

...

Read more!

This month Dino looks at AJAX control extenders again, adding more advanced features including masked editing and autocompletion.

Dino Esposito

MSDN Magazine February 2008

...

Read more!

Popular Articles

A Sidebar gadget is a powerful little too that's surprisingly easy to create. Get in on the fun with Donavon West.

Donavon West

MSDN Magazine August 2007

...

Read more!

Jeff Prosise explains when it's better to use UpdatePanel and when it's better to use asynchronous calls to WebMethods or page methods instead.

Jeff Prosise

MSDN Magazine June 2007

...

Read more!

When incorporating the ASP.NET DataGrid control into your Web apps, common operations such as paging, sorting, editing, and deleting data require more effort than you might like to expend. But all that is about to change. The GridView control--the successor to the DataGrid-- extends the DataGrid's functionality it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, as long as its bound data source object supports these capabilities. In addition, ...

Read more!

Jason Clark

MSDN Magazine July 2003

...

Read more!

The MVP pattern helps you separate your logic and keep your UI layer free of clutter. This month learn how.

Jean-Paul Boodhoo

MSDN Magazine August 2006

...

Read more!

Cutting Edge
Context-Sensitive PictureBox Controls
Dino Esposito

Code download available at: CuttingEdge2006_07.exe (1940 KB)
Browse the Code Online
Great ideas are timeless. A long time ago in Microsoft Systems Journal Paul DiLascia demonstrated a neat trick to display context-sensitive tooltips floating over pictures. As the user moved the mouse over the picture, the tooltip control updated its text to reflect the name of the pointed figure.
How can a tooltip control be smart enough to support a non-rectangular area that, for example, precisely follows the natural shape of a human figure in a painting? Hot spots in images are nothing new, but you normally define them through common, easy-to-describe shapes—rectangles, circles, or perhaps polygons.
Paul solved the issue brilliantly by loading two copies of the image—the original image and a hot spot map. The original photo is displayed in a picture-box component, and the map image is kept hidden and used to map each pixel of the original image to a particular color. The map image is nearly identical to the original except that it fills each hot spot area with a color. This way, each hot spot area corresponds to a unique color and each unique color can be linked to a tooltip text. Before the tooltip pops up, the pixel underneath the mouse is mapped to the corresponding pixel in the hot-spot map. The color on the hidden copy of the image is mapped to the hot-spot list and the related text finally pops up.
In this month’s installment of Cutting Edge, I’ll apply Paul’s trick to the standard Windows® Forms PictureBox control so that it can display context-sensitive tooltips and fire proper events when the user clicks on particular areas. This way, you can easily implement clickable maps using real-world pictures and enlarge or shrink them at your leisure with little effort.

The Trick Unveiled
The photo on the left in Figure 1 is displayed to the user; the one on the right is used under the hood to quickly determine whether any clicked pixel on the displayed image belongs to hot areas, which in this photo are red, lime green, cyan, magenta, and yellow. I have deliberately chosen a real-world picture to illustrate my point. You can use virtually any image you like and still be able to create a map of context-sensitive regions inside of it.
Figure 1 Image User Sees and Underlying Image with Hot Spots 
Let’s briefly examine a common scenario. Your user wants to click on a world map to select a given region and then examine sales figures. How would you detect clicking on the various regions? You can partition the map into polygons made by a sequence of points. Once you have a region, a graphic API in the Microsoft® .NET Framework can tell you whether a point belongs to it. This approach is common in ASP.NET applications where the ImageMap control makes implementing it relatively effortless. However, you’ll run into trouble if the final bitmap size changes somehow. As the map size changes, you need to recalculate all the points for all regions. Applying a scale factor may help, but a more flexible solution would be welcome.
There are two key benefits to using a second image. First, you can more carefully control the boundaries of each region. Second, editing regions is trivial—just a matter of coloring proper areas using a simple graphics editor, such as Microsoft Paint (see Figure 2).
Figure 2 Colored Hot Spots Defined on a World Map 
Using two images, especially in the context of Windows Forms applications, consumes negligible resources. In ASP.NET, memory consumption due to a double-image load is more critical. Fortunately, solutions can be architected to cache the image only once and share it among multiple requests and users.
The Windows Forms PictureBox control can display images in a variety of formats loaded from a variety of sources, including previously loaded Image objects, remote images at a known URLs, or images residing on disk. In the .NET Framework 2.0, PictureBox has been further enhanced to display a temporary picture while the control loads the main image and an error image when the selected image is not available.
In the .NET Framework 1.x, when the user clicks on the image, the control fires the Click event, but all that does is send notification of the user’s action; it provides no additional information about mouse position and so forth. Event handlers for the event receive a basic EventArgs:
Sub PictureBox1_Click(ByVal sender As Object, _
                      ByVal e As EventArgs) _
    Handles PictureBox1.Click
    ...
End Sub
In Windows Forms 2.0, you can take advantage of the new MouseClick event, which provides you with the position of the mouse at the time of the click:
Sub PictureBox1_MouseClick(ByVal sender As Object, _
                           ByVal e As MouseEventArgs) _
    Handles PictureBox1.MouseClick
    ...
End Sub
Similarly, if you want to display context-sensitive tooltips based on the portion of the image underneath the mouse, you can handle the MouseMove event from within the host form, process the position, and determine what to do.

Architecting a Richer PictureBox Control
The PictureBox control I’m going to implement in this column addresses both points. It accepts a collection of hot regions and fires client events if the user clicks on or moves over any such region. The overall programming interface mimics the programming interface of the ImageMap control in ASP.NET 2.0. The key difference is that the PictureBox control defines hot regions based on colors instead of points. The new PictureBox control derives from the Windows.Forms.PictureBox built-in control and features two essential properties: HotSpots and MapImage (see Figure 3).
Public Class PictureBox : Inherits Windows.Forms.PictureBox
    Private m_tooltip As ToolTip
    Private m_bmp As Bitmap
    Private m_hotSpots As New HotSpotElementCollection

    Public Sub New()
        InitializeAsSmartPictureBox()
    End Sub

    Public Property MapImage() As Image
        Get
            Return DirectCast(m_bmp, Image)
        End Get
        Set(ByVal value As Image)
            m_bmp = New Bitmap(value)
        End Set
    End Property

    <DesignerSerializationVisibility(
     DesignerSerializationVisibility.Content)> _
    Public ReadOnly Property HotSpots() As HotSpotElementCollection
        Get
            Return m_hotSpots
        End Get
    End Property

    Private Sub InitializeAsSmartPictureBox()
        If Me.SizeMode = PictureBoxSizeMode.Normal Then
            m_tooltip = New ToolTip
            m_tooltipTitle = "You’re on"
            AddHandler Me.MouseClick, AddressOf OnClickInternal
        End If
    End Sub
    ...
End Class
The MapImage property is of type Image and represents the companion image of the displayed picture where hot regions are drawn using a well-known palette of colors. The Image is wrapped by a Bitmap object—a standard GDI+ object—that exposes methods to get the color of a given pixel.
Look at the map in Figure 2. The Image property of the PictureBox will be bound to the topmost image; the new MapImage property will be associated with the bottom image. The two images must meet a few requirements. First, images must have the same size. Second, the companion image must be saved using a non-lossy format such as BMP or perhaps PNG. JPEG images should be avoided because they use color approximation in their compression. If the color of some pixels in a hot region is modified, the whole point detection mechanism breaks. GIF files are fine as long as the colors you use to mark hot regions are within the color palette. Otherwise, like with JPEG images there’s the risk of color approximation. The displayed image can be any format.
The Windows Forms PictureBox control features a SizeMode property that indicates how the image is displayed. Valid values for this property come from the PictureBoxSizeMode enumeration. The default is Normal, meaning that the image is rendered starting from the upper-left corner of the control, and any part of the image that exceeds the PictureBox’s area is clipped. The StretchImage value, on the other hand, causes the displayed image to stretch or shrink to fit the PictureBox’s size. This poses a critical issue given the expected behavior of the extended PictureBox control: the companion image should be resized by the same factor. This can be done programmatically using the GDI+ classes. For example, you can use the GetThumbnailImage method on the Image class or, better yet, the DrawImage method on the Graphics class. To calculate the ratio, you compare the size of the PictureBox to the size of the image. Note that there are good reasons to avoid using GetThumbnailImage in this scenario. It may have very low fidelity relative to the original image, and in rare scenarios it may actually be a completely different image. Though this is very unlikely, it would be quite difficult to diagnose. The sample PictureBox control won’t provide additional features if its size mode is anything but Normal. If this does not meet your needs, you can augment the sample code available for download from the MSDN®Magazine Web site.
To promptly catch any value changes in the SizeMode property, and enable additional capabilities, you need to handle the SizeModeChanged event in the control:
Protected Overrides Sub OnSizeModeChanged(ByVal e As EventArgs)
    MyBase.OnSizeModeChanged(e)
    m_beSmart = (Me.SizeMode = PictureBoxSizeMode.Normal)
End Sub
An internal private member will track the working mode of the PictureBox. This member, the m_beSmart variable in the preceding code snippet, returns false if the SizeMode property is something other than Normal.

Defining Hot Regions
HotSpots is the second key property for the new PictureBox control. It is a collection of custom types, as you can see in Figure 4.
Imports System.Collections.ObjectModel

Public Class HotSpotEventArgs : Inherits EventArgs
    Public HotSpot As HotSpotElement
    Public CancelTooltip As Boolean
End Class

Public Class HotSpotElement
    Private m_hotSpotColor As Color
    Private m_hotSpotID As Integer
    Private m_description As String
    Private m_title As String

    Public Property HotSpotColor() As Color
        Get
            Return m_hotSpotColor
        End Get
        Set(ByVal value As Color)
            m_hotSpotColor = value
        End Set
    End Property

    Public Property HotSpotID() As Integer
        Get
            Return m_hotSpotID
        End Get
        Set(ByVal value As Integer)
            m_hotSpotID = value
        End Set
    End Property

    Public Property Description() As String
        Get
            Return m_description
        End Get
        Set(ByVal value As String)
            m_description = value
        End Set
    End Property

    Public Property Title() As String
        Get
            Return m_title
        End Get
        Set(ByVal value As String)
            m_title = value
        End Set
    End Property

    Public Overrides Function ToString() As String
        Return String.Format("HotSpot: {0} ({1}-{2}-{3})", _
            HotSpotID, HotSpotColor.R, HotSpotColor.G, HotSpotColor.B)
    End Function
End Class

Public Class HotSpotElementCollection
        Inherits Collection(Of HotSpotElement)
    Public Function ContainsColor(ByVal clr As Color) As Boolean
        For i As Integer = 0 To Me.Count - 1
            Dim elem As HotSpotElement = Me.Item(i)
            If elem.HotSpotColor.ToArgb() = clr.ToArgb() Then Return True
        Next
        Return False
    End Function

    Public Function FindHotSpot(ByVal clr As Color) As HotSpotElement
        For i As Integer = 0 To Me.Count - 1
            Dim elem As HotSpotElement = Me.Item(i)
            If elem.HotSpotColor.ToArgb() = clr.ToArgb() Then Return elem
        Next
        Return Nothing
    End Function

    Protected Overrides Sub InsertItem( _
            ByVal index As Integer, ByVal item As HotSpotElement)
        If Not ContainsColor(item.HotSpotColor) Then
            MyBase.InsertItem(index, item)
        End If
    End Sub

    Protected Overrides Sub SetItem( _
            ByVal index As Integer, ByVal item As HotSpotElement)
        If Not ContainsColor(item.HotSpotColor) Then
            MyBase.SetItem(index, item)
        End If
    End Sub
End Class
HotSpots is of type HotSpotElementCollection, which is a generic type obtained from the Collection (Of T) type composed with the HotSpotElement type. The HotSpotElement class defines a region of color in a map image that is meaningful. In this context, a hot region is identified with a unique numeric ID, an RGB color, a title, and a description. The color is used to uniquely identify the region. If you’re interested in capturing user clicks, then the ID provides a numeric value to test on the client and determine what to do. It is likely that the color information would be enough to uniquely identify the clicked area; from this perspective, the ID information is redundant. However, it might be more useful than colors in rich data-binding scenarios where you want to associate regions on a map with the ID they have in the back-end database. In this way, the color information is not coupled with region information.
Title and description serve in another role as well—they can be used for context-sensitive tooltips that pop up as the user moves the mouse over the displayed picture.
You can populate the HotSpots property programmatically or declaratively from within the Visual Studio® 2005 designer. It’s nice that Visual Studio 2005 automatically recognizes collection properties and binds them to the built-in collection editor (see Figure 5).
Figure 5 Editing the Hotspots Collection Property 
The default collection editor adds collection members and displays them with any text that results from the member’s ToString method. In the rightmost grid, you see all the properties of the member type. Each property is editable as in the Visual Studio 2005 parent property grid.
If you’re familiar with ASP.NET control development, you know that changes entered through the designer are not persisted to the designer.vb or designer.cs codebehind file. If you don’t believe me, just try it. Drop the new PictureBox control on a form and populate the HotSpots collection. When you’re finished, save and start the form. The PictureBox control behaves as if the collection were empty, and the designer.vb file or the form has no hot spot elements saved. Why is that?
The answer to the problem is that you need to claim a particular designer serialization policy for the HotSpots collection property. In general, you need to do this for any collection properties in both Windows Forms and ASP.NET custom controls. The following attribute on the collection property does the trick:
<DesignerSerializationVisibility( _
    DesignerSerializationVisibility.Content)> 
Without this attribute no changes made to the collection at design time are ever persisted to the designer.vb file in Windows Forms or the ASPX source page in ASP.NET.
The HotSpotElementCollection type inherits from the generic Collection type and extends it with a couple of finder methods—ContainsColor and FindHotSpot. Both take a color as their input and return a Boolean value or a HotSpotElement object respectively, based on their findings. (Look back at Figure 4.) FindHotSpot, in particular, returns the HotSpotElement that matches the specified color, if any.
In addition, the HotSpotElementCollection class overrides a couple of methods—InsertItem and SetItem—to make sure that the color doesn’t conflict with something already in the collection.
Armed with full support for image hot regions, let’s proceed to implement a couple of events for the host form. I’ll define two events—HotSpotFound and HotSpotClicked. HotSpotFound fires when the mouse is moving over a hot spot; HotSpotClicked fires when the user clicks on a hot spot.

Adding the Necessary Event Handling
The HotSpotFound event is declared using the new generic version of the EventHandler type, as follows:
Public Event HotSpotFound As EventHandler(Of HotSpotEventArgs)
You saw the HotSpotEventArgs data structure back in Figure 4. It basically extends the base EventArgs class with a couple of properties—HotSpot and CancelTooltip. The HotSpot property references the hot spot area that has been found or clicked. The CancelTooltip property indicates whether, in case of mouse movements, the corresponding tooltip should be canceled. This property gives client code the final word on the tooltip displayed for the hot spot. By handling the event, the client form can programmatically change or even cancel the tooltip.
The HotSpotClicked event follows an identical schema.
Public Event HotSpotClicked As EventHandler(Of HotSpotEventArgs)
In this case, though, the CancelTooltip property is not used by the PictureBox control once the event handler returns. Whatever value you assign to the property in the event handler is blissfully ignored by the PictureBox control.
The PictureBox control registers its own handler for the MouseClick event upon instantiation, as you saw in Figure 3. The internal handler, OnClickInternal, is shown in Figure 6.
Protected Overridable Sub OnClickInternal( _
            ByVal sender As Object, ByVal e As MouseEventArgs)
    PrepareAndRaiseClickEvent(e.X, e.Y)
End Sub

Private Sub PrepareAndRaiseClickEvent( _
        ByVal x As Integer, ByVal y As Integer)
    Dim elem As HotSpotElement = GetUnderlyingHotSpot(x, y)
    If elem Is Nothing Then
        HideTooltip()
        Return
    End If

    ‘ Raise an event to the host form with the underlying color
    Dim args As New HotSpotEventArgs
    args.HotSpot = elem
    RaiseEvent HotSpotClicked(Me, args)
End Sub

Private Function GetUnderlyingHotSpot( _
        ByVal x As Integer, ByVal y As Integer) As HotSpotElement
    If Not m_beSmart Then Return Nothing

    ‘ Ensure the point is inside the bitmap
    If x >= m_bmp.Width Or y >= m_bmp.Height Then Return Nothing

    ‘ Get the underlying color and check it against HotSpot collection
    Return HotSpots.FindHotSpot(m_bmp.GetPixel(x, y))
End Function

Private Sub HideTooltip()
    m_tooltip.SetToolTip(Me, String.Empty)
End Sub

When a point in the client area of the PictureBox control is clicked, the control receives the client coordinates of the point from the event data structure—the MouseEventArgs class.
The next step entails finding the color of the pixel at the specified position. Note that the internal MouseClick event fires even when the user clicks outside the image’s boundaries. You need to catch these clicks and return without calling GetPixel on the Bitmap. If you call GetPixel with coordinates outside the image’s size, an exception is thrown. GetPixel is a method on the Bitmap class that returns the color of the pixel at the given location. You then take this color and look up for a region associated with that color. If any is found, the corresponding HotSpotElement object is returned:
Dim elem As HotSpotElement = HotSpots.FindHotSpot(clr)
You use the FindHotSpot method on the hot spot collection to retrieve information about the found region.
But, what if your image naturally contains pixels with the same color as one of your hot spot colors? If the user were to move the mouse exactly over that pixel, a conflict would occur. To avoid that, for hot regions you should ideally choose colors that are not used elsewhere in the image. With over 16 million colors available, finding an unused color is possible, but not easy. However, because of the way colored pixels are distributed in a real image, your color will probably be found in one pixel here and there and may not become a problem.
A more elegant solution would consist in reserving a color (white or transparent perhaps) for all parts of the image that do not have a region defined. In this way, you color uniformly all the underlying image except hot regions. The reserved color might be exposed as a public property to let developers choose it at will.

Putting the Pieces Together
The PictureBox control allows you to define hot regions inside displayed images. Each region is painted on a copy of the image—the map image—using a distinct color, possibly a color not used in the rest of the image. After you drop the PictureBox control onto a form, you set the map image and populate the hot spot collection. The hot spot collection informs the control about "hot" colors in the image. By default, when the user clicks on a hot region, the HotSpotClicked event is fired. Here’s a typical event handler:
Sub PictureBox1_HotSpotClicked(ByVal sender As Object, _
                               ByVal e As HotSpotEventArgs) _
        Handles PictureBox1.HotSpotClicked
    MessageBox.Show(e.HotSpot.Description)
End Sub
If the PictureBox is enabled to capture mouse movements, it registers an internal handler for the MouseMove event. After the event is fired, the control will present a tooltip. Title and text of the tooltip usually reflect the values set in the hot spot element. However, these parameters can be changed in the client handler. As mentioned, the tooltip can also be canceled:
RaiseEvent HotSpotFound(Me, args)
If Not args.CancelTooltip Then
    m_tooltip.ToolTipTitle = args.HotSpot.Title
    m_tooltip.SetToolTip(Me, args.HotSpot.Description)
Else
    HideTooltip()
End If
Figure 7 shows the control in action and the standard tooltip it displays. The title of the tooltip is changed programmatically and the description of the hot region is also displayed on the form’s status strip:
Sub PictureBox1_HotSpotFound(ByVal sender As Object, _
                             ByVal e As HotSpotEventArgs) _
        Handles PictureBox1.HotSpotFound
    Info.Text = e.HotSpot.Description
    e.HotSpot.Title = "EMEA"
End Sub
Figure 7 Context-Sensitive Tooltips Displayed on a Map 

Data Binding Support
The built-in PictureBox control features a bindable property called Image. The addition of the HotSpots collection raises the need of a stronger form of data binding. For example, it would be great if the collection could be populated from a database, thus saving developers from editing source files if a color or a description changed at some time. You might want to add a DataSource property and map its contents to the HotSpots collection or perhaps transform the HotSpots collection into a read/write property that can be set programmatically as an object. If you opt for the classic DataSource property, you also need to define a few mapper properties to indicate which fields on the bound data source map to bindable properties of the hot spot elements. For example, you might want to have DataDescriptionField, DataTitleField, DataColorField, DataValueField properties to map fields on the data source to hot spot elements. For DataColorField, you are responsible for inventing a translation algorithm that generates a .NET Framework color type based on a string or a number stored in the bound data source. This could be done easily using the ColorConverter class in the System.Drawing namespace.
The PictureBox control’s bindable Image property is marked with the Bindable attribute and added to the DataBindings collection. You might want to extend this behavior to the MapImage property, too. To make this happen, you can add the Bindable attribute to the MapImage property in the source code of the control:
<Bindable(True)> _
Public Property MapImage() As Image
  ...
End Property

An ASP.NET ImageMap Control
I discussed context-sensitive picture box controls in the context of a Windows Forms application; however, it’s easy to build a similar ASP.NET control starting from either the Image or ImageMap control.
In ASP.NET, the ImageMap control provides behavior similar to what I’ve described throughout this column and defines a number of HotSpot regions. When the user clicks a hot spot, the control either posts back or navigates to a specified URL. The ASP.NET framework comes with a number of predefined HotSpot objects including the CircleHotSpot, RectangleHotSpot, and PolygonHotSpot classes. Also, you can derive from the abstract HotSpot class to define your own custom hot spot object, which you may find handy in situations like the one I’ve described here.

Send your questions and comments for Dino to cutting@microsoft.com.


Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.

Page view tracker