Click to Rate and Give Feedback
Related Articles
Here the author introduces SQL Server Data Services, which exposes its functionality over standard Web service interfaces.

By David Robinson (July 2008)
Here the author answers questions regarding the Entity Framework and provides an understanding of how and why it was developed.

By Elisa Flasko (July 2008)
Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.

By Josh Smith (July 2008)
Systems that handle failure without losing data are elusive. Learn how to achieve systems that are both scalable and robust.

By Udi Dahan (July 2008)
More ...
Articles by this Author
Presented here is the LINQ Enumerable class, which allows you to manipulate data in any class that implements IEnumerable(Of T).

By Ken Getz (July 2008)
LINQ to XML and the Microsoft SDK for Open XML Formats simplify access to the parts of a 2007 Office system Open XML document when retrieving or modifying data, resulting in shorter, less complex code.

By Ken Getz (March 2008)
This month Advanced Basics flaunts the power of generics and reflection and shows how you get more flexible and efficient development by combining the two.

By Ken Getz (January 2008)
Ken Getz prepares Visual Basic developers to use RibbonX.

By Ken Getz (June 2007)
This month Ken Getz writes a demo-creation system for Windows-based applications, which he calls a switchboard.

By Ken Getz (December 2006)
The System.Array and System.Collections.Generic.List classes provide methods that let you avoid writing code to loop through every element of an array or list to find the items you’re looking for. Ken Getz explains.

By Ken Getz (September 2006)
The last time I wrote this column (March 2006), I shared an application that allows you to update all the Microsoft® Word documents in a folder and its subfolders. Each time the application finds a document in the specified path, it updates the document properties to match those you specified in the application.

By Ken Getz (June 2006)
At the beginning of another lovely day of writing courseware in mad pursuit of unrealistic deadlines, I received a frantic call from a business partner. He was at the end of a long consulting project and had several hundred Microsoft® Word documents, all of which required their document properties to be set identically, except the Title property of the document, which was to be based on the document file name, minus the .

By Ken Getz (March 2006)
More ...
Popular Articles
Learn how to create a workflow that uses InfoPath forms and other office documents for passing data to targeted activities and for use in Office documents.

By Rick Spiewak (June 2008)
Here the author uses Document Information Panels in the Microsoft 2007 Office system to manipulate metadata from Office docs for better discovery and management.

By Ashish Ghoda (April 2008)
Speech Server 2007 lets you create sophisticated voice-response applications with Microsoft .NET Framework and Visual Studio tool integration. Here’s how.

By Michael Dunn (April 2008)
Microsoft Robotics Studio is not just for playing with robots. It also allows you to build service-based applications for a wide range of hardware devices.

By Sara Morgan (June 2008)
More ...
Read the Blog
There are many things called threat modeling. Rather than argue about which is "the one true way," a good practice is to consider your needs and what your skills, abilities, and schedules are, and then work with a method that's best for you. In the July 2008 issue of MSDN Magazine, ...
Read more!
Want to develop games for Xbox Live? Want to get paid for it, too? Click on over to the XNA Team Blog to learn more about their initial rollout of the XNA Creators Club for XNA Game Studio. ...
Read more!
The Microsoft Entity Data Model (EDM), based on Dr. Peter Chen's Entity Relationship (ER) model, is the driving force behind the ADO.NET Entity Framework. The EDM is also the feature that most significantly differentiates the Entity Framework from other ORM-style technologies in the marketplace. In the July 2008 issue of MSDN ...
Read more!
System.IO.File is a handy helper class for reading and writing data, but its methods support only synchronous operation. Is there an easy way to provide File’s functionality for asynchronous file I/O? In the July 2008 issue of MSDN Magazine, Stephen Toub walks through several ...
Read more!
Remember .NET Terrarium, the interactive game meant to introduce .NET development techniques? Well, the Windows SDK team has released the source code for .NET Terrarium 2.0 on CodePlex. You can read more about this release on the Windows SDK blog and at Microsoft ...
Read more!
The Enumerable class plays an important role in every LINQ query you create. Because the Enumerable class's extension methods can process many other classes—including Array and List—you can use methods of the Enumerable class not only to create LINQ queries, but also to manipulate the behavior of arrays and other data structures. In the July 2008 issue of MSDN ...
Read more!
More ...
GDI+
A Primer on Building a Color Picker User Control with GDI+ in Visual Basic .NET or C#
Ken Getz
Code download available at: GDIColorPicker.exe (395 KB)
Browse the Code Online

This article assumes you're familiar with C# and Visual Basic .NET
Level of Difficulty 1 2 3
SUMMARY
Although most developers and APIs use the RGB scheme when working with colors, it's not the only available way to represent or select colors. For instance, the standard Windows color-selection dialog box allows you to work with the HSL color scheme in an indirect way. In this article, the author describes several color selection schemes, and uses GDI+ (via the System.Drawing namespace) to create a component that makes it possible for your own applications to provide a simpler, friendlier color chooser. Along the way, you'll get tips to help you use GDI+ in your own apps.
Because Windows® provides a standard common dialog box for selecting colors (see Figure 1) it's easy to assume that this is the only method for selecting colors. That is not true, however, and in this article I'll provide a simple-to-use replacement for the standard color-selection dialog box. The sample's layout and appearance are easy to modify. You can lay out the indicators just about any way you like, and the sample application includes two different layouts for the dialog box. This article focuses on two specific areas: investigating different color-selection techniques and explaining the members of the System.Drawing namespace that make the sample application work.

Color Spaces
The standard Windows color-selection dialog box represents colors in just two of the many ways you might represent colors in code. This representation of color choices is often called a "color space" because color representations normally allow you to refer to colors by a set of coordinates in three-dimensional space. Figure 1 shows the standard Windows dialog box with the Define Custom Colors button selected, so the right-hand pane appears, allowing you to define your own colors. Disregarding the left-hand side of the dialog box, which simply allows you to select from a palette of preselected colors, the right-hand pane allows you to drag the color selector with your mouse, or enter colors using two different color spaces, Red/Green/Blue (RGB) or Hue/Saturation/Luminosity (HSL). In this dialog box, the individual RGB values contain 8-bit values and range from 0 all the way up to 255. The HSL values range from 0 to 240.
Figure 1 Standard Color Selection in Windows 
The RGB color system combines three color coordinates, along with a value that indicates the transparency of the color that's often referred to as the "alpha" component of the color, and creates a 32-bit unsigned integer value that represents the color. Note that the dialog box in Figure 1 doesn't provide any way to select an alpha component other than 255, allowing only fully opaque colors. The AlphaRGB (ARGB) value represents the color in a way that's easy for a computer to decipher. For example, with the color selected in Figure 1, the combined ARGB value is 13578293 decimal or FF30CFCB hex. Breaking this 4-byte value up into individual bytes, you'll find, as you might expect given the color in Figure 1, the values 255, 48, 207, and 203 for the alpha, red, green, and blue components of the color. Disregarding the alpha component, which will always be 255 (FF) when using this particular dialog box, it's easy to programmatically convert from the integer representation of the color to the color's individual bytes, hence the propensity for Windows, and therefore application developers, to use the RGB scheme to describe colors.
Although using the RGB color space does make it easier for the computer to convert back and forth between its internal representation of the color and the representation humans need in order to select a color, RGB doesn't provide any useful ordering of colors for selection. Some development team arbitrarily selected the layout of the color grid on the right-hand side of Figure 1—there's no reason it has to look this way. If you take a few minutes to investigate the meaning of contiguous colors within the color-selection square, watching the values for the R, G, and B components of the color as you move the mouse about the screen, you'll be able to discern a pattern. The pattern isn't obvious, however; nor could you ever guess an RGB value given the position of your selection. In a more logical color-selection scheme, the position of the selector would have some relationship to the selected color.
Back in the days before it was easy to use the Windows common dialog boxes, I remember attempting to create my own color-selection dialog box. Not knowing any better, I began by trying to create a two-dimensional grid providing the capability of selecting any RGB color. Quickly, I realized that I was attempting to solve a three-dimensional problem with two-dimensional tools. My simple solution at the time was to provide a list of available colors, the easy way out. As a matter of fact, the RGB color space consists of normal Cartesian coordinates, with the red, green, and blue portions of the colors acting as the x, y, and z values. Specifying a color using the RGB color space selects a point in three dimensions, where each component indicates the distance from black (0, 0, 0) on each of three axes. The color white (255, 255, 255) represents the corner on the color "cube" diagonally opposite the origin.
So RGB is convenient, but not for humans. Humans are accustomed to thinking about color in terms of hue (what color is it?), intensity (how strong is the color?), and brightness (is it pale or vivid?). In order to meet these needs, the standard color-selection dialog box also provides an alternative representation of the color value, the HSL format. This representation makes more sense to you and me, although it requires more work for the computer as it performs somewhat complex mathematical computations to convert HSL-formatted values to the more common RGB values. If you drag the selector left and right and then up and down within the dialog box and watch the HSL values, you'll see the pattern right away. That is, as you move from left to right across the square, the Hue value increases from 0 to 240. As you move from top to bottom in the square, the Saturation value goes from 240 to 0. As you drag the separate vertical slider from top to bottom, the Luminosity value goes from 240 (white) to 0 (black). Given these physical arrangements, you could, with relative ease, become proficient at converting from the position of the two pointers in the dialog box to a reasonable approximation of the corresponding HSL color values. Not so with RGB.
The Hue/Saturation/Value (HSV) color space is a modification of HSL in which the Value axis measures the brightness of the color, and you cannot progress beyond full brightness. In comparison, HSL allows you to move past full brightness, through pastel colors, all the way to white. HSV uses a Saturation value of 0 to provide all colors that have the same red, green, and blue components instead. (That is, shades of gray.) HSV is the color scheme demonstrated in the sample application, and it's simple to explain.
The first component of an HSV value, the Hue, corresponds to an angle on a circle. This circle contains the same colors as shown on the right-hand side of Figure 1, but arranged in circular fashion (see Figure 2). Given this layout, the color red corresponds to an angle of 0 degrees, yellow to 60 degrees, and so on. The particular variation of HSV used for the sample project scales the degrees on the circle so that values for the Hue portion of the color range from 0 to 255—the scaling is done internally, in the code.
Figure 2 Color Circle 
The Saturation value corresponds to the distance from the center of the circle. Colors on the edge of the circle are fully saturated and the center of the circle contains no saturation—it's white. Figure 3 shows the range of saturation values for the color green, as if extracted from the circle shown in Figure 2. Again, my selection of 0 to 255 as the range is arbitrary and only serves to make the selection of colors using HSV feel more like the familiar RGB. In reality, the example could just as easily have used floating point values between 0 and 1 to represent saturation.
Figure 3 Saturation 
The Value portion contains the brightness of the selected color, and it ranges from 255 (fully bright) to 0 (black). Again, the exact values aren't important—the scaling here is just for convenience. Figure 4 shows the color green as its Value coordinate changes from 255 to 0.
Figure 4 Value 
What you really have, when working with the HSV color space, is a cone of color. The circle in Figure 2 acts as the top of the cone, and a line from each point in the circle down to the vertex of the cone, representing the color black, provides the brightness range for each color. Figure 5 shows the cone using the brightness axis for three colors: green, cyan, and purple. Any color can be represented as a Hue coordinate, measuring an angle around the circle, a Saturation coordinate as the distance from the center of the circle, and a Value coordinate as the distance from the vertex at the bottom of the cone.
Figure 5 Color Cone 
Coincidentally, I ran across an application using a dialog box that allows users to select colors using the HSV color scheme (much like the one shown in Figure 6) right at the time I was attempting to teach myself how to use GDI+ in the Microsoft® .NET Framework. Fascinated by this fresh approach to selecting colors, I took on a "nights and weekends" project to create the same effect using Visual Studio® .NET and Visual Basic® .NET. That effort became the sample project discussed in this article. Along the way, I grew quite comfortable with the basics of GDI+ and many of the objects and members of the System.Drawing namespace. The remainder of this article discusses the particular GDI+ techniques, objects, and members used by this sample application. I can't describe each object and member in any detail here. The code in this article is a starting point, but the .NET Framework documentation is a major resource.
Figure 6 Sample Form 
Run the sample application and load the sample form shown in Figure 6. You should be able to predict the behavior of the Hue, Saturation, and Brightness controls as you interact with the form. Click on the circle and then move the mouse in a circular fashion around the circle, keeping the distance from the center as constant as possible. You'll see just the Hue value change. Moving the mouse radially, from the center of the circle to the edge, the Saturation value increases. In either case, the Brightness value remains constant. Drag the pointer on the vertical bar to the right of the circle, and the Brightness value changes corresponding to the height of the pointer.

Investigating the Sample App
The sample includes two versions of the same project: ColorChooserVB.sln and ColorChooserCSharp.sln. You can download it from the link at the top of this article. The code is essentially the same in both projects, but I'll demonstrate with code samples from the Visual Basic .NET version. The classes in the sample projects are described in Figure 7. Each class has been saved in its own file, so it's easy to find each individual class. The ColorWheel class is the centerpiece of the application—it provides the support for drawing the various elements of the color-selection forms. Besides the fields, properties, and methods in the class, the ColorWheel class also raises the ColorChanged event whenever you move the mouse to select a new color on the color wheel or on the vertical brightness bar.
It's important to understand the design goals of the sample application. I knew that however I designed the color-selection form, the user might have a different opinion about how it should look or how it should be used. Because of this, I've taken special care to separate the specific user interface of the host form from the display of the color wheel, brightness bar, and selected color rectangle. As a matter of fact, the ColorWheel class maintains no specific information about its host. Each form or control creates a new instance of the ColorWheel class, passing Rectangle objects corresponding to the three areas that the ColorWheel instance needs to manage. The ColorWheel instance raises the ColorChanged event back to the host form or control when it determines that a new color has been selected. It's up to your host form to lay out the three areas, pass Rectangle objects corresponding to the areas to the ColorWheel class's constructor, and to call the ColorWheel class's Draw method whenever it's necessary. When the user drags the color or brightness selection pointers, or otherwise selects a new color, your form must call the ColorWheel.Draw method. See the code for the sample forms—they show two different examples of this interaction.

Converting Colors
Throughout the ColorWheel class and the sample project, the code needs to convert color values between RGB and HSV format. Generally, the code uses HSV format for most internal uses—it closely maps to the way the colors are represented on the screen—but uses RGB values when interacting with the user or when displaying the selected color on the screen. Although I made a valiant attempt at working out the conversion formulas during a cross-country flight, once I reconnected and did a quick search, I found several sites listing pretty much the same algorithm in several different languages, but none in C# or Visual Basic .NET. I converted the code to C# and Visual Basic .NET, and the ColorHandler class in the sample project provides shared/static methods that handle the conversions between RGB and HSV. The class also contains HSV and RGB structures that are used throughout the application. They are simple and provide an easy way to cart around RGB and HSV values.
Specifically, the ColorHandler class provides HSVToRGB, RGBToHSV, and HSVToColor methods that perform the color conversions. The Color structure within the .NET Framework provides FromArgb and ToArgb methods, so there's no need to create those methods here. If you're interested in the details of performing the conversions to and from the HSV color scheme, check out the code in the ColorHandler class. I haven't focused on this code here, as you're unlikely to need to modify the code for use in other projects—you can simply import the code or package it in any way you like for reuse.
It is important to note, however, that both the HSVToRGB and RGBToHSV methods expect that the individual color components—the R, G, B and H, S, V values—contain integers between 0 and 255. Although there's no reason that these exact values must be used, I chose to maintain consistency between the two color spaces, using the same range for both. If you want to handle the color values in a different way, you'll need to modify the code in the ColorHandler class.

