Ask Kathleen

Add Custom Colors to Your Forms

Make your Windows Forms apps shine with custom color palettes; pros and cons for creating apps with an Office 2007 look-and-feel; and drill down on anonymous methods in C# and VB .NET.

Q
My application needs to retain a modern look, such as a gradient toolbar. How can I make different installations of my Windows Forms application use different color sets

A
WinForms 2.0 opens the door to several tricks for customizing the look of your application, including controlling visual elements of the toolbar through custom renderers and traditional techniques such as modifying foreground and background colors (Figure 1).

It's not too difficult to accomplish creating a custom look, but you need to proceed with caution and consider the usability, comfort, and accessibility of your application. Color perception varies among individuals and between screen displays. These variations are common, not easily predicted, and can make your application more difficult or less pleasant to use. High contrast displays are also a key accessibility feature for visually impaired users. So, if you create a display with colors the user didn't pick, be sure to let the user to opt out of your colors. Accessibility is more important than a corporate look.

The best place to start is with the toolstrip, which has a tremendous impact on the user's overall impression of your application. Windows Forms uses renderers to display the toolstrip and provides three renderers you can use unchanged in many situations. The ToolstripSystemRenderer gives the look of the operating system, the ToolstripProfessionalRenderer provides the Office 2003 look, and the ToolstripRenderer is the abstract base class if you want to develop a unique look and feel. You can derive a custom renderer from any of these classes.

In most cases, you want to derive from the ToolstripProfessionalRenderer because it typically requires the least modification to achieve the look you want. You can derive from this class to change the appearance of the toolstrip background, button background, and 18 other aspects of the visual display of toolstrips. You customize each by overriding an OnRender method such as OnRenderButtonBackground. Each of your overriding methods can use GDI techniques to draw whatever you need and be simple or complex.

You don't need to override these methods if you want to change the colors, but keep the gradient and control shape look provided by the ToolstripProfessionalRenderer. Instead, you can use the color table of the renderer. The color table provides specific color details for each of 56 different UI items. You alter the color table by deriving your own class from the ProfessionalColorTable and overriding the methods that retrieve each of the 56 colors. You pass an instance of this derived custom color table to the ToolstripProfessionalRenderer constructor to activate your color scheme. For example, assume you want to derive Altered-ColorTable from ProfessionalColorTable:

renderer = New ToolStripProfessionalRenderer( _
	New AlteredColorTable())

Some developers might have the patience to determine each of these 56 colors manually based on visual appeal in each of your color schemes, but I don't. Fortunately, you don't have to. Windows Forms features a Control-Paint class that provides a variety of utility methods for visual output. You can simplify the colorizing process by setting a base color in your custom color table so it returns a modified version for each of these 56 values. Once you set the base color, the custom color table uses the ControlPaint class's Dark and Light methods to modify the color intensity for display. The Dark and Light methods return colors that are lighter or darker than the colors you pass, increasing or decreasing their luminosity. Overloads of the Dark and Light methods let you specify the change as a percent. This handful of lines from the altered color table sets several aspects of the color scheme in relation to a base color supplied at runtime:

Public Overrides ReadOnly Property _
	ToolStripGradientBegin() _
	As System.Drawing.Color
	Get
		Return ControlPaint.Light(mBaseColor, 1)
	End Get
End Property

Public Overrides ReadOnly Property _
	ToolStripGradientEnd() _
	As System.Drawing.Color
	Get
		Return ControlPaint.Dark(mBaseColor, 0)
	End Get
End Property

You can download the remainder of the class here. You might want to make additional adjustments to the colors, which you can accomplish by fine-tuning the custom color table return values.

This takes care of the backgrounds, but the Toolstrip-ProfessionalColorTable doesn't include text colors, nor does it include the extra colors you need to set other controls on the form. You can extend your custom color table to include these new properties. Your custom color table can contain as many additional color values as you need, and you can set these as absolute colors or relative colors using the ControlPaint class.

For most other controls, the old-fashioned method of setting the background and foreground colors is effective. There are two issues to resolve here: how to access every control on the form and where to get the colors. It makes sense to extend the color table to contain these values. This provides a single point for color definitions and maximizes your ability to reuse colors. It also makes it easy to synchronize your toolstrip and other controls.

You probably want a single color table used throughout your application. In a simple, single-form application, you could just retrieve the color table from the toolbar renderer. In a more complex application, it makes more sense to keep the renderer and color table in a single location. You can create "singleton" access stored in any logical class of your application. This sample supports several colors, so it relies on a ColorManager class that handles the renderer and various colors (Listing 1).

If your user opts out of your coloring scheme, colors are provided by the ProfessionalColorTable. However, you can't use the ProfessionalColorTable class directly if you've created new color properties because it doesn't support them. The base class BaseApplicationColorTable defines all of the additional color properties the sample uses (Figure 2). The AlteredColorTable (Listing 2) and the ApplicationProfessionalColorTable interpret these and the original ProfessionalColor-Table properties.

Once you've established your colors, you can assign them to your controls with a recursive method that calls itself. You need to do this because the controls of a form might be deeply nested in containers. You'll also need special handling for controls that support unique color settings, such as the DateTimePicker (Listing 3).

This approach doesn't work for all controls. Some, like the ProgressBar, ignore the BackColor settings. Others, such as the CheckBox and DateTimePicker, don't offer complete control. The sample relies on a light color wash on well-behaved controls, which enables you to see these anomalies more easily. You can use the Paint event of these forms if you need additional control, but the Paint event requires GDI to take significant responsibility for painting. You must consider all situations within a paint event, such as different alignments and right-to-left behavior. In many cases, you can avoid this extra work by modifying the color tables and perhaps defaulting to the Windows and Windows-Text colors for the textbox and combo box. To give the project a test drive, download this article's sample code.


