将WPF ComboBox绑定到自定义列表


183

我有一个ComboBox,它似乎没有更新SelectedItem / SelectedValue。

ComboBox ItemsSource绑定到ViewModel类上的一个属性,该类将一堆RAS电话簿条目作为CollectionView列出。然后,我(在不同的时间)将ViewModel 的SelectedItem或绑定SelectedValue到了另一个属性。我已经在保存命令中添加了一个MessageBox来调试由数据绑定设置的值,但是未设置SelectedItem/ SelectedValue绑定。

ViewModel类看起来像这样:

public ConnectionViewModel
{
    private readonly CollectionView _phonebookEntries;
    private string _phonebookeEntry;

    public CollectionView PhonebookEntries
    {
        get { return _phonebookEntries; }
    }

    public string PhonebookEntry
    {
        get { return _phonebookEntry; }
        set
        {
            if (_phonebookEntry == value) return;
            _phonebookEntry = value;
            OnPropertyChanged("PhonebookEntry");
        }
    }
}

_phonebookEntries集合正在从业务对象的构造函数中初始化。ComboBox XAML看起来像这样:

<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
    DisplayMemberPath="Name"
    SelectedValuePath="Name"
    SelectedValue="{Binding Path=PhonebookEntry}" />

我只是在实际字符串值兴趣显示在组合框,而不是对象,因为这任何其他属性是我需要跨传递给RAS时,我想使VPN连接,因此价值DisplayMemberPathSelectedValuePath是双方的名称属性ConnectionViewModel。ComboBox在DataTemplate应用于ItemsControlDataContext已设置为ViewModel实例的Window上。

ComboBox可以正确显示项目列表,并且我可以在UI中选择一个项目而没有问题。但是,当我从命令显示消息框时,PhonebookEntry属性中仍具有初始值,而不是ComboBox中的选定值。其他TextBox实例正在更新,并且显示在MessageBox中。

我对数据绑定ComboBox缺少什么?我已经进行了很多搜索,但似乎找不到我做错的任何事情。


这是我所看到的行为,但是由于某种原因,在我的特定情况下它无法正常工作。

我有一个MainWindowViewModel,其中有一个CollectionViewConnectionViewModels。在后面的MainWindowView.xaml文件中,我将DataContext设置为MainWindowViewModel。MainWindowView.xaml具有ItemsControl对ConnectionViewModels集合的绑定。我有一个包含ComboBox以及其他一些TextBoxes的DataTemplate。使用可以将TextBox直接绑定到ConnectionViewModel的属性Text="{Binding Path=ConnectionName}"

public class ConnectionViewModel : ViewModelBase
{
    public string Name { get; set; }
    public string Password { get; set; }
}

public class MainWindowViewModel : ViewModelBase
{
    // List<ConnectionViewModel>...
    public CollectionView Connections { get; set; }
}

XAML背后的代码:

public partial class Window1
{
    public Window1()
    {
        InitializeComponent();
        DataContext = new MainWindowViewModel();
    }
}

然后XAML:

<DataTemplate x:Key="listTemplate">
    <Grid>
        <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
            DisplayMemberPath="Name"
            SelectedValuePath="Name"
            SelectedValue="{Binding Path=PhonebookEntry}" />
        <TextBox Text="{Binding Path=Password}" />
    </Grid>
</DataTemplate>

<ItemsControl ItemsSource="{Binding Path=Connections}"
    ItemTemplate="{StaticResource listTemplate}" />

所有TextBox都正确绑定,并且数据在它们和ViewModel之间移动没有问题。只是ComboBox不起作用。

您对PhonebookEntry类的假设是正确的。

我所做的假设是,我的DataTemplate使用的DataContext是通过绑定层次结构自动设置的,因此我不必为中的每个项目显式设置它ItemsControl。对我来说,这似乎有点愚蠢。


这是一个基于上述示例的演示该问题的测试实现。

XAML:

<Window x:Class="WpfApplication7.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">
    <Window.Resources>
        <DataTemplate x:Key="itemTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBox Text="{Binding Path=Name}" Width="50" />
                <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
                    DisplayMemberPath="Name"
                    SelectedValuePath="Name"
                    SelectedValue="{Binding Path=PhonebookEntry}"
                    Width="200"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Path=Connections}"
            ItemTemplate="{StaticResource itemTemplate}" />
    </Grid>
</Window>

后面代码

namespace WpfApplication7
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new MainWindowViewModel();
        }
    }

    public class PhoneBookEntry
    {
        public string Name { get; set; }
        public PhoneBookEntry(string name)
        {
            Name = name;
        }
    }

    public class ConnectionViewModel : INotifyPropertyChanged
    {

        private string _name;

        public ConnectionViewModel(string name)
        {
            _name = name;
            IList<PhoneBookEntry> list = new List<PhoneBookEntry>
                                             {
                                                 new PhoneBookEntry("test"),
                                                 new PhoneBookEntry("test2")
                                             };
            _phonebookEntries = new CollectionView(list);
        }
        private readonly CollectionView _phonebookEntries;
        private string _phonebookEntry;

        public CollectionView PhonebookEntries
        {
            get { return _phonebookEntries; }
        }

        public string PhonebookEntry
        {
            get { return _phonebookEntry; }
            set
            {
                if (_phonebookEntry == value) return;
                _phonebookEntry = value;
                OnPropertyChanged("PhonebookEntry");
            }
        }

        public string Name
        {
            get { return _name; }
            set
            {
                if (_name == value) return;
                _name = value;
                OnPropertyChanged("Name");
            }
        }
        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

    public class MainWindowViewModel
    {
        private readonly CollectionView _connections;

        public MainWindowViewModel()
        {
            IList<ConnectionViewModel> connections = new List<ConnectionViewModel>
                                                          {
                                                              new ConnectionViewModel("First"),
                                                              new ConnectionViewModel("Second"),
                                                              new ConnectionViewModel("Third")
                                                          };
            _connections = new CollectionView(connections);
        }

        public CollectionView Connections
        {
            get { return _connections; }
        }
    }
}

如果运行该示例,您将得到我正在谈论的行为。当您编辑文本框时,它会很好地更新其绑定,但组合框却不会。真正使我感到困惑的是,我引入了父级ViewModel。

我目前的工作印象是,绑定到DataContext的子项的项目具有该子项作为其DataContext。我找不到任何一种可以清除这种情况的文档。

窗口-> DataContext = MainWindowViewModel
..Items->绑定到DataContext.PhonebookEntries
.... Item- > DataContext = PhonebookEntry(隐式关联)

我不知道这是否可以更好地解释我的假设(?)。


为了确认我的假设,将TextBox的绑定更改为

<TextBox Text="{Binding Mode=OneWay}" Width="50" />

这将显示TextBox绑定根(我正在与DataContext进行比较)是ConnectionViewModel实例。

Answers:


189

您将DisplayMemberPath和SelectedValuePath设置为“ Name”,因此我假设您具有带公用属性Name的PhoneBookEntry类。

您是否已将DataContext设置为ConnectionViewModel对象?

我复制了您的代码,并做了一些小的修改,看来工作正常。我可以设置viewmodels PhoneBookEnty属性,并且更改组合框中的所选项目,也可以更改组合框中的所选项目,并且正确设置了视图模型PhoneBookEntry属性。

这是我的XAML内容:

<Window x:Class="WpfApplication6.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">
<Grid>
    <StackPanel>
        <Button Click="Button_Click">asdf</Button>
        <ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
                  DisplayMemberPath="Name"
                  SelectedValuePath="Name"
                  SelectedValue="{Binding Path=PhonebookEntry}" />
    </StackPanel>
</Grid>
</Window>

这是我的代码背后:

