开发者

WPF ComboBox SelectedItem

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;}开发者_StackOverflow社区
}

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?


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>


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.


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.


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


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.


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.


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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