Mirror

Visual Styles in Delphi (Views: 732)

Problem/Question/Abstract:

Visual Styles in Delphi

Answer:

Microsoft Windows XP introduces (yet again) a new look. Windows, buttons, and other controls all look different than the way they used to. Some people like the new look; others would prefer that Microsoft didn't keep changing the rules on them. Personally, I find it visually appealing, but would rather have my CPU spend its time on something other than making sure hint windows fade properly.

When Microsoft introduces a new look (as they do with every version of Windows and Office), a lot of users expect new programs to support that look. A lot of programmers want to do that, too. Usually, when Windows controls change their appearance, the change is global. That is, every Windows application automatically gets the new look, whether it wants to or not. This is not the case with Windows XP. In fact, Microsoft has gone out of its way to make it difficult to support the new standard.

Visual Styles: Coming Soon to a Theatre Near You

Windows XP introduces a new concept: visual styles. Basically speaking, visual styles are window skins, just like those WindowBlindshttp://www.windowblinds.net/ used to support. In fact, WindowBlinds now supports XP visual styles as well. I won't get into the technical details of how visual styles are implemented and supported in this article - except to say that the visual styles API is accessible through ComCtl32.dll. That's important, because this DLL used to contain only the Windows "Common Controls". These controls were originally included with Internet Explorer, and then integrated into Windows. The library includes things like list views, tree views, and other interesting controls. It does not include the standard, or basic, Windows controls, such as edit boxes, combo boxes, and scroll bars. That is, it didn't use to. It does now.

If the new controls are simply implemented in a standard system DLL, why aren't applications automatically updated? The answer is simple - they aren't aware of the new DLL. The new version of the DLL (version 6.0) is only used if an application specifically asks for it. Otherwise, applications only see the older version of the DLL - pretty much the same version that was available on older versions of Windows. Microsoft actually had a technical reason for this - the new DLL works a little differently, and they couldn't guarantee full backward compatibility. So they made the programmers choose the version of the DLL: if you ask for the newer version, you're saying you know how to use it.

Visual Styles in Delphi

Delphi 7 adds support for visual styles. The common control wrappers (and some custom controls, such as TPanel) are now aware of XP themes and visual styles, and use the new version of the ComCtl32 library. For most applications, supporting visual styles is as simple as dropping a TXPManifest component on one of your forms.

TXPManifest is an interesting component - take a look at the source code, if you have it. It does nothing. Nothing at all. The component has no methods or properties. All it does is include the XPMan unit in your project. This in turn causes a certain resource file to be included in your project's executable file. The resource includes a manifest - a small piece of XML describing your application. The important point about this manifest is that this is how an application tells Windows it want the new version of ComCtl32. A section in the manifest specified the supported version of ComCtl32 - in this case, version 6. You're going to hear a lot about application manifests in the future - they're a key feature of .Net, and are Microsoft's solutionhttp://msdn.microsoft.com/library/en-us/dndotnet/html/dplywithnet.asp to the dreaded DLL Hellhttp://msdn.microsoft.com/library/en-us/dnsetup/html/dlldanger1.asp.

That's all well and good for normal applications, but what happens when you need to draw something, and it just doesn't look right? Custom components, owner drawn controls, or just plain drawing code that worked great on previous versions doesn't look right on XP. Things we've learned to take for granted, like 3D borders, are no longer part of the operating system's standard appearance.

Framed Controls: I Didn't Do It

A common example of how visual styles affect drawing is the DrawFrameControl function. This function, supported on all versions of Windows since Windows 95, draws the standard frame controls - scroll bars, buttons, check boxes, and so on. Calling the function in XP draws the controls using the old style - even if your application uses the new version of ComCtl32. Suppose you want to display a checkbox in a grid, or write a custom control that has a combo box drop down button: you used to be able to call DrawFrameControl. This doesn't work anymore.

You could use Microsoft's Theme API. The Theme API is quite complex. Fortunately, Delphi provides a wrapper class around it.

Let's start with a simple task: drawing a checked check box. We'll place a TPaintBox control on a form, and paint the check box in the OnPaint event handler.

procedure TForm1.PaintBox1Paint(Sender: TObject);
begin
DrawFrameControl(PaintBox1.Canvas.Handle, PaintBox1.ClientRect,
DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_CHECKED);
end;

OK, that's ugly. We forgot something - to get the proper size of a check box. We'll use the GetSystemMetrics function, and change the size of our paint area accordingly.



