6

我正在使用标准的 wpf/mvvm 应用程序,我将组合框绑定到 ViewModel 上的集合。

我需要能够从下拉列表中取消选择一个项目。意思是,用户应该能够选择一些东西,然后决定他们想要取消选择它(选择无)。问题是我的绑定集合中没有空元素

我最初的想法只是在集合中插入一个新项目,这将导致集合顶部有一个空项目。

虽然这是一个 hack,但它会影响在视图模型上使用该集合的所有代码。

例如,如果有人要写

_myCollection.Frist(o => o.Name == "foo") 

这将引发空引用异常。

可能的解决方法是:

_myCollection.Where(o => o != null).First(o => o.Name == "foo");

这将起作用,但无法确保该集合的任何未来使用不会导致任何中断。

什么是能够添加空项目以便用户取消选择的好模式/解决方案。(我也知道 CollectionView 结构,但对于如此简单的事情来说,这似乎有点过头了)

更新

接受@hbarck 的建议并实施了 CompositeCollection(快速概念证明)

    public CompositeCollection MyObjects {
        get {
            var col = new CompositeCollection();

            var cc1 = new CollectionContainer();
            cc1.Collection = _actualCollection;

            var cc2 = new CollectionContainer();
            cc2.Collection = new List<MyObject>() { null }; // PROBLEM

            col.Add(cc2);
            col.Add(cc1);
            return col;
        }
    }

此代码适用于现有的绑定(包括 SelectedItem),这很棒。

这样做的一个问题是,如果项目完全为空,则在选择它时永远不会调用 SelectedItem 设置器

如果我将这一行修改为:

            cc2.Collection = new List<MyObject>() { new MyObject() }; // PROBLEM

调用了setter,但现在我选择的项目只是一个基本的初始化类而不是null..我可以在setter中添加一些代码来检查/重置,但这并不好。

4

5 回答 5

3

我认为最简单的方法是使用 CompositeCollection。只需将您的集合附加到另一个仅包含空项(null 或占位符对象,无论您需要什么)的集合,然后将 CompositeCollection 设置为 ComboBox 的 ItemsSource。这可能是它的目的。

更新:

事实证明这比我最初想象的要复杂,但实际上,我想出了这个解决方案:

<Window x:Class="ComboBoxFallbackValue"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:TestWpfDataBinding"
    xmlns:s="clr-namespace:System;assembly=mscorlib"
    xmlns:w="clr-namespace:System.Windows;assembly=WindowsBase"
Title="ComboBoxFallbackValue" Height="300" Width="300">
<Window.Resources>
    <t:TestCollection x:Key="test"/>
    <CompositeCollection x:Key="MyItemsSource">
        <x:Static Member="t:TestCollection.NullItem"/>
        <CollectionContainer Collection="{Binding Source={StaticResource test}}"/>
    </CompositeCollection>
    <t:TestModel x:Key="model"/>
    <t:NullItemConverter x:Key="nullItemConverter"/>
</Window.Resources>
<StackPanel>
    <ComboBox x:Name="cbox" ItemsSource="{Binding Source={StaticResource MyItemsSource}}" IsEditable="true" IsReadOnly="True" Text="Select an Option" SelectedItem="{Binding Source={StaticResource model}, Path=TestItem, Converter={StaticResource nullItemConverter}, ConverterParameter={x:Static t:TestCollection.NullItem}}"/>
    <TextBlock Text="{Binding Source={StaticResource model}, Path=TestItem, TargetNullValue='Testitem is null'}"/>
</StackPanel>

基本上,该模式是您声明用作项目的类的单例 NullInstance,并在设置 VM 属性时使用将这个实例转换为 null 的 Converter。转换器可以通用写,像这样(是VB,希望你不要介意):

Public Class NullItemConverter
Implements IValueConverter

Public Function Convert(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
    If value Is Nothing Then
        Return parameter
    Else
        Return value
    End If
End Function

Public Function ConvertBack(value As Object, targetType As System.Type, parameter As Object, culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
    If value Is parameter Then
        Return Nothing
    Else
        Return value
    End If
End Function

结束类

由于您可以重用转换器,因此您可以在 XAML 中进行设置;在代码中唯一需要做的就是提供单例 NullItem。

于 2012-09-28T17:23:17.213 回答
2

就个人而言,我倾向于在我绑定的集合中添加任何对象的“空”版本。因此,例如,如果您要绑定到字符串列表,那么在您的视图模型中,在集合的开头插入一个空字符串。如果您的模型具有数据集合,则将其与视图模型中的另一个集合一起包装。

模型:

public class Foo
{
    public List<string> MyList { get; set;}
}

查看型号:

public class FooVM
{
    private readonly Foo _fooModel ;

    private readonly ObservableCollection<string> _col;
    public ObservableCollection<string> Col // Binds to the combobox as ItemsSource
    {
        get { return _col; }
    }

    public string SelectedString { get; set; } // Binds to the view

    public FooVM(Foo model)
    {
        _fooModel = model;
        _col= new ObservableCollection<string>(_fooModel.MyList);
        _col.Insert(0, string.Empty);
    }
}
于 2012-09-28T18:53:27.413 回答
1

您还可以扩展 ComboBox 以启用取消选择。添加一个或多个允许用户将 设置SelectedItem为空的钩子(例如,按下退出键)。

using System.Windows.Input;

public class NullableComboBox : ComboBox
{
    public NullableComboBox()
        : base()
    {
        this.KeyUp += new KeyEventHandler(NullableComboBox_KeyUp);

        var menuItem = new MenuItem();
        menuItem.Header = "Remove selection";
        menuItem.Command = new DelegateCommand(() => { this.SelectedItem = null; });
        this.ContextMenu = new ContextMenu();
        this.ContextMenu.Items.Add(menuItem);
    }

    void NullableComboBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
    {
        if (e.Key == Key.Escape || e.Key == Key.Delete) 
        {
            this.SelectedItem = null;
        }
    }
}

编辑刚刚注意到 Florian GI 的评论,上下文菜单可能是另一个很好的取消选择钩子添加。

于 2012-09-28T17:05:22.563 回答
0

一种选择是创建一个适配器集合,专门为需要初始“空”元素的消费者公开。您需要创建一个实现 IList(如果您想要与 ObservableCollection 相同的性能)和 INotifyCollectionChanged 的​​包装类。您需要在包装的集合上收听 INotifyCollectionChanged,然后重新广播事件,并将索引上移一。所有相关的列表方法也需要将索引移一。

public sealed class FirstEmptyAdapter<T> : IList<T>, IList, INotifyCollectionChanged
{
    public FirstEmptyCollection(ObservableCollection<T> wrapped)
    {
    }

    //Lots of adapter code goes here...
}

如果你想避免 IList 方法,最低限度是实现 INotifyCollectionChanged 和IEnumerable<T>.

于 2012-09-28T17:04:07.457 回答
0

一种简单的方法是重新模板化组合框,以便当有一个项目选择时,框的右侧会出现一个小 X。单击清除所选项目。

这样做的好处是不会让您的 ViewModel 变得更复杂

于 2012-09-29T02:20:20.433 回答