Search code examples
c#winformsobjectlistview

Am I incorrectly using ShowGroups in FastObjectListView (2.9.1) or is it a bug?


I have hit a problem with FastObjectListView in v2.9.1.

As a minimal test example: I have a FastObjectListView to which I programatically add two OLVColumn objects. When the first column is not Groupable and I turn on ShowGroups I Get a Null Ref Exception in FastListGroupingStrategy.GetGroup.

This is because the indexToGroupMap is never instantiated in FastListGroupingStrategy.GetGroups because of a check within ObjectListView.BuildGroups that skips Group building for a non-Groupable column. as per this code fragment:

 if (parms.GroupByColumn != null)
     args.Canceled = !parms.GroupByColumn.Groupable;
 this.OnBeforeCreatingGroups(args);
 if (args.Canceled)
    return;

So the question is, am I misusing the control? Is it supposed to be up to me to turn ShowGroups on and off depending upon whether the current column is sortable?

Or is it a bug? (which might be fixed by changing VirtualObjectListView.GetDisplayOrderOfItemIndex to return itemIndex if this.OLVGroups == null)?

Example code

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        InitColumns();
        fastObjectListView1.ShowGroups = true;
        modelObjects_.Add(new ModelObject { Name = "Anne", Age = 20 });
        modelObjects_.Add(new ModelObject { Name = "Beth", Age = 20 });
        modelObjects_.Add(new ModelObject { Name = "Claire", Age = 32 });
        modelObjects_.Add(new ModelObject { Name = "Della", Age = 31 });
        modelObjects_.Add(new ModelObject { Name = "Ellie", Age = 20 });
        modelObjects_.Add(new ModelObject { Name = "Fiona", Age = 31 });
        fastObjectListView1.Objects = modelObjects_;
    }


    void InitColumns()
    {
        {
            OLVColumn col = new OLVColumn();
            col.Name = "NameColumn";
            col.IsVisible = true;
            col.AspectGetter = o => CastToModel(o).Name;
            col.AspectToStringConverter = a => a.ToString();
            col.Groupable = false;
            col.IsEditable = false;
            col.Text = "Name";
            col.Width = 150;
            fastObjectListView1.AllColumns.Add(col);
        }
        {
            OLVColumn col = new OLVColumn();
            col.Name = "AgeColumn";
            col.IsVisible = true;
            col.AspectGetter = o => CastToModel(o).Age;
            col.AspectToStringConverter = a => a.ToString();
            col.Groupable = true;
            //col.ImageGetter = 
            col.IsEditable = false;
            col.Text = "Age";
            col.Width = 50;
            fastObjectListView1.AllColumns.Add(col);
        }
        fastObjectListView1.RebuildColumns();
    }

    ModelObject CastToModel(object o)
    {
        return (ModelObject)o;
    }  

List<ModelObject> modelObjects_ = new List<ModelObject>();
}

with

public class ModelObject
{
    public string Name;
    public int Age;
}

There is another side effects (unexpected by me):

Sorting/grouping on a Groupable column via clicking column header works ok

Then sorting/grouping on a non-Groupable column leaves previous grouping in place.


Solution

  • Using the col.Groupable = true; tells the ListView "Is it possible to group by the column or not".

    But the first column is a special column, and one of the reasons is because this is used for the groups, at least initially. This means that you need to override this by setting the following;

    col.Name = "AgeColumn";
    ...
    fastObjectListView1.AlwaysGroupByColumn = col;
    

    However, this has the consequence that you can't group by any other column. Not a problem in your example, but this might be an issue if you expand your Model.

    So another solution is to use the GroupKeyGetter instead.

    Here, you need to define your first column a bit different, and set the GroupKeyGetter only for this column.

    //Define your first column at the class level;
    private OLVColumn colName;
    
    //Define InitColumns
    void InitColumns()
    {
        colName = new OLVColumn();
        colName.Name = "NameColumn";
        colName.Groupable = true;  //Make this groupable again, as we effectively override it.
        ...
        //Rest same as before
    }
    
    //In form_load
    private void Form1_Load(object sender, EventArgs e)
    {
        InitColumns();            
        ...
        //define your objects, same as before
    
        this.colName.GroupKeyGetter = delegate (object rowObject)
        {
            ModelObject item = (ModelObject)rowObject;
            return item.Age;
        };
    
        fastObjectListView1.ShowGroups = true;
        fastObjectListView1.Objects = modelObjects_;
    }
    

    This has the advantage that it "overrides" the way the first column is grouped (effectively using the same as the second column), but still allows you to group by all the other columns with no issues.

    There is another side effects (unexpected by me):

    Sorting/grouping on a Groupable column via clicking column header works ok

    Then sorting/grouping on a non-Groupable column leaves previous grouping in place.

    This is expected behaviour. This is what the "Groupable" attribute is about.