带有复选框的 WPF ComboBox 显示有关已选中项目的信息?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30988691/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 13:13:17  来源:igfitidea点击:

WPF ComboBox with CheckBoxes display info about checked items?

c#wpfcombobox

提问by Erik83

Im trying to make a ComboBox that have checkboxes as items and based on what is checked display different things when the combobox is "closed".

我试图制作一个将复选框作为项目的组合框,并根据选中的内容在组合框“关闭”时显示不同的内容。

The look Im trying achieve can be seen in the image.

我试图实现的外观可以在图像中看到。

enter image description here

在此处输入图片说明

Ideally I dont want the user to be able to select the text in the top ( in the image).

理想情况下,我不希望用户能够选择顶部(图像中)的文本。

Is there a simple solution to this? I've seen solutions where one can display more information when all the items are shown by using DataTriggers to hide different nested controls, but that is not really what Im looking for.

有没有简单的解决方案?我见过一些解决方案,当通过使用 DataTriggers 隐藏不同的嵌套控件来显示所有项目时,可以显示更多信息,但这并不是我真正想要的。

Any ideas?

有任何想法吗?

/Erik

/埃里克

回答by Xavier

Here is a way to achieve most of what you want using a ComboBox, except that the text can still be selected (using custom text only works when IsEditableis true). It is not editable though because of IsReadOnly="true".

这是一种使用 a 实现您想要的大部分内容的方法ComboBox,除了仍然可以选择文本(使用自定义文本仅在IsEditable为真时才有效)。但由于IsReadOnly="true".

View

看法

<ComboBox
    IsEditable="True"
    IsReadOnly="True"
    ItemsSource="{Binding Items}"
    Text="{Binding Text}">
    <ComboBox.ItemTemplate>
        <DataTemplate
            DataType="{x:Type local:Item}">
            <CheckBox
                Content="{Binding Name}"
                IsChecked="{Binding IsChecked}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Viewmodel

视图模型

// ObservableObject is a custom base class that implements INotifyPropertyChanged
internal class MainWindowVM : ObservableObject
{
    private ObservableCollection<Item> mItems;
    private HashSet<Item> mCheckedItems;

    public IEnumerable<Item> Items { get { return mItems; } }

    public string Text
    {
        get { return _text; }
        set { Set(ref _text, value); }
    }
    private string _text;

    public MainWindowVM()
    {
        mItems = new ObservableCollection<Item>();
        mCheckedItems = new HashSet<Item>();
        mItems.CollectionChanged += Items_CollectionChanged;

        // Adding test data
        for (int i = 0; i < 10; ++i)
        {
            mItems.Add(new Item(string.Format("Item {0}", i.ToString("00"))));
        }
    }

    private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.OldItems != null)
        {
            foreach (Item item in e.OldItems)
            {
                item.PropertyChanged -= Item_PropertyChanged;
                mCheckedItems.Remove(item);
            }
        }
        if (e.NewItems != null)
        {
            foreach (Item item in e.NewItems)
            {
                item.PropertyChanged += Item_PropertyChanged;
                if (item.IsChecked) mCheckedItems.Add(item);
            }
        }
        UpdateText();
    }

    private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsChecked")
        {
            Item item = (Item)sender;
            if (item.IsChecked)
            {
                mCheckedItems.Add(item);
            }
            else
            {
                mCheckedItems.Remove(item);
            }
            UpdateText();
        }
    }

    private void UpdateText()
    {
        switch (mCheckedItems.Count)
        {
            case 0:
                Text = "<none>";
                break;
            case 1:
                Text = mCheckedItems.First().Name;
                break;
            default:
                Text = "<multiple>";
                break;
        }
    }
}

// Test item class
// Test item class
internal class Item : ObservableObject
{
    public string Name { get; private set; }

    public bool IsChecked
    {
        get { return _isChecked; }
        set { Set(ref _isChecked, value); }
    }
    private bool _isChecked;

    public Item(string name)
    {
        Name = name;
    }

    public override string ToString()
    {
        return Name;
    }
}

If the selectable text is an issue, you may want to create a custom ComboBox control template (default example here). Alternatively, you could use something else instead of a ComboBox, and make it look like a ComboBox.

