ListBox with single select and also unselect on click…?

戏子无情 提交于 2019-12-06 17:25:25

问题


I need a listbox that selects on first click and un-selects on second click, so that only zero or one item is selected at any time.

The select/unselect is implemented in the listbox (with SelectionMode="Single") when you hold down crtl, but unfortunately, none of my users can be expected to know that.

With SelectionMode="Multiple" we have the exact functionality I want, except that you can select more than one item...

More background: I want the user to first choose which installation to log into, then to give credentials (and some other choices)

To achieve this I have used a listbox with expanding content. To aid the expansion I have on the left side of the listboxitem made a triangle that points right when unexpanded that turns to point down when you have selected the listbox item.

So, first the user see the list over the installations, and then, when he has chosen the item he wants by selecting it, the listboxitem expands to the rest of the info he need to enter. It's quite nice, and works well, but testing reports that they want a second click to the triangle to unselect (and thus collapse the expanded section). And I must admit that I have clicked the ¤%& arrow too, expecting the action to result in a collapse... :-(

Anyone has an idea how this can be achieved (preferably without code behind)?


回答1:


try that:

you use a ToggleButton as the "Expander" of the detailed content. The "IsChecked" Property of the toggle Button you can bind to the IsSelected Property of the item

here the code:

<ListBox SelectionMode="Single">
   <ListBox.ItemsSource>
      <x:Array Type="{x:Type sys:String}">
         <sys:String>test1</sys:String>
         <sys:String>test2</sys:String>
         <sys:String>test3</sys:String>
         <sys:String>test4</sys:String>
         <sys:String>test5</sys:String>
         <sys:String>test6</sys:String>
      </x:Array>
   </ListBox.ItemsSource>
   <ListBox.ItemTemplate>
      <DataTemplate>
         <StackPanel Orientation="Horizontal">
            <ToggleButton IsChecked="{Binding 
                          RelativeSource={RelativeSource FindAncestor, 
                          AncestorType={x:Type ListBoxItem}},
                          Path=IsSelected}"
            >btn</ToggleButton>
         </StackPanel>
      </DataTemplate>
   </ListBox.ItemTemplate>
</ListBox>

how it works: In the Listbox can only one item be selected. As we select an item the Toggler gets expanded cause his IsChecked is bound to ListBoxItem.IsSelected (ListBoxItem is a Control which gets wrapped around the Content of each Item) of his parent ListBoxItem. As the selectionMode is single as soon as another item gets selected the following happens:

  • Deselect the actually selected item
  • Through the binding the Toggler gets unchecked too
  • Select the new item
  • the New items toggler gets checked through its binding

and if just the actually selected item's toggler gets unchecked the item deselects itself through the binding...




回答2:


The common way to do this is to set SelectionMode mode to Multiple and then unselect all items but the newly selected one in the SelectionChanged event.

See the follow links

  • WPF: ListBox unselecting
  • ListBoxItem - Deselect on second click

Here is an Attached Behavior that does this which can be used like this

<ListBox local:ListBoxSelectionBehavior.ClickSelection="True"
         ...>

ListBoxSelectionBehavior

public static class ListBoxSelectionBehavior 
{
    public static readonly DependencyProperty ClickSelectionProperty = 
        DependencyProperty.RegisterAttached("ClickSelection", 
                                            typeof(bool),
                                            typeof(ListBoxSelectionBehavior),
                                            new UIPropertyMetadata(false, OnClickSelectionChanged));
    public static bool GetClickSelection(DependencyObject obj) 
    {
        return (bool)obj.GetValue(ClickSelectionProperty); 
    }
    public static void SetClickSelection(DependencyObject obj, bool value) 
    {
        obj.SetValue(ClickSelectionProperty, value); 
    }
    private static void OnClickSelectionChanged(DependencyObject dpo, 
                                                             DependencyPropertyChangedEventArgs e) 
    {
        ListBox listBox = dpo as ListBox;
        if (listBox != null) 
        { 
            if ((bool)e.NewValue == true) 
            {
                listBox.SelectionMode = SelectionMode.Multiple;
                listBox.SelectionChanged += OnSelectionChanged;
            } 
            else 
            {
                listBox.SelectionChanged -= OnSelectionChanged;
            } 
        } 
    }
    static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count > 0)
        {
            ListBox listBox = sender as ListBox;
            var valid = e.AddedItems[0];
            foreach (var item in new ArrayList(listBox.SelectedItems))
            {
                if (item != valid)
                {
                    listBox.SelectedItems.Remove(item);
                }
            }
        }
    }
}



