Introduction
This article presents a component that is used to add a sort indicator image to GridView
columns.
The Internet provides a lot of sites that show how you can add some sort of sort indicator to a GridView
, but all of them do this for a single GridView
. Our application has quite a lot of GridView
s that need this feature, and therefore I decided that a component would be the right way to go. A component has the advantage that I can code the behaviour just once and not for every single GridView
over and over again.
The rest of this article shows you how you can use the GridViewSortExtender
(that's the name of the component) and how it works on the inside.
Just use it
To use the GridViewSortExtender
, simply put one on your pages along with the GridView
you want to extend:
Then, set the properties for the images, and specify which GridView
should be extended on the Property pane:
That's it! Run the application. It will look something like this:
On the inside
The GirdViewSortExtender
is quite a simple component, but there are still some interesting things about it that I'll discuss in this section.
How to add the indicator to the sort column
I use the OnPreRender
method of the GridViewSortExtender
to modify the extended GridView
. This works in my projects because I normally do not modify the page content in the OnPreRender
method of the page anymore. Note: the OnPreRender
on the page is called after the OnPreRender
method of the contained components.
Note: The image is always added because otherwise it will be lost on postbacks that do not bind the GridView
(see the message section below for more information).
public class GridViewSortExtender : Control
{
protected override void OnPreRender(EventArgs e)
{
base.OnInit(e);
GridView extendee =
this.NamingContainer.FindControl(this.ExtendeeID) as GridView;
if (extendee != null &&
extendee.AllowSorting &&
extendee.HeaderRow != null &&
!String.IsNullOrEmpty(extendee.SortExpression))
{
int field = GetSortField(extendee);
if (field >= 0)
{
Image img = new Image();
img.ImageUrl =
extendee.SortDirection == SortDirection.Ascending ?
this.AscendingImageUrl : this.DescendingImageUrl;
img.ImageAlign = ImageAlign.TextTop;
extendee.HeaderRow.Cells[field].Controls.Add(img);
}
}
}
private int GetSortField(GridView extendee)
{
int i = 0;
foreach (DataControlField field in extendee.Columns)
{
if (field.SortExpression == extendee.SortExpression)
{
return i;
}
i++;
}
return -1;
}
}
The first thing is to get the extended GridView
. This is accomplished by calling the FindControl
method on the NamingContainer
of the GridViewSortExtender
. The NamingContainer
is the container where the GridViewSortExtender
is defined in. Therefore, the GridViewSortExtender
has to be in the same container as the GridView
it extends.
If the corresponding GridView
is found, then it is checked whether this GridView
allows sorting and has a header row - only then is it reasonable to add the sort indicator.
Then, depending on the SortDirection
of the extended GridView
, an image is instantiated.
The tricky part is how to find the correct column where the image is added. I loop through all columns of the extended GridView
, and check its SortExpression
against the SortExpression
of the GridView
- if it's the same, then I have found the correct column.
Finally, I add the image to the header cell of the column.
A drawback of using the SortExpression
is that the GridViewSortExtender
works only on BoundField
columns. This is especially a problem when you have a GridView
with dynamically generated columns.
A side note: normally, I override the event invoker methods (like OnPreRender
) and don't register events (like PreRender
), because I don't like it that an instance registers events of itself. I consider this bad OO design :-)
How to make it designer ready
I use this component a lot of times in my applications, and therefore I wanted a component that has a nice behaviour in the designer.
Reference images
The GridViewSortExtender
has two references to images. I use the Editor
attribute to get a nice selection dialog in the designer. The URL of the images is then stored within the ViewState
.
[DefaultValue("")]
[Editor(typeof(ImageUrlEditor), typeof(UITypeEditor))]
[Description("Image that is displayed when SortDirection is ascending.")]
public string AscendingImageUrl
{
get { return this.ViewState["AscendingImageUrl"] != null ?
(string)this.ViewState["AscendingImageUrl"] : ""; }
set { this.ViewState["AscendingImageUrl"] = value; }
}
Reference a GridView
To get a similar behaviour for the selection of the GridView
to extend, I choose the IDReferenceProperty
on the ExtendeeID
property. Unfortunately, the result was not stable: sometimes the designer provided me all the GridView
s on the page in the selection list in the property list, sometimes not. Therefore, I added the TypeConverter
attribute and a custom TypeConverter
(GridViewIDConverter
). Now, the designer provides a selection of all DataGrid
s on the page reliably.
[DefaultValue("")]
[IDReferenceProperty(typeof(GridView))]
[TypeConverter(typeof(GridViewIDConverter))]
[Description("The GridView that is extended.")]
public string ExtendeeID
{
get { return this.ViewState["Extendee"] != null ?
(string)this.ViewState["Extendee"] : ""; }
set { this.ViewState["Extendee"] = value; }
}
The GridViewIDConverter
is derived from the generic ControlIDConverter
that does most of the work for us. The only thing we still have to do is to define which controls should be visible in the list that the designer provides to the programmer. In this case, I simply define that the control has to be a GridView
.
public class GridViewIDConverter : ControlIDConverter
{
protected override bool FilterControl(Control control)
{
return control is GridView;
}
}
Display the GirdViewSortExtender in the designer
I added a custom control designer to get a nice look of the component in design mode. The Designer
attribute tells that I want to use the GridViewSortExtenderDesigner
as a designer for my component. GridViewSortExtenderDesigner
is derived from CodeDesigner
, and overrides the GetDesignTimeHtml
method to return the HTML that is used to display the component in design mode.
Note: You can see how it looks in the image further above.
[Designer(typeof(GridViewSortExtenderDesigner))]
public class GridViewSortExtender : Control
{
... some code ...
}
public class GridViewSortExtenderDesigner : ControlDesigner
{
public override string GetDesignTimeHtml()
{
return
"<div style=\"background-color: #C8C8C8; " +
"border: groove 2 Gray;\"><b>GridViewSortExtender</b> - " +
this.Component.Site.Name + "</div>";
}
}
Possible Extensions
- Add spacer image when column is not sorted (prevent column widths from changing).
Conclusion
The GridViewSortExtenderDesigner
is a simple component to add a sort indicator to a GridView
, and has a nice design time behaviour to ease the usage in a project with several sortable GridView
s.
Source of the Code
The sample code is extracted from a web application that I wrote for bbv Software Services AG.
Thanks for letting me write about it.
History
- 2007-07-23 - Initial version.
- 2007-08-06 - Uses
DataBound
event on GridView
. Thanks to Michael Tucker for his idea. - 2007-08-07 - Changed back to
PreRender
approach because otherwise the sort indicator is lost on postbacks that do not bind the GridView
. Thanks to Kelly Herald for the feedback.