procedure TForm1.PaintBox1Paint(Sender: TObject);
var
R: TRect;
BtnWidth, BtnHeight: Integer;
begin
BtnWidth := GetSystemMetrics(SM_CXMENUCHECK);
BtnHeight := GetSystemMetrics(SM_CYMENUCHECK);
R := PaintBox1.ClientRect;
R.Left := (R.Right - BtnWidth) div 2;
R.Right := R.Left + BtnWidth;
R.Top := (R.Bottom - BtnHeight) div 2;
R.Bottom := R.Top + BtnHeight;
DrawFrameControl(PaintBox1.Canvas.Handle, R,
DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_CHECKED);
end;


That's better. The problem, however, is that even if we drop a TXPManifest component in the form, and everything else uses the new visual style, our check box still looks the same.

This is where we use ThemeServices, Delphi's wrapper for the Theme API. The ThemeServices object is declared in the Themes unit, so you'll need to add that to your uses clause.

Drawing a check box using the ThemeServices object isn't very complicated, but it is different from our previous code.

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
Details: TThemedElementDetails;
begin
Details := ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
ThemeServices.DrawElement(PaintBox1.Canvas.Handle, Details,
PaintBox1.ClientRect);
end;

The result is a check box that looks just right. Unless, of course, you try to click it. Which brings up another point: control states.

One of the things implemented by the new visual styles is support for various control states. In the world of themes, a state can be anything that affects how the control is drawn. It can be the actual state of the control, such as checked or disabled, or it can be related to user interaction. Specifically, controls now support the "hot" and "pressed" states. A control is hot when the mouse cursor passes over it. It is pressed when the used pushes it with the mouse.

You'll notice that before we drew the check box using the DrawElement method, we asked the ThemeServices object to fill a TThemedElementDetails record. This record contains the actual information Windows needs in order to draw the element. We asked for drawing information that would work for a checked, normal check box. If we needed to draw a clear check box, or a hot check box, we would have used a different parameter.

The important thing to remember here is that when using visual styles, we need to be aware of all possible states, and draw the control accordingly. Of course, this was true before, but there are more states to consider now.

Something else you might have noticed is that the code no longer check for the size of the check box. That's because the new-style check box always uses the system-defined size. Other elements (for example, scroll bar buttons) don't follow the same rule. It's just another quirk to remember. We get a bunch of them with every new version of Windows.

Backward Compatibility

So now we have code that draws a check box, and we know a little about ThemeServices and visual styles. What happens if you try to run this on an older version of Windows? Easy. It doesn't work.

Before using visual styles, we need to make sure they're actually supported and enabled. If not, we need to provide an alternative. The ThemeServices.ThemesEnabled property is set to True if themes are available on the system and the program is using the new version of ComCtl32. In all other cases, we should use the older style.

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
Details: TThemedElementDetails;
R: TRect;
BtnWidth, BtnHeight: Integer;
begin
if ThemeServices.ThemesEnabled then
begin
Details := ThemeServices.GetElementDetails(tbCheckBoxCheckedNormal);
ThemeServices.DrawElement(PaintBox1.Canvas.Handle, Details,
PaintBox1.ClientRect);
end
else
begin
BtnWidth := GetSystemMetrics(SM_CXMENUCHECK);
BtnHeight := GetSystemMetrics(SM_CYMENUCHECK);
R := PaintBox1.ClientRect;
R.Left := (R.Right - BtnWidth) div 2;
R.Right := R.Left + BtnWidth;
R.Top := (R.Bottom - BtnHeight) div 2;
R.Bottom := R.Top + BtnHeight;
DrawFrameControl(PaintBox1.Canvas.Handle, R,
DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_CHECKED);
end;
end;

Conclusion and More Code

Visual themes are a part of Windows XP, and we're going to have to get used to them. If wewant our user interfaces to match the system standard, we'll have to support them as well.

To wrap up this article, I've written a small component that demonstrates themed drawing of standard controls. TSPSysFrameButton is a TSpeedButton descendant that can look like one of the framed controls. You can download it herehttp://download.shorterpath.com/SPSysBtn.zip. You'll need to create a registration unit (one that implements the Register procedure) to install it.


Component Download: http://download.shorterpath.com/SPSysBtn.ziphttp://download.shorterpath.com/SPSysBtn.zip


<< Back to main page