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

Mat*_*ton 42 data-binding wpf combobox

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

这是一些显示我的意思的代码.首先,C#声明:

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

public class Bar 
{
    public string Name { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

接下来,我的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>
Run Code Online (Sandbox Code Playgroud)

最后,我的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();
    }
}
Run Code Online (Sandbox Code Playgroud)

与我一起?我有一个ComboBox,其项目绑定到Bar实例列表,其中一个为null.我已将窗口绑定到Foo的实例,并且ComboBox正在显示其Bar属性的值.

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

使用箭头键选择空值按预期工作,并以编程方式设置它也可以.它只能用鼠标选择不起作用.

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

Tim*_*son 15

键盘根本没有选择空"项目" - 而是取消选择前一项并且不能(可以)选择后续项目. 这就是为什么在用键盘"选择"空项后,你不能再重新选择之前选择的项目("你好") - 除了通过鼠标!

简而言之,您既不能选择也不能取消选择ComboBox中的空项.当您认为自己这样做时,您宁愿取消选择或选择上一个或新项目.

通过向ComboBox中的项添加背景,可以最好地看到这一点.当您选择"Hello"时,您会注意到ComboBox中的彩色背景,但是当您通过键盘取消选择时,背景颜色会消失.我们知道这不是null项,因为当我们通过鼠标向下放下列表时,null项实际上具有背景颜色!

从原始问题中修改的以下XAML将在项目后面放置一个LightBlue背景,以便您可以看到此行为.

<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>
Run Code Online (Sandbox Code Playgroud)

如果你想进一步验证,可以在ComboBox上处理SelectionChanged事件,看看"选择空项"实际上在SelectionChangedEventArgs中给出了一个空数组AddedItems,并且"用鼠标选择'Hello'取消选择空项"给出一个空数组的RemovedItems.


And*_*lov 11

好吧,我最近遇到了与ComboBox的null值相同的问题.我用两个转换器解决了这个问题:

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

    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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 对于SelectedValue属性:这个属性相同,但对于单个值和两种方式:

    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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

使用示例:

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

结果:

在此输入图像描述

注意:如果绑定到ObservableCollection,则会丢失更改通知.此外,您不希望集合中有多个值.

  • 很好的解决方案,对 EnumerableNullReplaceConverter.Convert 的改进。然后不用加空值就可以使用`var collection = (IEnumerable)value; var list = collection.Cast&lt;object&gt;().ToList(); 列表.Insert(0, 参数); 返回列表; (2认同)

小智 8

我为这个问题找到了一个新的解决方案."使用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}"/>
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

在此输入图像描述

您可以使用关闭按钮清除内容.

谢谢.


Gal*_*mon 5

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

从我作为程序员和用户(而不是.NET)的角度来看,选择空值是件坏事."null"应该是缺少值,而不是你选择的东西.

如果您需要明确不选择某些内容的能力,我会建议您提到的解决方法(" - ","na"或"none"作为值)或更好

  • 使用可以取消选中的复选框包装组合框以禁用组合框.从用户的角度和编程方面来看,这是最干净的设计.

  • 我不相信这个解决方案.从用户的角度来看,为组合框中明确包含的内容添加额外的控件(作为空项,或标记为"(无)"的项目或其他内容),因此需要额外的点击并进一步复杂化输入表单,这可能被认为是糟糕的UI设计.在编程方面,`null`确实表示没有值,同时保持与实际属性类型的类型兼容,而不是任何占位符"none selected"-objects. (9认同)
  • 有时(作为用户)您希望将值重置为不存在的值。用户不明白为什么他/她不能选择 null。默认情况下,Null 在很多地方被用作未知或没有。因此,想要将某些内容重置为 null 一点也不奇怪。如果它为空,并且我可以设置一个值,为什么我不能将其重置为空? (2认同)

Joh*_*ohn 5

我花了一天的时间来找到有关在组合框中选择空值的问题的解决方案,最后,是的,我终于在此 url 上写的一篇文章中找到了解决方案:

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(); 
} 
Run Code Online (Sandbox Code Playgroud)

}

 <ComboBox ItemsSource="{Binding  TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}" 
      SelectedValue="{Binding SelectedID}" 
      SelectedValuePath="ID" 
      DisplayMemberPath="Name" />
Run Code Online (Sandbox Code Playgroud)