Click here to Skip to main content
15,792,063 members
Articles / Programming Languages / C#
Article

Themed RichTextBox - A RichTextBox with XP-styled borders

Rate me:
Please Sign up or sign in to vote.
4.42/5 (16 votes)
11 Oct 2006CPOL3 min read 150.7K   1.5K   53   41
An article about XP-theming.

Sample Image

Contents

  1. Introduction
  2. Background
  3. Where to draw the border
  4. The implementation in C#
    1. Filter some Windows messages
    2. Find out if you should render with XP styles
    3. Get the device context of the window frame
    4. Draw the border using uxTheme.dll
  5. Points of interests
  6. History

1. Introduction

In this article, I will show you how to apply XP-styles to a RichTextBox. The only thing I do here is to derive from the RichTextBox and add support for XP-themed border style. The control is able to run on .NET 1.1 and .NET 2.0.

2. Background

Visual Styles are not fully supported in NET 1.1. There is a bug in the Application.EnableVisualStyles method, and some controls do not render the XP-styles correctly. Here is a good overview about what is going wrong in NET 1.1: VisualStyles. But now we have NET 2.0 and all these bugs are fixed, and nearly all controls render with XP-styles. The only exception is the RichTextBox control. So I decided to write this article.

3. Where to draw the border

Normally, in NET, all paintings are done in the OnPaint or OnPaintBackground methods of a control. The problem is, inside these methods, you always draw to the client area (ClientRectangle) of a control. However, the borders are drawn outside this area in the window frame that should be drawn during the WM_NCPAINT message. For a better understanding, look at this illustration...

windows frames

4. The implementation in C#

4.1 Filter some Windows messages in the RichTextBox

When we customize the window frame, we must filter some messages and process them.

  1. WM_NCPAINT message tells us that the border should be redrawn.
  2. WM_NCCALCSIZE message tells us that the client area should be calculated.
C#
/// <summary>
/// Filter some message we need to draw the border.
/// </summary>
protected override void WndProc(ref Message m)
{
    switch(m.Msg)
    {
        case NativeMethods.WM_NCPAINT:
        // the border painting is done here.
            WmNcpaint(ref m);
            break;
        case NativeMethods.WM_NCCALCSIZE:
        // the size of the client area is calcuated here.
            WmNccalcsize(ref m);
            break;
        default:
            base.WndProc (ref m);
            break;
    }
}

4.2 Find out if you should render with XP styles

To find out if we should render an application with visual styles, is not an easy task. There are three things to consider.

  1. Check if the OS supports XP-styles. Windows XP or above supports theming.
  2. Check if XP-styles are activated in the current environment.
  3. Check if XP-styles are enabled for the current application.

After Googling around in the web, I found a method that does the job: IsVisualStylesEnabled Revisited.

4.3 Get the device context of the window frame

To draw in the window frame, we need some Win32-APIs to get the corresponding device context (DC).

  1. GetWindowDC to get the device context of the window frame.
  2. GetWindowRect to get the rectangle of the window frame.
  3. ExcludeClipRect to exclude the client area of the window frame.
  4. ReleaseDC to release the DC.

Here are the declarations in C#:

C#
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

