Прив’язування WPF ComboBox до спеціального списку


183

У мене є ComboBox, який, схоже, не оновлює SelectedItem / SelectedValue.

ComboBox ItemsSource пов'язаний із властивістю класу ViewModel, у якому перелічено купу записів телефонної книги RAS як CollectionView. Тоді я зв'язав (в окремий час) і те, SelectedItemабо SelectedValueінше властивість 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, а не будь-які інші властивості об'єкта, оскільки це значення, яке мені потрібно передати RAS, коли я хочу встановити VPN-з'єднання, отже, DisplayMemberPathі SelectedValuePathвони є властивістю Name ConnectionViewModel. ComboBox DataTemplateзастосовується до ItemsControlвікна, який DataContext був встановлений для екземпляра ViewModel.

ComboBox відображає список елементів правильно, і я можу вибрати його в інтерфейсі без проблем. Однак, коли я показую вікно повідомлення з команди, властивість PhonebookEntry все ще має в ньому початкове значення, а не вибране значення з ComboBox. Інші екземпляри TextBox оновлюються штрафом і відображаються в MessageBox.

Що мені не вистачає при зв’язуванні даних ComboBox? Я багато шукав і, здається, не знаходжу нічого, що я роблю неправильно.


Таку поведінку я бачу, однак вона не працює чомусь у моєму конкретному контексті.

У мене є MainWindowViewModel, який має CollectionViewConnectionViewModels. У коді за файлом MainWindowView.xaml я встановив DataContext на MainWindowViewModel. MainWindowView.xaml ItemsControlпов'язаний з колекцією ConnectionViewModels. У мене є DataTemplate, який містить ComboBox, а також деякі інші TextBoxes. TextBoxes прив'язані безпосередньо до властивостей 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}" />

Усі TextBoxes прив’язуються правильно, і дані переміщуються між ними та ViewModel без проблем. Тільки ComboBox не працює.

Ви правильні у своїх припущеннях щодо класу PhonebookEntry.

Я припускаю, що DataContext, який використовується моїм DataTemplate, автоматично встановлюється через ієрархію прив'язки, тому мені не потрібно явно встановлювати його для кожного елемента в 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. Я не можу знайти жодної документації, яка так чи інакше очищає це питання.

Тобто,

Вікно -> DataContext =
MainWindowViewModel ..Items -> Зв'язане до DataContext.PhonebookEntries
.... Елемент -> DataContext = Телефонна книга (Включено неявно)

Я не знаю, чи це пояснює моє припущення краще (?).


Щоб підтвердити моє припущення, змініть прив'язку TextBox до

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

І це покаже корінь прив'язки TextBox (який я порівнюю з DataContext) - це екземпляр ConnectionViewModel.

Відповіді:


189

Ви встановлюєте DisplayMemberPath та SelectedValuePath на "Ім'я", тож я вважаю, що у вас є клас PhoneBookEntry з загальнодоступною назвою.

Ви встановили DataContext для вашого об'єкта ConnectionViewModel?

Я скопіював вас код і вніс незначні зміни, і він, здається, працює добре. Я можу встановити властивості перегляду моделей 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;
    }
}

Редагувати: Другий приклад Джеффса, здається, не працює, що здається мені трохи дивним. Якщо я зміню властивість PhonebookEntries на ConnectionViewModel типу типу ReadOnlyCollection , двостороння прив'язка властивості SelectedValue у комбобоксі справно працює.

Можливо, є проблема з CollectionView? Я помітив попередження на вихідній консолі:

System.Windows.Data Увага: 50: Використання CollectionView безпосередньо не підтримується повністю. Основні функції працюють, хоча з деякою неефективністю, але розширені функції можуть зустріти відомі помилки. Подумайте про використання похідного класу, щоб уникнути цих проблем.

Edit2 (.NET 4.5): Вміст DropDownList може базуватися на ToString (), а не на DisplayMemberPath, тоді як DisplayMemberPath визначає члена лише для вибраного та відображуваного елемента.


1
Я помітив , що повідомлення , як добре, але я припустив , що було покрито вже було б обов'язковим основні дані. Я думаю, що не. :) Зараз я розкриваю властивості як IList <T >та в Getter властивості за допомогою _list.AsReadOnly (), аналогічно тому, як ви згадали. Це працює так, як я б сподівався, що оригінальний метод матиме. Крім того, мені зрозуміло, що в той час, як прив'язка ItemSource працює нормально, я могла просто використати властивість Поточний у ViewModel для доступу до вибраного елемента в ComboBox. Однак це не так природно, як зв'язування властивості ComboBoxes SelectedValue / SelectedItem.
Джефф Беннетт

3
Я можу підтвердити, що зміна колекції, до якої ItemsSourceприв’язана властивість, до колекції лише для читання змушує її працювати. У моєму випадку мені довелося змінити це ObservableCollectionна ReadOnlyObservableCollection. Горіхи. Це .NET 3.5 - не впевнений, чи встановлено це в 4.0
ChrisWue

74

Для прив’язки даних до 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; } 
}

Це рішення не працює для мене. ItemSource працює добре, але властивості Path неправильно перенаправляють на значення ComboData.
Coneone

3
Idі Valueповинні бути властивостями , а не класовим полем, як-от:public class ComboData { public int Id { get; set; } public string Value { get; set; } }
Едгар

23

У мене було те, що спочатку здавалося ідентичною проблемою, але це виявилося через проблеми сумісності NHibernate / WPF. Проблема була викликана тим, як WPF перевіряє рівність об'єктів. Мені вдалося змусити свої роботи працювати, використовуючи властивість ідентифікатора об'єкта у властивостях SelectedValue та SelectedValuePath.

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

Детальні відомості див. У блозі від Chester, WPF ComboBox - SelectedItem, SelectedValue та SelectedValuePath з NHibernate .


1

У мене була подібна проблема, коли SelectedItem ніколи не оновлювався.

Моя проблема полягала в тому, що вибраний елемент не був тим самим екземпляром, як елемент, що міститься у списку. Тому мені просто довелося перекрити метод Equals () у своєму MyCustomObject і порівняти ідентифікатори цих двох примірників, щоб сказати ComboBox, що це той самий об’єкт.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.