小编典典

将 WPF ComboBox 绑定到自定义列表

all

我有一个似乎没有更新 SelectedItem/SelectedValue 的组合框。

ComboBox ItemsSource 绑定到 ViewModel 类的一个属性,该类将一堆 RAS 电话簿条目列为
CollectionView。然后我已经(在不同的时间)绑定SelectedItem或绑定SelectedValue到 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 中显示的实际字符串值感兴趣,而不是对象的任何其他属性,因为这是我在建立 VPN 连接时需要传递给 RAS
的值,因此DisplayMemberPath它们SelectedValuePath都是的 Name
属性连接视图模型。ComboBoxDataTemplate应用于ItemsControl其 DataContext 已设置为 ViewModel
实例的 Window 上。

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

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


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

我有一个 MainWindowViewModel,它有一个CollectionViewConnectionViewModels。在
MainWindowView.xaml 文件代码隐藏中,我将 DataContext 设置为
MainWindowViewModel。MainWindowView.xamlItemsControl绑定到 ConnectionViewModels
的集合。我有一个包含 ComboBox 以及其他一些 TextBoxes 的 DataTemplate。文本框直接绑定到
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}" />

文本框都正确绑定,数据在它们和 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,

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

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


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

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

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


阅读 145

收藏
2022-07-01

共1个答案

小编典典

您将 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 仅指定所选和显示项目的成员。

2022-07-01