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:
- A text hyperlink:
- An image:
- A button with HTML content (see this):
- A hyperlink with HTML content:
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:ExtendedButtonrunat="server"ButtonType="Button"ID="button"Text="Button"/>
2: 3:<web:ExtendedButtonrunat="server"ButtonType="Link"ID="link"Text="Link"/>
4: 5:<web:ExtendedButtonrunat="server"ButtonType="Image"ID="image"ImageUrl="~/Images/button.png"/>
6: 7:<web:ExtendedButtonrunat="server"ButtonType="Button"ID="buttonWithTemplate">
8:<Template>
9:<asp:Imagerunat="server"ImageUrl="~/Images/button.png"/>
10: Button With Template11:</Template>
12:</web:ExtendedButton>
13: 14:<web:ExtendedButtonrunat="server"ButtonType="Link"ID="linkWithTemplate">
15:<Template>
16:<asp:Imagerunat="server"ImageUrl="~/Images/button.png"/>
17: Link With Template18:</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:publicclass ExtendedButton : WebControl, IButtonControl, IPostBackEventHandler, INamingContainer, ITextControl, IPostBackDataHandler
8: {9:#region Private staticreadonly fields
10:privatestaticreadonly Object EventClick = new Object();
11:privatestaticreadonly 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:protectedoverridevoid 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:elseif (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:protectedoverridevoid 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:protectedoverridevoid 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:protectedoverridevoid 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:protectedoverride 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:publicoverridevoid 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:protectedvirtual 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:protectedvirtualvoid 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:protectedvirtualvoid 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:publicevent 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:publicevent 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: (thisas IPostBackEventHandler).RaisePostBackEvent(String.Empty);
428: } 429: }430:else
431: {432: (thisas 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!