Click here to Skip to main content
Email Password   helpLost your password?

Introduction

This article is about exploring the ability to create custom controls in the .NET platform, that make use of Windows XP's visual styles and themes. To that end I have included a couple of fairly simple controls that mimic some of the behaviors that Windows Explorer implements in XP.

Background

This work is really an extension of what I'm doing at my day job. The project I'm working on right now is a new, .NET version of an existing application. The existing version is your standard MFC app and has all of the familiar Windows 95 era GUI idioms. As part of the new version, we are attempting to add a much-needed update to the user interface.

Part of this effort is the desire to conform to Windows XP user interface styles and guidelines in all parts of the application.

The nice thing about writing a user interface in .NET is the ease with which controls can be created and wired together. Compared to MFC, with its message maps, SubclassDlgItems and WndProcs, concentrating on the needs of the user rather than the needs of the compiler is finally a possibility.

As much as I prefer this new way of creating robust user interface features, I do feel that there is one glaring omission from the .NET framework's System.Windows.Forms namespace: support for themes.

Sure, the Button class will pick the correct look and many of the other controls will take on a themed appearance, but as other articles on Code Project have pointed out, the support for themeing in .NET controls is only superficial.

This is especially true if you are creating your own controls, that need to define there own graphical features.

In order to ease the access to Windows XP's themeing API, I've created some wrapper classes to the UxTheme DLL. This set of controls makes use of that library.

Using the code

The trick to creating a theme aware custom control is that, you really have to take two paths through the painting process. One has to work on non-XP operating systems and on XP when themes are not enabled. The other has to work when the application is being themed on Windows XP.

The controls ExplorerBar, ExplorerGroup, SideBar and their common base class ThemeablePanel demonstrate this approach.

They all override OnPaintBackgournd and/or OnPaint and decide what sort of rendering to do, based on whether themeing is currently enabled. If it is not, they basically just call the base class functionality, and everything works as normal. If it is, they use the managed theme API to get the correct ThemePart objects that they want to use for their themed representation, and use it to draw the appropriate background and foreground features.

To do this the painting code finds the correct theme part and state, and renders itself using that object:

protected override OnPaint( PaintEventArgs e )
{
    ThemeInfo info = new ThemeInfo();
    WindowTheme window = info["EXPLORERBAR"];

    if ( UxTheme.IsAppThemed && window != null )

    {
        ThemePart part = window.Parts["HEADERCLOSE"];
        part.DrawBackground( e.Graphics, 
              new Rectangle( 0, 0, Bounds.Width, Bounds.Height );
    }
    else
       base.OnPaint( e );
}

That's about all there is to it.

You'll also notice that these controls also take over the rendering of some of their constituent controls by doing custom rendering in their Paint events. One could go an extra step, and make those constituent controls do their own theme rendering, by creating some derived classes. That is probably the direction I will head as things get more complex, but both approaches work.

Another thing you'll notice right away is that, there a number of classes derived from the common .NET controls like Label and PictureBox with names like ThemeLabel and ThemePictureBox. The reason for this is that, the .NET control classes don't like rendering themselves on a theme textured window (or at least I haven't been able to figure out how to get them to do so). Even when set to be transparent, there are visible artifacts around their edges when rendered over a themed background.

To correct this, you'll notice that all of these controls have a single method that overrides OnPaintBackground:

protected override void OnPaintBackground( PaintEventArgs pevent )
{
    if ( UxTheme.IsThemeDialogTextureEnabled( this.Parent ) && !DesignMode )
        UxTheme.DrawThemeParentBackground( this, 
           pevent.Graphics, new Rectangle( 0, 0, Width, Height ) );            
    else
        base.OnPaintBackground (pevent);
}

This override paints the control correctly on a themed background by rendering the background of the parent window rather than the window itself.

Another important point is that the user can change or disable themes at any point during the lifetime of your control. For this reason it is dangerous to hold on to any WindowTheme, ThemePart or ThemePartState object for any longer than a single paint operation. This ensures that your control will always paint correctly even if the environment changes. If it imposes a performance issue with reacquiring these objects with every paint, attach to the Microsoft.Win32.SystemEvents.DisplaySettingsChanged event, and dispose off and refresh your theme objects there.

Points of interest

The first control I worked on was the ExplorerBar class. After working on it an entire weekend (when I should have been outside enjoying the last of a beautiful Minnesota summer), I thought I pretty much had it nailed. I had been switching between the standard XP Blue Luna theme and the non-themed version of XP. So just to see how it looked in the green version of Luna, I switched over to the Olive color scheme.

And guess what! My ExplorerBar was still blue. After hours of scratching my head, digging and re-digging through the API it, began to dawn on me: Windows Explorer doesn't use UxTheme to render its ExplorerBar, even though the theme API defines parts and states for that type of control. Well it turns out that Windows Explorer uses yet another DLL named ShellStyles.dll to render its ExplorerBar, apparently bypassing UxTheme altogether.

My suspicion is that UxTheme is really not really fully cooked, and that it wasn't up to the requirements of Windows Explorer at the time they were developing it, and perhaps still isn't.

Regardless, this leaves the rest of us kind of out in the cold, because most of the custom themes I've looked at don't bother to redefine the ExplorerBar themes defined in UxTheme, but rather use the ones defined by VisualStyles.dll. So no matter what theme you choose, the ExplorerBar in this library will look like the Blue Luna version.

Is this a bug in my implementation? I'd like to say, no it isn't, because there's obviously an abstraction leak in the UxTheme API and it's really a bug there and in Windows Explorer. But I don't have $49 billion in cash reserves, so in reality the answer is probably yes from a user's perspective.

The fix, unfortunately, is to figure out how to dig the bitmaps out of VisualStyles.dll and render them in the correct places (if anybody's already done this and would like to share the code I'd love to see it).

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
QuestionVista compatiblty?
The Dogcow Farmer
4:45 29 Aug '08  
See question name.

Chuck Norris has the greatest Poker-Face of all time. He won the 1983 World Series of Poker, despite holding only a Joker, a Get out of Jail Free Monopoloy card, a 2 of clubs, 7 of spades and a green #4 card from the game UNO.
In the movie "The Matrix", Chuck Norris is the Matrix. If you pay close attention in the green "falling code" scenes, you can make out the faint texture of his beard.
Chuck Norris actually owns IBM. It was an extremely hostile takeover.

AnswerRe: Vista compatiblty?
Don Kackman
8:33 29 Aug '08  
Probably not. Might not break completely but likely won't help much with Aero stuff.
Generalman you are from future, cool ;}
radioman.lt@gmail.com
20:45 11 Dec '07  
"08/26/2203 - Initial release"

peace & serenity

JokeRe: man you are from future, cool ;}
Don Kackman
8:33 27 Jul '08  
Hehe - I travel back in time to post articles in antique languages. In 2203 we are up to the .NET Framework 124.5 and Y#.
GeneralHow can I move this to .Net 2?
fewfewfewfgwew
17:02 4 Mar '06  
Hi,

