tags:

views:

375

answers:

5

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?

A: 

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
When the List type is a reference type it does not behave the same. See my updated post
Jose
A: 

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

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
SelectedItem binding is TwoWay by default.
Tri Q
A: 

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
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
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
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
+1  A: 

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