为什么我不能在ComboBox中选择空值?

50

在 WPF 中,似乎无法使用鼠标从 ComboBox 中选择“null”值。 编辑 为了澄清,这里是 .NET 3.5 SP1。

下面是一些代码来说明我的意思。首先是 C# 声明:

public class Foo
{
    public Bar Bar { get; set; }
}

public class Bar 
{
    public string Name { get; set; }
}

接下来是我的Window1 XAML:

<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <ComboBox x:Name="bars" 
                  DisplayMemberPath="Name" 
                  Height="21" 
                  SelectedItem="{Binding Bar}"
                  />
    </StackPanel>
</Window>

最后,我的Window1类:

public partial class Window1 : Window
{
    public Window1()
    {
        InitializeComponent();

        bars.ItemsSource = new ObservableCollection<Bar> 
        {
            null, 
            new Bar { Name = "Hello" }, 
            new Bar { Name = "World" } 
        };
        this.DataContext = new Foo();
    }
}

跟上我了吗?我有一个 ComboBox,其选项绑定到 Bar 实例的列表,其中一个实例是 null。我已将窗口绑定到 Foo 的实例,并且 ComboBox 正在显示其 Bar 属性的值。

当我运行此应用程序时,ComboBox 从空显示开始,因为默认情况下 Foo.Bar 是 null。这很好。如果我使用鼠标将 ComboBox 放下并选择“Hello”项目,那也可以。但是,如果我尝试重新选择列表顶部的空项目,则 ComboBox 会关闭并返回其先前值“Hello”!

使用箭头键选择 null 值按预期工作,并且以编程方式设置它也可以正常工作。只有使用鼠标选择不起作用。

我知道一个简单的解决方法是拥有代表 null 的 Bar 实例并通过 IValueConverter 运行它,但是有人可以解释一下为什么在 WPF 的 ComboBox 中使用鼠标选择 null 值不起作用吗?


这与我在此处的问题有关:https://dev59.com/00bRa4cB1Zd3GeqPxytc - Bryan Anderson
你如何使用箭头键选择空值?是箭头移动后按Tab键,还是箭头移动后按回车键?这样做有区别吗? - Dave
箭头键无论我如何离开控件都能正常工作。鼠标根本不允许选择空值,这似乎是一个仅限于鼠标的限制。 - Matt Hamilton
我也遇到了同样的问题。不幸的是,代表“null”的Bar实例对我来说不是可行的解决方案,因为我的对象不能在“空中”实例化(它们在构造函数中向所有者对象注册自己),因此我无法创建任何额外的实例。尽管如此,还是要给这个问题点赞。 - O. R. Mapper
10个回答

21

最近我遇到了ComboBoxnull值的问题。

我通过使用两个转换器解决了这个问题:

  1. 对于ItemsSource属性:它将集合中的null值替换为在转换器参数中传递的任何值:

    class EnumerableNullReplaceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var collection = (IEnumerable)value;
    
            return
                collection
                .Cast<object>()
                .Select(x => x ?? parameter)
                .ToArray();
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }
    
  2. 对于SelectedValue属性:它与SelectedValues属性类似,但是它只提供了单个值,并以两种方式提供:

  3. class NullReplaceConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value ?? parameter;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value.Equals(parameter) ? null : value;
        }
    }
    

使用示例:

<ComboBox 
    ItemsSource="{Binding MyValues, Converter={StaticResource EnumerableNullReplaceConverter}, ConverterParameter='(Empty)'}" 
    SelectedValue="{Binding SelectedMyValue, Converter={StaticResource NullReplaceConverter}, ConverterParameter='(Empty)'}"
    />

结果:

enter image description here

注意: 如果绑定到 ObservableCollection,则将丢失更改通知。同时,您不希望在集合中有多个null值。


2
不错的解决方案,对 EnumerableNullReplaceConverter.Convert 进行了改进。这样你就可以在不添加空值的情况下使用它了:var collection = (IEnumerable)value; var list = collection.Cast<object>().ToList(); list.Insert(0, parameter); return list; - Marcel Mateman

16
键盘无法选择空“项目”,相反,前一个项目被取消选择,而后续项目也不能被选择。 因此,在用键盘“选择”空项后,您随后无法重新选择先前选择的项目(“Hello”),除非使用鼠标!
简而言之,在ComboBox中既不能选择也不能取消选择空项。当您认为在这样做时,实际上您是在取消选择或选择前一个或新项目。
可以通过为ComboBox中的项目添加背景来更好地观察到这一点。当您选择“Hello”时,ComboBox中会出现有颜色的背景,但是当您通过键盘取消选择它时,背景颜色会消失。我们知道这不是空项,因为当我们通过鼠标展开列表时,空项实际上具有背景颜色!
下面的XAML代码修改自原问题中的代码,将在项目后添加浅蓝色背景,以便您可以看到这种行为。
<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <ComboBox x:Name="bars" Height="21" SelectedItem="{Binding Bar}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <Grid Background="LightBlue" Width="200" Height="20">
                        <TextBlock Text="{Binding Name}" />
                    </Grid>
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</Window>
如果你需要进一步验证,你可以在ComboBox上处理SelectionChanged事件,并查看"选择空项"实际上会在其SelectionChangedEventArgs的AddedItems中给出一个空数组,而"通过鼠标选择'Hello'来取消选择空项"则会给出一个空的RemovedItems数组。

你尝试使用SelectedIndex检查过吗?该属性会更改为null项的索引。因此,将选择null项。(-1) - Rekshino

10

我有一个新的解决方案,"使用Mahapps"

  xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"


  <ComboBox x:Name="bars"  **controls:TextBoxHelper.ClearTextButton="True"**
              DisplayMemberPath="Name" 
              Height="21" 
              SelectedItem="{Binding Bar}"/>

在这里输入图像描述

在这里输入图像描述

你可以使用关闭按钮清除内容。

谢谢。


有没有一种方式可以在不改变其他控件的情况下使用它? - The One
我一直在寻找一种方法来将 null 添加到组合框项目中。但是那个删除值按钮是一个更好的想法,而且更容易实现。 - Hossein Ebrahimi

7

我知道这个答案不是你想要的(关于为什么它不能用鼠标的解释),但我认为前提是有缺陷的:

从程序员和用户(不是.NET)的角度来看,选择null值是一件坏事。“null”应该是没有值的缺失,而不是你选择的东西。

如果你需要明确地不选择某些东西的能力,我建议你采用你提到的解决方法(将“-”、“n.a.”或“none”作为一个值),或者更好地

  • 用一个可以取消选中combobox的复选框包装它。从用户和编程的角度来看,这是最干净的设计。

1
是的,那正是我们所做的。而且,是的,我同意这并没有回答问题。 :) - Matt Hamilton
11
我对这个解决方案并不完全信服。从用户的角度来看,增加一个额外的控件用于某些明显包含在组合框中的内容(例如,一个空项或标记为“(无)”之类的项),会导致需要额外点击和进一步复杂化输入表单,这可能被认为是不好的用户界面设计。而从编程方面来看,null确实表示值的缺失,并保持与实际属性类型相容的类型兼容性,而不像任何占位符“未选择”对象。 - O. R. Mapper
2
有时(作为用户),您希望将值重置为不存在的值。用户不会理解为什么他/她不能选择 null。在许多地方,null 默认用作未知或空值。因此,想要将某些内容重置为 null 一点也不奇怪。如果它是 null,并且我可以设置一个值,为什么我不能将其重置为 null? - Wouter Schut
1
这就是为什么程序员不应该被允许设计用户界面的原因。 - Simon_Weaver
确切地说,添加一个复选框是最糟糕的用户界面选择,因为 null 无法执行某些操作。 我宁愿选择代码转换或手动转换,而不是破坏用户界面来反映我的意见。 - Code Name Jack

5

我花了一天时间寻找有关在组合框中选择空值的问题的解决方案,最终,在这个网址上写的一篇文章中,我终于找到了一个解决方案:

http://remyblok.tweakblogs.net/blog/7237/wpf-combo-box-with-empty-item-using-net-4-dynamic-objects.html

public class ComboBoxEmptyItemConverter : IValueConverter 
{ 
/// <summary> 
/// this object is the empty item in the combobox. A dynamic object that 
/// returns null for all property request. 
/// </summary> 
private class EmptyItem : DynamicObject 
{ 
    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
        // just set the result to null and return true 
        result = null; 
        return true; 
    } 
} 

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
{ 
    // assume that the value at least inherits from IEnumerable 
    // otherwise we cannot use it. 
    IEnumerable container = value as IEnumerable; 

    if (container != null) 
    { 
        // everything inherits from object, so we can safely create a generic IEnumerable 
        IEnumerable<object> genericContainer = container.OfType<object>(); 
        // create an array with a single EmptyItem object that serves to show en empty line 
        IEnumerable<object> emptyItem = new object[] { new EmptyItem() }; 
        // use Linq to concatenate the two enumerable 
        return emptyItem.Concat(genericContainer); 
    } 

    return value; 
} 

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
{ 
    throw new NotImplementedException(); 
} 

}

 <ComboBox ItemsSource="{Binding  TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}" 
      SelectedValue="{Binding SelectedID}" 
      SelectedValuePath="ID" 
      DisplayMemberPath="Name" />