Drawing the Color Wheel
To be honest, I started on this project mostly because I had seen an application that used an HSV color selection and was convinced that it was possible to create the circular gradient the form uses for selecting colors without writing much code using GDI+. As I suspected, it's really simple to create the gradient. Working with a PathGradientBrush object, you supply an array of points that define the edge of the gradient, indicate the center color and center point, supply a corresponding array of colors in a one-to-one mapping, and you're all set. GDI+ does all the work, filling in the designated region with the gradient you've requested. (Both the PathGradientBrush class and the LinearGradientBrush class discussed later inherit from the Brush class, and are among several means of filling a region.)
The CreateGradient procedure handles all these tasks, but in a way you might not expect. As I originally wrote it, the procedure generated the gradient each time it was called, and this procedure might be called each time you move the mouse. Of course, this inefficiency made it impossible to track the mouse effectively even on the most beefy hardware, and the current solution was born out of necessity—that is, rather than continually redrawing the gradient, the CreateGradient procedure (see Figure 8) draws the gradient once, creates an image of the gradient, and then displays the image whenever the host needs to be repainted.
The CreateGradient procedure needs to retrieve an array of points along the edge of the gradient, and the GetPoints procedure handles this calculation. GetPoints iterates through 1536 points on the edge of the circle, converting from polar to Cartesian coordinates. Although this example uses a specific number of points—1536, or 256 colors in each of six regions of the circle—you could certainly get by with fewer.
For those of you who have purged all your high school trigonometry from long-term memory, polar coordinates define locations in terms of the center of a circle, the radius of the circle, and an angle between 0 and 360 degrees on the circle. Cartesian coordinates are plain old x and y—horizontal and vertical—values. Converting between the two involves some ugly but simple math using the Sin and Cos methods of the Math class. Figure 8 contains the code for the GetPoint and GetPoints methods so if you're in a high school trig kind of mood, dig in!
The CreateGradient procedure starts by creating a new PathGradientBrush object, specifying the array of points returned by the GetPoints method:
pgb = New PathGradientBrush( _
    GetPoints(radius, New Point(radius, radius)))
The code then sets the necessary properties of the PathGradientBrush object, filling in the SurroundColors property with the array of colors returned by the GetColors method:
pgb.CenterColor = Color.White
pgb.CenterPoint = New PointF(radius, radius)
pgb.SurroundColors = GetColors()
Finally, the code creates a new Bitmap object the same size as the rectangle bounding the color wheel, then creates a new Graphics object and uses its FillEllipse method to fill the bitmap with the gradient:
colorImage = New Bitmap( _
    colorRectangle.Width, colorRectangle.Height, _
    PixelFormat.Format32bppArgb)

newGraphics = Graphics.FromImage(colorImage)
newGraphics.FillEllipse(pgb, 0, 0, _
    colorRectangle.Width, colorRectangle.Height)
Drawing the linear gradient displaying the range of brightness values for the selected color is much simpler. For this vertical bar, the code simply creates a LinearGradientBrush object, supplying the top and bottom colors, and the direction in which to fill—top to bottom in this case. The code can then take the LinearGradientBrush object and use it to fill the rectangle. The ColorWheel.DrawLinearGradient procedure does the work, as shown in Figure 9.
The CreateGradient and DrawLinearGradient procedures should call the Dispose method of the GDI+ managed wrapper objects instead of waiting for garbage collection, to avoid overusing window handles on older OSs. The Visual Basic .NET code handles this in the Finally block, ensuring that the objects get released correctly. C# provides a simpler mechanism, the using statement, which guarantees that the object(s) listed in the statement will have their Dispose method called automatically at the end of the block. In Visual Basic .NET, which doesn't provide a construct like the using statement, procedures require a bit more code. The following code sample shows the DrawLinearGradient procedure from the C# example, taking advantage of the using statement:
private void DrawLinearGradient(Color TopColor) 
{
    using (LinearGradientBrush lgb = 
        new LinearGradientBrush(brightnessRectangle, TopColor, 
        Color.Black, LinearGradientMode.Vertical))
  { g.FillRectangle(lgb, brightnessRectangle); } 
}
Compare this to the Visual Basic .NET version shown in Figure 9.

Finishing the Drawing
The sample code uses GDI+ to draw the square and triangular pointers and to draw the selected color rectangle. The DrawColorPointer procedure calls the DrawRectangle method of the Graphics class, passing in a Pen object and coordinates for the rectangle. I used one of the predefined Pen objects, but you can also create your own Pen, passing a color and, optionally, a width:
Private Sub DrawColorPointer(ByVal pt As Point)
  ' Given a point, draw the color selector. 
  ' The constant SIZE represents half
  ' the width — the square will be twice
  ' this value in width and height.
  Const SIZE As Integer = 3
  g.DrawRectangle(Pens.Black, _
      pt.X - SIZE, pt.Y - SIZE, _
      SIZE * 2, SIZE * 2)
End Sub
The DrawBrightnessPointer procedure draws the triangular pointer, using the FillPolygon method of the Graphics class. This method allows you to specify a Brush (again, using one of the built-in brushes) and an array of points:
Private Sub DrawBrightnessPointer(ByVal pt As Point)
  ' Draw a triangle for the brightness indicator that "points"
  ' at the provided point.
  Const HEIGHT As Integer = 10
  Const WIDTH As Integer = 7

  Dim Points(2) As Point
  Points(0) = pt
  Points(1) = New Point(pt.X + WIDTH, pt.Y + HEIGHT \ 2)
  Points(2) = New Point(pt.X + WIDTH, pt.Y - HEIGHT \ 2)
  g.FillPolygon(Brushes.Black, Points)