Thanks for the nice control, I trying to upgrade the control two .Net2 visual studio 2005, but failed. Anyone tried?

Thanks,
GeneralRe: How can I move this to .Net 2?
1tg46
17:44 4 Mar '06  
Have you tried making a 2005 solution and then just add the files for the current project into the 2005 solution. Then you can attempt to run the program and if it gives you a bunch of errors see if you can fix them. If this doesn't help you I don't know what to say. I would try to do this myself, but on the computer that I am using at the time I only have VS.NET 2003.

Regards,
1tg46

Check out 3D Game Development with Dark Basic Professional [^]programming.
GeneralRe: How can I move this to .Net 2?
John Tan Jin Kiat
1:21 20 Apr '06  
Due to the migration from managed C++ to C++/CLI, the codes need to be modified quite considerably.
QuestionRe: How can I move this to .Net 2?
onlydeepak4u
0:51 10 May '07  
DO you have the changed code?

Deepak
AnswerRe: How can I move this to .Net 2?
dttvn
13:44 20 Aug '07  
Try this

First, change whatever with this wchar_t __pin* as following. Then do for others compilation error
//wchar_t __pin* name = PtrToStringChars( parentPartName );
const wchar_t __pin* name = NULL;
name = PtrToStringChars( parentPartName );

+ comment out these lines in AssemblyInfo.cpp of System.Windows.Forms.Themes.
//[assembly:AssemblyKeyFileAttribute("key.snk")];
//[assembly:AssemblyKeyNameAttribute("")];

+ Add System.configuration to ExplorerControls project under Reference.

GeneralFree library
Stephen Lamb
18:39 16 Oct '05  
Users of this article may want to take a look at the free library here, http://www.skybound.ca/developer/visualstyles/default.aspx[^] (Please note that I am not affiliated in any way with the company that produces this library.) It has support for painting with themes and like the article, provides a wrapper around the UxTheme stuff.

I use the library mainly to get tab pages and numeric up/down controls to paint properly when theme-ing is enabled.


GeneralPoints of interest - fixed?
BaShOr
14:07 6 Apr '05  
I downloaded the demo project and tried to reproduce the effect outlined by the author in the last part of his article.
Has this already been fixed? I've discovered no problems after switching to the green theme. It seems to be displayed correctly.
GeneralRe: Points of interest - fixed?
Mathew Hall
15:30 6 Apr '05  
Looks like Microsoft finally got the colours right within luna.msstyles with XP SP2. However if you compare to the Windows Explorer version they are still different

"I think I speak on behalf of everyone here when I say huh?" - Buffy
GeneralRe: Points of interest - fixed?
Don Kackman
15:15 6 Sep '05  
Cool, I hadn't noticed that this was fixed in SP2 (haven't changed my theme in ages).