在 ConvertBack 方法中,只需添加以下代码:返回值是 EmptyItem 吗?若是则返回 null,否则返回 value。 - TravisWhidden
1
在SelectedItem绑定上使用相同的转换器。对我有用! - TravisWhidden

1

这可能并不完全解决你的问题,但希望能指引你朝正确的方向前进:

  1. 你是否安装了SP1?

来自Scott Gu博客的链接:

NET 3.5 SP1包含了多个数据绑定和编辑方面的改进,其中包括:
  • 在{{Binding}}表达式中支持StringFormat,以便轻松格式化绑定值
  • 在从ItemsControl派生的控件中新增交替行支持,使得更容易设置行的交替属性(例如:交替背景颜色)
  • 更好地处理和转换可编辑控件中的null值,对整个绑定项应用验证规则的项级验证
  • MultiSelector支持以处理多选和批量编辑场景
  • IEditableCollectionView支持将数据控件与数据源接口连接,以启用事务方式的编辑/添加/删除项目
  • 当绑定到IEnumerable数据源时,性能有所提升
  • 强类型数据集的限制

    在这里解释了NullValueDataSet

    但是现在针对.Net 3.5的SP1应该已经解决了这个问题。


    是的,.NET 3.5 SP1。在两台不同的机器上进行了确认(我先在一个同事的项目中看到它,所以我自己编写了这个测试程序)。 - Matt Hamilton

    1

    我曾经遇到过同样的问题,我们进行了一些调整,比如为集合项添加一个值属性,像这样:

     public class Bar
    
       {
          public string Name { get; set; }
          public Bar Value
          {
             get { return String.IsNullOrEmpty(Name) ?  null :  this; } // you can define here your criteria for being null
          }
       }
    

    然后在添加项目时,我使用相同的对象而不是null:

      comboBox1.ItemsSource=  new ObservableCollection<Bar> 
            {
                new Bar(),
                new Bar { Name = "Hello" }, 
                new Bar { Name = "World" } 
            };
    

    而不是绑定到selecteditem,我将其绑定到selectedvalue:

    <ComboBox Height="23" Margin="25,40,133,0" DisplayMemberPath="Name"
                  SelectedValuePath="Value" 
                  SelectedValue="{Binding Bar}"
                  Name="comboBox1" VerticalAlignment="Top" />
    

    我知道这不是一个完整的解决方案,只是我使用的一种变通方法


    是的,这正是我在问题的最后一段中所说的“拥有一个表示null的Bar实例”的意思。 - Matt Hamilton

    0

    1
    Rudi,我知道FallbackValue在使用null对象模式时会很有用,但是当我想要明确地将“null”作为选项时,它如何帮助呢?你能否更新你的答案并提供一些XAML或代码示例? - Matt Hamilton

    0

    无论ComboBox的项有多简单,它都需要一个DataTemplate来进行显示。 DataTemplate的工作方式如下:从实例.[路径]获取一个值,例如

    bar1.Car.Color
    

    所以它无法从中获取值

    null.Car.Color
    

    它将抛出一个空引用异常。因此,空实例将不会被显示。但是,如果Color是引用类型,则允许其为null,因为在这种情况下不会发生异常。


    但是空实例确实被显示出来了 - 它被显示为列表中的空白项,并且可以使用键盘进行选择。只有当您尝试使用鼠标选择它时,ComboBox 才会恢复到先前的值。我理解您的逻辑,但证据并不支持它。 - Matt Hamilton
    抱歉我没有先试过就急着回答。我下面会提供另一个答案。 - redjackwong

    0

    只是猜测,但我认为这听起来很合理。

    假设combobox使用"ListCollectionView"(lcv作为其实例)作为其项集合,它应该是这样的。 如果你是一名程序员,你会怎么做?

    我将响应键盘和鼠标。

    一旦我获得键盘输入,我就会使用

    lcv.MoveCurrentToNext();
    

    或者

    lcv.MoveCurrentToPrevious();
    

    所以,键盘肯定工作正常。

    然后我正在处理鼠标输入响应。这时出现了问题。

    1. 我想监听我的项目的“MouseClick”事件。但是可能我的项目没有生成,它只是一个占位符。因此,当用户单击此占位符时,我什么也得不到。

    2. 如果我成功获取事件,接下来该怎么办?我会调用

      lcv.MoveCurrentTo(selectedItem);

    我认为在这里使用空值作为“selectedItem”参数是不可接受的。

    无论如何,这只是猜测。虽然我有能力进行调试,但我没有时间。我有一堆缺陷要修复。祝好运。 :)


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