c# - 将 WPF ComboBox 绑定(bind)到自定义列表

标签 c# wpf data-binding mvvm combobox

我有一个似乎没有更新 SelectedItem/SelectedValue 的 ComboBox。
ComboBox ItemsSource 绑定(bind)到 ViewModel 类上的一个属性,该类将一堆 RAS 电话簿条目列为 CollectionView。然后我(在不同的时间)绑定(bind)了 SelectedItemSelectedValue到 ViewModel 的另一个属性。我在保存命令中添加了一个 MessageBox 来调试数据绑定(bind)设置的值,但 SelectedItem/SelectedValue未设置绑定(bind)。
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 的 Name 属性。 ComboBox 位于 DataTemplate应用于 ItemsControl在其 DataContext 已设置为 ViewModel 实例的 Window 上。
ComboBox 正确显示项目列表,我可以毫无问题地在 UI 中选择一个。但是,当我从命令中显示消息框时,PhonebookEntry 属性中仍然包含初始值,而不是从 ComboBox 中选择的值。其他 TextBox 实例正在更新并显示在 MessageBox 中。
数据绑定(bind) ComboBox 时我缺少什么?我做了很多搜索,似乎找不到任何我做错的地方。

这是我看到的行为,但是由于某种原因在我的特定上下文中它不起作用。
我有一个 MainWindowViewModel,它有一个 CollectionView连接 View 模型。在 MainWindowView.xaml 文件代码隐藏中,我将 DataContext 设置为 MainWindowViewModel。 MainWindowView.xaml 有一个 ItemsControl绑定(bind)到 ConnectionViewModels 的集合。我有一个包含组合框和其他一些文本框的 DataTemplate。 TextBoxes 使用 Text="{Binding Path=ConnectionName}" 直接绑定(bind)到 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 都正确绑定(bind),数据在它们和 ViewModel 之间移动没有问题。只有 ComboBox 不起作用。
您对 PhonebookEntry 类的假设是正确的。
我所做的假设是我的 DataTemplate 使用的 DataContext 是通过绑定(bind)层次结构自动设置的,因此我不必为 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 时,它会很好地更新其绑定(bind),但 ComboBox 不会。非常令人困惑,因为我所做的唯一一件事就是引入父 ViewModel。
我目前的印象是绑定(bind)到 DataContext 的子项的项目将该子项作为其 DataContext。我找不到任何文件可以以一种或另一种方式解决这个问题。
IE。,
窗口 -> DataContext = MainWindowViewModel
..Items -> 绑定(bind)到 DataContext.PhonebookEntries
....Item -> DataContext = PhonebookEntry(隐式关联)
我不知道这是否能更好地解释我的假设(?)。

为了确认我的假设,将 TextBox 的绑定(bind)更改为
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
这将显示 TextBox 绑定(bind)根(我将其与 DataContext 进行比较)是 ConnectionViewModel 实例。

最佳答案

您将 DisplayMemberPath 和 SelectedValuePath 设置为“Name”,因此我假设您有一个带有公共(public)属性 Name 的类 PhoneBookEntry。

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

我复制了你的代码并做了一些小的修改,它似乎工作正常。
我可以设置 viewmodels PhoneBookEnty 属性和组合框中的选定项更改,我可以更改组合框中的选定项并且正确设置了 View 模型 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 绑定(bind)工作正常。

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

System.Windows.Data Warning: 50 : Using CollectionView directly is not fully supported. The basic features work, although with some inefficiencies, but advanced features may encounter known bugs. Consider using a derived class to avoid these problems.



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

关于c# - 将 WPF ComboBox 绑定(bind)到自定义列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/561166/

相关文章:

c# - .NET 工具 : Extract Interface and Implement Wrapper Class

c# - 在 C# 中从 JPEG、XMP 或 EXIF 读取数据元数据

c# - 空路径名不合法

c# - 数据绑定(bind)到对象 - 如何取消数据源更改

javascript - AngularJS - 删除绑定(bind)以避免内存泄漏

wpf - 在 silverlight/WP7 应用程序中使用 MVVM 样式模式

c# - 如何将 PostgreSql 与 EntityFramework 6.0.2 集成?

c# - vs 2008企业版调试线程--c#

c# - 显示当前时间 WPF

c# - 如何在没有 XAML 的情况下将换行符插入 TextBlock?