Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC
Article

Designing a Windows Control - Part 1

Rate me:
Please Sign up or sign in to vote.
4.96/5 (57 votes)
7 Nov 2002Ms-RL16 min read 248.3K   188   43
How to design a commercial quality Windows control

Introduction

This is the first of three articles about creating Windows controls. Article 1 (this) will explain how to design a good control, number 2 goes into coding details by creating a new control step-by-step and article 3 shows the Q&A aspects of controls and how to test and validate Windows controls.

Here we go…

Designing a Control

I will try to give you some tips how to design a better control. I have been doing GUI development for almost 10 years now and all of this writing is out of my own experience.

Many controls, which are available on websites like CodeProject, are nice pieces of code but not useable at all. At a first glance they seem to fit perfectly into the project you develop and do all that you need. You download it and with a little tweaking here and there, you build it into your application and the Boss is happy that you finished the project in time.

Unfortunately, this is seldom the end of the story when you develop a commercial application. Even if the new control passes your Q&A division (you have one, don’t you?), eventually the support team will report problems.

The most commonly encountered problems with custom controls are:

  • incomplete or even missing keyboard handling
  • distorted appearance with altered display settings
  • non-conformance with the users Windows settings
  • incorrect size with altered font size settings

All of this does really happen and it will happen to your customers. The biggest mistake a developer can make is testing the application on a developer’s computer. Never ever do this. Take the computer of the secretary, your aunt’s old 486, or even install a clean OS without the latest service packs and just choose a different display scheme. Also, test other OS languages – remember there is a world outside your office.