namespace WpfApplication6
{

    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            ConnectionViewModel vm = new ConnectionViewModel();
            DataContext = vm;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ((ConnectionViewModel)DataContext).PhonebookEntry = "test";
        }
    }

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

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

        public override string ToString()
        {
            return Name;
        }
    }

    public class ConnectionViewModel : INotifyPropertyChanged
    {
        public ConnectionViewModel()
        {
            IList<PhoneBookEntry> list = new List<PhoneBookEntry>();
            list.Add(new PhoneBookEntry("test"));
            list.Add(new PhoneBookEntry("test2"));
            _phonebookEntries = new CollectionView(list);
        }

        private readonly CollectionView _phonebookEntries;
        private string _phonebookEntry;

        public CollectionView PhonebookEntries
        {
            get { return _phonebookEntries; }
        }

        public string PhonebookEntry
        {
            get { return _phonebookEntry; }
            set
            {
                if (_phonebookEntry == value) return;
                _phonebookEntry = value;
                OnPropertyChanged("PhonebookEntry");
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }
}

编辑:杰夫斯的第二个例子似乎没有用,这对我来说有点奇怪。如果我将ConnectionViewModel上的PhonebookEntries属性更改为ReadOnlyCollection类型,则组合框上的SelectedValue属性的TwoWay绑定可以正常工作。

也许CollectionView有问题?我在输出控制台中注意到一个警告:

System.Windows.Data警告:50:不完全支持直接使用CollectionView。基本功能可以运行,尽管效率低下,但是高级功能可能会遇到已知的错误。考虑使用派生类来避免这些问题。

Edit2(.NET 4.5): DropDownList的内容可以基于ToString()而不基于DisplayMemberPath,而DisplayMemberPath仅为所选和显示的项目指定成员。


1
我也注意到,消息为好,但我认为什么覆盖会一直基本数据绑定。我猜不会。:)我现在使用_list.AsReadOnly()将属性公开为IList <T >并在属性getter中公开,与您提到的方式类似。正如我希望的原始方法那样,它正在工作。另外,我想到,虽然ItemsSource绑定工作正常,但我可以只使用ViewModel中的Current属性来访问ComboBox中的所选项目。但是,它不像绑定ComboBoxes SelectedValue / SelectedItem属性那样自然。
杰夫·贝内特

3
我可以确认将ItemsSource属性绑定到的集合更改为只读集合可以正常工作。就我而言,我必须将其从更改ObservableCollectionReadOnlyObservableCollection。坚果 这是.NET 3.5-不确定是否已在4.0中修复
ChrisWue 2012年

74

将数据绑定到ComboBox

List<ComboData> ListData = new List<ComboData>();
ListData.Add(new ComboData { Id = "1", Value = "One" });
ListData.Add(new ComboData { Id = "2", Value = "Two" });
ListData.Add(new ComboData { Id = "3", Value = "Three" });
ListData.Add(new ComboData { Id = "4", Value = "Four" });
ListData.Add(new ComboData { Id = "5", Value = "Five" });

cbotest.ItemsSource = ListData;
cbotest.DisplayMemberPath = "Value";
cbotest.SelectedValuePath = "Id";

cbotest.SelectedValue = "2";

ComboData看起来像:

public class ComboData
{ 
  public int Id { get; set; } 
  public string Value { get; set; } 
}

此解决方案不适用于我。ItemsSource可以正常工作,但是Path属性无法正确重定向到ComboData值。
Coneone

3
IdValue必须是属性,而不是课程字段,例如:public class ComboData { public int Id { get; set; } public string Value { get; set; } }
Edgar 2016年

23

起初我遇到的似乎是一个完全相同的问题,但事实证明这是由于NHibernate / WPF兼容性问题所致。问题是由WPF检查对象相等性的方式引起的。通过使用SelectedValue和SelectedValuePath属性中的对象ID属性,我可以使工作正常。

<ComboBox Name="CategoryList"
          DisplayMemberPath="CategoryName"
          SelectedItem="{Binding Path=CategoryParent}"
          SelectedValue="{Binding Path=CategoryParent.ID}"
          SelectedValuePath="ID">

有关详细信息,请参阅Chester的博客文章,带有NHibernate的WPF ComboBox-SelectedItem,SelectedValue和SelectedValuePath


1

我有一个类似的问题,其中SelectedItem从未更新。

我的问题是所选项目与列表中包含的项目不是同一实例。因此,我只需要重写MyCustomObject中的Equals()方法并比较这两个实例的ID即可告诉ComboBox它是同一对象。

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.