Search code examples
c#winformspropertygrid

PropertyGrid selection bug when properties have identical DisplayName?


I have a PropertyGrid whose selected object contains multiple properties with [DisplayName]s of "Speed", all in different categories (the real property names in the code are of course all unique). I've noticed that if I have (for example) Speed #3 selected and PropertyGrid.Refresh() is called, the selection will automatically move to Speed #1. What's more, the value of Speed #3 will sometimes be shown next to Speed #1 too. The situation resolves itself as soon as I click the grid and change the selection, but it's obviously not desired behavior.

I am currently hacking around this by adding different numbers of \t characters to the DisplayNames to make them all unique. This is an acceptable workaround since the tab characters aren't actually rendered, but I'd of course prefer not to have to do it.

Is there a rule that all DisplayNames must be unique or is this a bug in PropertyGrid?


Update: Since someone's bound to ask for a code sample, stick one of these in a PropertyGrid and then call Refresh() on it from a timer every two seconds or so:

class Demo
{
    [Category("Cat1")]
    [DisplayName("Speed")]
    public int Speed1 { get; set; }

    [Category("Cat2")]
    [DisplayName("Speed")]
    public int Speed2 { get; set; }

    [Category("Cat3")]
    [DisplayName("Speed")]
    public int Speed3 { get; set; }
}

Solution

  • I don't think it's a bug, it's probably a feature (with side effects :-). You can check the property grid source on Microsoft site. The relevant portion seems this in GridEntry.cs code:

    public override bool Equals(object obj) {
      if (NonParentEquals(obj)) {
        return((GridEntry)obj).ParentGridEntry == this.ParentGridEntry;
      }
      return false;
    }
    
    internal virtual bool NonParentEquals(object obj) {
      if (obj == this) return true;
      if (obj == null) return false;
      if (!(obj is GridEntry)) return false;
      GridEntry pe = (GridEntry)obj;
    
      return pe.PropertyLabel.Equals(this.PropertyLabel) &&
        pe.PropertyType.Equals(this.PropertyType) && pe.PropertyDepth == this.PropertyDepth;
    }
    

    As you see, it's the PropertyLabel which is used. If you follow the code a bit more, the label will ultimately use the property's DisplayName (or the name if the DisplayName attribute is not defined).