Why can't I select a null value in a ComboBox?
Asked Answered
A

10

52

In WPF, it seems to be impossible to select (with the mouse) a "null" value from a ComboBox. Edit To clarify, this is .NET 3.5 SP1.

Here's some code to show what I mean. First, the C# declarations:

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

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

Next, my 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>

And lastly, my Window1 class:

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

With me? I have a ComboBox whose items are bound to a list of Bar instances, one of which is null. I have bound the window to an instance of Foo, and the ComboBox is displaying the value of its Bar property.

When I run this app, the ComboBox starts with an empty display because Foo.Bar is null by default. That's fine. If I use the mouse to drop the ComboBox down and select the "Hello" item, that works too. But then if I try to re-select the empty item at the top of the list, the ComboBox closes and returns to its previous value of "Hello"!

Selecting the null value with the arrow keys works as expected, and setting it programatically works too. It's only selecting with a mouse that doesn't work.

I know an easy workaround is to have an instance of Bar that represents null and run it through an IValueConverter, but can someone explain why selecting null with the mouse doesn't work in WPF's ComboBox?

Audette answered 6/2, 2009 at 0:3 Comment(4)
It's related my question here: #422578Howitzer
How do you select the null value with the arrow keys? Arrow to it and tab off, or arrow to it and hit enter? Does it make a difference?Abramabramo
The arrow keys work fine, regardless of how I leave the control. The mouse won't allow the selection of the null value at all. It seems to be a mouse-only restriction.Audette
I stumbled over the exact same problem. Unfortunately, the Bar instance that represents null is not a viable solution for me as my objects cannot be instantiated "in the air" (they register themselves with the owner object in their constructor), hence I cannot create any additional instances. +1 for the question, nonetheless.Myer
D
16

The null "item" is not being selected by the keyboard at all - rather the previous item is being unselected and no subsequent item is (able to be) selected. This is why, after "selecting" the null item with the keyboard, you are thereafter unable to re-select the previously selected item ("Hello") - except via the mouse!

In short, you can neither select nor deselect a null item in a ComboBox. When you think you are doing so, you are rather deselecting or selecting the previous or a new item.

This can perhaps best be seen by adding a background to the items in the ComboBox. You will notice the colored background in the ComboBox when you select "Hello", but when you deselect it via the keyboard, the background color disappears. We know this is not the null item, because the null item actually has the background color when we drop the list down via the mouse!

The following XAML, modified from that in the original question, will put a LightBlue background behind the items so you can see this behavior.

<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>

If you want further validation, you can handle the SelectionChanged event on the ComboBox and see that "selecting the null item" actually gives an empty array of AddedItems in its SelectionChangedEventArgs, and "deselecting the null item by selecting 'Hello' with the mouse" gives an empty array of RemovedItems.

Denote answered 14/12, 2009 at 23:12 Comment(1)
Have you tried to check it with SelectedIndex? The property is changed to the index of null-item. So null-item will be selected. (-1)Rodrigorodrigue
H
22

Well I recently ran into the same problem with null value for ComboBox. I've solved it by using two converters:

  1. For ItemsSource property: it replaces null values in the collection by any value passed inside converter's parameter:

    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. For SelectedValue property: this one does the same but for the single value and in two ways:

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

Example of use:

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

Result:

enter image description here

Note: If you bind to ObservableCollection then you will lose change notifications. Also you don't want to have more than one null value in the collection.

