7

Ok been working with WPF for a while but I need some help.

I have a ComboBox like below:

<TabControl>
    <TabItem Header="1">
        <ComboBox ItemsSource="{Binding MyList}" SelectedItem="{Binding MyListSelection}"/>
    </TabItem>
    <TabItem Header="2"/>
</TabControl>

Whenever I move away from tab 1 and then come back to it the selection gets removed. I think the reason for that is that the controls get destroyed when they go out of scope and then back in. But in the process of that the SelectedItem becomes null which isn't really what the user wanted, it's an event due to the UI lifecycle.

So I'm wondering what is the best route to take? I'm building this app with MVVM so I could ignore a set call on the MyListSelection Property in my ViewModel but I have ComboBoxes all over the place and don't like modifying my ViewModel for what I consider a bug of WPF.

I could subclass the WPF ComboBox, but there is no SelectedItemChanging event I can only add a handler when SelectedItem changed.

Any ideas?

UPDATE:

Okay, after beating my head against the wall I found out why my problem couldn't get reproduced. If the list item type is a class for some reason the SelectedItem gets set by WPF to null but if it's a value type it doesn't.

here's my test class(VMBase is just an abstract class that implements INotifyPropertyChanged):

public class TestListViewModel : VMBase
{
    public TestListViewModel()
    {
        TestList = new List<TestViewModel>();
        for (int i = 0; i < 10; i++)
        {
            TestList.Add(new TestViewModel(i.ToString()));
        }
    }

    public List<TestViewModel> TestList { get; set; }

    TestViewModel _SelectedTest;
    public TestViewModel SelectedTest
    {
        get { return _SelectedTest; }
        set
        {
            _SelectedTest = value;
            OnPropertyChanged("SelectedTest");
        }
    }
}

public class TestViewModel : VMBase
{
  public string Name {get;set;}
}

So when I change TestList to type int and go back and forth between tabs SelectedItem stays the same. But when it is of type TestViewModel SelectedTest gets set to null when the tabitem goes out of focus.

What's going on?

Nathan
  • 1,080
  • 7
  • 16
Jose
  • 10,891
  • 19
  • 67
  • 89

7 Answers7

10

I've the exact same problem, and till now I couldn't figure what the problem is. I tested in 4 different machines with the same OS, .Net version and hardware specifications and could reproduce the issue in two of them, in the other ones worked just fine. The workaround I could find that works for me is to define the SelectedItem binding before the ItemsSource. Strangely if I follow this pattern, everything works as expected. That said, you just have to do the following:

<Window x:Class="ComboBoxInTabItemSpike.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>
        <TabControl>
            <TabItem Header="1">
                <ComboBox SelectedItem="{Binding MySelect}" ItemsSource="{Binding MyList}"/>
            </TabItem>
            <TabItem Header="2"/>
        </TabControl>
        <TextBlock Text="{Binding MySelect}"/>
    </StackPanel>
</Window>
0

EDITED after change in OP. Hi Jose, I am unable to reproduce the error you mention. So your assumption about the Control being destroyed is wrong. The Combobox behaves as expected with the code behind below even it is now using a reference type. Some other piece of your code must kick in when you change TabItems.

<Window x:Class="ComboBoxInTabItemSpike.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>
        <TabControl>
            <TabItem Header="1">
                <ComboBox ItemsSource="{Binding MyList}"
                          SelectedItem="{Binding MySelect}"/>
            </TabItem>
            <TabItem Header="2"/>
        </TabControl>
        <TextBlock Text="{Binding MySelect}"/>
    </StackPanel>
</Window>

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;

namespace ComboBoxInTabItemSpike
{
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        public Window1()
        {
            InitializeComponent();
            MyList=new ObservableCollection<TestObject>(
                new[]{new TestObject("1"),new TestObject("2"),new TestObject("3") });
            DataContext = this;
        }

        public ObservableCollection<TestObject> MyList { get; set; }

        private TestObject mySelect;
        public TestObject MySelect
        {
            get { return mySelect; }
            set{ mySelect = value;
            if(PropertyChanged!=null)
                PropertyChanged(this,new PropertyChangedEventArgs("MySelect"));} 
        }

        public TestObject MySelectedItem
        {
            get { return (TestObject)GetValue(MySelectedItemProperty); }
            set { SetValue(MySelectedItemProperty, value); }
        }

        public static readonly DependencyProperty MySelectedItemProperty =
            DependencyProperty.Register("MySelectedItem",
                                typeof(TestObject),
                                typeof(Window1),
                                new UIPropertyMetadata(null));

        public event PropertyChangedEventHandler PropertyChanged;
    }

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

        public TestObject(string name)
        {
            Name = name;
        }

        public override string ToString()
        {
            return Name;
        }
    }
}
Dabblernl
  • 15,831
  • 18
  • 96
  • 148
  • 1
    When the List type is a reference type it does not behave the same. See my updated post – Jose Jan 28 '10 at 21:09
0

I would recommend checking the bindings. If anything else in your app is changing the selected item or the items source, then your binding will break. You can also look in Visual Studio at the output window to see if there are any errors.

Alex B
  • 2,766
  • 1
  • 17
  • 8
0

I think what you may be missing here is a TwoWay binding on the SelectedItem. When you bind your ViewModel class which contains the MyList(bound ItemsSource) and MyListSelection(Bond to SelectedItem in your Case) will always have those information even though you went to different tabs. So when you come back to this Tab the MyListSelection will bind back to the ComboBoc.SelectedItem again and you will be good. Try this and let me know.

Jobi Joy
  • 49,102
  • 20
  • 108
  • 119
0

I think this could be solved with a simple null check.

public TestViewModel SelectedTest
{
    get { return _SelectedTest; }
    set
    {
        if(value != null)
            _SelectedTest = value;
        OnPropertyChanged("SelectedTest");
    }
}

This is because ComboBox has a tendency to reset its SelectedIndex when recycled. This simple null check will force it to rebind to the last valid item.

Tri Q Tran
  • 5,500
  • 2
  • 37
  • 58
  • Yes that is an option that I've employed many times, but the app has lots of comboboxes and listviews, it's pretty annoying to do that every time. – Jose Jan 29 '10 at 17:07
  • Indeed this could be quite annoying, but then again having to raise the property changed even on every property is also annoying. WPF is far from perfect. GL – Tri Q Tran Jan 30 '10 at 12:29
  • 1
    This isn't always acceptable since null values could sometimes be valid values within a collection. Also what about cases where the property is actually a dependency property? Then you would have to get into looking at Coerce and Change notification events in order to do something similar which is just a mess. In my opinion this really isn't an acceptable solution in general. – jpierson May 10 '10 at 09:52
0

I was having the exact same problem with a reference type in my list. The solution was to override Equals() in my TestViewModel so that WPF would be able to do a value equality check (instead of a reference check) between the objects to determine which one is the SelectedItem. Mine happened to have an ID field that was really the identifying feature of a TestViewModel.

marina
  • 406
  • 3
  • 4
0

This behavior by the combobox, should be implemented by the compiler in a better fashion than it is... IE the compiler should check and see if the types for the ItemsSource and the type reference value of the property that the SelectedItem is bound to will EVER return the a value that is comparable

It should warn that you might consider overriding the Equals() and GetHashCode() methods...

Wasted a lot of time on this today !!

Greg Foote
  • 171
  • 11