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:
-
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: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!