Liên kết một ComboBox WPF vào danh sách tùy chỉnh


183

Tôi có một ComboBox dường như không cập nhật mục Chọn / ChọnValue.

ComboBox ItemSource được liên kết với một thuộc tính trên lớp ViewModel liệt kê một loạt các mục nhập danh bạ RAS dưới dạng CollectionView. Sau đó, tôi đã ràng buộc (tại các thời điểm riêng biệt) cả SelectedItemhoặc SelectedValuevới một thuộc tính khác của ViewModel. Tôi đã thêm MessageBox vào lệnh lưu để gỡ lỗi các giá trị được đặt bởi cơ sở dữ liệu, nhưng liên kết SelectedItem/ SelectedValuekhông được đặt.

Lớp ViewModel trông giống như thế này:

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

Bộ sưu tập _phonebookEntries đang được khởi tạo trong hàm tạo từ một đối tượng kinh doanh. ComboBox XAML trông giống như thế này:

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

Tôi chỉ quan tâm đến giá trị chuỗi thực được hiển thị trong ComboBox, không phải bất kỳ thuộc tính nào khác của đối tượng vì đây là giá trị tôi cần chuyển qua RAS khi tôi muốn tạo kết nối VPN, do đó DisplayMemberPathSelectedValuePathcả hai đều là thuộc tính Tên của ConnectionViewModel. ComboBox được DataTemplateáp dụng cho ItemsControlmột Cửa sổ mà DataContext đã được đặt thành phiên bản ViewModel.

ComboBox hiển thị danh sách các mục chính xác và tôi có thể chọn một mục trong giao diện người dùng mà không gặp vấn đề gì. Tuy nhiên, khi tôi hiển thị hộp thông báo từ lệnh, thuộc tính PhonebookEntry vẫn có giá trị ban đầu, không phải giá trị được chọn từ ComboBox. Các phiên bản TextBox khác đang cập nhật tốt và hiển thị trong MessageBox.

Tôi còn thiếu gì với databinding ComboBox? Tôi đã thực hiện rất nhiều tìm kiếm và dường như không thể tìm thấy bất cứ điều gì mà tôi đang làm sai.


Đây là hành vi tôi đang thấy, tuy nhiên nó không hoạt động vì một số lý do trong bối cảnh cụ thể của tôi.

Tôi có MainWindowViewModel có CollectionViewConnectionViewModels. Trong mã tệp MainWindowView.xaml phía sau, tôi đặt DataContext thành MainWindowViewModel. MainWindowView.xaml có ItemsControlràng buộc với bộ sưu tập ConnectionViewModels. Tôi có một DataTemplate chứa ComboBox cũng như một số TextBox khác. Các TextBox được liên kết trực tiếp với các thuộc tính của ConnectionViewModel bằng cách sử dụng 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; }
}

Mã XAML phía sau:

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

Sau đó, 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}" />

Tất cả các TextBox đều liên kết chính xác và dữ liệu di chuyển giữa chúng và ViewModel mà không gặp sự cố. Đó chỉ là ComboBox không hoạt động.

Bạn đúng trong giả định của mình về lớp PhonebookEntry.

Giả định mà tôi đưa ra là DataContext được sử dụng bởi DataTemplate của tôi được đặt tự động thông qua hệ thống phân cấp ràng buộc, do đó tôi không phải đặt nó rõ ràng cho từng mục trong ItemsControl. Điều đó có vẻ hơi ngớ ngẩn với tôi.


Dưới đây là một triển khai thử nghiệm thể hiện vấn đề, dựa trên ví dụ trên.

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>

Các mã sau :

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

Nếu bạn chạy ví dụ đó, bạn sẽ có hành vi mà tôi đang nói đến. TextBox cập nhật ràng buộc của nó khi bạn chỉnh sửa nó, nhưng ComboBox thì không. Rất khó hiểu khi thực sự là điều duy nhất tôi đã làm là giới thiệu một phụ huynh ViewModel.

Tôi hiện đang chuyển dạ theo ấn tượng rằng một mục được liên kết với con của DataContext có con đó là DataContext của nó. Tôi không thể tìm thấy bất kỳ tài liệu nào xóa cái này bằng cách này hay cách khác.

I E,

Window -> DataContext = MainWindowViewModel
..Items -> Bound to DataContext.PhonebookEntries
.... Item -> DataContext = PhonebookEntry (liên kết ngầm)

Tôi không biết nếu điều đó giải thích giả định của tôi tốt hơn (?).


Để xác nhận giả định của tôi, hãy thay đổi ràng buộc của TextBox thành

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

Và điều này sẽ hiển thị gốc liên kết TextBox (mà tôi đang so sánh với DataContext) là phiên bản ConnectionViewModel.

Câu trả lời:


189

