Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple WPF ListBoxes with only one item selected at a time

I am using MVVM and am displaying two listboxes on one window. I am binding from both of those listboxes simultaneously to different fields call them A and B. A and B are then both modifying C. To make this work, I want to only have one item from the two listboxes IsSelected at once, so that A does not override C when B IsSelected. How can I restrict this?

like image 534
user1722960 Avatar asked Oct 06 '12 11:10

user1722960


2 Answers

I can think of a couple of ways to do this.

One way is you could bind the ListBox.SelectedIndex of your 2 ListBoxes to change-notifying ViewModel properties.

For example in your View:

<ListBox SelectedIndex="{Binding SelectedIndexA}">
     <ListBoxItem Content="Item 1"/>
     <ListBoxItem Content="Item 2"/>
</ListBox>
<ListBox SelectedIndex="{Binding SelectedIndexB}">
     <ListBoxItem Content="Item 1"/>
     <ListBoxItem Content="Item 2"/>
</ListBox>

And in your ViewModel:

public int SelectedIndexA
{
    get { return _selectedIndexA; }
    set
    {
        _selectedIndexA = value;
        _selectedIndexB = -1;
        OnPropertyChanged("SelectedIndexB");
    }
}

public int SelectedIndexB
{
    get { return _selectedIndexB; }
    set
    {
        _selectedIndexB = value;
        _selectedIndexA = -1;
        OnPropertyChanged("SelectedIndexA");
    }
}

Another way would be with an attached property like 'GroupName' where you can group Selectors (ListBox inherits from Selector) to ensure only one Selector in the group has a selected item at any one time.

For example:

public static class SingleSelectionGroup
{
    public static readonly DependencyProperty GroupNameProperty =
        DependencyProperty.RegisterAttached("GroupName", typeof(string), typeof(SingleSelectionGroup),
                                            new UIPropertyMetadata(OnGroupNameChanged));

    public static string GetGroupname(Selector selector)
    {
        return (string) selector.GetValue(GroupNameProperty);
    }

    public static void SetGroupName(Selector selector, string value)
    {
        selector.SetValue(GroupNameProperty, value);
    }

    private static void OnGroupNameChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        var selector = (Selector) dependencyObject;

        if (e.OldValue != null)
            selector.SelectionChanged -= SelectorOnSelectionChanged;
        if (e.NewValue != null)
            selector.SelectionChanged += SelectorOnSelectionChanged;
    }

    private static void SelectorOnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (e.AddedItems.Count == 0)
            return;

        var selector = (Selector) sender;
        var groupName = (string) selector.GetValue(GroupNameProperty);
        var groupSelectors = GetGroupSelectors(selector, groupName);

        foreach (var groupSelector in groupSelectors.Where(gs => !gs.Equals(sender)))
        {
            groupSelector.SelectedIndex = -1;
        }
    }

    private static IEnumerable<Selector> GetGroupSelectors(DependencyObject selector, string groupName)
    {
        var selectors = new Collection<Selector>();
        var parent = GetParent(selector);
        GetGroupSelectors(parent, selectors, groupName);
        return selectors;
    }

    private static DependencyObject GetParent(DependencyObject depObj)
    {
        var parent = VisualTreeHelper.GetParent(depObj);
        return parent == null ? depObj : GetParent(parent);
    }

    private static void GetGroupSelectors(DependencyObject parent, Collection<Selector> selectors, string groupName)
    {
        var childrenCount = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            var selector = child as Selector;
            if (selector != null && (string) selector.GetValue(GroupNameProperty) == groupName)
                selectors.Add(selector);

            GetGroupSelectors(child, selectors, groupName);
        }
    }
}

And in your View:

<ListBox my:SingleSelectionGroup.GroupName="Group A">
    <ListBoxItem Content="Item 1 (Group A)"/>
    <ListBoxItem Content="Item 2 (Group A)"/>
</ListBox>
<ListBox my:SingleSelectionGroup.GroupName="Group A">
    <ListBoxItem Content="Item 1 (Group A)"/>
    <ListBoxItem Content="Item 2 (Group A)"/>
</ListBox>

<ListBox my:SingleSelectionGroup.GroupName="Group B">
    <ListBoxItem Content="Item 1 (Group B)"/>
    <ListBoxItem Content="Item 2 (Group B)"/>
</ListBox>
<ListBox my:SingleSelectionGroup.GroupName="Group B">
    <ListBoxItem Content="Item 1 (Group B)"/>
    <ListBoxItem Content="Item 2 (Group B)"/>
</ListBox>

If you have to click an item twice before it is highlighted you can use a quick workaround like this:

<Style TargetType="ListBoxItem">
    <Style.Triggers>
        <EventTrigger RoutedEvent="GotKeyboardFocus">
            <BeginStoryboard>
                <Storyboard>
                    <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(ListBoxItem.IsSelected)">
                        <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True" />
                    </BooleanAnimationUsingKeyFrames>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Style.Triggers>
</Style>
like image 129
Simon Brydon Avatar answered Nov 16 '22 04:11

Simon Brydon


If anybody has used ListBox/ListView inside ItemsControl and has the following problem after using the above answer:

  1. When you click on any Item in listbox1 the item is selected.
  2. When you click on any item in listbox2 the selected item of listbox1 is unselected but no item in listbox2 is selected.
  3. When you click again on any item in listbox2 the item is selected.

Just add below xaml to the style of your ListBoxItem:

<Style  TargetType="ListBoxItem">
    <Style.Triggers>
        <Trigger Property="IsKeyboardFocusWithin" Value="True">
             <Setter Property="IsSelected" Value="True"></Setter>
        </Trigger>
    </Style.Triggers>
</Style>

Also, If anybody is getting the following error after using code in above answer:

GroupName is already registered by Selector

Please change the third parameter typeof(......) in dependency property declaration to Name of your class.

like image 38
Vishal Avatar answered Nov 16 '22 02:11

Vishal