取消选择MVVM中的ComboBoxItems

时间:2012-09-28 16:47:36

标签: wpf mvvm

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

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

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

这是一个黑客攻击,它会影响在视图模型上使用该集合的所有代码。

例如,如果有人要写

_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),这很棒。

这个的一个问题是,如果项目完全为null,则选择它时从不调用SelectedItem setter

如果我将这一行修改为:

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

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

5 个答案:

答案 0 :(得分: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,并使用Converter在设置VM属性时将此实例转换为null。转换器可以普遍编写,就像这样(它是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。

答案 1 :(得分:2)

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

MODEL:

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

答案 2 :(得分:1)

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

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的评论,上下文菜单可能是另一个很好的取消选择钩子。

答案 3 :(得分: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>

答案 4 :(得分:0)

一种简单的方法是重新模板化ComboBox,以便在有项目时选择一个小X出现在框的右侧。单击该按钮将清除所选项目。

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