Housecarl answered 28/4, 2015 at 13:50 Comment(1)
Nice solution, Improvement to EnumerableNullReplaceConverter.Convert. Then you can use it without adding a null value `var collection = (IEnumerable)value; var list = collection.Cast<object>().ToList(); list.Insert(0, parameter); return list;Bullroarer
D
16

The null "item" is not being selected by the keyboard at all - rather the previous item is being unselected and no subsequent item is (able to be) selected. This is why, after "selecting" the null item with the keyboard, you are thereafter unable to re-select the previously selected item ("Hello") - except via the mouse!

In short, you can neither select nor deselect a null item in a ComboBox. When you think you are doing so, you are rather deselecting or selecting the previous or a new item.

This can perhaps best be seen by adding a background to the items in the ComboBox. You will notice the colored background in the ComboBox when you select "Hello", but when you deselect it via the keyboard, the background color disappears. We know this is not the null item, because the null item actually has the background color when we drop the list down via the mouse!

The following XAML, modified from that in the original question, will put a LightBlue background behind the items so you can see this behavior.

<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>

If you want further validation, you can handle the SelectionChanged event on the ComboBox and see that "selecting the null item" actually gives an empty array of AddedItems in its SelectionChangedEventArgs, and "deselecting the null item by selecting 'Hello' with the mouse" gives an empty array of RemovedItems.

Denote answered 14/12, 2009 at 23:12 Comment(1)
Have you tried to check it with SelectedIndex? The property is changed to the index of null-item. So null-item will be selected. (-1)Rodrigorodrigue
A
10

I got a new solution for this question. "USING 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}"/>

enter image description here

enter image description here

You can use the close button to clear the content.

Thanks.

Allegorist answered 18/4, 2017 at 7:39 Comment(2)
Is there a way to use this without altering other controls?Autopsy
I was searching to find a way to add null to combobox items. But that remove value button is a better idea and easier to implement.Bland
A
7

I know this answer isn't what you asked for (an explanation of why it doesn't work with the mouse), but I think the premise is flawed:

From my perspective as a programmer and user (not .NET), selecting a null value is a bad thing. "null" is supposed to be the absence of a value, not something you select.

If you need the ability explicitly not to select something, I would suggest either the work-around you mentioned ("-", "n.a." or "none" as a value), or better

  • wrap the combobox with a checkbox that can be unchecked to disable the combobox. This strikes me as the cleanest design both from a user's perspective and programmatically.
Arrowy answered 17/2, 2009 at 8:8 Comment(5)
Yes, that's exactly what we've done. And yes, I agree it doesn't answer the question. :)Audette
I am not convinced of this solution. From a user point of view, adding an additional control for something that would be clearly included in the combobox (as an empty item, or an item labeled "(none)" or something ilke that), thus requiring an extra click and further complicating the input form, this could be considered bad UI design. On the programming side, null indeed indicates the absence of a value, while staying type-compatible to the actual property type, as opposed to any placeholder "none selected"-objects.Myer
Sometimes (as a user) you want to reset a value to an absence of a value. A user wouldn't understand why he/she couldn't select null. Null is used as unknown or nothing in a lot of places by default. So its not weird at all to want to reset something to null. If it was null, and I can set a value, why should i not be able to reset it to null?Knavery
... and this is why programmers shouldn't be allowed to design user interfacesConveyancer
Exactly, Adding a checkbox because null can't do something will be the worst UI choice. I would any day prefer code behind or Manual conversion over destroying UI to reflect what my couldn'tRecension
P
5

I spent one day to find a solution about this problem of selecting a null value in combobox and finally, yeah finally I found a solution in an article written at this 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(); 
} 

}

 <ComboBox ItemsSource="{Binding  TestObjectCollection, Converter={StaticResource ComboBoxEmptyItemConverter}}" 
      SelectedValue="{Binding SelectedID}" 
      SelectedValuePath="ID" 
      DisplayMemberPath="Name" />
Poetess answered 27/7, 2013 at 22:52 Comment(2)
On ConvertBack, just put in: return value is EmptyItem ? null : value;Starr
And on the SelectedItem binding use the same converter. Worked for me!Starr
H
1

this might not address your answer completely, but hopefully its a hit in the right direction:

  1. Have you installed SP1?

From Scott Gu's Blog:

  • NET 3.5 SP1 includes several data binding and editing improvements to
    WPF. These include:
  • StringFormat support within {{ Binding }} expressions to enable easy formatting of bound values
  • New alternating rows support within controls derived from ItemsControl, which makes it easier to set alternating properties on rows (for example: alternating background colors)
  • Better handling and conversion support for null values in editable controls Item-level validation that applies validation rules to an entire bound item
  • MultiSelector support to handle multi-selection and bulk editing scenarios
  • IEditableCollectionView support to interface data controls to data sources and enable editing/adding/removing items in a transactional way
  • Performance improvements when binding to IEnumerable data sources

Sorry if I wasted your time and this was not even close..but I think the problem is inherited from:

constraints of the strongly typed dataset

NullValueDataSet Explained here

But now the SP1 for .Net 3.5 should have addressed this issue..

Herrington answered 6/2, 2009 at 0:49 Comment(1)
Yep, .NET 3.5 SP1. Confirmed on two different machines (saw it first in a different project on a co-worker's so I wrote this test program on my own).Audette
B
1

I had the same kind of problem we did some work around like adding a value property to the collection item like this :

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

Then while adding items instead of null I use the same object :

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

And instead of selecteditem I bind it to selectedvalue :

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

I know It is not a complete solution, just one workaround I use

Birdsong answered 9/2, 2009 at 9:1 Comment(1)
Yes, that's exactly what I meant by " have an instance of Bar that represents null" in the last paragraph of my question.Audette
C
0

Try Binding.FallbackValue

From 6 Things I Bet You Didn't Know About Data Binding in WPF

Courbevoie answered 6/2, 2009 at 13:32 Comment(1)
Rudi, I get that FallbackValue would be useful when using the null object pattern, but how would it help when I want to explicitly have "null" as an option? Can you update your answer with some XAML or code?Audette
E
0

ComboBox needs a DataTemplate to display the item no matter how simple it is. DataTemplate works like this: get a value from instance.[path], e.g.

bar1.Car.Color

So it cannot get a value from

null.Car.Color

It will throw a null reference exception. So, the null instance will not be displayed. But the the Color - if it is a reference type - is allowed to be null because there will be no exception in this case.

Eelworm answered 17/2, 2009 at 7:41 Comment(2)
But the null instance is displayed - it's displayed as a blank item in the list, and it's possible to select using the keyboard. It's only when you try to select it with the mouse that the ComboBox reverts back to its previous value. I understand your logic but the evidence doesn't support it.Audette
Sorry for my quick answer without trying it out. I will give another answer below.Eelworm
E
0

Just a guess, but I think it sounds reasonable.

Assume combobox is using "ListCollectionView" (lcv as its instance) as its item collection, which it should be. If you are a programmer, what you gonna do?

I will respons to both Keyboard and Mouse.

Once I get Keyboard input, I use

lcv.MoveCurrentToNext();

or

lcv.MoveCurrentToPrevious();

So, sure keyboard works well.

Then I am working on respons Mouse inputs. And it comes the problem.

  1. I want to listen 'MouseClick' event of my item. But probably, my Item doesn't generated, it is just a placeholder. So when user click on this placeholder, I get nothing.

  2. If I get the event successfully, what's next. I will invoke

    lcv.MoveCurrentTo(selectedItem);

the "selectedItem" which would be null is not an acceptable parameter here I think.

Anyway, it's just guessing. I don't have time to debug into it though I am able to. I have a bunch of defects to fix. Good Luck. :)

Eelworm answered 17/2, 2009 at 9:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.