Building simple Custom Web Server Control, ASP.NET GroupBox

in #utopian-io8 years ago (edited)

What Will I Learn?

In this tutorial you will learn how to create the ASP.NET GroupBox custom web server control.

Requirements

ASP.NET Core

Difficulty

Intermediate

Tutorial Contents

  1. Html element that represent a GroupBox like shape
  2. Overriding the WebControl Class
  3. Overriding the rendering related methods of WebControl Class
  4. AddAttributesToRender Method
  5. RenderBeginTag Method
  6. RenderEndTag Method
  7. Render Method
  8. Maintaining ViewState
  9. TrackViewState Method
  10. SaveViewState Method
  11. LoadViewState Method
  12. Building the GroupBox typed style, GroupBoxStyle

Building simple Custom Web Server Control, ASP.NET GroupBox

The web control we are about to build is considered a container control, just like the ASP.NET Panel control. Actually if you set the GroupingText property of the Panel control you'll end up with what we are going to implement in few seconds, however with some limitations.

Html element that represent a GroupBox like shape

The <fieldset></fieldset> element with the <legend></legend> element embedded inside will give you the shape of a GroupBox. It's worth mentioning that the fieldset element is a block element just like the div element. Our web control will render a fieldset html tag, and if the caption is defined, a legend html will be rendered inside the fieldset element.

Overriding the WebControl Class

Before we go through this, I'd like to mention that the WebControl class is a good example to show how Template design patterns are implemented. The WebControl class defines an overridable method called Render(HtmlTextWriter writer) which is inherited from the Control class. This method acts as Template Method. The typical implementation of this method looks like this:

protected override void Render(HtmlTextWriter writer)
{
   this.RenderBeginTag(writer);
   this.RenderContents(writer);
   this.RenderEndTag(writer);
}

As you can see above, the method invokes three other methods in sequence RenderBeginTag(writer), RenderContents(writer) and RenderEndTag(writer). In our GroupBox control, we will override the 1st and 3rd methods, and let the base Render method do its job as a Template and invoke our overridden methods.

Overriding the rendering related methods of WebControl Class

In this section, we will go through the implementation of the following methods:

  1. AddAttributesToRender(HtmlTextWriter writer)
  2. RenderBeginTag(HtmlTextWriter writer)
  3. RenderEndTag(HtmlTextWriter writer)

AddAttributesToRender Method

This method adds the necessary HTML attributes and styles that need to be rendered to the specified HtmlTextWriterTag method. The simplest implementation of this method just makes a call to the base.AddAttributesToRender(writer), and this is what most of us will do, in addition to any extra attributes you might wish to render, e.g. specifying content's direction or horizontal alignment. Although these properties are specified and handled by the GroupBoxStyle as we will see later, I thought it is worth mentioning how to use the AddAttributesToRender method incase they are not specified in the control style class.

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
 base.AddAttributesToRender(writer);
 HorizontalAlign horAlign = this.HorizontalAlign;
 if (horAlign != HorizontalAlign.NotSet)
 {
   TypeConverter tc = TypeDescriptor.GetConverter(typeof(HorizontalAlign));
   writer.AddStyleAttribute(HtmlTextWriterStyle.TextAlign,
               tc.ConvertToInvariantString(horAlign).ToLowerInvariant());
 }
}

RenderBeginTag Method

This method renders the HTML opening tag of the control to the specified writer. Our GroupBox control specifies the <fieldset> tag as the TagKey in the constructor as seen in the following code statement:

public GroupBox(): base(HtmlTextWriterTag.Fieldset){}

The GroupBox implementation of the RenderBeginTag invokes the AddAttributesToRender method at the beginning and checks if the Caption is specified. If it is, it will render the Caption using the <legend>tag.

public override void RenderBeginTag(HtmlTextWriter writer)
{
 this.AddAttributesToRender(writer);
 writer.RenderBeginTag(this.TagKey);
 if (!String.IsNullOrEmpty(this.Caption))
 {
   RenderCaption(writer);
 }
}
private void RenderCaption(HtmlTextWriter writer)
{
 if (CaptionStyle != null)
 {
   writer.EnterStyle(CaptionStyle, HtmlTextWriterTag.Legend);
   writer.Write(Caption);
   writer.ExitStyle(CaptionStyle, HtmlTextWriterTag.Legend);
 }
 else
 {
   writer.RenderBeginTag(HtmlTextWriterTag.Legend);
   writer.Write(Caption);
   writer.RenderEndTag();
 }            
}

As you might notice in the above code, the RenderCaption method is used to render the <legend> tag to display the Caption; it will also render the CaptionStyle if the Style is specified. The CaptionStyle is an instance of Style. In the Panel control you cannot specify the style for the GroupingText. The CaptionStyle is stored in the ViewState. Later in this article we will see how to manage the ViewState to track the CaptionStyle property.

RenderEndTag Method

This method renders the HTML closing tag of the control into the specified writer. As long as our control is very simple and does not include complex rendering mechanism; this method can simply call the base.RenderEndTag(writer), of course this will render the closing tag for fieldset tag.

public override void RenderEndTag(HtmlTextWriter writer)
{
 base.RenderEndTag(writer);
}

Render Method

You might ask yourself, whether we shall implement the Render method or not? The answer was mentioned earlier in this article. The WebControl class already implements this method in a way that we can use it as shown in the first code snippet. We will also not implement RenderContents method as there is no need for that.

Maintaining ViewState

Earlier we mentioned something about the CaptionStyle property; this property is a Style object stored in the ViewState and used to render GroupBox's Caption style. The code below shows how the CaptionStyle property is flagged for the ViewState for the tracking purpose.

private Style _captionStyle;
public Style CaptionStyle
{
 get
 {
   if (_captionStyle == null)
   {
     _captionStyle = new Style();
     if(IsTrackingViewState)
       ((IStateManager)_captionStyle).TrackViewState();
   }
   return _captionStyle;
 }
}

To complete the ViewState tracking and maintaining, we'll have to override the following three methods

  1. TrackViewState()
  2. SaveViewState()
  3. LoadViewState(object savedState)

It is worth mentioning that the style object can maintain its own ViewState by itself, so this will simplify our job; as we will just make calls to the related ViewState tracking methods, TrackViewState,SaveViewState and LoadViewState.

TrackViewState Method

The TrackViewState method causes the control to track changes to its ViewState so that they can be stored in the object's ViewState property. Again here we will invoke the TrackViewState method for our _captionStyle instance.

protected override void TrackViewState()
{
 base.TrackViewState();
 if (_captionStyle != null)
   ((IStateManager)_captionStyle).TrackViewState();
}

SaveViewState Method

This method saves any state that was modified after the TrackViewState method was invoked. We will invoke the base.SaveViewState(), this should return what is saved by the base class. Then we will call the SaveViewState method of our _captionStyle instance.

protected override object SaveViewState()
{
 object[] state = new object[2];
 //Retrieve object saved by base class
 state[0] = base.SaveViewState();
 IStateManager sm = _captionStyle as IStateManager;
 state[1] = (sm  != null) ? sm.SaveViewState() : null;
 //Make sure that we are returning a state that contains something
 for (int i = 0; i < 2; i++)
 {
   if (state[i] != null)
     return state;
 }
 //otherwise return null
 return null;
}

LoadViewState Method

This method restores the ViewState information from the previous request that was saved with the SaveViewState method. Here we are loading the saved information from the ViewState.

protected override void LoadViewState(object savedState)
{
 if (savedState == null)
   base.LoadViewState(savedState);
 else
 {
   object[] state = (object[])savedState;
   //Check for state lengh, in SaveViewState we only save 2 items
   if (state.Length != 2)
     throw new ArgumentException("Invalid view state");

   base.LoadViewState(state[0]);
   //Load Caption Style ViewState
   if (state[1] != null)
     ((IStateManager)_captionStyle).LoadViewState(state[1]);
 }
}

Building the GroupBox typed style, GroupBoxStyle

I thought of building a custom typed style class for the GroupBox control, the GroupBoxStyle. It will contain two properties to specify the horizontal alignment as well as the direction (right to left or left to right). As a beginning we will override the CreateControlStyle method of the GroupBox control to return a new instance of GroupBoxStyle.

protected override Style CreateControlStyle()
{
 return new GroupBoxStyle(this.ViewState);
}

GroupBoxStyle

The GroupBoxStyle typed style class will be used to delegate the style functionality. It exposes 2 style properties, HorizontalAlign and Direction. Of course you might suggest more properties like Wrap or Display. For simplicity will only focus on our 2 properties. These two properties are also defined in the GroupBoxStyle class. Before we go further, here are the basic steps to write the typed style class:

  1. Create a class they derives from System.Web.UI.WebControls.Style
  2. Define style properties that your control will offer and store them in your style's ViewState. In our case they are HorizontalAlign and Direction
  3. Override Style.CopyFrom and Style.MergeWith methods to copy from or to merge the properties you defined with properties for a given style
  4. Override Style.Reset method to remove the properties you added to the ViewState
  5. Override the Style.AddAttributesToRender method to generate HTML and CSS attributes as part of the rendering process.

The GroupBoxStyle Derives from Style class

The GroupBoxStyle class derives directly from the Style class and defines two properties HorizontalAlign and Direction. These properties support the corresponding style properties in our control.

public class GroupBoxStyle : Style
{
 private const int PROP_HORIZONTALALIGHN = 1;
 private const int PROP_DIRECTION = 2;

 public GroupBoxStyle(StateBag bag) : base(bag)
 {
 }

 public HorizontalAlign HorizontalAlign
 {
   get
   {
     //Check if the property is already set, if yes retrieve from VeiwState
     if (this.IsSet(GroupBoxStyle.PROP_HORIZONTALALIGHN))
       return (HorizontalAlign)ViewState["HorizontalAlign"];
     return HorizontalAlign.NotSet;
   }
   set
   {
     if (value < HorizontalAlign.NotSet || value > HorizontalAlign.Justify)
       throw new ArgumentOutOfRangeException("value");
     ViewState["HorizontalAlign"] = value;
   }
 }

 public ContentDirection Direction
 {
   get
   {
     //Check if the property is already set, if yes retrieve from VeiwState
     if (this.IsSet(GroupBoxStyle.PROP_DIRECTION))
       return (ContentDirection)ViewState["Direction"];
     return ContentDirection.NotSet;
   }
   set
   {
     if ((value < ContentDirection.NotSet) ||
         (value > ContentDirection.RightToLeft))
       throw new ArgumentOutOfRangeException("value");
     ViewState["Direction"] = value;
   }
 }

 private bool IsSet(int propertyNumber)
 {
   string key = null;
   switch (propertyNumber)
   {
     case 1:
       key = "HorizontalAlign";
       break;
     case 2:
       key = "Direction";
       break;
   }
   if (key != null)
     return ViewState[key] != null;

   return false;
 }
 ...
}

Overriding the Style.AddAttributesToRender

The GroupBoxStyle class calls the base class method and then performs its own logic, just as the following:

public override void AddAttributesToRender(HtmlTextWriter writer, WebControl owner)
{
 base.AddAttributesToRender(writer, owner);
 //Check if the property is set
 if (this.IsSet(PROP_HORIZONTALALIGHN))
 {
   HorizontalAlign hAlign = this.HorizontalAlign;
   if (hAlign != HorizontalAlign.NotSet)
   {
     TypeConverter hac = TypeDescriptor.GetConverter(typeof(HorizontalAlign));
     //Write the style attribute
     writer.AddStyleAttribute( HtmlTextWriterStyle.TextAlign,
                               hac.ConvertToInvariantString(hAlign));
   }
 }

 if (this.IsSet(PROP_DIRECTION))
 {
   ContentDirection direction = this.Direction;
   if (direction != ContentDirection.NotSet)
   {
     TypeConverter dirc = TypeDescriptor.GetConverter(typeof(ContentDirection));
     //Write the direction attribute
     writer.AddAttribute(HtmlTextWriterAttribute.Dir,
                         dirc.ConvertToInvariantString(direction));
   }
 }
}

Overriding the Style.CopyFrom and Style.MergeWith methods

The CopyFrom method will replace the existing property values with those properties that have been set in the Style instance that are passed to the method. This method invokes its base class method and then performs its own logic.

public override void CopyFrom(Style s)
{
 if (s != null)
 {
   base.CopyFrom(s);
   if (s is GroupBoxStyle)
   {
     GroupBoxStyle grps = (GroupBoxStyle)s;
     if (!grps.IsEmpty)
     {
       if (grps.IsSet(PROP_HORIZONTALALIGHN))
         this.HorizontalAlign = grps.HorizontalAlign;
       if (grps.IsSet(PROP_DIRECTION))
         this.Direction = grps.Direction;
     }
   }
 }
}

The MergeWith method will preserve the values of the properties that are already set and copies other properties that are set in the Style instance passed to the method. This method invokes its base class method and then performs its own logic as well.

public override void MergeWith(Style s)
{
 if (s != null)
 {
   if (this.IsEmpty)
   {
     /*Merging with an empty style is equivalent to copying
     which is more efficient.*/

     this.CopyFrom(s);
     return;
   }

   base.MergeWith(s);
   if (s is GroupBoxStyle)
   {
     GroupBoxStyle grps = (GroupBoxStyle)s;
     if (!grps.IsEmpty)
     {
       if (grps.IsSet(PROP_HORIZONTALALIGHN) &&
           !this.IsSet(PROP_HORIZONTALALIGHN))
         this.HorizontalAlign = grps.HorizontalAlign;
       if (grps.IsSet(PROP_DIRECTION) &&
           !this.IsSet(PROP_DIRECTION))
         this.Direction = grps.Direction;
     }
   }
 }
}

Overriding the Style.Reset method

The Reset method will remove the properties that were added to the ViewState. Again this method invokes the base class method and performs its own logic.

public override void Reset()
{
 base.Reset();
 if (this.IsEmpty)
   return;

 if (this.IsSet(PROP_HORIZONTALALIGHN))
   ViewState.Remove("HorizontalAlign");

 if (this.IsSet(PROP_DIRECTION))
   ViewState.Remove("Direction");
}

Style ViewState

I'm sure you have noticed that we used Style ViewState to store our properties as the Style class already maintains the ViewState by default.

Conclusion

In this article we discussed how to build a simple container control similar to the ASP.NET Panel control. We have seen how to give our control additional style features by supporting the style for the caption and building custom typed styles for the control.The attached code supports design time features for the GroupBox control. As this tutorial got long enough, I thought I might discuss the design time support by building a simple custom control designer on another article referencing this article. Just for a hint, if you wished to build a custom control designer for container control like this one, you'll need to derive from the ContainerControlDesigner class.

You also could also easily add additional style properties like support for the background image and scrolling contents. You might also add a caption image etc... All these are sample practices to put your hand on first steps to develop custom controls. 



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]

Hey @elissa I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Suggestions

  • Contribute more often to get higher and higher rewards. I wish to see you often!
  • Work on your followers to increase the votes/rewards. I follow what humans do and my vote is mainly based on that. Good luck!

Get Noticed!

  • Did you know project owners can manually vote with their own voting power or by voting power delegated to their projects? Ask the project owner to review your contributions!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Coin Marketplace

STEEM 0.04
TRX 0.33
JST 0.094
BTC 63953.34
ETH 1800.94
USDT 1.00
SBD 0.39