Xアイテム以上がリストボックスで選択されている場合は、以前の選択に戻ります
-
09-09-2019 - |
質問
選択したアイテムの数をMaxSelectionに制限したいテキストボックスがあります。望ましい動作は、最大選択項目が選択されると、将来の選択が無視されることです。 (したがって、この質問は異なります」vb.netのリストボックスの選択を制限します").
これを達成しようとするリストボックスのSelectedIndExexhedイベントのイベントハンドラーがあります。ユーザーがCTRLクリックを使用して(MaxSelection+1)THアイテムを選択した場合、選択は以前の選択に戻ります。
問題は、ユーザーがアイテムを選択してから、リストの下にアイテムをシフトクリックすると、リストのさらに下に1つのアイテムをクリックします。この場合、複数のSelectedIndExedChangedイベントが発生します。1つはシフトクリックされたアイテムを選択するシフトクリック用で、1つは元の選択とシフトクリック選択の間のすべてのアイテムを選択します。これらのイベントの最初のイベントにより、ユーザーはシフトクリックアイテム(技術的に正しい)を選択できます。2番目のイベントは、最初のイベントの後に選択を選択します(最初に選択されたアイテムとシフトになります。 - クリックされたアイテム)。望ましいのは、最初のイベントの前にコードが選択を選択に戻すことです(これは当初選択されたアイテムのみです)。
シフトクリック前に選択を保持する方法はありますか?
ありがとう、ロブ
これが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();
}
}
解決
一部のサードパーティコンポーネントには、beforeselectedindexededなどのキャンセル可能なイベントがあります。
しかし、MSデフォルトコンポーネントを使用する場合、あなたのアプローチは基本的に必要なものだと思います。また、変更を変更する前にトリガーされることが知られている他のイベント(MousedownやKeydownなど)に選択を保存することもできます。
他のヒント
別のイベントに選択を保存するためにコードを配置できるという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);
}
}
これは簡単な方法だと思います。例では、制限は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);
}
}