WPF ComboBox选定项

7

我已经一段时间在使用WPF,但是我需要帮助。

我有一个如下所示的ComboBox

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

每当我从选项卡1移开,然后再回到它时,选择就会被取消。我认为原因是在控件超出范围并返回时被销毁。但在此过程中,SelectedItem变为null,这不是用户想要的结果,而是由于UI生命周期引起的事件。
所以我想知道最好的方法是什么?我正在使用MVVM构建此应用程序,因此我可以忽略ViewModel中MyListSelection属性的设置调用,但是我在各个地方都有ComboBox,不喜欢为我认为是WPF错误的问题修改ViewModel。
我可以子类化WPF ComboBox,但是没有SelectedItemChanging事件,我只能在SelectedItem更改时添加处理程序。
有什么想法吗?
更新:
好的,在我头痛欲裂之后,我发现了为什么无法重现我的问题。如果列表项类型是类,则由于某种原因,WPF将SelectedItem设置为null,但如果是值类型,则不会。
这是我的测试类(VMBase只是实现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;}
}

当我将TestList更改为int类型并在选项卡之间来回切换时,SelectedItem保持不变。但是,当它是TestViewModel类型时,当选项卡失去焦点时,SelectedTest会被设置为null。

发生了什么?

7个回答

10

我遇到了完全相同的问题,但至今还没有找出问题所在。我在4台具有相同操作系统、.Net版本和硬件规格的不同机器上进行了测试,并且在其中两台机器上复现了该问题,在另外两台机器上却正常工作。 我找到的解决方法是在定义ItemsSource之前定义SelectedItem绑定。奇怪的是,如果按照这种模式进行操作,一切都能按预期工作。 也就是说,你只需要按照以下步骤进行:

<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

在 OP 更改后进行了编辑。 嗨,Jose,我无法重现你提到的错误。因此,你关于控件被销毁的假设是错误的。即使现在使用引用类型,下面的代码仍然可以正常地控制 Combobox 的行为。当你更改 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;
        }
    }
}

1
当List类型是引用类型时,它的行为不同。请看我的更新帖子。 - Jose

0

我认为这个问题可以通过简单的空值检查来解决。

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

这是因为ComboBox在回收时有重置其SelectedIndex的倾向。这个简单的空值检查将强制它重新绑定到最后一个有效项。

是的,我已经采用了这个选项很多次,但是应用程序有很多组合框和列表视图,每次都这样做非常烦人。 - Jose
确实,这可能会非常令人恼火,但是每个属性都必须引发属性更改事件也很烦人。WPF远非完美。GL - Tri Q Tran
1
这并不总是可接受的,因为在集合中,null 值有时可能是有效值。而且如果属性实际上是依赖属性,那么你就必须查看 Coerce 和 Change 通知事件,以执行类似的操作,这只会让事情变得混乱。在我看来,这真的不是一个普遍可接受的解决方案。 - jpierson

0
我在我的列表中遇到了同样的引用类型问题。解决方案是在我的TestViewModel中覆盖Equals()方法,这样WPF就能够进行值相等性检查(而不是引用检查)来确定哪一个是SelectedItem。我的TestViewModel恰好有一个ID字段,它是TestViewModel的识别特征。

0
我建议检查绑定。如果应用程序中的其他任何内容更改了所选项或项目源,则绑定将会中断。您还可以在Visual Studio中查看输出窗口,以查看是否有任何错误。

0
我认为你可能忽略了SelectedItem的双向绑定。当你绑定包含MyList(绑定的ItemsSource)和MyListSelection(在你的情况下绑定到SelectedItem)的ViewModel类时,即使你切换到不同的选项卡,它们也会一直保留这些信息。因此,当你回到这个选项卡时,MyListSelection将重新绑定到ComboBox.SelectedItem,然后你就可以正常使用了。请尝试这样做并让我知道结果。

1
默认情况下,SelectedItem绑定是双向的。 - Tri Q Tran

0

这个下拉框的行为应该由编译器以更好的方式来实现...也就是说,编译器应该检查并确定ItemsSource的类型和SelectedItem绑定的属性的类型引用值是否会返回可比较的值。

它应该警告你可能需要重写Equals()和GetHashCode()方法...

今天浪费了很多时间在这上面!!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接