一次只能选择一个项目的多个WPF列表框

8

我正在使用MVVM模式,并在一个窗口上显示两个列表框。同时,我将这两个列表框绑定到不同的字段A和B。然后A和B都会修改C。为了让这个例子工作起来,我希望只有一个列表框中的项目被选中,这样当B被选中时,A不会覆盖C。我该如何限制这个呢?


1
当一个ListBox中进行选择时,您希望另一个ListBox中的选择被清除吗?我发现您的问题很难理解。 - Simon Brydon
我想知道如何将类似于IsFocused / IsSelected ListBox属性的内容绑定到视图模型中的布尔值。 - user1722960
2
你可以将 ListBox 的 SelectedIndex 绑定到视图模型中的属性。例如,当设置 ListBoxA 的 SelectedIndexA 时,你可以将 ListBoxB 的 SelectedIndexB 设置为 -1,以清除 ListBoxB 的选择,反之亦然。这是你的意图吗? - Simon Brydon
这应该能解决问题,谢谢! - user1722960
1
Simon 给了你一个很好的答案,不接受是不礼貌的...而且你也应该给它点个赞。对其他用户友善,他们也会回报你的好意。 - Hannish
2个回答

12

我能想到几种方法来完成这个任务。

其中一种方法是将两个ListBox的ListBox.SelectedIndex绑定到具有变化通知功能的ViewModel属性。

例如,在你的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>

而在你的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");
    }
}

另一种方法是使用类似于'GroupName'的附加属性,您可以将选择器(ListBox继承自Selector)分组,以确保在任何时候只有一个组中的Selector具有选定的项目。

例如:

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);
        }
    }
}

而在您的视图中:

<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>
如果您需要在高亮之前单击两次项目,则可以使用以下快速解决方法:
<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>

非常好的答案!!!我进行了一些更正,请查看下面的答案。但我仍然有一个小问题。如果您能尝试解决它,那将非常好。如果您有空,请看看我的问题:https://dev59.com/dX3aa4cB1Zd3GeqPakT- - Vishal

6
如果有人在ItemsControl中使用ListBox/ListView并且遇到以下问题(在使用上述答案后)
  1. 当您单击listbox1中的任何项目时,该项目将被选择。
  2. 当您单击listbox2中的任何项目时,listbox1中的选定项目将取消选择,但listbox2中没有项目被选择。
  3. 再次单击listbox2中的任何项目时,该项目将被选择。
只需将下面的XAML添加到ListBoxItem的样式中即可:
<Style  TargetType="ListBoxItem">
    <Style.Triggers>
        <Trigger Property="IsKeyboardFocusWithin" Value="True">
             <Setter Property="IsSelected" Value="True"></Setter>
        </Trigger>
    </Style.Triggers>
</Style>

此外,如果有人在使用上述答案中的代码后遇到以下错误:...
GroupName is already registered by Selector

请将依赖属性声明中的第三个参数 typeof(......)更改为您的类的名称

感谢关注这个问题。直到今天我才需要所附属性,因此发现了选定的项目未被突出显示的问题。为了快速解决,我使用了一个EventTrigger而不是上述的方式,因为常规触发器在值为false时会重置,这意味着当项目失去键盘焦点时选择将被清除,这在我的情况下是不需要的。 - Simon Brydon
@Simon 谢谢你的新快速修复。 - Vishal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接