[DllImport("gdi32.dll")]
public static extern int ExcludeClipRect(IntPtr hdc, 
       int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

4.4 Draw the border using uxTheme.dll

Drawing visual styles is provided through the uxTheme.dll. We only need a few functions to draw the border.

  1. OpenThemeData to get a theme handle.
  2. GetThemeBackgroundContentRect to retrieve the size of the border.
  3. DrawThemeBackground draws the border.
  4. CloseThemeData releases the theme handle.

Here are the declarations in C#.

C#
[DllImport("uxtheme.dll", ExactSpelling=true, CharSet=CharSet.Unicode)]
public static extern IntPtr 
       OpenThemeData(IntPtr hWnd, String classList);

[DllImport("uxtheme", ExactSpelling=true)]
public extern static Int32 GetThemeBackgroundContentRect(IntPtr hTheme, 
       IntPtr hdc, int iPartId, int iStateId, 
       ref RECT pBoundingRect, out RECT pContentRect);

[DllImport("uxtheme", ExactSpelling=true)]
public extern static Int32 DrawThemeBackground(IntPtr hTheme, 
       IntPtr hdc, int iPartId, int iStateId, 
       ref RECT pRect, IntPtr pClipRect);

[DllImport("uxtheme.dll", ExactSpelling=true)]
public extern static Int32 CloseThemeData(IntPtr hTheme);

5. Points of interests

The .NET Framework 2.0 contains new classes for drawing visual styles. You can find them in the System.Windows.Forms.VisualStyles namespace. Also a method to check, if visual styles are enabled, is available in the new framework. Look for the Application.RenderWithVisualStyles property. However, I had not made two versions, because I think having one version for both is less to do on modifications and thus the better solution.

6. History

  • 11-10-2006 - Bug fix: Themed border disappears after MultiLine is set to false.
  • 17-07-2006 - Bug fix: Problem when mixing NET Framework versions.
  • 07-04-2006 - First release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) bemento
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralTried to apply same to UserControl but didn't work Pin
SerialHobbyist23-Sep-06 9:52
SerialHobbyist23-Sep-06 9:52 
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Bernhard Elbl2-Oct-06 6:17
Bernhard Elbl2-Oct-06 6:17 
QuestionRe: Tried to apply same to UserControl but didn't work Pin
Simon P Stevens14-Nov-06 3:55
Simon P Stevens14-Nov-06 3:55 
AnswerRe: Tried to apply same to UserControl but didn't work Pin
Bernhard Elbl14-Nov-06 5:42
Bernhard Elbl14-Nov-06 5:42 
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Simon P Stevens14-Nov-06 5:50
Simon P Stevens14-Nov-06 5:50 
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Bernhard Elbl14-Nov-06 6:16
Bernhard Elbl14-Nov-06 6:16 
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Simon P Stevens14-Nov-06 6:48
Simon P Stevens14-Nov-06 6:48 
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Bernhard Elbl14-Nov-06 9:09
Bernhard Elbl14-Nov-06 9:09 
You are on the right way. To avoid the toolbar/toolstrip or other control overlaying your border put them into panels and set the panel´s Anchor property.

For reasons of backward compatibility you should draw the border only when the border is set to Fixed3D

Here my updated version...

public class MyControl : UserControl
{
public MyControl()
{
}

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);

// draw the border only when visual styles are enabled and the border style is set to Fixed3D -> backward compatibility
if (this.BorderStyle == BorderStyle.Fixed3D && Application.RenderWithVisualStyles)
{
VisualStyleRenderer renderer = new VisualStyleRenderer(VisualStyleElement.TextBox.TextEdit.Normal);

// exclude the area inside the border, so only the border is drawn
Rectangle clipInsideBorderRectangle = renderer.GetBackgroundContentRectangle(e.Graphics, ClientRectangle);
e.Graphics.ExcludeClip(clipInsideBorderRectangle);

renderer.DrawBackground(e.Graphics, ClientRectangle);
}
}

protected override CreateParams CreateParams
{
get
{
CreateParams p = base.CreateParams;

// remove the Fixed3D border style, when visual styles are enabled
if (Application.RenderWithVisualStyles && (p.ExStyle & 0x200) == 0x200)
p.ExStyle ^= 0x200;

return p;
}
}
}

Bernhard
GeneralRe: Tried to apply same to UserControl but didn't work Pin
Simon P Stevens14-Nov-06 23:20
Simon P Stevens14-Nov-06 23:20 
GeneralLooks Good ... But Pin
RPANOSH25-Jul-06 4:42
RPANOSH25-Jul-06 4:42 
GeneralRe: Looks Good ... But Pin
Bernhard Elbl25-Jul-06 8:00
Bernhard Elbl25-Jul-06 8:00 
GeneralNice job! Pin
Realmax7-Apr-06 23:07
Realmax7-Apr-06 23:07 
GeneralRe: Nice job! Pin
Bernhard Elbl7-Apr-06 23:59
Bernhard Elbl7-Apr-06 23:59 
GeneralRe: Nice job! Pin
Pear Redsome23-Jul-07 22:29
Pear Redsome23-Jul-07 22:29 
GeneralDoesn't appear themed Pin
Dominik Reichl7-Apr-06 9:31
Dominik Reichl7-Apr-06 9:31 
GeneralRe: Doesn't appear themed Pin
Jon Kruger13-Jul-06 8:05
Jon Kruger13-Jul-06 8:05 
GeneralRe: Doesn't appear themed Pin
Jon Kruger13-Jul-06 8:07
Jon Kruger13-Jul-06 8:07 
GeneralRe: Doesn't appear themed Pin
Bernhard Elbl14-Jul-06 5:09
Bernhard Elbl14-Jul-06 5:09 
GeneralRe: Doesn't appear themed Pin
Bernhard Elbl20-Jul-06 8:44
Bernhard Elbl20-Jul-06 8:44 

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.