Search code examples
c#objectlistview

Skip to next matching row in ObjectListView (C#) when 'N' key is pressed?


Assume I have an ObjectListView and one of the columns is an OLVColumn "olvcName" which displays a name as a string. When a row is selected in the ObjectListView I'd like to be able to press the 'N' key and have the selected row advance to the next row with an identical name. How can I accomplish this?

I've looked through the ObjectListView Cookbook and Getting Started as well as the demo application and don't see anything that matches this functionality. I know ObjectListView can catch key presses because I've been able to set a breakpoint in the ObjectListView.cs method HandleKeyDown() which appears to check for Keys.Space as well as index changes resulting from navigation using arrow keys. I can add code there to detect 'N' being pressed but from there I'm not sure how to accomplish the search. I feel like this should be a delegate of some kind applied to the OLVColumn olvcName.

Any assistance appreciated!


Solution

  • So this was an awkward one to get working!

    So if you look at the ObjectListView (OLV) you will see that it has the Objects property which is an Enumerable of all of the objects you assign to it.

    The problem is, that this does not maintain the display order that the items/objects are shown in. In fact, as the items can be filtered then it can even have more items than are displayed.

    Add to that, the fact that you can sort items and their groups, then the order gets totally messed up. So you need to use a helper Method which is part of the OLV called GetDisplayOrderOfItemIndex(). But you first need to get the traditional item index. So you end up doing this.

    int index = objectListView1.IndexOf(objectListView1.FocusedObject);
    var displayIndex = this.objectListView1.GetDisplayOrderOfItemIndex(index);
    

    Next you need to try to identify which is the next item in the display order, well you still don't have a list of items in the right order. So actually we need to go through every item in display order until you find the next one which matches. So we need a helper function to help you find what is the next item in display order.

    This is taken from here: https://sourceforge.net/p/objectlistview/discussion/812922/thread/850cb5fd/

    public OLVListItem GetNthItemInDisplayOrder(int n)
    {
        if (!objectListView1.ShowGroups)
            return objectListView1.GetItem(n);
    
        foreach (ListViewGroup lgv in objectListView1.Groups)
        {
            if (n < lgv.Items.Count)
                return (OLVListItem)lgv.Items[n];
    
            n -= lgv.Items.Count;
        }
    
        return null;
    }
    

    Then finally, we can start to put this together into a working solution where we will;

    1. Capture the keypress and check for 'n' or 'N'
    2. Get the index and then display index for the current selected item
    3. Loop through all remaining items in display order to find the next matching item
    4. Select/Highlight the found item

    It is still a bit of a hack, as we need to loop through all items and wouldn't be efficient on a larger dataset, but it works!

    Assuming I have a data class such as below, where we will use the "Owner" property for searching.

    internal class MyData
     {
         public string Manufacturer { get; set; }
         public string Model { get; set; }
         public string Colour { get; set; }
         
         public string Owner { get; set; }
         public decimal Value { get; set; }
    
         public MyData(string manufacturer, string model, string colour, string owner, decimal value)
         {
             Manufacturer = manufacturer;
             Model = model;
             Colour = colour;
             this.Value = value;
             Owner = owner;
    
         }
    
         public override string ToString()
         {
             return $"{Manufacturer} {Model} {Colour} {Value} {Owner}";
         }
     }
    

    I create my form such as

    private List list;

    public Form1()
    {
       InitializeComponent();
    
       list = new List<MyData>()
       {
           new MyData("Ford", "Anglia", "Blue", "Jones", 19331),
           new MyData("Ford", "Mondeo", "Silver", "Jones", 14331),
           new MyData("Ford", "Escort", "Black", "Smith", 4234),
           new MyData("Audi", "TT", "Black", "Harris", 24110),
           new MyData("Audi", "S3", "Red", "Kaiser", 16445),
           new MyData("Audi", "A2", "White", "Jones", 8125),
           new MyData("Fiat", "500", "White", "Smith", 18115),
           new MyData("Fiat", "Doblo", "Pink", "Jones", 3456)
       };
    
       objectListView1.SetObjects(list);
       this.objectListView1.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.objectListView1_KeyPress);
    }
    

    Then my code to provide the solution is

    private void objectListView1_KeyPress(object sender, KeyPressEventArgs e)
    {
        if(e.KeyChar == 'n' || e.KeyChar == 'N')
        {
            if(objectListView1.FocusedObject is MyData current)
            {
                int index = objectListView1.IndexOf(current);
                var displayIndex = this.objectListView1.GetDisplayOrderOfItemIndex(index);
    
                MyData next = FindNext(current.Owner, displayIndex + 1);
    
                if (next != null)
                {
                    this.objectListView1.SelectedObject = next;
                    objectListView1.Select();
                    e.Handled = true;
                }
            }
        }
    }
    
    private MyData FindNext(string searchText, int startIndex)
    {
        for(int i= startIndex; i < this.objectListView1.Items.Count; i++)
        {
            OLVListItem item = GetNthItemInDisplayOrder(i);
            if (item.RowObject is MyData found)
            {
                if (found.Owner.Equals(searchText))
                    return found;
            }
        }
        return null;
    }
    
    
    /// <summary>
    /// Return the n'th item (0-based) in the order they are shown to the user. 
    /// If the control is not grouped, the display order is the same as the
    /// sorted list order. But if the list is grouped, the display order is different.
    /// </summary>
    /// <param name="n"></param>
    /// <returns></returns>
    public OLVListItem GetNthItemInDisplayOrder(int n)
    {
        if (!objectListView1.ShowGroups)
            return objectListView1.GetItem(n);
    
        foreach (ListViewGroup lgv in objectListView1.Groups)
        {
            if (n < lgv.Items.Count)
                return (OLVListItem)lgv.Items[n];
    
            n -= lgv.Items.Count;
        }
    
        return null;
    }
    

    This should work, no matter what filtering or ordering you have on the OLV.

    So now, you select a row and press 'n' to move to the next row which has the same "Owner".