Extended ASP.NET Button Control

I once had the need to have a button control that would change its look depending on a theme, for example, it would render either as regular button, an image or a link. Of course, the only way I had to achieve this was by manually swapping the Button control for a ImageButton or a LinkButton, which wasn’t really a solution, so I started to think of a control that could do the trick… and here it is!

Basically, I wrote a button control that displays in one of 5 ways:

  • A regular button:
image
  • A text hyperlink:

image

  • An image:

image

  • A button with HTML content (see this):

image

  • A hyperlink with HTML content:

image

In ASP.NET terms, I have a server-side control with a ButtonType property. The markup that produces each effect is as follows:

   1: <web:ExtendedButton runat="server" ButtonType="Button" ID="button" Text="Button"/>
   2:  
   3: <web:ExtendedButton runat="server" ButtonType="Link" ID="link" Text="Link"/>
   4:  
   5: <web:ExtendedButton runat="server" ButtonType="Image" ID="image" ImageUrl="~/Images/button.png"/>
   6:  
   7: <web:ExtendedButton runat="server" ButtonType="Button" ID="buttonWithTemplate">
   8:     <Template>
   9:             <asp:Image runat="server" ImageUrl="~/Images/button.png" />
  10:             Button With Template
  11:         </Template>
  12: </web:ExtendedButton>
  13:  
  14: <web:ExtendedButton runat="server" ButtonType="Link" ID="linkWithTemplate">
  15:         <Template>
  16:             <asp:Image runat="server" ImageUrl="~/Images/button.png" />
  17:             Link With Template
  18:         </Template>
  19: </web:ExtendedButton>

For the Image value of ButtonType, the only useful properties are ImageUrl, ImageAlign and AlternateText. These will work in the exact same way as the ImageButton control.

For Link and Button, if the Template property is not specified, Text will be used for the textual description of the button or link. If instead a Template is available, it will be used instead of the Text. Keep in mind that you can specify almost any HTML you like for the Template, as long as it can be surrounded by an A (in the case of the Link type) or BUTTON (for Button) tags. If no Template is supplied, it will render and behave just like a LinkButton or a Button.

This control implements IButtonControl, so it shares the usual behavior of regular button controls, like having a text property, a validation group, a postback URL, Click and Command events, event bubbling, etc. It uses the control state to save some properties, so it is safe to turn off view state in it.

I almost forgot: here is the code!

   1: [ParseChildren(true)]
   2: [DefaultEvent("Click")]
   3: [PersistChildren(false)]
   4: [DefaultProperty("Text")]
   5: [SupportsEventValidation]
   6: [ToolboxData("<{0}:ExtendedButton runat=\"server\" Text=\"\" />")]
   7: public class ExtendedButton : WebControl, IButtonControl, IPostBackEventHandler, INamingContainer, ITextControl, IPostBackDataHandler
   8: {
   9:     #region Private static readonly fields
  10:     private static readonly Object EventClick = new Object();
  11:     private static readonly Object EventCommand = new Object();
  12:     #endregion
  13:  
  14:     #region Public constructor
  15:     public ExtendedButton() : base(HtmlTextWriterTag.Input)
  16:     {
  17:         this.ButtonType = ButtonType.Button;
  18:         this.CommandArgument = String.Empty;
  19:         this.CommandName = String.Empty;
  20:         this.OnClientClick = String.Empty;
  21:         this.ImageAlign = ImageAlign.NotSet;
  22:         this.ImageUrl = String.Empty;
  23:         this.PostBackUrl = String.Empty;
  24:         this.CausesValidation = true;
  25:         this.ValidationGroup = String.Empty;
  26:         this.UseSubmitBehavior = true;
  27:         this.Text = String.Empty;
  28:     }
  29:     #endregion
  30:  
  31:     #region Protected override methods
  32:     protected override void AddAttributesToRender(HtmlTextWriter writer)
  33:     {
  34:         base.AddAttributesToRender(writer);
  35:  
  36:         this.Page.VerifyRenderingInServerForm(this);
  37:  
  38:         if (this.ButtonType == ButtonType.Button)
  39:         {
  40:             if (this.Template == null)
  41:             {
  42:                 if (this.UseSubmitBehavior == true)
  43:                 {
  44:                     writer.AddAttribute(HtmlTextWriterAttribute.Type, "submit");
  45:                 }
  46:                 else
  47:                 {
  48:                     writer.AddAttribute(HtmlTextWriterAttribute.Type, "button");
  49:                 }
  50:             }
  51:  
  52:             writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
  53:             writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
  54:         }
  55:         else if (this.ButtonType == ButtonType.Image)
  56:         {
  57:             writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
  58:             writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
  59:             writer.AddAttribute(HtmlTextWriterAttribute.Alt, this.AlternateText);
  60:             writer.AddAttribute(HtmlTextWriterAttribute.Type, "image");
  61:             writer.AddAttribute(HtmlTextWriterAttribute.Src, HttpUtility.HtmlEncode(this.ResolveClientUrl(this.ImageUrl)));
  62:  
  63:             if (this.ImageAlign != ImageAlign.NotSet)
  64:             {
  65:                 writer.AddAttribute(HtmlTextWriterAttribute.Align, this.ImageAlign.ToString().ToLower());
  66:             }
  67:         }
  68:  
  69:         String firstScript = this.OnClientClick;
  70:         PostBackOptions postBackOptions = this.GetPostBackOptions();
  71:                     
  72:         if (this.IsEnabled == true)
  73:         {
  74:             if (this.HasAttributes == true)
  75:             {
  76:                 String script = this.Attributes[HtmlTextWriterAttribute.Onclick.ToString()];
  77:  
  78:                 if (String.IsNullOrWhitespace(script) == false)
  79:                 {
  80:                     firstScript = String.Join(";", new String[] { firstScript, script });
  81:                     this.Attributes.Remove(HtmlTextWriterAttribute.Onclick.ToString());
  82:                 }
  83:             }
  84:  
  85:             String postBackEventReference = this.Page.ClientScript.GetPostBackEventReference(postBackOptions, true);
  86:  
  87:             if (String.IsNullOrWhiteSpace(postBackEventReference) == false)
  88:             {
  89:                 firstScript = firstScript + postBackEventReference;
  90:  
  91:                 if ((this.ButtonType == ButtonType.Link) || ((this.ButtonType == ButtonType.Button) && (this.Template != null)))
  92:                 {
  93:                     writer.AddAttribute(HtmlTextWriterAttribute.Href, postBackEventReference);
  94:                 }
  95:             }
  96:             else
  97:             {
  98:                 if (this.ButtonType == ButtonType.Link)
  99:                 {
 100:                     writer.AddAttribute(HtmlTextWriterAttribute.Href, "javascript:void(0)");
 101:                 }
 102:             }
 103:         }
 104:  
 105:         if (firstScript.Length > 0)
 106:         {
 107:             if (this.ButtonType == ButtonType.Button)
 108:             {
 109:                 if (this.UseSubmitBehavior == false)
 110:                 {
 111:                     writer.AddAttribute(HtmlTextWriterAttribute.Onclick, firstScript);
 112:                 }
 113:             }
 114:             else
 115:             {
 116:                 if (String.IsNullOrWhiteSpace(this.OnClientClick) == false)
 117:                 {
 118:                     writer.AddAttribute(HtmlTextWriterAttribute.Onclick, this.OnClientClick);
 119:                 }
 120:             }
 121:         }
 122:  
 123:         if ((this.Enabled == true) && (this.IsEnabled == false))
 124:         {
 125:             writer.AddAttribute(HtmlTextWriterAttribute.Disabled, HtmlTextWriterAttribute.Disabled.ToString().ToLower());
 126:         }
 127:     }
 128:  
 129:     protected override void OnInit(EventArgs e)
 130:     {
 131:         this.Page.RegisterRequiresControlState(this);
 132:  
 133:         if (this.ButtonType == ButtonType.Image)
 134:         {
 135:             this.Page.RegisterRequiresPostBack(this);
 136:         }
 137:  
 138:         base.OnInit(e);
 139:     }
 140:  
 141:     protected override void RenderContents(HtmlTextWriter writer)
 142:     {
 143:         if ((this.ButtonType == ButtonType.Link) || (this.ButtonType == ButtonType.Button))
 144:         {
 145:             if (this.Template != null)
 146:             {
 147:                 PlaceHolder placeHolder = new PlaceHolder();
 148:                 this.Template.InstantiateIn(placeHolder);
 149:                 this.Controls.Add(placeHolder);
 150:                 base.RenderContents(writer);
 151:             }
 152:             else
 153:             {
 154:                 if (this.ButtonType == ButtonType.Link)
 155:                 {
 156:                     writer.WriteEncodedText(this.Text);
 157:                 }
 158:             }
 159:         }
 160:     }
 161:  
 162:     protected override void LoadControlState(Object savedState)
 163:     {
 164:         Object [] state = savedState as Object [];
 165:  
 166:         this.OnClientClick = (String) state [ 1 ];
 167:         this.CausesValidation = (Boolean) state [ 2 ];
 168:         this.ValidationGroup = (String) state [ 3 ];
 169:         this.ButtonType = (ButtonType) state [ 4 ];
 170:         this.PostBackUrl = (String) state [ 5 ];
 171:         this.UseSubmitBehavior = (Boolean) state [ 6 ];
 172:         this.CommandArgument = (String) state [ 7 ];
 173:         this.CommandName = (String) state [ 8 ];
 174:         this.Text = (String) state [ 9 ];
 175:         this.ImageUrl = (String) state [ 10 ];
 176:         this.ImageAlign = (ImageAlign) state [ 11 ];
 177:  
 178:         base.LoadControlState(state [ 0 ]);
 179:     }
 180:  
 181:     protected override Object SaveControlState()
 182:     {
 183:         Object [] state = new Object [] { base.SaveControlState(), this.OnClientClick, this.CausesValidation, this.ValidationGroup, this.ButtonType, this.PostBackUrl, this.UseSubmitBehavior, this.CommandArgument, this.CommandName, this.Text, this.ImageUrl, this.ImageAlign };
 184:         return (state);
 185:     }
 186:     #endregion
 187:  
 188:     #region Public override methods
 189:     public override void RenderBeginTag(HtmlTextWriter writer)
 190:     {
 191:         this.AddAttributesToRender(writer);
 192:  
 193:         switch (this.ButtonType)
 194:         {
 195:             case ButtonType.Button:
 196:                 if (this.Template != null)
 197:                 {
 198:                     writer.RenderBeginTag(HtmlTextWriterTag.Button);
 199:                 }
 200:                 else
 201:                 {
 202:                     writer.RenderBeginTag(HtmlTextWriterTag.Input);
 203:                 }
 204:                 break;
 205:  
 206:             case ButtonType.Image:
 207:                 writer.RenderBeginTag(HtmlTextWriterTag.Input);
 208:                 break;
 209:  
 210:             case ButtonType.Link:
 211:                 writer.RenderBeginTag(HtmlTextWriterTag.A);
 212:                 break;
 213:         }
 214:     }
 215:     #endregion
 216:  
 217:     #region Protected virtual methods
 218:     protected virtual PostBackOptions GetPostBackOptions()
 219:     {
 220:         PostBackOptions options = new PostBackOptions(this, String.Empty);
 221:         options.ClientSubmit = false;
 222:  
 223:         if ((this.CausesValidation == true) && (this.Page.GetValidators(this.ValidationGroup).Count > 0))
 224:         {
 225:             options.PerformValidation = true;
 226:             options.ValidationGroup = this.ValidationGroup;
 227:         }
 228:  
 229:         if (String.IsNullOrWhiteSpace(this.PostBackUrl) == false)
 230:         {
 231:             options.ActionUrl = HttpUtility.UrlPathEncode(this.ResolveClientUrl(this.PostBackUrl));
 232:         }
 233:  
 234:         if ((this.ButtonType == ButtonType.Link) || ((this.ButtonType == ButtonType.Button) && (this.Template != null)))
 235:         {
 236:             options.ClientSubmit = true;
 237:             options.RequiresJavaScriptProtocol = true;
 238:         }
 239:  
 240:         return (options);
 241:     }
 242:  
 243:     protected virtual void OnClick(EventArgs e)
 244:     {
 245:         EventHandler handler = (EventHandler) this.Events [ EventClick ];
 246:  
 247:         if (handler != null)
 248:         {
 249:             handler(this, e);
 250:         }
 251:     }
 252:  
 253:     protected virtual void OnCommand(CommandEventArgs e)
 254:     {
 255:         CommandEventHandler handler = (CommandEventHandler) this.Events [ EventCommand ];
 256:  
 257:         if (handler != null)
 258:         {
 259:             handler(this, e);
 260:         }
 261:     }
 262:     #endregion
 263:  
 264:     #region IButtonControl Members
 265:     public event EventHandler Click
 266:     {
 267:         add
 268:         {
 269:             this.Events.AddHandler(EventClick, value);
 270:         }
 271:         remove
 272:         {
 273:             this.Events.RemoveHandler(EventClick, value);
 274:         }
 275:     }
 276:  
 277:     public event CommandEventHandler Command
 278:     {
 279:         add
 280:         {
 281:             this.Events.AddHandler(EventCommand, value);
 282:         }
 283:         remove
 284:         {
 285:             this.Events.RemoveHandler(EventCommand, value);
 286:         }
 287:     }
 288:  
 289:     [DefaultValue(true)]
 290:     [Themeable(false)]
 291:     public Boolean CausesValidation
 292:     {
 293:         get;
 294:         set;
 295:     }
 296:  
 297:     [Bindable(true)]
 298:     [DefaultValue("")]
 299:     [Themeable(false)]
 300:     public String CommandArgument
 301:     {
 302:         get;
 303:         set;
 304:     }
 305:  
 306:     [Themeable(false)]
 307:     [DefaultValue("")]
 308:     public String CommandName
 309:     {
 310:         get;
 311:         set;
 312:     }
 313:  
 314:     [DefaultValue("")]
 315:     [Themeable(false)]
 316:     [UrlProperty("*.aspx")]
 317:     public String PostBackUrl
 318:     {
 319:         get;
 320:         set;
 321:     }
 322:  
 323:     [DefaultValue("")]
 324:     [Themeable(false)]
 325:     public String ValidationGroup
 326:     {
 327:         get;
 328:         set;
 329:     }
 330:  
 331:     [DefaultValue("")]
 332:     [Localizable(true)]
 333:     [Bindable(true)]
 334:     public String Text
 335:     {
 336:         get;
 337:         set;
 338:     }
 339:     #endregion
 340:  
 341:     #region Public properties
 342:     [Browsable(false)]
 343:     [TemplateContainer(typeof(ExtendedButton))]
 344:     [TemplateInstance(TemplateInstance.Single)]
 345:     [PersistenceMode(PersistenceMode.InnerProperty)]
 346:     public ITemplate Template
 347:     {
 348:         get;
 349:         set;
 350:     }
 351:  
 352:     [DefaultValue(ButtonType.Button)]
 353:     public ButtonType ButtonType
 354:     {
 355:         get;
 356:         set;
 357:     }
 358:  
 359:     [Themeable(false)]
 360:     [DefaultValue("")]
 361:     public String OnClientClick
 362:     {
 363:         get;
 364:         set;
 365:     }
 366:  
 367:     [DefaultValue("")]
 368:     [Themeable(false)]
 369:     [UrlProperty("*.jpg;*.gif;*.png")]
 370:     public String ImageUrl
 371:     {
 372:         get;
 373:         set;
 374:     }
 375:  
 376:     [DefaultValue(ImageAlign.NotSet)]
 377:     public ImageAlign ImageAlign
 378:     {
 379:         get;
 380:         set;
 381:     }
 382:  
 383:     [DefaultValue("")]
 384:     [Themeable(false)]
 385:     public String AlternateText
 386:     {
 387:         get;
 388:         set;
 389:     }
 390:  
 391:     [DefaultValue(true)]
 392:     [Themeable(false)]
 393:     public Boolean UseSubmitBehavior
 394:     {
 395:         get;
 396:         set;
 397:     }
 398:     #endregion
 399:  
 400:     #region IPostBackEventHandler Members
 401:     void IPostBackEventHandler.RaisePostBackEvent(String eventArgument)
 402:     {
 403:         this.Page.ClientScript.ValidateEvent(this.UniqueID, eventArgument);
 404:  
 405:         if (this.CausesValidation == true)
 406:         {
 407:             this.Page.Validate(this.ValidationGroup);
 408:         }
 409:  
 410:         this.OnClick(EventArgs.Empty);
 411:         this.OnCommand(new CommandEventArgs(this.CommandName, this.CommandArgument));
 412:  
 413:         this.RaiseBubbleEvent(this, EventArgs.Empty);
 414:     }
 415:     #endregion
 416:  
 417:     #region IPostBackDataHandler Members
 418:  
 419:     Boolean IPostBackDataHandler.LoadPostData(String postDataKey, NameValueCollection postCollection)
 420:     {
 421:         if (postDataKey == this.UniqueID)
 422:         {
 423:             if (this.ButtonType == ButtonType.Image)
 424:             {
 425:                 if ((String.IsNullOrWhiteSpace(postCollection[postDataKey + ".x"]) == false) && (String.IsNullOrWhiteSpace(postCollection[postDataKey + ".y"]) == false))
 426:                 {
 427:                     (this as IPostBackEventHandler).RaisePostBackEvent(String.Empty);                    
 428:                 }
 429:             }
 430:             else
 431:             {
 432:                 (this as IPostBackEventHandler).RaisePostBackEvent(String.Empty);
 433:             }
 434:         }
 435:  
 436:         return(false);
 437:     }
 438:  
 439:     void IPostBackDataHandler.RaisePostDataChangedEvent()
 440:     {
 441:     }
 442:  
 443:     #endregion
 444: }

As usual, I hope you find it useful!

                             

No Comments