thanks for the heads up!
GeneralSideBar hides itself when mouse is over a scrollbar
Peter Kenyon
18:00 28 Feb '05  
If one of the child controls has a scrollbar and the user mouses over it, the SideBar thinks that the mouse has left the control, and hides itself.

Other than that, excellent work Smile
GeneralRe: SideBar hides itself when mouse is over a scrollbar
Don Kackman
16:14 19 Mar '05  
Peter,

Glad you like it. This was an early exploration of WinForms and there a couple of other mistakes in there as well:
- Using event rather than overrides in derived classes
- Not disposing pens, brushes etc.
- Not detaching from child control events
- Using a namespace that I don't control

C'est la vie. Live and learn I guess Sniff

Anyway, try this to fix the scrollbar problem:
private void timer1_Tick(object sender, System.EventArgs e)
{
System.Diagnostics.Debug.Assert( this.Parent != null );

Rectangle r = this.Parent.RectangleToScreen( this.Bounds );
if ( r.Contains( Control.MousePosition ) == false )
{
Showing = false;
timer1.Enabled = false;
}
}

One of these days I'll clean this up a bit and sumbit a new version.
GeneralNelsonWise
yeehoo3000
1:20 17 Feb '05  
Not sure why you go through all that trouble as in .NET 1.1 now you just call Application.EnableVisualStyles() in the Main() method.
GeneralRe: NelsonWise
Don Kackman
14:04 17 Feb '05  
EnableVisualStyles works for buttons etc., but if you are implementing a control that isn't made up entirely of built in windows controls, you want it to take on a look similar to the user's theme.

The ExplorerBar used in this article is one example of a custom control that you would want to look one way on 98/2000/unthemed XP and another way on XP with a theme applied.

GeneralAlso, getting rid of graphic artefacts when resizing a ThemeablePanel
dzCepheus
14:03 29 May '04  
Just add the following override to the ThemeablePanel object to get rid of those pesky graphic artifacts that sometimes appear when resizing it (I noticed these artifacts particularly when playing with the ExplorerGroup controls):

protected override void OnResize(EventArgs e)
{
base.OnResize(e);
this.Invalidate();
}

Hope this helps.

Skydive -- Testing gravity, one jump at a time.
GeneralRe: Also, getting rid of graphic artefacts when resizing a ThemeablePanel
Don Kackman
9:10 30 May '04  
Thanks for the suggestion. I haven't been in this code for awhile but here's a quick thought:
Does a call to SetStyle( ControlStyles.ResizeRedraw, true ) also fix the problem?
Might be a better way of solving it.

don
GeneralRe: Also, getting rid of graphic artefacts when resizing a ThemeablePanel
dzCepheus
10:20 30 May '04  
Ah, why yes it does. Smile A bit faster, too.

On the whole, though, the explorerGroup that Explorer uses doesn't resize, although I figured it's a customization option that might be nice to have.

As for the namespace -- true, always best to stay out of Microsoft's namespaces. You never know -- they may publish an explorerGroup control of their own. Wink

Oh, and I'm curious -- if I were to use this code in a commercial application, would you mind? :P

Thanks,
Erik

Skydive -- Testing gravity, one jump at a time.
GeneralRe: Also, getting rid of graphic artefacts when resizing a ThemeablePanel
Don Kackman
10:34 30 May '04  
dzCepheus wrote: Oh, and I'm curious -- if I were to use this code in a commercial application, would you mind?
If by "in a commercial application" you mean something unrelated to the re-selling components and conrols, then I would be flattered. I'll just expect a free copy Wink . (Seriously I'd be curious to hear more about the app if you do decide to use this.)


GeneralRe: Also, getting rid of graphic artefacts when resizing a ThemeablePanel
dzCepheus
12:26 30 May '04  
Hehe, it's a deal Smile If we decide to use it, I'll let you know. Smile Thanks a bunch!

Skydive -- Testing gravity, one jump at a time.
GeneralNamespace for SideBar incorrect
dzCepheus
13:53 29 May '04  
All of the controls use namespace System.Windows.Forms, except for SideBar, which uses namespace System.Windows.Fors

Fairly sure this isn't by design... Wink

Just thought I'd bring this to your attention. By the way -- excellent article, and great code sample. Thanks for sharing it! Smile

Skydive -- Testing gravity, one jump at a time.
GeneralRe: Namespace for SideBar incorrect
Don Kackman
9:06 30 May '04  
No that isn't by design. In truth, none of these controls shoulg be in System.Windows.Forms since that is a namespace which I do not control (unless I take over Microsoft some day Wink ). I'm not sure what I was thinking when I did that.
GeneralHow to use in VB.NET ?
mistert006
16:32 26 May '04  
Is there a way to use this technique with VB.NET?

Thanks


Last Updated 1 Sep 2003 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010