Q
I like the new Office 2007 look. Can I incorporate it into my applications?

A
There are two related issues for creating apps with an Office 2007 look-and-feel, one technical and one legal or political. Microsoft put a huge investment into this UI look-and-feel and is positioning itself to protect it. Assuming you don't compete directly with specific Office products, you can receive a license from Microsoft to use the Office 2007 look-and-feel. (It's a non-exclusive, perpetual, royalty-free license.)

Microsoft does requires that you follow specific design guidelines if you exercise this license and mimic the Office 2007 look-and-feel because Microsoft believes that the UI look-and-feel should be used together, not in bits and pieces. This is good because the work of creating a great and consistent UI is already done. It's bad because completely implementing this guideline on top of Windows Forms is extremely difficult. My gut feeling is that to meet the design guidelines would take multiple man-years of effort, which your project probably could not absorb. So, if you want this new UI look, you're probably best off using a third party control.


Q
What's an anonymous method? I heard a lot about them during the beta, but I haven't needed one, and I'm wondering if I'm overlooking something.

A
An anonymous method is better described as an anonymous delegate. Anonymous delegates allow you to define a piece of code that you can pass around like a delegate. Anonymous delegates differ from standard delegates because you define them using an abbreviated syntax that doesn't require defining a method to hold the code called by the delegate. Anonymous delegates are available in C#, but not yet available in Visual Basic. You define an anonymous delegate like this:

delegate(Customer customer)
	{ return customer.Country == "UK"; }

The first line declares that this is a delegate that takes a Customer object as a parameter. The return type is inferred from the second line. You can use this anonymous delegate anywhere you need a delegate that takes a Customer as a parameter and returns a Boolean value.

A great place to use an anonymous method is with the Find method and its siblings in the generic List class. The Find method takes a delegate parameter. The signature for this delegate has a single parameter that contains the current item in the list and returns a Boolean indicating whether the item matches. Without anonymous methods, you would have to create a method that performs the comparison and passes this method as a delegate reference to the Find method of the generic List class. With an anonymous delegate, you can skip the separate method:

public class CustomerCollection : List<Customer>
	public List<Customer> GetCustomersInUK()
	{
		return this.FindAll(delegate(
			Customer customer)
		{ return customer.Country == "UK"; });
	}

The problem is when you want to find items matching a value you won't know until runtime. The previous code returns the customers in the UK. You're more likely to need the customers in a specific country that's unknown until runtime. The key difficulty is passing this value into the delegate method. Anonymous delegates provide a solution because you can include the private value in the in-line code:

public List<Customer> GetCustomersInCountry(
	string country)
{
	return this.FindAll(delegate(
		Customer customer)
	{ return customer.Country == country; });
}

The private values contained in the in-line code are captured and you can access them easily from the in-line code. Capturing values this way is known as "closures." This is much simpler to code and understand than the alternative thread-safe solutions. Anonymous methods also put the decision in line in your code, making it easier to read.

Like many programming techniques, you can misuse anonymous delegates. Placing a large amount of code in-line through anonymous delegates makes code difficult to read, so it's better to create a separate, well-named method. You can either use this method as a normal delegate if you don't need closures or call it from the anonymous method if you need closures.

public List<Customer> GetCustomersComplex(
	string country)
{
	return this.FindAll(delegate(
		Customer customer)
	{ return ComplexComparsion(
		customer, country); });
}

private Boolean ComplexComparsion(
	Customer customer, string country)
{
	// complex comparison goes here
	return customer.Country == country;
}

You can use the local variables of the calling code within the body of the anonymous delegate however you want.


Q
I'm working in Visual Basic, and I have a function that finds values in a generic list using List.FindAll. But, I don't know the value I'm looking for until the user enters it at runtime. Can I store the value I'm looking for in a class-level variable so it's accessible to the FindAll delegate?

A
Using a class-level variable (also called a field) to hold a temporary value during the running of your application isn't a good idea because it's not thread safe. One of the big changes your code will weather in the next few years is increasing use of multiple threads as developers take advantage of multiple CPUs. Getting in the habit of writing thread-safe code, particularly utility code, will help you avoid costly debugging later. Without anonymous delegates, thread safety requires extra work.

Begin by placing the comparison method in a new nested or standalone class. You also need to include a constructor that takes the value you need to capture as an argument to the constructor.

Private Class CompareWithCountry
	Private mCountry As String

	Public Sub New(ByVal country As String)
		mCountry = country
	End Sub

	Public Function MatchesCountry( _
		ByVal customer As Customer) As Boolean
		Return customer.Country = mCountry
	End Function

End Class

Next, create an instance of this class and use the instance's comparison method as the delegate. This transitory instance is specific to this run of the CustomerCollection method, so no interference with calls from other threads can occur:

Public Class CustomerCollection
	Inherits List(Of Customer)
	Public Function GetCustomersInCountry( _
		ByVal country As String) _
			As List(Of Customer)
	Return Me.FindAll(AddressOf New _
		CompareWithCountry( _
			country).MatchesCountry)
	End Function 

About the Author

Kathleen is a consultant, author, trainer and speaker. She’s been a Microsoft MVP for 10 years and is an active member of the INETA Speaker’s Bureau where she receives high marks for her talks. She wrote "Code Generation in Microsoft .NET" (Apress) and often speaks at industry conferences and local user groups around the U.S. Kathleen is the founder and principal of GenDotNet and continues to research code generation and metadata as well as leveraging new technologies springing forth in .NET 3.5. Her passion is helping programmers be smarter in how they develop and consume the range of new technologies, but at the end of the day, she’s a coder writing applications just like you. Reach her at [email protected].

comments powered by Disqus

Featured

Subscribe on YouTube