Tip: most developers do not have the resources to cleanstall (clean-install) various operating systems on various computers, there is a simple, yet efficient solution: emulators like VM-Ware (<a href="http: www.vmware.com="" "="">http://www.vmware.com [^]). They have a reasonable price and are perfectly suited for a developer’s testing environment.

These mistakes are the result of the fact that (almost) all these controls are developed for a specific purpose. End of the line. Few programmers go the extra mile to complete the design and deliver a fully functional control that will pass all tests. Even many commercial GUI libraries fail here.

Further reading on controls and control design:

http://msdn.microsoft.com/library/en-us/shellcc/platform/commctls/common/user.asp

http://msdn.microsoft.com/nhp/default.asp?contentid=28000443

Choose Your Weapons Wisely

When you intend to write your own control, stop before you write a single line of code and figure out if you really need to create a custom control or if it can be solved with a standard Windows control or a combination of them.

Windows provides a rich variety of controls, which are suited for almost any purpose and they are 99% bug free. Your custom control will reach this level only after many, many hours of use, testing and debugging. Often it requires a change in design to achieve the required results with the standard controls, but normally it is worth the change. You gain stability and standard conformance. Your users do not need to learn how to use your new control; they are familiar with the Windows ones. If you think that your users must have a button with a different background color and another font, go out and ask your users. They may like the colorful appearance, but if you confront them with a standard solution, they will agree that the standard solution is better to use.

Remember: Usability and customization seldom go hand in hand.

Ok, so you decided you must have an extraordinary whatever-control? Fine, lets go ahead and figure out how to do it right.

Figure out what is the target audience, the average user, of your application. This determines how you represent your data, how you label it, how it has to look.

Let us take an (bad) example: a color chooser. Most color chooser controls offer various methods to select or manipulate a color. One type of representation is the HSB/HSL model. In this (and other) model, the color is represented by three numeric values, named H, S and B (Hue, Saturation and Brightness). Now ask yourself, how many average home users know what this three letters mean?

Image 1 Look at the picture. This shot is taken from a widely used text processing application. Is “Sat:” a meaningful label? (Besides, in this specific application there would be even enough space to write the complete word instead of an abbreviation.)

If developers think before they write, they might come up with a better, more intuitive solution.

Ok, back to our design. Now that you know your audience, you can make a clear decision on the representation. Always keep in mind, Joe Averageuser does not want a cool looking control, but one that is easy to handle, that he understand it without pressing F1 or reading the manual. If it satisfies these requirements and is still cool looking, then you are one-step further on your bosses raise list.

A word about the keyboard: handle keys like Windows does – up is up and down is down. If you use the Enter key to select an item, your users will not understand it. MSDN has nice lists on keys and their suggested use.

Before you start coding, make a mockup of your control. Fire up your favorite paint program and sketch the control. Place the bitmap in your application. See how it looks and ask others what they think of it. Do they understand what it is? How would they use it?

If your dummy survives this simple test, you can – almost – start coding.

“Captain, focus ahead”

Now it is time to think about the controls usage, its navigation. In most cases, you know instinctively how to use it with a mouse. However, what about the keyboard? Many users (that includes me) like to use the keyboard for navigation, just because it is faster, just because you have to type something in a nearby field, or simply because the computer does not have a mouse attached.

Keyboard navigation always includes showing a focus. The focus is a visual feedback to indicate which part of your control the user is interacting with. This is the user’s only clue what he is doing. Again, look at the Windows controls to see how a focus is shown. The button shows a dotted rectangle and changes border, an edit control shows a caret at the insert position, a menu shows a colored selection. Imitate this behavior. Joe Averageuser is used to it.

I want to show you a small flaw in a Windows control, which confuses almost any (keyboard-) user and looks like a ‘forgotten’ feature: the focus rectangle in the Listview control. When it contains items, but none of the items are selected and the user navigates to the list by using the tab key, the list does receive the focus, but it does not show the focus rectangle. To the user it looks like no control has the focus. A simple solution is to intercept the WM_SETFOCUS message and set the LVIS_FOCUS state on the first list item if no item is selected or focused.

If you do not get it right at first, don’t worry – even Microsoft doesn’t always. (MS: How are we supposed to use the dropdown function of the “Open” button in Word XP’s File Open dialog?) Always ask others for their opinion about your control and if they understand it. Watch them using it.

Don’t forget that you have to remove the focus if your control or the application looses focus. There are certain windows messages, which you should handle because they may change focus state or style. To make things more complicated, these messages vary on different Windows versions.

Focus management means keeping some state variables around and up-to-date. This can be a tricky part of your controls logic. Depending on the complexity of your control you might even need to show focus in various forms – a caret when the user interacts with text, a dotted line on a button-type area, etc.

Another important visual feedback for focus – mouse focus this time – is hot tracking. Hot tracking means changing the visual appearance of an item or area when the user moves (hovers) the mouse over it. A good example, are the buttons in the toolbar of Internet Explorer. Here, the search button normal and hot-tracked.

Image 2

Image 3

When you implement features like this, adhere to the Windows settings and capabilities of the installed OS and IE version. This will make your control appear much more “Windows like” because it acts and reacts like all other standard controls.

The Windows API functions GetSystemMetrics() and SystemParametersInfo() are your best friends. They provide you all information that you need. If you cache this information in your own variables, you have to process the change notifications sent by Windows whenever the user changes a setting. Look at the WM_xxxCHANGED notifications in your MSDN.

Ma’, why is it so big?

Do you calculate the size of your control in pixel? Do you draw at certain locations? Don’t do it. When you place your control on a dialog, it is measured in dialog units. Dialog units scale depending on the display settings. When the user chooses a big default font, the dialog will be bigger and so will your control. Try it; select “Large Fonts” in the display properties. How does your control look now?

Always calculate sizes and locations relative. If you are going to display simple iconic graphics, consider using a TrueType font. Windows also uses a font to display the symbols on any window. The Minimize, Maximize, Close icons but also the scroll arrows are just letters from a custom font. Outlook uses a custom font for the attachment, priority and other symbols. Why? Because it scales. Bitmaps don’t scale. And it is yet ten times easier to output text than display a bitmap or even WMF file.

If you create items, which relate to Windows items, mimic Windows. Use GetSystemMetrics() to retrieve the size, dimensions or depth of the corresponding Windows element. For example, when you draw a 3D element, you can get the correct size by calling GetSystemMetrics(SM_CXEDGE) and GetSystemMetrics(SM_CYEDGE). This ensures that your control looks right in all configurations and Windows versions.

When you must display bitmaps or icons then be very careful with transparency and background colors. Often programmers forget that the background color of an icon is not gray. You can notice this in many pre-Windows 2000 applications when Microsoft used a different gray for the background of dialogs and windows. When you run these applications with altered colors (or under Windows 2000/XP) then you notice that the icons have a different gray. Image 4

So, make the background color transparent and set the transparency color correctly. You can easily check if you got it right, by changing the color of the 3D Objects in the display settings.

Never assume a color because the user will change it. Always use GetSysColor() and GetSysColorBrush() to get the currently active colors. What I said about the metrics, applies also for the colors: they may be changed at any time. Make use of the WM_xxxCHANGED notifications.

HWND, WPARAM and LPARAM

You cannot post virtual functions.

How are programmers going to use your control? Through a class, which implements the control, you will answer.

Not exactly the right answer. I know, it’s the common and most convenient way. But just look at the Windows controls – MFC programmers often forget that there is no function named SetExtendedStyle() in the Windows API. This is just the MFC class function which makes a simple SendMessage() with some parameters.

It takes some effort to implement a message-based interface instead of a class-based interface. But it has several advantages:

  • asynchronous calls (using PostMessage)
  • subclassing it and modifying/extending the behavior without source code
  • tracing messages with tools like Spy++ or Winspector[^]
  • integration with other programming languages and tools

Let us have a look at the conventional implementation:

class CMyControl
{
// ... some declarations here  
  void SetColor(RGB c) { m_color = c; Invalidate(); }
  RGB GetColor() { return m_color; }
private:  
  RGB m_color;
};

That is all that is. End of story. While this is the easiest and fastest implementation, it is also the most inflexible one. Even when you declare the SetColor/GetColor functions as virtual you don’t gain much. None of the advantages of a message-based implementation is here.

Now, the same in a message-based implementation:

// in the main header file MyControl.h
#define MC_SETCOLOR     (MC_BASEMSG + 1)
#define MC_GETCOLOR     (MC_BASEMSG + 2)

 

// in the class definition file MyControl_impl.h
class CMyControlImpl
{
// ... some declarations here
  void SetColor(RGB c) { SendMessage(MC_SETCOLOR, 0, (LPARAM)c); }  
RGB GetColor() { return SendMessage(MC_GETCOLOR); }
   
// if this is using MFC we have here the message handler declarations
  afx_msg LRESULT OnMsgSetColor(UINT, WPARAM, LPARAM);
 
private:
  RGB m_color;
};

 

// in the class implementation file MyControl_impl.cpp
 
BEGIN_MESSAGE_MAP(CMyControlImpl, CWnd)
  //{{AFX_MSG_MAP(CMyControlImpl)  
  ON_MESSAGE(MC_SETCOLOR, OnMsgSetColor)
  ON_MESSAGE(MC_GETCOLOR, OnMsgGetColor)  
  //}}AFX_MSG_MAP
END_MESSAGE_MAP()
 
LRESULT CMyControlImpl::OnMsgSetColor(UINT, WPARAM c, LPARAM)
{
  m_color = (RGB)c;
  Invalidate();
}
 
LRESULT CMyControlImpl::OnMsgGetColor(UINT, WPARAM, LPARAM)
{
  return (LRESULT) m_color;
}

This is quite a lot more code to write. But any developer using this control will need only the first file MyControl.h which contains all message definitions. If your control is complex or you have enough time and motivation at hand, then you provide a wrapper class and/or macros.

#define MyControl_SetColor (hwndMC, c) \
    (int)SNDMSG((hwndMC), MC_SETCOLOR, (WPARAM)(c), 0L)
#define MyControl_GetColor (hwndMC) \
    (int)SNDMSG((hwndMC), MC_GETCOLOR, 0, 0L)

class CMyControl
{
publicvoid SetColor(RGB c) { MyControl_SetColor(m_hWnd, c); }  
  RGB GetColor() { return MyControl_GetColor(m_hWnd); }
  }

This is exactly what MFC is doing (with a little more error checking in-between) with all the standard Windows controls. Have a look at the CEdit class in the MFC source files. Learn how they did it. Also, look into the commctrl.h file in the VC include directory. This file contains the definitions for all Windows controls.

Using this method, you gain all advantages mentioned before. It is more work because of all the definitions and extra header files. In the end, you cannot avoid this extra step when you want to create commercial quality controls.

If you ever wrote controls, you will know the pain of debugging GUI code. With the message-based approach things get easier – attach Spy++ or even better Winspector[^] to your control and see the messages flow. You can easily spot any wrongly sent message, any out-of-range parameter and any return value.

One last point to enhance your control: do not call exposed functions directly. Let me demonstrate this with our sample.

Assume we have another function called UpdateData(), which at some point has to set the color and thus either calls the OnMsgSetColor() directly or even worse, modifies the m_color variable. You will never be able to figure out why the control changes color, if you don’t trace into the UpdateData() function. Would this function send a MC_SETCOLOR message, it would be obvious why the color change happens.

Of course, for more complex functions you might need to define structures which are passed by pointer to the control. Even if you have to pass more then 2 parameters you will need to squeeze them into a structure already or you manage to use short int parameters and pass them with the MAKEWPARAM() or MAKELPARAM() macros.

Who Would “Print” a Control?

Well, most probably not many people do this. I agree that “print” is not the best choice of words for the purpose. Something like “render” would have been more appropriate.

By now, you may have realized that I am talking about the WM_PRINT and WM_PRINTCLIENT messages. They are not much different from the normal WM_PAINT message, but they may be invoked without an invalid area. At any time, the application can request a control to paint itself into a given DC by sending a WM_PRINT or WM_PRINTCLIENT message.

To implement these messages you only have to move your painting code into a separate function, which takes a DC as input parameter. The difference between WM_PRINT and WM_PRINTCLIENT is that the first should render the whole control window including non-client areas and the later should only render the client area.

Implementing these messages is required to support some of the newer Windows API functions like AnimateWindow().

Terminal Services Awareness

Terminal services are no longer a buzzword but reality for many companies and all Windows XP users. They are available by default on any Windows XP installation just that they are now called Remote Desktop. Also available as Windows NT 4.0 Terminal Services and Citrix Terminal Server and probably under some other names. Do not underestimate how widely used this is in enterprises. This is definitely a feature you want to add to your windows control.

There are few things to consider when you plan to make your control TS aware: the painting code must be optimized and mouse interaction should be minimized. By detecting TS sessions and handling the WM_WTSESSION_CHANGE message, the control can minimize graphic effects when used in a terminal session. Advanced features like hovering can be disabled; the ever-popular flat controls, which show/hide the border when active, may revert to normal operations, and so on.

With careful planning, TS awareness can be added without breaking compatibility to Win9x systems.

The Modern Architecture Thingy

With each new Windows version, Microsoft adds some new features to the existing controls, new windows messages, new styles. Most of these features are easy to implement if you plan your control carefully.

One of these new things is the UI status feedback. You can turn it on or off in Display Settings / Appearance / Effects. It is titled “Hide underlined letters for keyboard navigation until I press the Alt key”. The context help of this item already reveals that there is more to it than they tell with this clunky title.

This special feature is hidden behind three new windows messages: WM_QUERYUISTATE, WM_UPDATEUISTATE and WM_CHANGEUISTATE. These messages simply take or give a bitmap specifying which feature should be turned off or on. The only thing your control has to do is consider these bits when drawing the control or its visual feedback.

Another big thing is Windows XP’s Theme support. Fully supporting themes is lots of work, if your control uses default windows elements (buttons, scrollbars, dropdown indicators, etc) as parts of the window, you must implement Theme support. There is no way around. Otherwise, your control looks awful between all the themed controls in your customer’s application. Fortunately, there are a few helper classes on sites like CodeProject, which you can easily use in your implementation.

Conclusion

Writing good controls is hard work. Designing a controls interface takes time. A control that you created for your own application will never be a complete solution. It just implements what you needed. The next programmer using your control may have very different requirements to the interface or environment. If you intend to publish your control, review the interface and code carefully and add functionality that is missing.

Any Set… function should have a corresponding Get… function. Even if you do not need it in your application, others will need. When your control provides the function already, then it is reusable. Otherwise, it is just a code fragment, which needs massive work.

If your control uses constants or #defined measures, think if they can be made configurable. Do not impose limits just because you did not need more.

 

That’s all for now. I will update this article based on your feedback and any further ideas. I hope you will read on when the next part of this series is written.

License

This article, along with any associated source code and files, is licensed under Microsoft Reciprocal License


Written By
Software Developer (Senior)
Portugal Portugal
Software Smith, Blacksmith, Repeat Founder, Austrian, Asgardian.

Comments and Discussions

 
QuestionDesigning a PROFESSIONAL-looking Winform application Pin
robertd9038-Apr-22 4:59
robertd9038-Apr-22 4:59 
GeneralMy vote of 1 Pin
Pandele Florin23-Feb-09 23:20
Pandele Florin23-Feb-09 23:20 
GeneralInteroperabillity Pin
docrob129-Jan-07 3:47
docrob129-Jan-07 3:47 
Generalwonderful Pin
Saffarian27-Aug-05 7:31
Saffarian27-Aug-05 7:31 
GeneralStill Waiting Pin
Anonymous8-Dec-03 1:53
Anonymous8-Dec-03 1:53 
GeneralDoubt regarding activex Pin
Bhavnesh24-Jul-03 20:31
Bhavnesh24-Jul-03 20:31 
GeneralAnxiously waiting for 2nd article??? :( Pin
alex.barylski23-Apr-03 22:26
alex.barylski23-Apr-03 22:26 
GeneralRe: Anxiously waiting for 2nd article??? :( Pin
Andreas Saurwein24-Apr-03 1:14
Andreas Saurwein24-Apr-03 1:14 
GeneralRe: Anxiously waiting for 2nd article??? :( Pin
Andreas Saurwein22-Nov-03 18:00
Andreas Saurwein22-Nov-03 18:00 
QuestionRe: Anxiously waiting for 2nd article??? :( Pin
mlyons27-Jan-09 15:43
mlyons27-Jan-09 15:43 
GeneralNothing replaces hands-on experience Pin
TBiker14-Nov-02 16:54
TBiker14-Nov-02 16:54 
GeneralRe: Nothing replaces hands-on experience Pin
Andreas Saurwein15-Nov-02 15:09
Andreas Saurwein15-Nov-02 15:09 
GeneralWM_PRINT versus WM_PRINTCLIENT Pin
Bletch8-Nov-02 16:06
Bletch8-Nov-02 16:06 
GeneralRe: WM_PRINT versus WM_PRINTCLIENT Pin
Andreas Saurwein10-Nov-02 3:48
Andreas Saurwein10-Nov-02 3:48 
QuestionMFC Implementation Details? Pin
Maxime Labelle30-Oct-02 23:33
Maxime Labelle30-Oct-02 23:33 
AnswerRe: MFC Implementation Details? Pin
Andreas Saurwein31-Oct-02 1:43
Andreas Saurwein31-Oct-02 1:43 
GeneralCreating drop in re-placements Pin
alex.barylski28-Oct-02 22:45
alex.barylski28-Oct-02 22:45 
GeneralRe: Creating drop in re-placements Pin
Andreas Saurwein30-Oct-02 3:02
Andreas Saurwein30-Oct-02 3:02 
GeneralPostMessage / SendMessage Pin
DrFretBoard28-Oct-02 21:34
DrFretBoard28-Oct-02 21:34 
GeneralRe: PostMessage / SendMessage Pin
Andreas Saurwein30-Oct-02 3:08
Andreas Saurwein30-Oct-02 3:08 
QuestionWhat is LVIS_FOCUS? Pin
nilaysoft25-Oct-02 20:18
nilaysoft25-Oct-02 20:18 
AnswerRe: What is LVIS_FOCUS? Pin
Andreas Saurwein26-Oct-02 9:05
Andreas Saurwein26-Oct-02 9:05 
GeneralVery good. Pin
Matt Gullett25-Oct-02 3:55
Matt Gullett25-Oct-02 3:55 
GeneralRe: Very good. Pin
Andreas Saurwein25-Oct-02 5:14
Andreas Saurwein25-Oct-02 5:14 
GeneralGreat tips Pin
Roger Allen25-Oct-02 3:27
Roger Allen25-Oct-02 3:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.