Wednesday, February 21, 2007

Sorting in a DataGrid

There doesn't seem to be a lot of information around on getting sorting in a WinForms DataGrid to work with custom collections. So I've done the hard work for you and here it is. First I wrote a sorting class, hopefully generic enough to sort pretty much any kind of simple data types (if your collection contains other objects you'll probably need to update this somewhat).

  internal class Comparer : IComparer
  {
    private PropertyDescriptor sortProperty;
    private ListSortDirection sortDirection;
    public Comparer(PropertyDescriptor sortProperty, ListSortDirection sortDirection)
    {
      this.sortProperty = sortProperty;
      this.sortDirection = sortDirection;
    }

    #region IComparer Members

    public int Compare(object x, object y)
    {      
      Type propType = sortProperty.PropertyType;
      int sort = 0;
      if (propType == typeof(string))
        sort = string.Compare((string)sortProperty.GetValue(x), (string)sortProperty.GetValue(y));
      else if (propType == typeof(DateTime))
        sort = DateTime.Compare((DateTime)sortProperty.GetValue(x), (DateTime)sortProperty.GetValue(y));
      else if (propType.IsEnum)
        sort = (int)sortProperty.GetValue(x) - (int)sortProperty.GetValue(y);
      else if (propType == typeof(int))
        sort = (int)sortProperty.GetValue(x) - (int)sortProperty.GetValue(y);
      else if (propType == typeof(Type))
        sort = string.Compare(sortProperty.GetValue(x).ToString(), sortProperty.GetValue(y).ToString());
      else if (propType == typeof(bool))
        sort = string.Compare(sortProperty.GetValue(x).ToString(), sortProperty.GetValue(y).ToString());
      else
        throw new NotSupportedException();

      if (sortDirection == ListSortDirection.Descending)
        sort = -sort;

      return sort;
    }

    #endregion
  }

Then I had to implement the IBindingList interface. Actually most of the IBindingList interface is redundant (makes me wonder why it wasn't broken down into a few smaller interfaces, ISortList, ISearchList etc), so here are the bits that are different to the stubs provided by Visual Studio.

    void IBindingList.ApplySort(PropertyDescriptor property, ListSortDirection direction)
    {
      sortProperty = property;
      sortDirection = direction;
      objectList.Sort(new Comparer(sortProperty, sortDirection));
    }

    private PropertyDescriptor sortProperty;
    PropertyDescriptor IBindingList.SortProperty
    {
      get
      {
        return sortProperty;
      }
    }

    bool IBindingList.SupportsSorting
    {
      get
      {
        return true;
      }
    }

    bool IBindingList.IsSorted
    {
      get
      {
        return (sortProperty != null);
      }
    }

    void IBindingList.RemoveSort()
    {
      sortProperty = null;
    }

    private ListSortDirection sortDirection;
    ListSortDirection IBindingList.SortDirection
    {
      get
      {
        return sortDirection;
      }
    }

objectList is my internal ArrayList that contains the collection of objects I'm interested in.

I'm not entirely happy with this solution. I have a lot of collection classes and each one has this same bit of code in it, which always smells a bit bad to me. At some point I might try to create a base class that encapsulates this functionality but it doesn't look too straightforward, since IBindingList requires IList, ICollection and IEnumerable implementations as well... 

Tuesday, February 20, 2007

Metastorm Resources

As well as getting hits from people searching for remedies to their medical problems with their bell ends (hint: go and see a doctor), I also get a few hits from people trying to track down information about Metastorm e-Work/BPM. So here's a short list of potentially useful sites.

Process Mapping - OK, it's my employer so I'm biased but it has some useful stuff. If you develop with e-Work be sure to grab a copy of the Procedure Documenter Designer add-in, which not only automatically documents your procedure but also checks for problems in your procedure that are missed by the Designer's validation.

Process Mapping Forums - OK, it's my employer again but if you're looking for an online community to discuss e-Work issues, this is definitely the best place to go. The official Metastorm newsgroups are like a ghost town, the once great eworkdev.org is now a site about Chinese babies and the only other one I'm aware of, eworkhelp.org, seems to be dying due to lack of people and increasing spam.

FreeFlow - Hmm, alright, this is mine... But if you want to integrate .NET and e-Work, these class libraries may well help. There's also a replacement for the abysmal Metastorm admin tools, which I use everyday (can't remember when I actually used the official tools) 

BRD - Thought I better add something that hasn't got anything to do with me just to make me look a little bit impartial. BRD produce the Swift client, an alternative web-based client, that provides quite a few features not available in the standard Metastorm client.

If you have any other links, let me know and I'll update this list. 

Thursday, February 15, 2007

DataGrid copy to clipboard

Here's some code I knocked together to copy the contents of a WinForms DataGrid to the clipboard. Probably won't work for all possible data sources but it was good enough for my needs. 

    public void CopyToClipboard()
    {
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < ts.GridColumnStyles.Count; i++)
      {
        if (i > 0)
          builder.Append("\t");
        builder.Append(ts.GridColumnStyles[i].HeaderText);
      }
      builder.Append(Environment.NewLine);

      CurrencyManager manager = (CurrencyManager)BindingContext[DataSource];

      if (DataSource is DataTable)
      {
        DataTable dt = (DataTable)DataSource;
        foreach(DataRowView r in manager.List)
        {
          for (int i = 0; i < dt.Columns.Count; i++)
          {
            if (i > 0)
              builder.Append("\t");
            builder.Append(r[i].ToString());
          }
          builder.Append(Environment.NewLine);
        }
      }
      else if (DataSource is IList)
      {
        for (int i = 0; i < manager.List.Count; i++)
        {
          object item = manager.List[i];
          Type objectType = item.GetType();
          for (int j = 0; j < ts.GridColumnStyles.Count; j++)
          {
            string colName = ts.GridColumnStyles[j].MappingName;
            PropertyInfo propInfo = objectType.GetProperty(colName);
            object propertyValue = propInfo.GetValue(item, null);
            if (j > 0)
              builder.Append("\t");
            builder.Append(propertyValue.ToString());
          }
          builder.Append(Environment.NewLine);
        }
      }
      else
        throw new NotSupportedException();

      Clipboard.SetDataObject(builder.ToString(), true);
    }

