The IconButton Control

Okay, time for something interesting. With the basics covered, it's time to dive in and create a simple control based on the information provided so far. The example we'll use creates a custom pushbutton that displays an icon to the left of the text. In addition to providing the C# implementation, the IconButton has also been implemented in VB .NET to show that it is possible to create the same types of controls regardless of language choice.

Choosing the base class for a custom control requires evaluating the functionality needed by the control. Because the IconButton control doesn't scroll and is not going to host other controls, the System.Windows.Forms.Control base class provides all the required functionality needed to serve as a starting point from which to build. Figure 2.6 shows the IconButton being used in an application.

Figure 2.6. The IconButton.


To create the IconButton control the following three steps are required:

1.
Create a new Class Library project.

2.
Add references to System.Drawing.dll and System.Windows.Forms.dll.

3.
Create the IconButton.cs source.

To create the IconButton, start by creating a new Class Library project in VS .NET, as shown in Figure 2.7, with the name IconButton.

Figure 2.7. The IconButton project.


Note

The reason for creating a Class Library instead of a Windows Control Library is that a Windows Control Library assumes you are going to build a UserControl. As such, unnecessary references and project files are created.


As always, VS .NET provides a default class1.cs file and opens it within the editor. You can delete this file, or rename it to IconButton.cs as you chose. Next, add references to System.Drawing.dll and System.Windows.Forms.dll to the project. Right-clicking the project name and choosing the Add Reference menu option can be used to add the references.

With the project workspace set up and the necessary references in place, all that remains is to copy the source from Listing 2.6 for C# or Listing 2.7 for VB .NET, depending on the project language type you chose.

