Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Listview Multiple Selection

Tags:

c#

winforms

Is there any way to force a listview control to treat all clicks as though they were done through the Control key?

I need to replicate the functionality of using the control key (selecting an item sets and unsets its selection status) in order to allow the user to easily select multiple items at the same time.

Thank you in advance.

like image 558
Evan Avatar asked Sep 17 '08 14:09

Evan


4 Answers

It's not the standard behaviour of the ListView control, even when MultiSelect is set to true.

If you wanted to create your own custom control you would need to do the following:

  1. Derive a control from ListView
  2. add a handler to the "Selected" event.
  3. In the "OnSelected", maintain your own list of selected items.
  4. If the newly selected item is not in your list, add it. If it is, remove it.
  5. In code, select all of the items in your list.

Should be simple enough to implement and feel like multi-select without using the control key!

like image 62
Ray Hayes Avatar answered Nov 17 '22 23:11

Ray Hayes


Here is a complete solution that is a modification of the solution provided by Matthew M. above.

It offers an improvement as well as a bit of added functionality.

Improvement:

  • left clicking the control gives focus to the control.
  • right mouse click behaviour is consistent (single selection)

Added functionality:

  • the control has a property (MultiSelectionLimit) that allows you to put a limit on how many items can be selected at once.

After my first posting I realised a minor problem with the code. Clearing multiple selections would lead to the ItemSelectionChanged event being invoked multiple times.
I could find no way to avoid this with the current inheritance, so instead I adopted a solution where the bool property SelectionsBeingCleared will be true while until all selected items have been deselected.

This way a simple call to that property will make it possible to avoid updating effects until all of the multiple selections have been cleared.

public class ListViewMultiSelect : ListView
{
    public const int WM_LBUTTONDOWN = 0x0201;
    public const int WM_RBUTTONDOWN = 0x0204;

    private bool _selectionsBeingCleared;
    /// <summary>
    /// Returns a boolean indicating if multiple items are being deselected.
    /// </summary>
    /// <remarks> This value can be used to avoid updating through events before all deselections have been carried out.</remarks>
    public bool SelectionsBeingCleared
    {
        get
        {
            return this._selectionsBeingCleared;
        }
        private set
        {
            this._selectionsBeingCleared = value;
        }
    }
    private int _multiSelectionLimit;
    /// <summary>
    /// The limit to how many items that can be selected simultaneously. Set value to zero for unlimited selections.
    /// </summary>
    public int MultiSelectionLimit
    {
        get
        {
            return this._multiSelectionLimit;
        }
        set
        {
            this._multiSelectionLimit = Math.Max(value, 0);
        }
    }

    public ListViewMultiSelect()
    {
        this.ItemSelectionChanged += this.multiSelectionListView_ItemSelectionChanged;
    }

    public ListViewMultiSelect(int selectionsLimit)
        : this()
    {
        this.MultiSelectionLimit = selectionsLimit;
    }

    private void multiSelectionListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
    {
        if (e.IsSelected)
        {
            if (this.MultiSelectionLimit > 0 && this.SelectedItems.Count > this.MultiSelectionLimit)
            {
                this._selectionsBeingCleared = true;
                List<ListViewItem> itemsToDeselect = this.SelectedItems.Cast<ListViewItem>().Except(new ListViewItem[] { e.Item }).ToList();

                foreach (ListViewItem item in itemsToDeselect.Skip(1)) { 
                    item.Selected = false; 
                }

                this._selectionsBeingCleared = false;
                itemsToDeselect[0].Selected = false;
            }
        }
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_LBUTTONDOWN:
                if (this.SelectedItems.Count == 0 || !this.MultiSelect) { break; }
                if (this.MultiSelectionLimit > 0 && this.SelectedItems.Count > this.MultiSelectionLimit) { this.ClearSelections(); }

                int x = (m.LParam.ToInt32() & 0xffff);
                int y = (m.LParam.ToInt32() >> 16) & 0xffff;
                ListViewHitTestInfo hitTest = this.HitTest(x, y);

                if (hitTest != null && hitTest.Item != null) { hitTest.Item.Selected = !hitTest.Item.Selected; }
                this.Focus();
                return;
            case WM_RBUTTONDOWN:
                if (this.SelectedItems.Count > 0) { this.ClearSelections(); }
                break;
        }
        base.WndProc(ref m);
    }

    private void ClearSelections()
    {
        this._selectionsBeingCleared = true;
        SelectedListViewItemCollection itemsToDeselect = this.SelectedItems;
        foreach (ListViewItem item in itemsToDeselect.Cast<ListViewItem>().Skip(1)) { 
            item.Selected = false; 
        }
        this._selectionsBeingCleared = false;
        this.SelectedItems.Clear();
    }
}
like image 26
rdongart Avatar answered Nov 18 '22 00:11

rdongart


You might want to also consider using Checkboxes on the list view. It's an obvious way to communicate the multi-select concept to your average user who may not know about Ctrl+Click.

From the MSDN page:

The CheckBoxes property offers a way to select multiple items in the ListView control without using the CTRL key. Depending on your application, using check boxes to select items rather than the standard multiple selection method may be easier for the user. Even if the MultiSelect property of the ListView control is set to false, you can still display checkboxes and provide multiple selection capabilities to the user. This feature can be useful if you do not want multiple items to be selected yet still want to allow the user to choose multiple items from the list to perform an operation within your application.

like image 2
Chris Karcher Avatar answered Nov 18 '22 01:11

Chris Karcher


Here is the complete solution that I used to solve this problem using WndProc. Basically, it does a hit test when the mouse is clicked.. then if MutliSelect is on, it will automatically toggle the item on/off [.Selected] and not worry about maintaining any other lists or messing with the ListView functionality.

I haven't tested this in all scenarios, ... it worked for me. YMMV.

public class MultiSelectNoCTRLKeyListView : ListView {
  public MultiSelectNoCTRLKeyListView() {

  }

  public const int WM_LBUTTONDOWN = 0x0201;
  protected override void WndProc(ref Message m) {
    switch (m.Msg) {
      case WM_LBUTTONDOWN:
        if (!this.MultiSelect)
          break;

        int x = (m.LParam.ToInt32() & 0xffff);
        int y = (m.LParam.ToInt32() >> 16) & 0xffff;

        var hitTest = this.HitTest(x, y);
        if (hitTest != null && hitTest.Item != null)
          hitTest.Item.Selected = !hitTest.Item.Selected;

        return;
    }

    base.WndProc(ref m);
  }
}
like image 2
Matthew M. Avatar answered Nov 17 '22 23:11

Matthew M.