End Sub
Every time the display needs updating—which is generally every time you move the mouse or select a new color—the UpdateDisplay procedure creates a new SolidBrush object based on the selected color, redraws the color wheel image, draws the selected color rectangle, draws the linear gradient containing brightness values, and updates the pointers. The code in Figure 10 handles these tasks, calling procedures you've already seen, along with System.Drawing methods.

Painting on Demand
In the sample applications, it's up to the forms hosting the ColorWheel class to call the ColorWheel.Draw method every time the display needs to be updated. This might occur when the user moves the mouse or selects a new color using the input method provided by the host form. The sample forms use NumericUpdown or HorizontalScrollbar controls. No matter when or how the updating needs to occur, the ColorWheel.Draw method expects to receive a Graphics object from its host, so it has a context on which to draw its graphic elements. Although you can call the CreateGraphics method at any time, doing so presents risks. For example, the Graphics object is only valid for the duration of the Windows message and must be explicitly disposed. I've found it simplest to perform all painting from within the host's Paint event handler, mostly because the Paint event receives a PaintEventArgs object as one of its parameters, and this object provides a Graphics property containing the container's graphics context. Writing it this way also allows double buffering, as you'll see in the next section.
Handling all the updates in the Paint event centralizes all the drawing code, but it means that your apps must manage their own state and take actions based on the current state from the Paint event handler. In the sample forms, you'll find an enumeration that tracks the type of change, so the Paint event handler can call the correct overloaded version of the ColorWheel.Draw method:
Private Enum ChangeStyle
  MouseMove
  RGB
  HSV
  None
End Enum

Private changeType As ChangeStyle = ChangeStyle.None
Private selectedPoint As Point
When you move the mouse or otherwise indicate that you want to modify the selected color, the host's code must track the current state and invalidate the host. This causes a request for the region to be painted, raising the Paint event. For example, moving the mouse on the sample forms causes the following code to run:
Private Sub HandleMouse( _
 ByVal sender As Object, ByVal e As MouseEventArgs) _
 Handles MyBase.MouseMove, MyBase.MouseDown

  ' If you have the left mouse button down, then update the
  ' selectedPoint value and force a repaint of the color wheel.
  If e.Button = MouseButtons.Left Then
    changeType = ChangeStyle.MouseMove
    selectedPoint = New Point(e.X, e.Y)
    Me.Invalidate()
  End If
End Sub
The call to the Invalidate method causes the form's Paint event handler to kick in, running the code shown in Figure 11. Selecting a value from one of the NumericUpdown controls on the first sample form that corresponds to an RGB value (look back to Figure 6) runs the code shown in Figure 12. This procedure first checks to make sure it's not being called recursively. It's impossible to determine if a NumericUpdown control is being updated by a user or in code, so the sample uses a class-level variable, isInUpdate, to avoid recursive changes to the controls. The code sets the change type, calculates the RGB and HSV values, and then invalidates the form, raising the Paint event and running the Paint event handler code.