Bạn đặt DisplayMemberPath và chọnValuePath thành "Tên", vì vậy tôi giả sử rằng bạn có một PhoneBookEntry lớp với Tên thuộc tính công khai.

Bạn đã thiết lập DataContext cho đối tượng ConnectionViewModel chưa?

Tôi đã sao chép mã của bạn và thực hiện một số sửa đổi nhỏ, và nó có vẻ hoạt động tốt. Tôi có thể đặt thuộc tính viewmodels PhoneBookEnty và mục đã chọn trong hộp tổ hợp thay đổi và tôi có thể thay đổi mục đã chọn trong hộp tổ hợp và thuộc tính kiểu xem PhoneBookEntry được đặt chính xác.

Đây là nội dung XAML của tôi:

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

Và đây là mã phía sau của tôi:

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

Chỉnh sửa: Ví dụ thứ hai của Geoff dường như không hoạt động, có vẻ hơi kỳ lạ đối với tôi. Nếu tôi thay đổi thuộc tính PhonebookEntries trên ConnectionViewModel thành loại ReadOnlyCollection , ràng buộc TwoWay của thuộc tính chọnValue trên combobox hoạt động tốt.

Có lẽ có vấn đề với CollectionView? Tôi nhận thấy một cảnh báo trong bảng điều khiển đầu ra:

System.Windows.Data Cảnh báo: 50: Sử dụng CollectionView trực tiếp không được hỗ trợ đầy đủ. Các tính năng cơ bản hoạt động, mặc dù có một số thiếu hiệu quả, nhưng các tính năng nâng cao có thể gặp phải các lỗi đã biết. Cân nhắc sử dụng một lớp dẫn xuất để tránh những vấn đề này.

Edit2 (.NET 4.5): Nội dung của DropDownList có thể dựa trên ToString () chứ không phải của DisplayMemberPath, trong khi DisplayMemberPath chỉ định thành viên cho mục được chọn và hiển thị.


1
Tôi cũng nhận thấy tin nhắn đó, nhưng tôi cho rằng những gì được đề cập sẽ là ràng buộc dữ liệu cơ bản. Tôi đoán là không. :) Bây giờ tôi đang hiển thị các thuộc tính là IList <T >và trong getter thuộc tính bằng cách sử dụng _list.AsReadOnly () tương tự như cách bạn đã đề cập. Nó hoạt động như tôi đã hy vọng phương pháp ban đầu sẽ có. Ngoài ra, tôi nghĩ rằng trong khi ràng buộc ItemSource hoạt động tốt, tôi có thể chỉ sử dụng thuộc tính Hiện tại trong ViewModel để truy cập vào mục đã chọn trong ComboBox. Tuy nhiên, nó không có cảm giác tự nhiên như ràng buộc thuộc tính ComboBoxes ChọnValue / chọnItem.
Geoff Bennett

3
Tôi có thể xác nhận rằng việc thay đổi bộ sưu tập, mà thuộc ItemsSourcetính bị ràng buộc, thành bộ sưu tập chỉ đọc làm cho nó hoạt động. Trong trường hợp của tôi, tôi đã phải thay đổi nó từ ObservableCollectionđến ReadOnlyObservableCollection. Quả hạch. Đây là .NET 3.5 - không chắc nó có được sửa trong 4.0 hay không
ChrisWue

74

Để liên kết dữ liệu với 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 trông giống như:

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

Giải pháp này không hiệu quả với tôi. ItemSource hoạt động tốt, nhưng các tiên đề Đường dẫn không chuyển hướng chính xác đến các giá trị ComboData.
Coneone

3
IdValuephải là thuộc tính , không phải trường lớp, như:public class ComboData { public int Id { get; set; } public string Value { get; set; } }
Edgar

23

Tôi đã có những gì ban đầu dường như là một vấn đề giống hệt nhau, nhưng hóa ra là do vấn đề tương thích NHibernate / WPF. Vấn đề được gây ra bởi cách WPF kiểm tra sự bình đẳng của đối tượng. Tôi đã có thể làm cho công cụ của mình hoạt động bằng cách sử dụng thuộc tính ID đối tượng trong các thuộc tính của ChọnValue và chọnValuePath.

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

Xem bài đăng trên blog từ Chester, ComboBox của WPF - chọnItem, chọnValue và chọnValuePath với NHibernate , để biết chi tiết.


1

Tôi đã có một vấn đề tương tự trong đó mục chọn không bao giờ được cập nhật.

Vấn đề của tôi là mục được chọn không giống với mục có trong danh sách. Vì vậy, tôi chỉ đơn giản là phải ghi đè phương thức Equals () trong MyCustomObject của tôi và so sánh ID của hai trường hợp đó để nói với ComboBox rằng đó là cùng một đối tượng.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.