如果可选文本有问题,您可能需要创建自定义 ComboBox 控件模板(此处为默认示例)。或者,您可以使用其他内容代替 a ComboBox,并使其看起来像 a ComboBox

Screenshot of example:

示例截图:

Screenshot

截屏

回答by Erik83

@Xaviers answer works 99% of the way. However the user is able to accidently select a checkbox and then the ToString() of the checkbox is shown as the selected text. This can happen quite alot actually. I havent yet worked out why this happens, but I've found a way to prevent this.

@Xaviers 的回答成功了 99%。然而,用户可能会意外地选择一个复选框,然后复选框的 ToString() 显示为所选文本。这实际上可以发生很多。我还没有弄清楚为什么会发生这种情况,但我已经找到了一种方法来防止这种情况发生。

Create a bool property that binds to the DropDownOpen property of the combobox and when the DropDownOpen has changed to false it means that the DropDown has just been closed and you might be facing the above problem. So here you just raise the propertychanged event for the viewmodel and pass the property bound to the Text property of the combobox.

创建一个绑定到组合框的 DropDownOpen 属性的 bool 属性,当 DropDownOpen 更改为 false 时,这意味着 DropDown 刚刚关闭,您可能会遇到上述问题。因此,在这里您只需引发 viewmodel 的 propertychanged 事件,并将绑定到组合框的 Text 属性的属性传递。

回答by Chris

Using a combination of the @Erik83 and @Xavier solution I still had the problem that selecting a ComboBoxItem in a location right from the CheckBox text closes the ComboBox-DropDown and shows the ToString() value of the ComboBoxItem as the CheckBox is not stretched to DropDown-Width. I solved it by adding

使用@Erik83 和@Xavier 解决方案的组合我仍然遇到问题,即从 CheckBox 文本中选择一个位置中的 ComboBoxItem 会关闭 ComboBox-DropDown 并显示 ComboBoxItem 的 ToString() 值,因为 CheckBox 未拉伸到下拉宽度。我通过添加解决了它

HorizontalContentAlignment="Stretch"

Horizo​​ntalContentAlignment="拉伸"

to the CheckBox and adding the ItemContainerStyle:

到 CheckBox 并添加 ItemContainerStyle:

<ComboBox x:Name="combobox"
    Background="White"
    Padding="2"
    Text="{Binding ElementName=DockPanelTemplateComboCheck, Path=ComboTextFilter}"
    IsEditable="True"
    IsReadOnly="True"
    HorizontalAlignment="Stretch"
    ItemsSource="{Binding ...}"
    IsDropDownOpen="{Binding Path=DropOpen, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, UpdateSourceTrigger=PropertyChanged}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Eintrag}" HorizontalContentAlignment="Stretch" Checked="CheckBox_Checked_Unchecked" Unchecked="CheckBox_Checked_Unchecked"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <ComboBox.ItemContainerStyle>
        <Style TargetType="{x:Type ComboBoxItem}">
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>

CodeBehind:

代码隐藏:

    private string combotextfilter = "<No Selection>";

    public string ComboTextFilter
    {
        get { return combotextfilter; }
        set
        {
            if (value != null && value.IndexOf("ComboModel") != -1) return;
            combotextfilter = value;
            NotifyPropertyChanged(nameof(ComboTextFilter));
        }
    }

    private void CheckBox_Checked_Unchecked(object sender, RoutedEventArgs e)
    {
        switch (((ObservableCollection<ComboModel>)combobox.ItemsSource).Count(x => x.IsChecked))
        {
            case 0:
                ComboTextFilter = "<No Selection>";
                break;
            case 1:
                ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).First().Eintrag;
                break;
            default:
                ComboTextFilter = ((ObservableCollection<ComboModel>)combobox.ItemsSource).Where(x => x.IsChecked).Select(x => x.Eintrag).Aggregate((i, j) => i + " | " + j);
                //ComboTextFilter = "<Multiple Selected>";
                break;
        }

        NotifyPropertyChanged(nameof(C_Foreground));
    }

    public bool DropOpen
    {
        get { return dropopen; }
        set { dropopen = value; NotifyPropertyChanged(nameof(ComboTextFilter)); }
    }
    private bool dropopen = false;