Listing 2.6. IconButton.cs C# Source
  1: ////////////////////////////////////////////////////////////////////////
  2: ///File        :IconButton.cs
  3: ///Author    :Richard L. Weeks
  4: ///
  5: /// Copyright (c) 2001 by Richard L. Weeks
  6: /// This file is provided for instructional purposes only.
  7: /// No warranties.
  8: ////////////////////////////////////////////////////////////////////////
  9:
 10: using System;
 11: using System.Windows.Forms;
 12: using System.Drawing;
 13:
 14:
 15: namespace SAMS.ToolKit.Controls {
 16:     /// <summary>
 17:     /// IconButton Class
 18:     /// </summary>
 19:     [System.ComponentModel.Description( "SAMS IconButton Control" )]
 20:     public class IconButton : System.Windows.Forms.Control {
 21:
 22:
 23:         #region STATIC MEMBER FIELDS
 24:
 25:         protected static int        EDGE_PADDING = 4;
 26:
 27:         #endregion
 28:
 29:         #region Implementation Member Fields
 30:
 31:         protected ButtonState        buttonState;
 32:         protected Icon                buttonIcon;
 33:         protected int                iconDrawWidth;
 34:         protected bool                mousePressed;
 35:
 36:         #endregion
 37:
 38:  #region IconButton Properties
 39:
 40:         [
 41:         System.ComponentModel.Description( "The Icon to be displayed in the button" ),
 42:         System.ComponentModel.Category( "Appearance" ),
 43:         System.ComponentModel.DefaultValue( null )
 44:         ]
 45:         public Icon Icon {
 46:             get {  return buttonIcon; }
 47:             set {
 48:                 buttonIcon = value;
 49:                 Invalidate( );
 50:                 Update( );
 51:             }
 52:         }
 53:
 54:         #endregion
 55:
 56:
 57:         #region Construction / Initialization
 58:         /// <summary>
 59:         /// Simple Constructor
 60:         /// </summary>
 61:         public IconButton( ) {
 62:             InitializeComponent(  );
 63:         }
 64:
 65:         /// <summary>
 66:         /// Initialize the default values for the IconButton
 67:         /// </summary>
 68:         private void InitializeComponent( ) {
 69:             buttonIcon        = null;
 70:             buttonState        = ButtonState.Normal;
 71:             mousePressed    = false;
 72:         }
 73:         #endregion
 74:
 75:         #region Control Method Overrides
 76:
 77:         /// <summary>
 78:         ///
 79:         /// </summary>
 80:         /// <param name="e"></param>
 81:         protected override void OnGotFocus( EventArgs e ) {
 82:             Invalidate( );
 83:    base.OnGotFocus( e );
 84:         }
 85:
 86:         protected override void OnLostFocus( EventArgs e ) {
 87:             Invalidate( );
 88:             base.OnLostFocus( e );
 89:         }
 90:
 91:         protected override void OnTextChanged( EventArgs e ) {
 92:             Invalidate( );
 93:             Update( );
 94:             base.OnTextChanged( e );
 95:         }
 96:
 97:         /// <summary>
 98:         ///
 99:         /// </summary>
100:         /// <param name="e"></param>
101:         protected override void OnSizeChanged( EventArgs e ) {
102:             base.OnSizeChanged( e );
103:             this.Invalidate( );
104:             this.Update( );
105:         }
106:
107:         /// <summary>
108:         /// Render the IconButton
109:         /// </summary>
110:         /// <param name="e"><see cref="PaintEventArgs"/></param>
111:         protected override void OnPaint( PaintEventArgs e ) {
112:             base.OnPaint( e );
113:             Draw( e.Graphics );
114:         }
115:
116:         /// <summary>
117:         ///
118:         /// </summary>
119:         /// <param name="e"></param>
120:         protected override void OnMouseDown( MouseEventArgs e ) {
121:             if( e.Button == MouseButtons.Left ) {
122:       Focus( );
123:                 Capture            = true;
124:                 buttonState        = ButtonState.Pushed;
125:                 mousePressed    = true;
126:                 Invalidate( );
127:                 Update( );
128:             }  else
129:                 base.OnMouseDown( e );
130:         }
131:
132:         /// <summary>
133:         ///
134:         /// </summary>
135:         /// <param name="e"></param>
136:         protected override void OnMouseUp( MouseEventArgs e ) {
137:
138:             if( mousePressed && e.Button == MouseButtons.Left ) {
139:                 Capture            = false;
140:                 buttonState        = ButtonState.Normal;
141:                 Invalidate( );
142:                 Update( );
143:             }  else
144:                 base.OnMouseUp( e );
145:
146:             mousePressed    = false;
147:         }
148:
149:
150:
151:         #endregion
152:
153:         #region Implementation
154:
155:         /// <summary>
156:         /// Render the IconButton
157:         /// </summary>
158:         /// <param name="g"></param>
159:         protected virtual void Draw( Graphics g ) {
160:
161:             DrawButton( g );
162:
163:             if( buttonIcon != null )
164:                 DrawIcon( g );
165:
166:             DrawText( g );
167:
168:             if( base.Focused )
169:                 DrawFocusClues( g );
170:         }
171:
172:         /// <summary>
173:         ///
174:         /// </summary>
175:         /// <param name="g"></param>
176:   protected virtual void DrawButton( Graphics g ) {
177:             Rectangle rcButton = new Rectangle( 0, 0, this.Width, this.Height );
178:             if( Focused )
179:                 rcButton.Inflate(-1,-1);
180:
181:             ControlPaint.DrawButton( g, rcButton, buttonState );
182:         }
183:
184:         /// <summary>
185:         ///
186:         /// </summary>
187:         /// <param name="g"></param>
188:         protected virtual void DrawText( Graphics g ) {
189:             int left    = (buttonIcon == null ?
190:                 IconButton.EDGE_PADDING :
191:                 iconDrawWidth + IconButton.EDGE_PADDING);
192:             int width    = Width - left;
193:             int top        = IconButton.EDGE_PADDING;
194:             int height    = Height - (2*IconButton.EDGE_PADDING);
195:
196:             RectangleF layoutRect = new RectangleF( left, top, width, height );
197:             if( ButtonState.Pushed == buttonState )
198:                 layoutRect.Offset( 1f, 1f );
199:
200:             StringFormat fmt    = new StringFormat( );
201:             fmt.Alignment        = StringAlignment.Center;
202:             fmt.LineAlignment    = StringAlignment.Center;
203:
204:             SolidBrush textBrush    = new SolidBrush( this.ForeColor );
205:             g.DrawString( Text, Font, textBrush, layoutRect, fmt );
206:
207:             textBrush.Dispose( );
208:         }
209:
210:         /// <summary>
211:         ///
212:         /// </summary>
213:         /// <param name="g"></param>
214:         protected virtual void DrawIcon( Graphics g ) {
215:             System.Diagnostics.Debug.Assert( buttonIcon != null, "IconButton Icon is
 null" );
216:
217:             int top        = ((Height/2) - (buttonIcon.Height/2));
218:             int height    = buttonIcon.Height;
219:             int width    = buttonIcon.Width;
220:
221:             if( (top + height) >= Height ) {
222:         //Scale the image to fit in (w,h) of button
223:                 top = IconButton.EDGE_PADDING;
224:                 int drawHeight = Height - (2*IconButton.EDGE_PADDING);
225:                 float scale = ((float)drawHeight / (float)height);
226:                 width = (int)((float)width*scale);
227:                 height = drawHeight;
228:             }
229:             Rectangle iconRect = new Rectangle( IconButton.EDGE_PADDING, top, width,
 height);
230:
231:             if( buttonState == ButtonState.Pushed )
232:                 iconRect.Offset(1,1);
233:
234:             g.DrawIcon( buttonIcon, iconRect );
235:
236:             this.iconDrawWidth = iconRect.Width;
237:         }
238:
239:         /// <summary>
240:         ///
241:         /// </summary>
242:         /// <param name="g"></param>
243:         protected virtual void DrawFocusClues( Graphics g ) {
244:             System.Drawing.Pen p = new Pen( System.Drawing.Color.Black, 1f );
245:             Rectangle frameRect = new Rectangle( 0, 0, this.Width, this.Height );
246:             g.DrawRectangle( p, frameRect );
247:
248:             p.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
249:             frameRect = new Rectangle( 2, 2, this.Width - 6, this.Height - 6);
250:             if( buttonState == ButtonState.Pushed )
251:                 frameRect.Offset( 1, 1 );
252:             g.DrawRectangle( p, frameRect );
253:
254:             p.Dispose( );
255:         }
256:         #endregion
257:
258:     }
259: }

Even a small control can quickly reach a couple hundred lines of code. The C# version of the IconButton control, from Listing 2.6, is designed to mimic the standard Windows button in terms of look and feel while also providing the capability to associate an icon to be drawn on the button.

The drawing code for the button is broken down into three tasks. The first task is to draw the button itself. The method DrawButton on line 176 handles drawing the basic button control. After the button has been rendered, the next step is to draw the associated icon if there is one. The DrawIcon method on line 214 handles this task.

Rather than simply drawing the icon image as is, the DrawIcon method scales the icon as necessary to fit within the button. The final width of the renderend icon must also be tracked because the text for the button will need this information to align itself properly. The final basic drawing step is to render the button text using the DrawText method on line 188.

The DrawText method determines the alignment for the text depending on factors such as the icon draw width and the current state of the button. If the button is in a pushed state, the text is offset by one pixel. This offsetting makes it appear as if the button is being pushed down and gives a sense of movement to the user.

The next major area of the IconButton is dealing with events. The most common events deal with the mouse. The IconButton overrides both the OnMouseDown and the OnMouseUp events of the control base class. The mouse events are used to change the state of the button from normal to pushed and back to normal, depending on the actions of the mouse. Other events include the paint and size events, along with responding to the TextChanged event.

Listing 2.7 is the VB .NET equivalent of the C# source found in Listing 2.6. In every way, these two code listings provide for the same functionality.

Listing 2.7. IconButton VB .NET Source
  1: Imports System.Windows.Forms
  2: Imports System.Drawing
  3:
  4: Namespace SAMS.ToolKit.VB.Controls
  5:
  6:     <System.ComponentModel.Description("SAMS IconButton Control (VB)")> _
  7:     Public Class IconButton
  8:         Inherits System.Windows.Forms.Control
  9:
 10: #Region "Const Member Fields"
 11:         Protected Const EDGE_PADDING As Integer = 4
 12: #End Region
 13:
 14: #Region "Implementation Member Fields"
 15:         Protected buttonState As System.Windows.Forms.buttonState
 16:         Protected buttonIcon As System.Drawing.Icon
 17:         Protected mousePressed As Boolean
 18:         Protected iconDrawWidth As Integer
 19: #End Region
 20:
 21: #Region "Properties"
 22:
 23:         <System.ComponentModel.Description("The Icon to be displayed in the button"),
 24:         System.ComponentModel.Category("Appearance")> _
 25:         Public Property Icon() As Icon
 26:             Get
 27:                 Icon = buttonIcon
 28:             End Get
 29:             Set(ByVal value As Icon)
 30:                 buttonIcon = value
 31:                 Invalidate()
 32:                 Update()
 33:             End Set
 34:         End Property
 35: #End Region
 36:
 37:
 38: #Region "Construction"
 39:
 40:         Public Sub IconButton()
 41:             buttonState = buttonState.Normal
 42:             buttonIcon = Nothing
 43:             iconDrawWidth = 0
 44:         End Sub
 45:
 46: #End Region
 47:
 48: #Region "Control Overrides"
 49:
 50:         Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms
.MouseEventArgs)
 51:             If (e.Button = MouseButtons.Left) Then
 52:                 Focus()
 53:                 Capture = True
 54:                 buttonState = buttonState.Pushed
 55:                 mousePressed = True
 56:                 Invalidate()
 57:                 Update()
 58:             Else
 59:       MyBase.OnMouseDown(e)
 60:             End If
 61:         End Sub
 62:
 63:
 64:         Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
 65:             If (mousePressed And (e.Button = MouseButtons.Left)) Then
 66:                 Capture = False
 67:                 mousePressed = False
 68:                 buttonState = buttonState.Normal
 69:                 Invalidate()
 70:                 Update()
 71:             Else
 72:                 MyBase.OnMouseUp(e)
 73:             End If
 74:         End Sub
 75:
 76:         Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
 77:             Draw(e.Graphics)
 78:             MyBase.OnPaint(e)
 79:         End Sub
 80:
 81:        Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
 82:             Invalidate()
 83:             MyBase.OnSizeChanged(e)
 84:        End Sub
 85:
 86:        Protected Overrides Sub OnTextChanged(ByVal e As System.EventArgs)
 87:             Invalidate()
 88:        End Sub
 89:
 90:        Protected Overrides Sub OnLostFocus(ByVal e As System.EventArgs)
 91:             Invalidate()
 92:        End Sub
 93:
 94:        Protected Overrides Sub OnGotFocus(ByVal e As System.EventArgs)
 95:             Invalidate()
 96:        End Sub
 97:
 98: #End Region
 99:
100: #Region "Implementation"
101:        Protected Overridable Sub Draw(ByRef g As System.Drawing.Graphics)
102:             DrawButton(g)
103:             If (Not buttonIcon Is Nothing) Then
104:                 DrawIcon(g)
105:             End If
106:             DrawText(g)
107:
108:  If (MyBase.Focused) Then
109:                 DrawFocusClues(g)
110:             End If
111:        End Sub
112:
113:         Protected Overridable Sub DrawButton(ByRef g As System.Drawing.Graphics)
114:             Dim rcButton As New Rectangle(0, 0, Width, Height)
115:             If (Focused) Then
116:                 rcButton.Inflate(-1, -1)
117:             End If
118:             ControlPaint.DrawButton(g, rcButton, buttonState)
119:         End Sub
120:
121:         Protected Overridable Sub DrawIcon(ByRef g As System.Drawing.Graphics)
122:             System.Diagnostics.Debug.Assert(Not buttonIcon Is Nothing, "IconButton is
 null")
123:
124:             Dim top, height, width As Integer
125:
126:             top = (Me.Height / 2) - (buttonIcon.Height / 2)
127:             height = buttonIcon.Height
128:             width = buttonIcon.Width
129:
130:             If ((top + height) >= Me.Height) Then
131:                 'Scale the image to fit in (w,h) of button
132:                 top = EDGE_PADDING
133:                Dim drawHeight As Integer = Me.Height - (2 * EDGE_PADDING)
134:                Dim scale As Single = drawHeight / height
135:                 width = width * scale
136:                 height = drawHeight
137:             End If
138:
139:             Dim iconRect As New Rectangle(EDGE_PADDING, top, width, height)
140:             If (buttonState.Pushed = buttonState) Then
141:                 iconRect.Offset(1F, 1F)
142:             End If
143:
144:             g.DrawIcon(buttonIcon, iconRect)
145:             iconDrawWidth = iconRect.Width
146:         End Sub
147:
148:         Protected Overridable Sub DrawText(ByRef g As System.Drawing.Graphics)
149:             Dim top, left, width, height As Integer
150:
151:  top = EDGE_PADDING
152:             left = IIf(buttonIcon Is Nothing, EDGE_PADDING, iconDrawWidth + EDGE_PADDING)
153:             width = Me.Width - left
154:             height = Me.Height - (2 * EDGE_PADDING)
155:
156:             Dim layoutRect As New RectangleF(left, top, width, height)
157:             If (buttonState.Pushed = buttonState) Then
158:                 layoutRect.Offset(1F, 1F)
159:             End If
160:
161:             Dim fmt As New StringFormat()
162:             fmt.Alignment = StringAlignment.Center
163:             fmt.LineAlignment = StringAlignment.Center
164:
165:             Dim textBrush As New SolidBrush(Me.ForeColor)
166:
167:             g.DrawString(Text, Font, textBrush, layoutRect, fmt)
168:
169:             textBrush.Dispose()
170:
171:         End Sub
172:
173:         Protected Overridable Sub DrawFocusClues(ByRef g As System.Drawing.Graphics)
174:             Dim p As New System.Drawing.Pen(System.Drawing.Color.Black, 1F)
175:             Dim frameRect = New Rectangle(0, 0, Me.Width, Me.Height)
176:             g.DrawRectangle(p, frameRect)
177:
178:             p.DashStyle = Drawing.Drawing2D.DashStyle.Dot
179:             frameRect = New Rectangle(2, 2, Me.Width - 6, Me.Height - 6)
180:             If (buttonState.Pushed = buttonState) Then
181:                 frameRect.offset(1, 1)
182:             End If
183:             g.DrawRectangle(p, frameRect)
184:
185:             p.Dispose()
186:         End Sub
187: #End Region
188:
189:     End Class
190:
191: End Namespace

The code for both C# and VB .NET is remarkably similar in structure and syntax. In fact, all the topics covered in this book can be implemented in C# or VB .NET with ease. The .NET base class libraries have finally leveled the playing field such that you may use the language of your choice to accomplish the task at hand.

Most of the code for the IconButton is fairly simple in nature and makes use of GDI+, Graphics Device Interface, to draw the associated icon for the button. The topic of GDI+ is covered in Chapter 4, “GDI+.” In addition, the IconButton uses the ControlPaint class to draw the button itself. Controls have two basic parts: presentation and logic.

The logic of a control depends on the purpose it's supposed to serve, and as such, developing two complete controls, both presented in this book, will help give you an understanding on that front. The presentation of a control consists of runtime and design-time behavior and the control's look and feel. Chapter 4, “GDI+,” covers the use of GDI+ for dealing with the drawing aspects of a control, and the remaining chapters cover VS .NET support for control development.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.126.122