Friday, December 14, 2007

Natural Sort in C#

Wilco Bauwer brought this article to my attention and as I'm always up for a good challenge I decided to give it a try. As I like regular expressions the first thing I decided was that I would use regular expressions to split the strings within the array instead of string methods and tokens. Now that that was out of the way I still needed to find a way to compare e.g. "10" and "5" so that "5" takes precedence. Obvious solution was to pad the strings with "0" so they would equal in length - i.e. the strings in the previous example would come "10" and "05" respectively. Enough of this chit chat as it's time to let the code speak for itself:

private static int CompareNaturally(string a, string b)
{
    MatchCollection m1 = Regex.Matches(a ?? "", @"\d+|[^\d]+");
    MatchCollection m2 = Regex.Matches(b ?? "", @"\d+|[^\d]+");

    for (int i = 0, c = 0; i < Math.Max(m1.Count, m2.Count); i++)
    {
        string s1 = i < m1.Count ? m1[i].Value : "";
        string s2 = i < m2.Count ? m2[i].Value : "";
        if (s1.Length > 0 && s2.Length > 0 && Char.IsDigit(s1[0]) && Char.IsDigit(s2[0]))
        {
            int len = Math.Max(s1.Length, s2.Length);
            c = s1.PadLeft(len, '0').CompareTo(s2.PadLeft(len, '0'));
        } else
            c = s1.CompareTo(s2);

        if (c != 0)
            return c;
    }

    return 0;
}

To use the above method for sorting an array one would simply use Array.Sort(myStringArray, CompareNaturally); where myStringArray is of course an array of strings. Wilco recommended I should use Regex.Replace() and MatchEvaluator to make it even shorter. So, here is a one-liner (okay, it's split on multiple lines but just for easier reading):

private static int CompareNaturally_Short(string a, string b)
{
    return Regex.Replace(a ?? "", @"\d+|[^\d]+", delegate(Match m) {
        return Char.IsDigit(m.Value[0]) ? m.Value.PadLeft(
            Math.Max(a.Length, b != null ? b.Length : 0), '0') : m.Value; 
    }).CompareTo(Regex.Replace(b, @"\d+|[^\d]+", delegate(Match m) {
        return Char.IsDigit(m.Value[0]) ? m.Value.PadLeft(
            Math.Max(a != null ? a.Length : 0, b.Length), '0') : m.Value; 
    }));
}
The idea behind this shorter version is pretty much the same with the exception that the MatchEvaluator takes care of padding the numeric strings. Don't hesitate contacting me or dropping a comment if you spot errors in either of the methods - Unfortunately I really didn't have time to test them thoroughly with different type of inputs.

Saturday, January 6, 2007

ASP.NET: Adding Style Property to Web Control

Let's start off with something relatively easy. There are some web controls in ASP.NET (Calendar, TreeView and Menu for instance) that allow you to change various aspects of their looks with style properties.

There are two ways to set a style property in .aspx file. First you can set the property as an attribute to the element that defines the control by concatenating the name of the style object with the property:

<asp:TreeView ID="TreeView1" runat="server"
    NodeStyle-CssClass="treeViewNode"/>

Another way is to write the style as its own element that is placed inside the element defining the control:

<asp:TreeView ID="TreeView1" runat="server">
    <NodeStyle CssClass="treeViewNode" />
</asp:TreeView>

Naturally the style can be defined in codebehind as well:

TreeView1.NodeStyle.CssClass = "treeViewNode";

Now, what if you want to add your own style property to your custom web control? The task is not that difficult but there are few things that need to be taken into consideration. First style object is what they call a complex object so writing a simple setter/getter property for the style in your custom control class doesn't cut it. Secondly you might want to store the style object to view state if you're going to change the style at some point and have the change persist.

We'll start off by adding an instance variable for the style object to the class:

private Style myCustomStyle = null;

Next we will add a property for the style:

[DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
PersistenceMode(PersistenceMode.InnerProperty)]
public Style MyCustomStyle
{
    get
    {
        if (myCustomStyle == null)
        {
            myCustomStyle = new Style();
            // Set some default values here.

            if (IsTrackingViewState)
                ((IStateManager)myCustomStyle).TrackViewState();
        }

        return myCustomStyle;
    }
}

Notice how there is no setter part in the above property. The framework will call the getter and set properties directly to the object our getter provides. TrackViewState() is called to notify the framework that our style object should be tracked and changes to it should be saved as part of the control view state. We also add PersistenceMode(PersistenceMode.InnerProperty) to the property's metadata to specify that the property should persist as an inner property.

We are almost done but we still need to take care of view state. We want the view state for our style object to be loaded and saved at the same time with the control's view state. This is achieved by simply overloading the control's LoadViewState(), SaveViewState() and TrackViewState() methods as follows:

protected override void LoadViewState(object state)
{
    object[] states = (object[])state;

    base.LoadViewState(states[0]);
    ((IStateManager)MyCustomStyle).LoadViewState(states[1]);
}

protected override object SaveViewState()
{
    object[] states = new object[2];

    states[0] = base.SaveViewState();
    states[1] = (myCustomStyle != null) ? ((IStateManager)myCustomStyle).SaveViewState() : null;

    return states;
}

protected override void TrackViewState()
{
    base.TrackViewState();

    if (myCustomStyle != null)
        ((IStateManager)myCustomStyle).TrackViewState();
}

That's it! Now you should be able to use your style property just like with built-in ASP.NET controls. What you might still want to do is to apply the styles defined in the style object to the HTML code the control renders by overriding the control's Render() method.

Update (18 Mar 2007): I had a small typo in LoadViewState() method. Instead of accessing the myCustomStyle instance variable in the method, the property MyCustomStyle should have been accessed instead. Thanks to kal-EL@EFNet for pointing that out.

Friday, January 5, 2007

So, Why Yet Another Blog?

I'm very well aware that these days everybody and their mother has a blog and about 90 % of them are never read by anyone. The thing with this blog, though, is that I don't care at all whether someone reads it or not. I'm mainly going to use this as a place to gather some programming tips and tricks I've encountered so that I can access them quickly if I ever have the need. So, yeah, this is probably going to be a rather boring and geeky blog but fortunately no one is forced to read it (at least I hope so). However, if this blog actually manages to help someone then I've accomplished more than I originally expected.

I can't say I enjoy ranting about things but sometimes I just can't help myself. So don't be surprised if you occasionally find a blog entry that contains ranting and whining about something totally insignificant and unrelated. Consider yourself warned.