Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / C#

Divider Panel - A Tutorial on Creating a Custom Windows Forms Control from Start to Toolbox

Rate me:
Please Sign up or sign in to vote.
4.92/5 (258 votes)
13 Sep 200417 min read 583K   6.6K   498   110
How to build a custom C# Windows Forms control
This is a tutorial on building a custom C# Windows Forms control, covering topics such as inheritance, attributes, overrides, documentation, designers, signing and VS.NET ToolBox integration.

Divider Panel Tuorial Part I- Creating a custom Windows Forms control from Start to Toolbox

Contents

Introduction

In this tutorial, we will walk through the process of creating a custom Windows Forms control from the start of the project all the way to inclusion in the Visual Studio ToolBox. The control will be a simple DividerPanel - an inherited Panel control that features selectable border appearance and border sides. After completing this tutorial, readers should have a basic foundation on class inheritance, creating custom properties, overriding base methods, using property comments, creating a ToolBox icon, creating a simple designer class and integrating a custom control into the Visual Studio ToolBox. Along the way, we will discuss some best practices and detail some shortcuts in Visual Studio that help to simplify control development.

Creating a New Solution

Creating a new Blank Solution

When creating a new project in Visual Studio for control development, it's usually a good idea to start with a new Blank Solution rather than jumping straight into a new Control Library project with the project wizard. In doing so, you can create multiple projects within the one solution - this allows your test application and control library to remain as separate projects, and also adds the ability to easily share linked classes and include global solution items.

To create a new Blank Solution, select File > New > Blank Solution. In the New Solution dialog, enter the solution name as Windows Forms Divider Panel and click OK.

Adding a new project

Once your new solution has been created, right-click on the solution title and select Add > New Project. When the Add New Project dialog opens, select the Windows Control Library option, and enter DividerPanel as the project name.

The wizard will create the control library with two files by default: UserControl1.cs and AssemblyInfo.cs. For this tutorial, we will delete UserControl1.cs and create our control in a new, empty class.

Highlight UserControl1.cs, then right-click and select Delete to remove it from the project.

Adding a new class

Next, right-click on the DividerPanel project in Solution Explorer, then select Add > Add Class from the context menu. In the Add Class dialog, enter DividerPanel.cs as the class name and click OK.

Inheriting From Existing Controls

Inheritance is one of the major factors that makes object oriented programming so powerful. When we inherit from an existing class, we automatically pick up all of the base classes' functionality and gain the ability to extend upon it to create a more specialized class. All Windows Forms controls at some point must inherit from System.Windows.Forms.Control, as it encapsulates all of the basic properties and methods the framework needs to host a class as a control on a Form.

Fortunately, inheriting from an existing control is a snap - once you have decided the control that has the base functionality you wish to extend upon, it takes just one addition to your class declaration line to make your class inherit from it:

C#
public class DividerPanel : System.Windows.Forms.Panel
{
}

For our DividerPanel control, we have specified that we are inheriting our base functionality from the standard System.Windows.Forms.Panel control. In doing this, our new control now has all of the Properties and Methods of the Panel control - we can now add our own custom properties to it, and override some of the Panel controls methods in order to implement our customizations.

Adding Properties and Accessors

In order to implement our DividerPanel, we are going to add two new properties: BorderSide and Border3DStyle. For the BorderSide property, we will be using a reference to a value from the System.Windows.Forms.Border3DSide enumeration, and for the Border3DStyle property, we will be using a reference to a value from the System.Windows.Forms.Border3DStyle enumeration.

While there a few different ways we can expose these properties, there is only one good way - create a private variable for use by methods within our control class, and a complementary public accessor for exposing the property to other classes that will use the control, like so:

C#
// This system of private properties with public accessors is a
// best practice coding style.
// Note that our private properties are in camelCasing -
// the first letter is lower case, and additional word 
// boundaries are capitalized.

private System.Windows.Forms.Border3DSide borderSide;
private System.Windows.Forms.Border3DStyle border3DStyle;

// Next we have our public accessors, Note that for public accessors
// we use PascalCasing - the first letter is capitalized and additional
// word boundaries are also capitalized.

public System.Windows.Forms.Border3DSide BorderSide
{
    get { return this.borderSide; }
    set
    {
        if( this.borderSide != value )
        {
            this.borderSide = value;
            this.Invalidate();
        }
    }
}

public System.Windows.Forms.Border3DStyle Border3DStyle
{
    get { return this.border3DStyle; }
    set
    {
        if( this.border3DStyle != value )
        {
            this.border3DStyle = value;
            this.Invalidate();
        }
    }
}

In the above code, we first define two private properties: borderSide and border3DStyle - these are the variables we will use within our class. As these are both defined with the private attribute, they cannot be accessed by any code outside of our control class. You will also note that I have specified the full path to the property objects:

C#
// good
private System.Windows.Forms.Border3DSide borderSide;

As I have included a using System.Windows.Forms directive for my class, I could have just declared the property as:

C#
// bad
private Border3DSide borderSide;

But it is always good practice to include the full path to alleviate possible naming obscurities.

Next we have two public properties, both using get and set accessors. In both cases, the get accessor simply returns the value of our private property, while the set accessor first checks if the value being set differs from current, and only if so it updates our private property and calls the Invalidate() method to force our control to repaint.

We could have simply created two public only properties as in the sample below, but then we could not do any processing such as the Invalidate() call on set, and there would be no way for our class to know when a value had been changed.

C#
// This is bad coding, never expose public properties like this!
// Always use private variables with complementary public accessors

public System.Windows.Forms.Border3DSide BorderSide;
public System.Windows.Forms.Border3DStyle Border3DStyle;

Even if you don't intend on doing any processing in you public accessors, you should still always stick to good quality coding conventions such as this. The next point is my choice of naming conventions - many people still use naming conventions from other languages such as m_BorderSide instead of my choice of borderSide. My personal choice is based on three different sources: first is the names that Visual Studio will automatically assign to controls that are dragged and dropped from the ToolBox, and second is that fact that the framework itself uses this naming convention for most of its private properties (a quick look at any of the framework classes with a decompiler or reflector will confirm this fact). If Microsoft decided it was the best practice naming convention for the .NET framework itself, it's natural to assume it's the best practice when coding for the .NET Framework.

The third reason for not using prefixes such as m_ to denote member variables is that you cannot take full advantage of Visual Studio's Intellisense features if you do so. Which brings me to another point of note in the accessor code, my usage of the this prefix - technically, there is no need and no advantage in using the this directive in the above accessor code. However, by using the this directive, you can take advantage of Intellisense's auto word completion system to fill in the rest of your variable name, saving coding time and saving possible problems with typos.

Any variables you define should always be explicitly assigned a value before they are used. Always set initial values in the class's constructor, or in a method called from your constructor. Variables without initial value assignments can sometimes create problems that can take hours to find at a later time. For our DividerPanel class, the Constructor looks like this:

C#
// This is the Constructor for our class. Any private variables 
// we have defined should have their initial values set here. It 
// is good practice to always initialize every variable to a specific 
// value, rather then leaving it as an inferred value. For example, 
// all bool's should be set to false rather than leaving them as an 
// inferred false, and any objects referenced that are initially null 
// should be explicitly set to null. (eg. myObject = null)

public DividerPanel()
{
    // Set default values for our control's properties
    this.borderSide = System.Windows.Forms.Border3DSide.All;
    this.border3DStyle = System.Windows.Forms.Border3DStyle.Etched;
} 

The constructor is the first method called in any class, and is called upon instantiation. You would normally carry out any initialization work necessary here, to ensure that everything is ready to go before any other methods can be called by code using your class. Constructors are always named the same as their parent class, and every class that is to be used as a control must have a parameter-less public constructor if it is to be used with the Visual Studio designer, or be visible to COM clients.

Overriding Inherited Methods

When you override a method from a base class, the CLR will run your code instead of the code normally contained in the base class's corresponding method. This allows you to easily change the behaviour and extend upon the functionality of most of the base controls in the framework. In order to add a border to our DividerPanel, we are going to override the OnPaint method in the base Panel control. Once we have overridden a base class's method, we can still call the base functionality by using the base keyword.

Viewing the inheritance path using the Class View

Naturally, every method has its own unique parameters and in order to perform an override, we need to know exactly what those parameters are. Fortunately, Visual Studio makes it a snap for us to add overrides when creating custom controls. On the right side of the Visual Studio window, you have the Solution Explorer displayed by default, at the bottom of the Solution Explorer panel, click the tab labelled Class View. Next, expand out the DividerPanel class and you will see something the same as pictured above.

The Class View is also handy for understanding the path of inheritance your control uses. In our case we can see that our inheritance path is: DividerPanel < Panel < ScrollableControl < Control < Component < MarshallByRefObject < Object.

Using the Class View to simplify overriding methods

For our DividerPanel control, we need to override the OnPaint method, which as you will see is inherited all the way down the tree from the Control class. Locate the OnPaint method, right-click it and select Add > Override.

Visual Studio will then insert an overridden version of the OnPaint method into our class, and we can then add the code that will give our control its extended functionality.

C#
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
    // allow normal control painting to occur first
    base.OnPaint(e);

    // add our custom border
    System.Windows.Forms.ControlPaint.DrawBorder3D (
        e.Graphics,
        this.ClientRectangle,
        this.border3DStyle,
        this.borderSide );
}

The first line we'll add to the overridden method is base.OnPaint(e); - this line executes the overridden code from the Panel control, passing the PaintEventArgs value. If we excluded this base.OnPaint(e); method call, we would have to take care of painting everything that our DividerPanel needs ourselves, which of course means we would have to write code that handles all of the painting required for a Panel control.

After the base control has been painted (and only after), we use the System.Windows.Forms.ControlPaint.DrawBorder3D method to paint our custom border(s) over the top of the Panel control, using the values from the custom properties we added earlier. As a side note, the System.Windows.Forms.ControlPaint class is worth your time to investigate as it contains a bevy of static methods that simplify the painting of many Windows type controls such as Buttons, CheckBoxes, RadioButtons, Grids, etc.

At this stage, we now have a fully functional DividerPanel control, that could be compiled and manually referenced in an application, but don't stop now because we are far from finished!

Adding Property Descriptions & Documentation Support

In order to make our control look and feel like one from the framework ToolBox, we need to add descriptions to our new public accessors, and create a new properties group. Without this, our new properties will be listed as Misc, and it will not be clear exactly what our new properties are intended to do to other developers. Once again, adding property descriptions is a snap, and while we're at it, we'll include support for creating XML documentation files which can later be created with utilities such as NDoc.

Adding property descriptions

To achieve these, we'll modify our public accessors using the code sample below. The <summary> lines are used to generate XML documentation files by Visual Studio - to add a summary to any property or class, simply position the caret to a line directly above your property/class and enter three forward slashes (///). Visual Studio will automatically build the required summary structure and all you need to do is fill in the blanks.

The next addition we make here is the designer attributes line, starting with the Bindable attribute. We'll step through the attributes added here one by one:

Bindable(true) - When set to true, any changes to this property will raise a property changed notification, which makes your designer class (discussed later) aware of changes made at design time.

Category("Border Options") - The Category attribute specifies where this property should be grouped in the Properties panel of the forms designer. If this attribute is not set, your new property will be listed under "Misc".

DefaultValue(System.Windows.Forms.Border3dSide.All) - This attribute tells the Visual Studio designer what the default value of the property is, and is used to highlight changes from the default value with bold text. In our screenshot, you can see that the value "Etched" is not in bold because we are using the default value, but the value "Top" is in bold because we have changed it from the default value of "All". Note that this attribute has nothing to do with the default value assigned to the property - you must still set a default value for all properties in your controls' constructor.

Description("Specifies the sides of the panel to apply a three-dimensional border to.") - the Description value is displayed at the bottom of the properties panel whenever a property is selected, and should clearly convey to developers what the property does.

C#
/// <summary>
/// Specifies the sides of the panel to apply a three-dimensional border to.
/// </summary>
[Bindable(true), Category("Border Options"), 
DefaultValue(System.Windows.Forms.Border3DSide.All),
Description("Specifies the sides of the panel to apply a 
three-dimensional border to.")]
public System.Windows.Forms.Border3DSide BorderSide
{
    get { return this.borderSide; }
    set
    {
        if( this.borderSide != value )
        {
            this.borderSide = value;
            this.Invalidate();
        }
    }
}

/// <summary>
/// Specifies the style of the three-dimensional border.
/// </summary>
[Bindable(true), Category("Border Options"), 
DefaultValue(System.Windows.Forms.Border3DStyle.Etched),
Description("Specifies the style of the three-dimensional border.")]
public System.Windows.Forms.Border3DStyle Border3DStyle
{
    get { return this.border3DStyle; }
    set
    {
        if( this.border3DStyle != value )
        {
            this.border3DStyle = value;
            this.Invalidate();
        }
    }
}

Adding Toolbox Support

Adding Toolbox support to our new control is just a simple, and involves creating a bitmap to be used as a Toolbox icon for our control, and setting a couple of attributes so that Visual Studio knows how to display our control in the Toolbox.

Adding a Toolbox icon

To create an icon for our control, right-click on our DividerPanel project entry in Solution Explorer, then click Add > New Item. In the dialog that opens, select Bitmap File and set the name to DividerPanel.bmp.

Now highlight DividerPanel.bmp in Solution Explorer and set the Build Action to Embedded Resource.

Next open the DividerPanel.bmp for editing, and set both the Height and Width to 16, and set the Colors property to 16.

Now paint your icon bitmap and save it:

Painting a Toolbox icon

The final step is to add two new attributes to our DividerPanel class, so that the compiler knows to associate the bitmap and to allow the control to be included in the Visual Studio Toolbox:

C#
[ToolboxItem(true)]
[ToolboxBitmap(typeof(DividerPanel))]
public class DividerPanel : System.Windows.Forms.Panel
{
}

The first attribute we are adding allows our class to be used as a Toolbox item - if you omit this attribute, Visual Studio will imply it is already set to true, but as always it is good coding practice to explicitly set this attribute so that your original intentions are always clear when revisiting your code. The second line tells the compiler to associate the DividerPanel.bmp file with our control. The name specified in this attribute is simply the resource name of the bitmap, excluding the .bmp extension. As a final note, Toolbox icons must always be bitmap files, with a maximum color depth of 16 colors, and dimensions of 16x16.

Adding a Simple Designer Class

We could now compile our control, add it to the Toolbox and start dragging onto Forms at will, but there's one potential problem we need to address first:

The Panel control we derived from has a BorderStyle property which can be set to None, FixedSingle or Fixed3D - as we are going to be painting our custom border over the top of the standard Panel control, we should remove the BorderStyle property so that there's no confusion about which property developers should use, and also to avoid ugly visual artifacts from someone setting styles for both sets of properties.

There are a few ways to achieve the result we want, but the cleanest way is to create a simple designer class that filters the property list, removing BorderStyle from the properties window altogether. Doing it this way leaves the BorderStyle property intact so that developers can still set it programmatically in their code should they wish to do so.

To start our designer class, right-click on the DividerPanel project in Solution Explorer, then select Add > Add Class from the context menu. In the Add Class dialog, enter DividerPanelDesigner.cs as the class name and click OK.

Adding a reference to System.Design.dll

To implement our designer class, our project needs to reference System.Design.dll from the framework. To add the reference, right-click on References in Solution Explorer, then click Add Reference. In the Add Reference dialog, select System.Design.dll as pictured above, and click OK.

Open DividerPanelDesigner.cs, and change the Class line so that we are inheriting from the System.Windows.Forms.Design.ScrollableControlDesigner class:

C#
public class DividerPanelDesigner : 
   System.Windows.Forms.Design.ScrollableControlDesigner
{
}

Overriding the PreFilterProperties method

Now change to Class View and expand our DividerPanelDesigner class until you get down to the ControlDesigner class, the inheritance path being: DividerPanelDesigner < ScrollableControlDesigner < ParentControlDesigner < ControlDesigner. Now locate the PreFilterProperties method, right-click it and select Add > Override.

As before, Visual Studio will automatically add the overridden method into our designer class code. All we have to do now is add the code to filter out our unwanted BorderStyle property:

C#
protected override void PreFilterProperties(
   System.Collections.IDictionary properties)
{
    properties.Remove("BorderStyle");
}

And finally, add a designer attribute to our DividerPanel class to link our designer class like so:

C#
[ToolboxItem(true)]
[ToolboxBitmap(typeof(DividerPanel))]
[DesignerAttribute(typeof(DividerPanelDesigner))]
public class DividerPanel : System.Windows.Forms.Panel
{
}

For this tutorial, that's all our designer class needs to do, so we'll leave it at that for now. Creating designer classes is a good subject for a book, not a simple tutorial such as this, but at least you now have an introduction on how to create and use them.

Assembly Attributes & Signing

The final steps we should do before releasing our new control on the unsuspecting public are related to the compilation process. The additions discussed here are all non-essential but do represent best practice coding and a very small amount of work, so there's no reason to omit them.

First, we'll open up the AssemblyInfo.cs file and provide some values to the default assembly attributes as follows:

C#
[assembly: AssemblyTitle("Divider Panel")]
[assembly: AssemblyDescription(
  "A Panel control with selectable border appearance")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("CodeShack")]
[assembly: AssemblyProduct("Divider Panel Tutorial")]
[assembly: AssemblyCopyright("Copyright (c) 2003-2004 CodeShack")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

Next, we'll add a couple more assembly attributes that are not included by default (I'll let my code comments speak for themselves):

C#
// Flagging your assembly with the CLS Compliant attribute
// lets the Framework know that it will work with all
// CLS Compliant languages, and theoretically with all 
// future framework platforms (such as Mono on Linux).
// If possible, you should avoid using any non-CLS compliant code 
// in your controls such as unsigned integers (uint) and
// calls to non-framework assemblies.
[assembly: System.CLSCompliant(true)] 

// The ComVisible attribute should always be explicitly set for controls.
// Note that in order for your control to be consumed by
// COM callers, you must use parameter-less constructors
// and you cannot use static methods.
[assembly: System.Runtime.InteropServices.ComVisible(true)]

And now, the final step before we can use our new control, signing your assembly. Signing your assembly is always good practice as it prevents modified or corrupted libraries from being loaded, protecting the application using it against things such as download errors or tampering.

Using the Strong Name utiltiy to create a new key pair

First, we must use the Strong Name utility provided with Visual Studio to create a new public/private key pair for our assembly. To do this, open up the Visual Studio Command Prompt by clicking Start > All Programs > Visual Studio.Net > Visual Studio .Net Tools > Visual Studio Command Prompt.

At the command prompt, enter:

sn -k [outputfile].snk

If you didn't specify the output file to be in your project directory, copy it there now and then modify the AssemblyInfo.cs file as follows, so that Visual Studio knows the relative path to your key file:

C#
[assembly: AssemblyKeyFile("..\\..\\..\\DividerPanel.snk")]

Using the Control

Now all that's left is to build our control, and add it to the Toolbox so we can use it in our applications. Once you are satisfied that your control is bug-free and feature complete, change the Build Configuration in Visual Studio to Release and hit F5 to compile it.

Adding a custom control to the Toolbox

Next, create a new Windows Application project and open a form in design view so that the Windows Forms Toolbox is visible. Right-click on the Toolbox and select Customize Toolbox, when the Customize Toolbox dialog opens, click the .NET Framework Components tab, click Browse, locate the DividerPanel.dll file in the bin\Release directory and click OK.

You will now see the DividerPanel control in the Toolbox items list, and you are ready to start dragging it into your applications.

Your new control in the Toolox, and ready to use

Summary

In this tutorial, we have touched on the following topics:

  • Creating a blank solution as a better starting point for a control project
  • Inheriting functionality from existing controls
  • Adding new Properties and Accessors to a control class
  • Property naming conventions
  • Overriding inherited methods
  • Adding property descriptions and documentation support
  • Adding Visual Studio Toolbox support
  • Simple designer class creation and usage
  • Specifying assembly attributes and signing a library using the strong name tool
  • Adding a custom control to the Visual Studio Toolbox

Release History

  • 29th August, 2003 - Version 1.0: Initial posting
  • 12th September, 2004 - Version 1.01: Edited some terminology, added additional tips.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Web Developer
Thailand Thailand
Furty will code for food.

Comments and Discussions

 
GeneralRe: VB Code for the same Pin
Furty14-Sep-05 15:47
Furty14-Sep-05 15:47 
Generalgood tutorial Pin
hyho9-Sep-05 17:01
hyho9-Sep-05 17:01 
General[assembly: System.CLSCompliant(true)] Pin
Nick Z.25-May-05 21:04
Nick Z.25-May-05 21:04 
GeneralUsing property editor for collection object in IDE Pin
TigerChan18-Apr-05 23:48
TigerChan18-Apr-05 23:48 
Generalto shadow the BorderStyle property instead of using a designer Pin
stax7621-Mar-05 14:20
stax7621-Mar-05 14:20 
GeneralRe: to shadow the BorderStyle property instead of using a designer Pin
Furty21-Mar-05 14:47
Furty21-Mar-05 14:47 
GeneralBorderSide selector like in Anchor field (see Layout section at Property grid) Pin
woo_hoo14-Jan-05 6:27
woo_hoo14-Jan-05 6:27 
QuestionBeginners? Pin
The Animator10-Jan-05 2:53
The Animator10-Jan-05 2:53 
I found this tutorial very helpful it has a great style and is easy to follow.

However, as a total beginner I have read several articles now on programming in C# but in common with many other computing languages, given a blank page, I always struggle because I do not know how to reference the background code.

For example in this code:-

[Bindable(true), Category("Border Options"),
DefaultValue(System.Windows.Forms.Border3DStyle.Etched),
Description("Specifies the style of the three-dimensional border.")]

public System.Windows.Forms.Border3DStyle Border3DStyle
{
get { return this.border3DStyle; }
set
{
if( this.border3DStyle != value )
{
this.border3DStyle = value;
this.Invalidate();
}
}
}
How do I know where to find this functionality, as a beginner, I would not have a clue where to start looking for this example.;PI can clearly understand how the code works if it is presented to me but I can't construct it myself as I do not know where to start looking.

Regards

The Animator
AnswerRe: Beginners? Pin
Furty10-Jan-05 12:30
Furty10-Jan-05 12:30 
GeneralRe: Beginners? Pin
dl4gbe16-Apr-06 23:52
dl4gbe16-Apr-06 23:52 
GeneralRe: Beginners? Pin
Furty23-Apr-06 14:42
Furty23-Apr-06 14:42 
GeneralAbout Designer Class Pin
zyw041011-Dec-04 21:39
zyw041011-Dec-04 21:39 
GeneralRe: About Designer Class Pin
Furty12-Dec-04 12:44
Furty12-Dec-04 12:44 
QuestionTaking it to the next level? Pin
bmoesk016-Jun-04 21:52
bmoesk016-Jun-04 21:52 
AnswerRe: Taking it to the next level? Pin
Furty6-Jun-04 22:38
Furty6-Jun-04 22:38 
GeneralSet the BorderStyle property programatically Pin
User 432383-Jun-04 9:38
User 432383-Jun-04 9:38 
GeneralRe: Set the BorderStyle property programatically Pin
Furty3-Jun-04 14:03
Furty3-Jun-04 14:03 
GeneralRe: Set the BorderStyle property programatically Pin
snailact1-Feb-07 13:34
snailact1-Feb-07 13:34 
GeneralFrom the Author.. Pin
Furty18-Feb-04 1:18
Furty18-Feb-04 1:18 
GeneralToolbox Bitmaps Pin
S.Cartwright17-Feb-04 4:07
S.Cartwright17-Feb-04 4:07 
GeneralRe: Toolbox Bitmaps Pin
Furty18-Feb-04 1:07
Furty18-Feb-04 1:07 
GeneralRe: Toolbox Bitmaps Pin
Paul M9-Aug-04 18:45
Paul M9-Aug-04 18:45 
GeneralRe: Toolbox Bitmaps Pin
David Fleming11-Feb-07 22:38
David Fleming11-Feb-07 22:38 
GeneralRe: Toolbox Bitmaps Pin
sendi_t346-Apr-06 11:15
sendi_t346-Apr-06 11:15 
GeneralRe: Toolbox Bitmaps Pin
sendi_t346-Apr-06 11:19
sendi_t346-Apr-06 11:19 

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.