首页 文章

将WPF ComboBox绑定到自定义列表

提问于
浏览
164

我有一个似乎没有更新SelectedItem / SelectedValue的ComboBox .

ComboBox ItemsSource绑定到ViewModel类的属性,该类将一堆RAS电话簿条目列为CollectionView . 然后我(在不同的时间)将 SelectedItemSelectedValue 绑定到ViewModel的另一个属性 . 我在save命令中添加了一个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}" />

我只对ComboBox中显示的实际字符串值感兴趣,而不是对象的任何其他属性,因为这是我想要 Build VPN连接时需要传递给RAS的值,因此 DisplayMemberPathSelectedValuePath 都是名称ConnectionViewModel的属性 . ComboBox在 DataTemplate 中应用于Window上的 ItemsControl ,其中DataContext已设置为ViewModel实例 .

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

使用数据绑定ComboBox我错过了什么?我做了很多搜索,似乎找不到任何我做错的事情 .


这是我所看到的行为,但是在我的特定情况下,它不能用于某些原因 .

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

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

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

code-behind

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

如果你运行那个例子,你将得到我正在谈论的行为 . TextBox在编辑时更新其绑定,但ComboBox没有 . 非常令人困惑的看到我真正做的唯一事情就是引入一个父ViewModel .

我目前正在努力工作的印象是绑定到DataContext的子项的项目将该子项作为其DataContext . 我找不到任何能够以这种方式清除它的文档 .

即,

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

我不知道这是否能更好地解释我的假设(?) .


要确认我的假设,请更改TextBox的绑定

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

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

4 回答

  • 173

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

    我的问题是所选项目与列表中包含的项目不是同一个实例 . 所以我只需要覆盖MyCustomObject中的Equals()方法,并比较这两个实例的ID,告诉ComboBox它是同一个对象 .

    public override bool Equals(object obj)
    {
        return this.Id == (obj as MyCustomObject).Id;
    }
    
  • 22

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

    Edit: 杰夫斯的第二个例子似乎不起作用,这对我来说似乎有点奇怪 . 如果我 change the PhonebookEntries property on the ConnectionViewModel to be of type ReadOnlyCollection ,组合框上的SelectedValue属性的TwoWay绑定工作正常 .

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

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

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

  • 1

    将数据绑定到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; } 
    }
    
  • 67

    我最初看起来似乎是一个相同的问题,但结果是由于NHibernate / WPF兼容性问题 . 问题是由WPF检查对象相等性的方式引起的 . 通过使用SelectedValue和SelectedValuePath属性中的对象ID属性,我能够使我的东西工作 .

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

    有关详细信息,请参阅Chester的博客文章,The WPF ComboBox - SelectedItem, SelectedValue, and SelectedValuePath with NHibernate .

相关问题