Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

A Managed C++ Button Control

4.70/5 (12 votes)
20 Apr 2008CPOL5 min read 1   1.8K  
This is an example custom button control, written entirely in Managed C++.

mehButton

If you like this article, please vote for it. If you don't like this article, please vote, and please tell me why you don't like it.

Introduction

I started this project to understand how one develops a managed custom control that is similar in quality to the standard .NET Button control. Like most .NET beginners, I started out inheriting from the UserControl class, but quickly learned there are significant limitations to some of the concepts I wanted to implement.

After a considerable number of searches of Google and MSDN documents, I settled on developing a button control that inherits from ButtonBase and IButtonControl. One of the problems with inheriting from ButtonBase is that the class is abstract. This means the button control cannot be obtained directly in the designer. However, it can be added to the toolbox, and it works perfectly well in the form designer.

The control is built for flexibility. It allows for either rounded corners or flat square controls. In addition, you can specify which corners are rounded and which are square. You may also change the radius of the corners. I made many of the colors configurable. I also included the ability to draw color gradients for both hover and normal colors. I implemented most of the events that a standard .NET button control can respond to. By implementing the IButtonControl interface, several standard button behaviors are also exposed.

Background

There are a lot of .NET custom controls uploaded to CodeProject. Why do we need another one? The answer is partly because I am a C++ programmer. I wanted to see what it would take to transfer my WTL and ATL skills to .NET. I wanted to understand the difference between architecting a control for use with WTL and a control for use in VB.NET or C#. I make extensive use of attributes for describing properties and assigning default values. I also added XML documentation tags so that Intellisense would be meaningful when using the control in your form.

I modeled this control based on the work of WiB and Alan Zhao. Both of their articles provided a great deal of inspiration and education.

Compiling and using the source

Download and unzip the demonstration project. Please make sure to preserve the directory structure of the source files.