Saturday, February 10, 2007

Resizing columns to fit in a DataGrid

I wanted to automatically set the width of columns in a WinForms DataGrid to something sensible based on the contents (using .NET 1.1 for a number of reasons). I've seen the solution to this in a couple of places but they didn't work for me because I'm using a grid that inherits from DataGrid and adds a few helper methods. So here's what I've come up with.

    public void AutosizeColumns()
    {
      Type t = GetType();
      t = t.BaseType;
      MethodInfo m = t.GetMethod("ColAutoResize", BindingFlags.Instance  BindingFlags.NonPublic);

      for (int i = 0; (i < TableStyles[0].GridColumnStyles.Count); i++)
      {
        m.Invoke(this, new object[]{i});
      }
    }

Sunday, February 04, 2007

The CSS media attribute

I was going to write about this some time ago and never got round to it but Jeff Atwood covers it much better than I could ever hope to. Another reason I thought I'd highlight this is after a conversation with one of our clients who'd paid a company to write an ActiveX control to produce printer friendly versions of web pages. There really wasn't any need, it's trivially simple to produce a stylesheet for printer-friendly output, generally all you need to do is hide some of your headers and menus etc. Even with my pathetic CSS skills I managed to create one for the Random Pub Finder.

On the other hand I'm not so sure about the use of a handheld stylesheet though. I did one of these for the Random Pub Finder as well, but as far as I can see we never get any hits from handheld devices. Also, I may be wrong but I think quite a few handheld devices actually ignore the handheld stylesheet and just render websites using the standard stylesheet. This is probably due to the lack of websites that actually provide a handheld stylesheet, meaning any handheld device needs to be able to cope with full-size pages anyway. And finally, it's a right pain testing your site against the huge number of devices out there, particularly as the emulators always seem to be a pain to set up.

Friday, February 02, 2007

Installing a .NET assembly with COM interop

I've written a .NET 1.1 assembly that needs to register for COM interop. I've been using the Visual Studio 2003 installer to build my installer, because it's generally good enough for what I'm doing and it's free. OK, it's not exactly free, but who buys Visual Studio for the installer bits?

Anyway, the installer had been working fine by setting the Register property of the assembly to vsdrpCOM but when I went to test the installer I'd built today after some changes to the assembly it wasn't getting registered. Oddly the install completed and didn't suggest there was a problem but the registry was empty. I could register the assembly using regasm, but that doesn't seem like such a good solution for customers!

So after a not so quick Google later I came across this newsgroup posting (near the bottom) which showed a solution to the problem. And it worked. Problem solved. What I particularly like about this solution is that I'm now back in control of registering my assemblies, rather than being in the hands of the installer's black box implementation, which I've had weird problems with before.

Thursday, February 01, 2007

So what is a bell end?

I noticed that a search for what's a bell end on Google brings up this site at number one, so I thought I better answer the question.

So here goes, bell end refers to the end of a man's, er, manhood. The name came about because it's shaped somewhat like a bell (go on have a quick check, nobody's looking) and it is, in fact, the end. It's a simple as that, although it's often used as something of a derogatory term, where someone might say "he's such a bell end".   

The obvious follow on question is why on earth I chose it as my blog name? And the answer to that is I'm not entirely sure. I've certainly been called it quite a few times (due to the surname) and couldn't think of anything more appropriate at the time.

BlogSpot not very well

Lots of people getting a bX-vjhbsj error, as am I. So you probably can't see this...