繁体   English   中英

将 WPF ComboBox 绑定到自定义列表

[英]Binding a WPF ComboBox to a custom list

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

ComboBox ItemsSource 绑定到 ViewModel 类上的一个属性,该类将一堆 RAS 电话簿条目作为 CollectionView 列出。 然后我(在不同的时间)将SelectedItemSelectedValue绑定到 ViewModel 的另一个属性。 我在保存命令中添加了一个 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 中显示的实际字符串值感兴趣,而不对对象的任何其他属性感兴趣,因为这是我在建立 VPN 连接时需要传递给 RAS 的值,因此DisplayMemberPathSelectedValuePath都是名称ConnectionViewModel 的属性。 ComboBox 位于应用到 Window 上的ItemsControlDataTemplate ,该窗口的 DataContext 已设置为 ViewModel 实例。

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

数据绑定 ComboBox 时我缺少什么? 我做了很多搜索,似乎找不到任何我做错的地方。


这是我看到的行为,但是由于某种原因在我的特定上下文中它不起作用。

我有一个 MainWindowViewModel,它有一个 ConnectionViewModels 的CollectionView 在 MainWindowView.xaml 文件代码隐藏中,我将 DataContext 设置为 MainWindowViewModel。 MainWindowView.xaml 有一个绑定到 ConnectionViewModels 集合的ItemsControl 我有一个包含 ComboBox 以及其他一些 TextBox 的 DataTemplate。 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();
    }
}

然后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}" />

TextBoxes 都正确绑定,数据在它们和 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; }
        }
    }
}

如果你运行那个例子,你会得到我正在谈论的行为。 当您编辑 TextBox 时,它会很好地更新其绑定,但 ComboBox 不会。 非常令人困惑,因为我所做的唯一一件事就是引入父 ViewModel。

我目前的印象是绑定到 DataContext 的子项的项目将该子项作为其 DataContext。 我找不到任何可以以一种或另一种方式解决这个问题的文档。

IE,

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

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


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

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

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

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

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

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

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

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

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

(注意IdValue必须是属性,而不是类字段)

我遇到了起初似乎是相同的问题,但结果证明是由于 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 和 SelectedValuePath with NHibernate

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM