Click here to Skip to main content
15,888,461 members
Articles / Programming Languages / C#

Graphic Button Controls for Skins

Rate me:
Please Sign up or sign in to vote.
3.00/5 (14 votes)
24 Nov 2002CPOL6 min read 148.1K   2K   83   16
Button classes that support 'skin' development in a Windows Forms-based .NET application

Sample Image - graphic_button_control.jpg

Introduction

I recently was involved in a project for the TabletPC where we had keep in mind that the user of the application would be using a pen to navigate the forms instead of the mouse.  In addition, our application was going to be on the tablet in the hands of users with little to no Microsoft Windows exposure.  We needed a controlled, idiot-proof, environment that was also friendly with a pen.

We decided to create our own 'skin' for the application, similar to many media and MP3 players available.  Therefore, we needed buttons that could handle the four states of user interaction:

  • Normal (Inactive)
  • Mouse Over
  • Pressed (Active)
  • Disabled

Control Architecture

The controls are implemented as derived classes from one another and one base class, which extend the .NET Framework's System.Windows.Forms.Button class. All controls are compatible with the windows forms designer in Visual Studio.

Images

Naturally, for a graphical button to work you must provide the graphics for it to use.  There is a property for the graphic to be used for each of the states indicated above, as a System.Drawing.Image object.  All the GraphicControls classes require at least the NormalImage to be assigned, or else an exception will be thrown.  If one of the states does not have an image assigned, the controls default to displaying the normal state image.  I chose not to implement these classes using image indexing, because it was easier for our Adobe PhotoShop graphics developer to manage the numerous images using seperate images.  These can be easily assigned using the forms designer or directly in the code.

Click Regions

Each control allows for the button to respond to mouse events from the entire control surface, or an optional ClickRegion can be assigned using in your code to restrict the area of the control surface.  When a mouse event occurs, the mouse position is checked against the figure created by the click region (in button window coordinates) to make sure it falls within the region before it is processed.  This allows for buttons which are not the standard rectangle shape to respond as the user would expect, with no ghost actions outside of the obvious button graphic.  There are several methods available to define the click region, but we found that the version accepting a Point[] array was best because we could use the image map (intended for a web control) point coordinates generated by Adobe PhotoShop.

Button State Echo

Our design provided for multiple visual cues (buttons) to trigger an eaction or indicate a state change among those listed above.  To allow these buttons to manage this relationship, all of them process and inherit the ability to echo a change in its own state to other interested buttons and to process the events triggered by other buttons it has interest in.  This is done through the standard .NET event-handler model.  Static methods are provided to manage the buttons interested in one another, EnableEchoButtonPair and DisableEchoButtonPair.  This allows a change in state due to a mouse over event for one button to also mimic in those buttons subscribed to the GraphicButtonStateEcho event.  This makes the multiple visual cues react consistently regardless of which button is being manipulated by the end user.  Note that the disable state is managed by the standard control property Enabled, and is also processed as an echo only through a different event.

GraphicControls Button Classes

GraphicButtonBase

This is the abstract base class defining the fundamental methods and properties needed to manage a graphic-based button in this context.  I defined this class in the event I needed to derive other graphic-based buttons which could not or should not inherit from the other controls, but I haven't found a need to yet.

GraphicButton

This is the graphic-based button which effectively mimics a standard Windows button.  All that is needed is the relevant images assigned and it works the same.  The property ButtonState is available at run-time to query the current state of the button.

GraphicToggleButton

This control is derived from the GraphicButton and alters the behavior so the button toggles itself on or off, like a light switch or toolbar setting.  The toggle button requires one addition image, the PressedImage, which is used to indicate the On or Active state.  It also has an additional property Active, to indicate whether or not the toggle button is Active or Inactive.  It disables the Click event, since it really doesn't make sense for this button and adds another event called Toggled, which can be handled to be notified when the event toggles between the active and inactive states.

GraphicMultiButtonPanelButton

I have been arguing with myself on the name for this control, and finally gave up and settled on this one.  This control mimics a selector panel, where only one button on the panel can be active at the same point in time.  This control is derived from the GraphicToggleButton, with an added twist that it automatically becomes inactive when one of the other buttons in the panel becomes active, it automatically changes itself to inactive.  This control also only triggers the Toggled event when it becomes active, so handlers do not get unnecessarily notified of the toggle buttons becoming inactive when only the new state of the selector panel is required.  I used it to manage a task progression/selection bar in our application.  I chose not to implement a logical or control container, so placement of the buttons can be anywhere within the application.

Demo and Sample Source

Looking at the snapshot of the demo application included you can see that the various feature available from these controls are shown.  Below I have cut out a minimal piece of code to show what needs to be added to get the additional functionality above the standard Windows button.  I also include the code generated by the forms designer for the control.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace GraphicControlDemo
{
	public class GraphicButtonsWithEcho : System.Windows.Forms.Form
	{
	    .
	    .
	    .
        private GraphicControls.GraphicToggleButton graphicToggleButton1;
        private GraphicControls.GraphicToggleButton graphicToggleButton2;
        
        private GraphicControls.GraphicMultiButtonPanelButton 
                                            graphicMultiButtonPanelButton1;
        private GraphicControls.GraphicMultiButtonPanelButton  
                                            graphicMultiButtonPanelButton2;
        .
        .
        .
		public GraphicButtonsWithEcho()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

            graphicMultiButtonPanelButton1.SetClickRegion(
                new Point[] { new Point( 1, 2 ), new Point( 49, 1 ),
                                new Point( 77, 17 ), new Point( 89, 51 ),
                                new Point( 1, 52 ) } );
            graphicMultiButtonPanelButton2.SetClickRegion( new Point[] 
            { new Point( 1, 52 ), new Point( 12, 19 ),
            new Point( 38, 4 ), new Point( 89, 1 ), 
            new Point( 88, 52 ) } );
            graphicMultiButtonPanelButton1.AddPanelButton(  
                                            graphicMultiButtonPanelButton2 );
            graphicMultiButtonPanelButton2.AddPanelButton(  
                                            graphicMultiButtonPanelButton1 );

            //initialize one button unless the initial state should be  
            // undetermined
            graphicMultiButtonPanelButton1.Active = true;

            // alternate assignment method
            // GraphicControls.GraphicMultiButtonPanelButton[] HandSelectors = 
            new GraphicControls.GraphicMultiButtonPanelButton[ 2 ];
            // HandSelectors[0] = graphicMultiButtonPanelButton1;
            // HandSelectors[1] = graphicMultiButtonPanelButton2;
            // graphicMultiButtonPanelButton1.AddPanelButtons( HandSelectors );
            // graphicMultiButtonPanelButton2.AddPanelButtons( HandSelectors );
		}
		.
		.
		.
		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
		    .
		    .
		    .
            this.graphicToggleButton1 =
                          new GraphicControls.GraphicToggleButton();
            this.graphicToggleButton2 = 
                          new GraphicControls.GraphicToggleButton();
            this.graphicMultiButtonPanelButton1 =  
                          new GraphicControls.GraphicMultiButtonPanelButton();
            this.graphicMultiButtonPanelButton2 =  
                          new GraphicControls.GraphicMultiButtonPanelButton();
            .
            .
            .
            // 
            // graphicToggleButton1
            // 
            this.graphicToggleButton1.DisabledImage = 
                          ((System.Drawing.Bitmap)(resources.GetObject( 
                                   "graphicToggleButton1.DisabledImage")));
            this.graphicToggleButton1.Location = 
                           new System.Drawing.Point(320, 80);
            this.graphicToggleButton1.Name = "graphicToggleButton1";
            this.graphicToggleButton1.NormalImage = 
                            ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton1.NormalImage")));
            this.graphicToggleButton1.OverImage = 
                            ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton1.OverImage")));
            this.graphicToggleButton1.PressedImage = 
                            ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton1.PressedImage")));
            this.graphicToggleButton1.Size = new System.Drawing.Size(112, 23);
            this.graphicToggleButton1.TabIndex = 10;
            this.graphicToggleButton1.Toggled += 
            new System.EventHandler(this.graphicToggleButton1_Toggled);
            // 
            // graphicToggleButton2
            // 
            this.graphicToggleButton2.DisabledImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton2.DisabledImage")));
            this.graphicToggleButton2.Location = 
                                     new System.Drawing.Point(320, 48);
            this.graphicToggleButton2.Name = "graphicToggleButton2";
            this.graphicToggleButton2.NormalImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton2.NormalImage")));
            this.graphicToggleButton2.OverImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton2.OverImage")));
            this.graphicToggleButton2.PressedImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                                     "graphicToggleButton2.PressedImage")));
            this.graphicToggleButton2.Size = new System.Drawing.Size(112, 23);
            this.graphicToggleButton2.TabIndex = 9;
            this.graphicToggleButton2.Toggled += 
            new System.EventHandler(this.graphicToggleButton2_Toggled);
            
            // 
            // graphicMultiButtonPanelButton1
            // 
            this.graphicMultiButtonPanelButton1.DisabledImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton1.DisabledImage")));
            this.graphicMultiButtonPanelButton1.Location = 
                             new System.Drawing.Point(0, 88);
            this.graphicMultiButtonPanelButton1.Name = 
                             "graphicMultiButtonPanelButton1";
            this.graphicMultiButtonPanelButton1.NormalImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton1.NormalImage")));
            this.graphicMultiButtonPanelButton1.OverImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton1.OverImage")));
            this.graphicMultiButtonPanelButton1.PressedImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton1.PressedImage")));
            this.graphicMultiButtonPanelButton1.Size = 
                             new System.Drawing.Size(92, 55);
            this.graphicMultiButtonPanelButton1.TabIndex = 1;
            // 
            // graphicMultiButtonPanelButton2
            // 
            this.graphicMultiButtonPanelButton2.DisabledImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton2.DisabledImage")));
            this.graphicMultiButtonPanelButton2.Location =
                              new System.Drawing.Point(352, 88);
            this.graphicMultiButtonPanelButton2.Name = 
                             "graphicMultiButtonPanelButton2";
            this.graphicMultiButtonPanelButton2.NormalImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton2.NormalImage")));
            this.graphicMultiButtonPanelButton2.OverImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton2.OverImage")));
            this.graphicMultiButtonPanelButton2.PressedImage = 
                             ((System.Drawing.Bitmap)(resources.GetObject(
                             "graphicMultiButtonPanelButton2.PressedImage")));
            this.graphicMultiButtonPanelButton2.Size = 
                             new System.Drawing.Size(92, 55);
            this.graphicMultiButtonPanelButton2.TabIndex = 2;
            // 
            // GraphicButtonsWithEcho
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(480, 374);
            this.Controls.AddRange(new System.Windows.Forms.Control[] {
                          this.panel1,
                          this.button4,
                          this.button5,
                          this.label3,
                          this.button6,
                          this.graphicToggleButton1,
                          this.graphicToggleButton2,
                          this.button3,
                          this.button2,
                          this.label2,
                          this.label1,
                          this.graphicButton3,
                          this.button1,
                          this.graphicButton2,
                          this.graphicButton1});
            this.Name = "GraphicButtonsWithEcho";
            this.Text = "Graphic Buttons with Echo";
            this.panel1.ResumeLayout(false);
            this.ResumeLayout(false);
        }
		#endregion
		.
		.
		.
        private void button1_Click(object sender, System.EventArgs e)
        {
            // enable the echo of the button state
            GraphicControls.GraphicButton.EnableEchoButtonPair(g
                             raphicButton1,graphicButton2);
            button1.Enabled = false;
            button2.Enabled = true;
            button3.Enabled = true;
        }
        .
        .
        .
        private void graphicToggleButton2_Toggled(object sender, 
                             System.EventArgs e)
        {
            MessageBox.Show( this, 
"GraphicToggleButton2 Toggled to " + 
(graphicToggleButton2.Active ? "Active!" : "Inactive!"));
        }

        private void graphicToggleButton1_Toggled(object sender, 
                             System.EventArgs e)
        {
            MessageBox.Show( this, 
"GraphicToggleButton1 Toggled to " + 
(graphicToggleButton1.Active ? "Active!" : "Inactive!"));
        }
	}
}

Using the Controls in Your Own Application with the Forms Designer

It is easy to use these controls with the forms designer.  You just have to add them to the Windows Forms component tab of the Toolbox.

  1. First, open the Toolbox in the Windows Forms Designer. Then, from the context menu in the toolbox (right mouse button click), select the 'Customize Toolbox...' menu item.
  2. Select the tab named '.NET Framework Components' and press the 'Browse...' button.
  3. Locate and select the GraphicControls.dll.
  4. Back in the component list on the tab, locate the three components added with this DLL and check the checkbox next to them, then press the 'OK' button. 

The controls will now be in the list for you to select from.  I know they do not have a customized snazzy toolbox icon, but I'm not the graphics guy.  Perhaps I will add them at a later time.

Open Issues

The only behavior I note with these controls is when the end user moves the mouse out of the control, sometimes the cursor momentarily appears to jump a little to the right.  I seem to remember this problem from a long time ago, but I can't figure it out.  I hope to have an answer soon from Microsoft. 

References

  1. The idea for creating support for buttons without rectangular surfaces came from another CodeProject article by Ryan LaNeve, C# Windows Forms ImageMap Control.
  2. Programming Microsoft Windows with C#, by Charles Petzold, Microsoft Press 2002.

License

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


Written By
Web Developer
United States United States
I have been developing C/C++ applications for Windows and UNIX platforms for 15 years, specializing in database and N-tier application architectures, including 7 years in hands-on project management (ugh!!). I also had a part in the OS/2 (what?) 2.1 development where I really got into multi-tasking, threads, synchronization and related technologies. Drank the Microsoft Kool-Aid long ago, though.

Comments and Discussions

 
GeneralMobile Devices Pin
stancosti14-May-07 5:04
stancosti14-May-07 5:04 
AnswerRe: Mobile Devices Pin
dzolee16-May-07 22:58
dzolee16-May-07 22:58 
QuestionWhat happen to the code, I downloaded as blank? Pin
riscy5-Feb-06 10:07
riscy5-Feb-06 10:07 
GeneralUsing GraphicControls in C++ Pin
Zeevm5-Jan-06 11:44
Zeevm5-Jan-06 11:44 
Generalc# .net change application skin Pin
arssa202013-Apr-04 19:46
arssa202013-Apr-04 19:46 
GeneralRe: c# .net change application skin Pin
hprahul19-Apr-04 3:38
hprahul19-Apr-04 3:38 
GeneralRe: c# .net change application skin Pin
lets_go29-Jul-04 10:00
lets_go29-Jul-04 10:00 
GeneralRe: c# .net change application skin Pin
hprahul29-Jul-04 14:24
hprahul29-Jul-04 14:24 
GeneralRe: c# .net change application skin Pin
Ahmed.mb4-Jan-07 6:54
Ahmed.mb4-Jan-07 6:54 
Has you got it yet???, I'm searching too.
thanks
GeneralProblem with Tranaparent GIF's Pin
26-Mar-03 21:30
suss26-Mar-03 21:30 
GeneralRe: Problem with Tranaparent GIF's Pin
Bill Sayles5-May-04 12:22
Bill Sayles5-May-04 12:22 
GeneralRe: Problem with Tranaparent GIF's Pin
Joe Dalton24-Jun-04 11:33
Joe Dalton24-Jun-04 11:33 
GeneralRe: Problem with Tranaparent GIF's Pin
knightbanshee23-Aug-06 9:58
knightbanshee23-Aug-06 9:58 
GeneralKeyboard Pin
Member 2181813-Mar-03 23:27
Member 2181813-Mar-03 23:27 
GeneralRe: Keyboard Pin
Bill Sayles12-Apr-03 17:51
Bill Sayles12-Apr-03 17:51 
GeneralSuggestion Pin
John O'Byrne25-Nov-02 22:25
John O'Byrne25-Nov-02 22:25 

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.