Keep it DRAFT-y

The other day I was grabbing screenshots from an app I’m working on. I noticed a huge visual problem with a a list in the product: in a one-column ListView in Details view, each item’s text was truncated, as though the column’s width was just left to the default of 20 pixels (this is in .NET CF, but the problem below applies to the full framework WinForms as well). This is code I inherited, mind you! I certainly wouldn’t have coded it this way (No sir, nah-uh), but I found the offending code that that was trying to set the column width to the largest item; something like this:

private void ArrangeColumnWidth()
{
    //this is called after all the items have been added
    using (Graphics g = this.listView.CreateGraphics())
    {
        int maxWidth = 0;
        foreach (object item in this.listView.Items)
        {
            SizeF s = g.MeasureString(item.ToString());
            if (s.Width > maxWidth)
                maxWidth = s.Width;
        }
        this.listView.Columns[0].Width = maxWidth;
    }
}

Pretty standard find-the-max code, right? Except it wasn’t working. Since I knew there would always be one column, and we’d never change out of Details view, I, without thinking too hard about it, changed the above code to the following:

private void ArrangeColumnWidth()
{
    this.listView.Columns[0].Width = this.listView.Width;
}

“There! That should do it,” I thought — in the few seconds that it took me to write it. Of course, you’ve just read that, and thought, “What a fool! Anyone can see that if there are enough items in the ListView to cause scrolling vertically, then the column width will be too big, so you’ll get a horizontal scroll bar also. Duh! Everyone knows that. What was he thinking?”

And you’d be right.

But sometimes, when I’m in the flow, I have really fast edit, compile, run cycles. The first run after the above edit showed me the problem that you astute readers already found.

At this point, in my younger days, I’d bash away at determining the width of a standard scroll bar and hardcoding that number in my arithmetic. But I’ve learned the following refrain from using .NET since it came out: It’s in the Framework, Dummy!

So, I did a Google search, found a forum post, read it, couldn’t believe the solution was so simple, then went to the MSDN docs to confirm that it was true. It was! If you set the ColumnHeader.Width value to -1, it will size to the largest item. If you set the ColumnHeader.Width value to -2, it will size the header to the header text. It’s all right there on MSDN!

I didn’t know this little tidbit. So, I re-wrote the code. I got rid of the ArrangeColumnWidth() method altogether and now when I create the column, I set it’s width to -1. Problem solved!

Two things occurred to me when I read about those helpful values.

The first is that that is some old-ass .NET code. Microsoft doesn’t write .NET code like that anymore. Instead, I think it would be something like the following:

public enum ColumnWidthStyle
{
    Normal,
    SizeToMaximumItem,
    SizeToColumnHeader
}

ColumnHeader c = new ColumnHeader();
c.WidthStyle = ColumnWidthStyle.SizeToMaximumItem;

That code is more verbose, sure; but it’s way more discoverable, especially with IntelliSense. It’s also more readable; imagine coming back to this code a few months later: it would still make sense. The way we’re stuck with is essentially a magic number, a vestigial bump from the dark ol’ days of Win32 programming. I have no doubt that the ColumnHeader class is managed spackle over the Win32 equivalent, so we get those negative width numbers for free.

Even with the awkwardness of the API as it is, I’m still going to use it. That’s one method I don’t have to write. The whole point of using the .NET framework is that it does a lot for you. And yet…

I still see commercial code shipping with reams and reams of classes and methods that duplicate functionality in the Framework. Why!?!

We’ve all heard the principle of DRY (Don’t Repeat Yourself). I’ve got my own corollary, the DRAFT principle: Don’t Repeat A Framework Type. .NET, J2EE, Rails, Django, CPAN, whatever language and framework you prefer: assume they solve all your problems, learn them, use them, abuse them. And only if they are truly lacking should you write your own.