The difference between understanding a concept…

Follow up posted at bottom of article

 

 

… and implementing it, can be the difference between your book being on time, and being, er, ah, well… late.

I’ve written chapters on custom controls at least a dozen times, and I’ve explained how to write custom controls at least a hundred times. You’d think I could do it in my sleep. In this first of a series of blog entries about Silverlight custom controls I’ll try to convey why creating custom controls is not difficult, but explaining it, or at least explaining it well, is.

There were custom controls in ASP.NET, in Windows Forms, in many other GUI platforms, and they’ve always been just  about the same idea. You typically had three options:

DropDownButton

Option One

The first way to create a custom control is to aggregate two or more existing controls into a new type; to make something new (or something monstrous). My favorite example is to combine a button with a DropDown to make a button whose action can change.

 

 

 

 

 

 

 

 

 

 

 

 

 

Option Two

The second approach is to derive from an existing control but to add a little new CountedButtonfunctionality. My standard  example is Clickable button, a button that knows how many times it has been clicked.

The code shown below is taken from Programming ASP.NET  but the concepts are the same across many GUI frameworks.

 

 

   1: using System;
   2: using System.ComponentModel;
   3: using System.Web.UI;
   4:  
   5: namespace CustomControls
   6: {
   7:     [DefaultProperty("Text")]
   8:     [ToolboxData("<{0}:CountedButton2 runat=server></{0}:CountedButton2>")]
   9:     public class CountedButton : System.Web.UI.WebControls.Button
  10:     {
  11:       private string displayString;
  12:  
  13:       public CountedButton(  )
  14:       {
  15:          displayString = "clicks";
  16:          InitValues(  );
  17:       }
  18:  
  19:       private void InitValues(  )
  20:       {
  21:          if (ViewState["Count"] == null)
  22:             ViewState["Count"] = 0;
  23:          this.Text = "Click me";
  24:       }
  25:    
  26:       public int Count 
  27:       {
  28:          get { return (int) ViewState["Count"]; }
  29:          set { ViewState["Count"] = value; }
  30:       }
  31:  
  32:       protected override void OnClick(EventArgs e)
  33:       {
  34:          ViewState["Count"] =  ((int)ViewState["Count"]) + 1;
  35:          this.Text = ViewState["Count"] + " " + displayString;
  36:          base.OnClick(e);
  37:       }
  38:     }
  39: }
  40:  

 

 

Option Three

And the third, typically most labor-intensive approach is to derive from a root Control object and (typically) draw the entire control and write all its logic yourself. An example that I’ve used a number of times is a custom clock face where the date travels around the circumference in Ferris Wheel like fashion and where you must draw the numbers, and the hour and minute hands and the position for the seconds indicator essentially “by hand”

CustomClock

This kind of control requires extensive coding in some environments, as shown in this cut down code listing (full and annotated listing in Programming WinForms.)

   1: public class ClockFaceCtrl : System.Windows.Forms.Control
   2: {
   3:     public ClockFaceCtrl()
   4:     {    }
   5:  
   6:     public void OnTimer(Object source, ElapsedEventArgs e)
   7:     {
   8:         Graphics g = this.CreateGraphics();
   9:         SetScale(g);
  10:         DrawFace(g);
  11:         DrawTime(g,false);
  12:         DrawDate(g);
  13:         g.Dispose();
  14:     }
  15:  
  16:     protected override void OnPaint ( PaintEventArgs e )
  17:     {     }
  18:  
  19:     private void SetScale(Graphics g)
  20:     {
  21:         g.TranslateTransform(Width / 2, Height / 2);
  22:         float inches = Math.Min(Width / g.DpiX, Height / g.DpiX);
  23:         g.ScaleTransform(inches * g.DpiX / 2000, inches * g.DpiY / 2000);
  24:     }
  25:  
  26:     private static float GetSin(float degAngle)
  27:     { return (float) Math.Sin(Math.PI * degAngle / 180f);     }
  28:  
  29:     private static float GetCos(float degAngle)
  30:     { return (float) Math.Cos(Math.PI * degAngle / 180f);     }
  31:  
  32:     private void DrawFace(Graphics g)
  33:     {
  34:         for (int i = 1; i <= numHours; i++)
  35:         {
  36:             SizeF stringSize = g.MeasureString(i.ToString(),font);
  37:             x = GetCos(i*deg + 90) * FaceRadius;
  38:             y = GetSin(i*deg + 90) * FaceRadius;
  39:             if ( currentTime.Second  == i * 5)
  40:                 g.DrawString(i.ToString(), font, greenBrush, -x, -y,format);
  41:             else
  42:                 g.DrawString(i.ToString(), font, brush, -x, -y,format);
  43:         }
  44:     }
  45:  
  46:     private void DrawTime(Graphics g, bool forceDraw)
  47:     {
  48:         rotation = GetSecondRotation();
  49:         state = g.Save();
  50:         g.RotateTransform(rotation);
  51:         g.FillEllipse(blankBrush,-25,-secondLength,50,50);
  52:         g.Restore(state);
  53:     }
  54:  
  55:     private float GetHourRotation()
  56:     { }
  57:     private float GetMinuteRotation()
  58:     { return( 360f * currentTime.Minute / 60f );  }
  59:  
  60:     private float GetSecondRotation()
  61:     { return(360f * currentTime.Second / 60f);  }
  62:  
  63:     private class LtrDraw
  64:     {    }
  65:  
  66:     private class StringDraw
  67:     { 
  68:         public StringDraw(string s, ClockFaceCtrl theControl)
  69:         {           }
  70:  
  71:         public void DrawString(Graphics g, Brush brush)
  72:         {
  73:             foreach (LtrDraw theLtr in theString)
  74:             {
  75:                 theLtr.DrawString(g,brush,theControl);
  76:             }
  77:             ClockFaceCtrl.offset += 1;
  78:         }
  79:     }
  80:  
  81:     private void DrawDate(Graphics g)
  82:     {
  83:         Brush brush = new SolidBrush(ForeColor);
  84:         sdToday.DrawString(g,brush);
  85:     }
  86: }

 

 

 

 

 

 

 

 

 

Why Is This GUI Framework Different From All Other Gui Frameworks?

Writing a templatable custom control in Silverlight is exactly the same, only totally different. That is, the architectural build/ buy/ make-do decisions are the same, and the derivation decisions are the same, but the new wrinkle is the separation of logic from visuals though a contract enforced by the Parts and States model and leveraged by the Visual State Machine.

It is to that, I will turn when I wake up, but I suddenly realized that it is 2:20 am, and I should be sleeping. Very much.

 

Follow-up
More here:  Digging Into Custom Controls

About Jesse Liberty

Jesse Liberty has three decades of experience writing and delivering software projects and is the author of 2 dozen books and a couple dozen online courses. His latest book, Building APIs with .NET will be released early in 2025. Liberty is a Senior SW Engineer for CNH and he was a Senior Technical Evangelist for Microsoft, a Distinguished Software Engineer for AT&T, a VP for Information Services for Citibank and a Software Architect for PBS. He is a Microsoft MVP.
This entry was posted in z Silverlight Archives and tagged . Bookmark the permalink.