回答3:


My solution is set ListBox SelectionMode to Multiple, add method forbidSelectionButOne on Click event and after that allow only one selected item as follows:

Private Sub forbidSelectionButOne(sender As Object, e As MouseButtonEventArgs)
    Dim lv As ListView = TryCast(sender, ListView)
    If lv IsNot Nothing Then
        If lv.SelectedIndex <> getCausesListViewItemIndex(sender, e) Then
            lv.SelectedIndex = getCausesListViewItemIndex(sender, e)
            e.Handled = True
        End If
        lv.Focus()
    End If
End Sub

And helping function to find ListViewItem that was clicked by mouse:

Private Function getCausesListViewItemIndex(ByVal sender As Object, e As RoutedEventArgs) As Integer
    Dim dep As DependencyObject = TryCast(e.OriginalSource, DependencyObject)
    Do While dep IsNot Nothing AndAlso Not TypeOf (dep) Is ListViewItem
        dep = VisualTreeHelper.GetParent(dep)
    Loop
    If dep Is Nothing Then
        Return -1
    Else
        Dim lv As ListView = TryCast(sender, ListView)
        If lv IsNot Nothing Then
            Dim i As Integer = lv.ItemContainerGenerator.IndexFromContainer(dep)
            Return i
        Else
            Return -1
        End If
    End If
End Function



回答4:


I allowed myself to supplement Fredrik's answer for UWP and .NET Framework 4.7:

public static class ListBoxSelectionBehavior
{
    public static readonly DependencyProperty ClickSelectionProperty =
    DependencyProperty.RegisterAttached("ClickSelection",
                                        typeof(bool),
                                        typeof(ListBoxSelectionBehavior),
                                        new PropertyMetadata(false, OnClickSelectionChanged));

    public static bool GetClickSelection(DependencyObject obj)
    {
        return (bool)obj.GetValue(ClickSelectionProperty);
    }
    public static void SetClickSelection(DependencyObject obj, bool value)
    {
        obj.SetValue(ClickSelectionProperty, value);
    }
    private static void OnClickSelectionChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
    {
        if (dpo is ListBox listBox)
        {
            if ((bool)e.NewValue == true)
            {
                listBox.SelectionMode = SelectionMode.Multiple;
                listBox.SelectionChanged += OnSelectionChanged;
            }
            else
            {
                listBox.SelectionChanged -= OnSelectionChanged;
            }
        }
    }
    static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count > 0)
        {
            ListBox listBox = sender as ListBox;
            var valid = e.AddedItems[0];
            foreach (var item in new ArrayList(listBox.SelectedItems.ToArray()))
            {
                if (item != valid)
                {
                    listBox.SelectedItems.Remove(item);
                }
            }
        }
    }
}



回答5:


in uwp apps, With SelectionMode = "Multiple"

private void MyListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (MyListBox.SelectedItems.Count <= 1)
    {
        // code related to SelectionChanged
    }
    while (MyListBox.SelectedItems.Count > 1)
    {
        MyListBox.SelectedItems.RemoveAt(0);
    }
}



回答6:


Even easier then this just add flag combain with SelectionMode="Multiple"

 private bool _ignoreSelectionFlag = false;
    private void LbHistory_OnSelectionChanged(object sender,SelectionChangedEventArgs e)
    {
        if (_ignoreSelectionFlag)
            return;

        if (e.AddedItems.Count > 0)
        {
            ListBox listBox = sender as ListBox;
            var valid = e.AddedItems[0];

            _ignoreSelectionFlag = true;
            LbHistory.UnselectAll();
            LbHistory.SelectedItems.Add(e.AddedItems[0]);
            e.Handled = true;
            _ignoreSelectionFlag = false;


        }
    }


来源:https://stackoverflow.com/questions/5139956/listbox-with-single-select-and-also-unselect-on-click

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!