如果在ListBox中选择了多个X项,则还原为上一个选择

时间:2009-04-14 19:40:29

标签: c# listbox multi-select

我有一个文本框,我想将所选项目的数量限制为MaxSelection。期望的行为是,一旦选择了MaxSelection项,则忽略任何进一步的选择。 (因此这个问题与“limit selections in a listbox in vb.net”不同。

我为列表框的SelectedIndexChanged事件设置了一个事件处理程序,试图完成此操作。如果用户使用Ctrl-单击选择(MaxSelection + 1)项,则选择将恢复为之前的选择。

问题是当用户选择一个项目,然后按住Shift键并单击列表中的项目,该项目是列表中的MaxSelection + 1项目。在这种情况下,会引发多个SelectedIndexChanged事件:一个用于Shift-单击,用于选择按住Shift键单击的项目,另一个用于选择原始选择和按住Shift键单击的选择之间的所有项目。这些事件中的第一个允许用户选择Shift-clicked项目(这在技术上是正确的),然后第二个事件将选择恢复为第一个事件之后的选择(这将是最初选择的项目和Shift - 点击项目)。所需要的是代码会在第一个事件(仅是最初选择的项目)之前将选择恢复为选择。

在按住Shift键单击之前,有没有办法保留选择?

谢谢, 罗布

这是SelectedIndexChanged事件处理程序:

    void ChildSelectionChanged(object sender, EventArgs e)
    {
        ListBox listBox = sender as ListBox;

        //If the number of selected items is greater than the number the user is allowed to select
        if ((this.MaxSelection != null) && (listBox.SelectedItems.Count > this.MaxSelection))
        {
            //Prevent this method from running while reverting the selection
            listBox.SelectedIndexChanged -= ChildSelectionChanged;

            //Revert the selection to the previous selection
            try
            {
                for (int index = 0; index < listBox.Items.Count; index++)
                {
                    if (listBox.SelectedIndices.Contains(index) && !this.previousSelection.Contains(index))
                    {
                        listBox.SetSelected(index, false);
                    }
                }
            }
            finally
            {
                //Re-enable this method as an event handler for the selection change event
                listBox.SelectedIndexChanged += ChildSelectionChanged;
            }
        }
        else
        {
            //Store the current selection
            this.previousSelection.Clear();
            foreach (int selectedIndex in listBox.SelectedIndices)
            {
                this.previousSelection.Add(selectedIndex);
            }

            //Let any interested code know the selection has changed.
            //(We do not do this in the case where the selection would put
            //the selected count above max since we revert the selection;
            //there is no net effect in that case.)
            RaiseSelectionChangedEvent();
        }

    }

3 个答案:

答案 0 :(得分:2)

某些第三方组件具有可取消的事件,例如BeforeSelectedIndexChanged。

但是在使用MS默认组件时,我认为您的方法基本上就是您所需要的。您还可以将选择存储在已知在更改之前触发的其他事件(例如MouseDown或KeyDown)中。

答案 1 :(得分:1)

感谢Lucero的洞察力,我可以将代码存储在另一个事件中,我可以使用MouseUp创建解决方案。正如对Lucero问题的评论中所述,MouseDown在SelectedValueChange事件之后触发,因此我必须使用MouseUp。这是代码:

    /// <summary>
    /// Handle the ListBox's SelectedValueChanged event, revert the selection if there are too many selected
    /// </summary>
    /// <param name="sender">the sending object</param>
    /// <param name="e">the event args</param>
    void ChildSelectionChanged(object sender, EventArgs e)
    {
        ListBox listBox = sender as ListBox;

        //If the number of selected items is greater than the number the user is allowed to select
        if ((this.MaxSelection != null) && (listBox.SelectedItems.Count > this.MaxSelection))
        {
            //Prevent this method from running while reverting the selection
            listBox.SelectedIndexChanged -= ChildSelectionChanged;

            //Revert the selection to the previously stored selection
            try
            {
                for (int index = 0; index < listBox.Items.Count; index++)
                {
                    if (listBox.SelectedIndices.Contains(index) && !this.previousSelection.Contains(index))
                    {
                        listBox.SetSelected(index, false);
                    }
                }
            }
            catch (ArgumentOutOfRangeException ex)
            {
            }
            catch (InvalidOperationException ex)
            {
            }
            finally
            {
                //Re-enable this method as an event handler for the selection change event
                listBox.SelectedIndexChanged += ChildSelectionChanged;
            }
        }
        else
        {
            RaiseSelectionChangedEvent();
        }
    }

    /// <summary>
    /// Handle the ListBox's MouseUp event, store the selection state.
    /// </summary>
    /// <param name="sender">the sending object</param>
    /// <param name="e">the event args</param>
    /// <remarks>This method saves the state of selection of the list box into a class member.
    /// This is used by the SelectedValueChanged handler such that when the user selects more 
    /// items than they are allowed to, it will revert the selection to the state saved here 
    /// in this MouseUp handler, which is the state of the selection at the end of the previous
    /// mouse click.  
    /// We have to use the MouseUp event since:
    /// a) the SelectedValueChanged event is called multiple times when a Shift-click is made;
    /// the first time it fires the item that was Shift-clicked is selected, the next time it
    /// fires, the rest of the items intended by the Shift-click are selected.  Thus using the
    /// SelectedValueChanged handler to store the selection state would fail in the following
    /// scenario:
    ///   i)   the user is allowed to select 2 items max
    ///   ii)  the user clicks Line1
    ///   iii) the SelectedValueChanged fires, the max has not been exceeded, selection stored
    ///        let's call it Selection_A which contains Line1
    ///   iii) the user Shift-clicks and item 2 lines down from the first selection called Line3
    ///   iv)  the SelectedValueChanged fires, the selection shows that only Line1 and Line3 are
    ///        selected, hence the max has not been exceeded, selection stored let's call it 
    ///        Selection_B which contains Line1, Line3
    ///   v)   the SelectedValueChanged fires again, this time Line1, Line2, and Line3 are selected,
    ///        hence the max has been exceeded so we revert to the previously stored selection
    ///        which is Selection_B, what we wanted was to revert to Selection_A
    /// b) the MouseDown event fires after the first SelectedValueChanged event, hence saving the 
    /// state in MouseDown also stores the state at the wrong time.</remarks>
    private void valuesListBox_MouseUp(object sender, MouseEventArgs e)
    {
        if (this.MaxSelection == null)
        {
            return;
        }

        ListBox listBox = sender as ListBox;

        //Store the current selection
        this.previousSelection.Clear();
        foreach (int selectedIndex in listBox.SelectedIndices)
        {
            this.previousSelection.Add(selectedIndex);
        }
    }

答案 2 :(得分:0)

我认为这是一种简单的方法,在这个例子中,限制是6项。

string[] lbitems;
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    ListBox listBox = (ListBox)sender;
    if (listBox.SelectedItems.Count == 7)
    {
        for (int i = 0; i < listBox.SelectedItems.Count; i++)
        {
            bool trovato = false;
            for (int j = 0; j < lbitems.Length; j++)
            {
                if (listBox.SelectedItems[i] == lbitems[j])
                {
                    trovato = true;
                    break;
                }
            }

            if (trovato == false)
            {
                listBox.SelectedItems.Remove(listBox.SelectedItems[i]);
                break;
            }
        }
    }
    else
    {
        lbitems = new string[listBox.SelectedItems.Count];
        listBox.SelectedItems.CopyTo(lbitems, 0);
    }
}