When you first load the solution, you may see a number of messages regarding a missing reference. This is because the assembly has not been built on your machine. (I didn't include any binary files in the source distribution, hence the missing references.) Simply select release or debug mode, and and recompile. The assembly will be built, and all references will be validated. You can add the control to your toolbox at this point, by dragging the assembly to the toolbox surface, or selecting "Add Item..." from the context menu and navigating to <root dir>\mehControls\release and selecting mehButton.dll.

Using the component

After you've added the control to your toolbox, you can drag it to any form. I've implemented a designer component so all properties show up correctly in the "Properties" view. You'll notice that I've removed some of the most difficult to implement properties in the PostFilterProperties method of the designer. Other properties, such as the "Flat" properties, are redundant. Programming the mehControl is fairly standard. The following code illustrates how the form designer wizard initializes the control in a C# project:

MC++
// 
// mehButton1
// 
this.mehButton1.BackColor = System.Drawing.SystemColors.Control;
this.mehButton1.ButtonStyle = mehControls.mehButton.ButtonStyles.Rectangle;
this.mehButton1.CurveMode = mehControls.mehButton.CornerCurveStyle.None;
this.mehButton1.DialogResult = System.Windows.Forms.DialogResult.None;
this.mehButton1.GradientStyle = mehControls.mehButton.GradientStyles.None;
this.mehButton1.HoverBorderColor = System.Drawing.SystemColors.ControlDark;
this.mehButton1.HoverColorA = System.Drawing.SystemColors.ButtonFace;
this.mehButton1.HoverColorB = System.Drawing.SystemColors.ButtonHighlight;
this.mehButton1.Image = null;
this.mehButton1.Location = new System.Drawing.Point(12, 122);
this.mehButton1.Name = "mehButton1";
this.mehButton1.NormalBorderColor = System.Drawing.SystemColors.WindowFrame;
this.mehButton1.NormalColorA = System.Drawing.SystemColors.ButtonHighlight;
this.mehButton1.NormalColorB = System.Drawing.SystemColors.ButtonFace;
this.mehButton1.Radius = 0;
this.mehButton1.Size = new System.Drawing.Size(127, 38);
this.mehButton1.SmoothingQuality = mehControls.mehButton.SmoothingQualities.AntiAlias;
this.mehButton1.TabIndex = 4;
this.mehButton1.Text = "m&ehButton Flat";
this.mehButton1.UseVisualStyleBackColor = false;
this.mehButton1.MouseLeave += new System.EventHandler(this.mehButton1_MouseLeave);

...

// The control's events are implmented in a standard fashion.
// The C# design wizard is able to auto-fill the events
// properly from the "Properties" sheet.
private void mehButton1_MouseLeave(object sender, EventArgs e)
{
    this.lblEventText.Text = "Mouse has left mehButton1";
}

Implementing a designer

When you develop a custom visual control for .NET, you will want to implement a designer. The designer may be simple (as the one I've implemented), or as complex as you want. The following code illustrates the definition of a minimal designer:

MC++
namespace mehControls
{
    ref class mehButtonDesigner : 
        public System::Windows::Forms::Design::ControlDesigner
    {
    public:
        mehButtonDesigner(void)
        {
        };

        ~mehButtonDesigner(void)
        {
        };

    protected:
        virtual void PostFilterProperties(IDictionary ^) override;
        virtual void OnPaintAdornments(PaintEventArgs ^e) override;

    };
}

Attributing properties

You'll want to properly attribute your properties. This will allow you to control where the property is grouped on the Properties display and the default value of the property.

MC++
[DefaultValue(System::Drawing::Color::typeid, "System::Drawing::SystemColors::Control")]
[Description("Background Color for button"), Category("Appearance")]
// This is how you split a property definition
// This is useful if the property has many lines of
// code or you do not want to expose the
// inner workings of your code in the header.
property virtual Color BackColor
{
    // The property function prototypes are specified here
    Color get() override;
    void set(Color value) override;
}

Handling the OnPaint event for the control

As with Native mode programming, most of the work is handled in the OnPaint (or OnPaintBackground) event of the control. In the case of the control, DrawRoundRectangle does most of the heavy-lifting. In order to implement rounded corners, I make heavy use of gp->AddArc(arc, angle1, angle2) where GraphicsPath ^gp = gcnew GraphicsPath().

Summary

The button implements many of the properties of a .NET 2.0 button. It was a lot of fun figuring out how to add the painting of the "hot" border. I had to use my tenth grade geometry to calculate the correct center of the arcs when drawing the rounded corners. This is the basic diagram I used for developing the rounder corner algorithm:

RoundedCorners1.JPG

I was also able to implement gradient drawing of the background, which allows for some very cool effects when there is a mouse over or click event for the button. The following enum illustrates the gradient types that are implemented:

C#
enum class GradientStyles
{
    None,
    Horizontal,
    Vertical,
    ForwardDiagonal,
    BackwardDiagonal
};

The control is far from complete. I've added attributes for all of the properties. However, there is considerable work to be done with the XML documentation tags. I also have not implemented themes in this version of the control.

Things you should know

There are a few items that I found to be a lot easier in managed and mixed mode C++. First of all, you do not have to worry about importing API calls. You include any header files from the Platform SDK, and Visual C++ will take care of the call for you. This is a huge help, and can save a lot of time when working with API calls. Moving back and forth between wchar_t and String is a lot simpler in a managed C++ project.

VS 2005 does not have the nice wizards for C++ that C# has. When you select a Windows Control project, that is what you get. No designer. No bells and whistles. You'll have to consult the documentation often in the early going. However, I found that I learned a lot more about my environment doing it the hard way. Before I knew it, I wasn't consulting the documentation very often.

Adding references to projects is different between C++ and C#. You use the project properties page to add references. Just navigate to Common Properties, and click the Add New Reference... button.

Specifying namespaces and resolving referenced names: in Managed C++, you'll use "::" instead of "." when resolving names.

MC++
// You'll use this syntax in managed C++
using namespace System::Windows::Forms;
// Instead of the C# syntax
//using System.Windows.Forms;

History

  • Version 1.0 - April 11, 2008 - Initial release of the control.

License

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