Repainting is Expensive
Even having optimized the drawing of the circular gradient so that the major calculations happen only once, the Draw method of the ColorWheel class needs to display the image of the gradient each time you move the mouse so it can redraw the rectangle pointer. This, too, is quite expensive and slow. There is a simple solution, however: double buffering. This technique creates an image of the form in memory. Each time you invalidate the form's image, the Windows Forms engine can figure out exactly which pixels need to be redisplayed by comparing the current image to the saved image and updating only the changed pixels. By creating an in-memory image of the display surface, the Windows Forms engine can redisplay only the necessary portion of the surface as your code calls the ColorWheel.Draw method repeatedly.
The System.Windows.Forms.Control class makes this easy—you can set bits in the ControlStyles field that affect the way the form draws itself, calling the SetStyle method. Although the SetStyle method provides a number of values you can combine to control painting, to reduce flicker by using double buffering you must set the UserPaint, AllPaintingInWmPaint, and DoubleBuffer style bits to True, like this:
Me.SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Me.SetStyle(ControlStyles.UserPaint, True)
Me.SetStyle(ControlStyles.DoubleBuffer, True)
See the .NET Framework documentation on the Control.SetStyle method for more information on these and other style bits. You might also want to try commenting out these lines in the sample forms (the code in the form's Load event handlers) to verify the difference that double buffering can make.

Cleaning Up
The ColorWheel class implements the IDisposable interface and so supplies the Dispose method required by the interface. The point is that the class takes advantage of some graphics resources that should always be disposed, when the class is no longer in use (see Figure 13).
When working with GDI+, it's important to remember that you should call the Dispose method of any object that provides one. That way, the wrappers around unmanaged objects provided by the operating system won't hog resources.

Investigating the Sample
The sample project contains the two forms shown in Figure 6 and Figure 14, in addition to a main form that allows you to test the three different color dialog boxes shown in this article. The two forms, ColorChooser1 (see Figure 6) and ColorChooser2 (see Figure 14), each take advantage of the fact that the class that actually generates and manages the color selection, ColorWheel, requires its clients to pass in the locations of the color wheel, brightness rectangle, and selected color rectangle as parameters to its constructor. In each case, the client passes a Rectangle object containing the coordinates of the corresponding area on the client form. That is, the ColorWheel class itself has no direct interaction with its host. Figure 15 shows the ColorChooser1 form in design view, with three Panel controls acting as placeholders for the graphic elements to be displayed by the ColorWheel class. The point here is that the ColorWheel class needs to know only the locations in which to display its color wheel, brightness selector, and selected color rectangle. Code in your form passes these locations to the constructor in the ColorWheel class.
Figure 14 Another Color Picker Form 
The sample forms declare a class-level variable, myColorWheel. In Visual Basic .NET, the WithEvents keyword makes it easy to hook up event handling. In C#, the sample hooks up the event handling declaratively, instead:
Private WithEvents myColorWheel As ColorWheel
As the form loads, the code within the form's Load event handler hides the panels and then retrieves their coordinates into Rectangle objects (see Figure 16).
Figure 15 Design View 
I decided to use Panel controls only because they provide a convenient way to lay out the forms. They're not required; you can build your forms by simply specifying the locations where you want the three user interface features of the ColorWheel class. I found it easiest to lay these out at design time, using the Panel controls, and then pass the coordinates of those panels to the constructor in the ColorWheel class.
Finally, the form creates a new ColorWheel object, passing in the three Rectangle objects, and hooks up the ColorChanged handler, as you'll see in the following code:
         myColorWheel = New ColorWheel( _
             ColorRectangle, BrightnessRectangle, _
             SelectedColorRectangle)
The ColorWheel class raises its ColorChanged event every time you select a new color, and the hosting form needs to react to that event, updating the display as it sees fit. The first sample form, shown in Figure 6, updates NumericUpdown controls as you move the mouse on the form. The second sample form, shown in Figure 14, updates HorizontalScrollbar controls. You aren't required, of course, to update any controls—you could simply allow the user to move the mouse, and the ColorWheel class would keep the area designated by the third rectangle that was passed by parameter to its constructor updated with the selected color.

Conclusion
If you want to take advantage of the ColorWheel class in your own applications, you could call it from any form, create a custom control that mimics the ColorDialog control, or use the class in any other way you find useful. In any case, you can simply examine the sample forms and the ColorWheel class, and go from there.
As you can see, you can easily break out of the RGB rut and offer your users or other developers an alternate means of selecting colors. In addition, you've seen in this example that you can take advantage of the powerful features provided by the System.Drawing namespace without having to write much code at all.

For related articles see:
Computer Graphics: Principles and Practice by James Foley et. al. (Addison-Wesley, 1995)
Programming Windows with C# by Charles Petzold (Microsoft Press, 2001)

For background information see:
About GDI+
Using GDI+

Ken Getz is a senior consultant with MCW Technologies and a Microsoft Regional Director for Southern California. Ken is coauthor of ASP.NET Developer's Jumpstart (Addison-Wesley, 2002), Access Developer's Handbook (Sybex, 2001), and VBA Developer's Handbook, 2nd Edition (Sybex, 2001). Reach him at keng@mcwtech.com.

Page view tracker