Łączenie Comboboxu WPF z listą niestandardową

Mam ComboBox, który nie wydaje się aktualizować SelectedItem / SelectedValue.

ComboBox ItemsSource jest powiązany z właściwością klasy ViewModel, która wymienia kilka wpisów książki telefonicznej RAS jako CollectionView. Następnie przywiązałem (w różnych momentach) zarówno SelectedItem, jak i SelectedValue do innej właściwości ViewModel. Dodałem MessageBox do polecenia save, aby debugować wartości ustawione przez databinding, ale SelectedItem/SelectedValue Wiązanie nie jest ustawiane.

The ViewModel Klasa wygląda mniej więcej tak:

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

Kolekcja _phonebookEntries jest inicjalizowana w konstruktorze z obiektu biznesowego. ComboBox XAML wygląda mniej więcej tak:

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

Interesuje mnie tylko rzeczywista wartość łańcucha wyświetlana w Comboboxie, a nie inne właściwości obiektu, ponieważ jest to wartość, którą muszę przekazać do RAS, gdy chcę nawiązać połączenie VPN, stąd DisplayMemberPath i SelectedValuePath są właściwościami Name dla ConnectionViewModel. ComboBox jest w DataTemplate zastosowany do ItemsControl W oknie, którego DataContext został ustawiony na instancję ViewModel.

ComboBox wyświetla listę elementów poprawnie, i mogę wybrać jeden w interfejsie bez problemu. Jednak gdy wyświetlam okno wiadomości z polecenia, właściwość PhonebookEntry nadal ma wartość początkową, a nie wybraną wartość z Comboboxu. Inne instancje TextBox są aktualizowane i wyświetlane w MessageBox.

Czego mi brakuje z databinding ComboBox? Dużo szukałem i nie mogę znaleźć niczego, co robię źle.


To jest zachowanie, które widzę, jednak z jakiegoś powodu nie działa w moim konkretnym kontekście.

Mam MainWindowViewModel, który ma CollectionView z ConnectionViewModels. W widoku głównego okna.Kod pliku xaml-za, ustawiłem DataContext na MainWindowViewModel. Widok Głównego Okna.xaml ma ItemsControl związane ze zbiorem ConnectionViewModels. Mam DataTemplate, który zawiera ComboBox, a także kilka innych pól tekstowych. Pola tekstowe są powiązane bezpośrednio z właściwościami ConnectionViewModel za pomocą 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; }
}

Kod XAML-za:

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

Następnie 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}" />

Wszystkie Pola tekstowe wiążą się poprawnie, a dane bez problemu przemieszczają się między nimi a modelem widoku. Tylko ComboBox nie działa.

Masz rację w swoim założeniu dotyczącym Zajęcia z telefonowania.

Zakładam, że DataContext używany przez mój DataTemplate jest automatycznie ustawiany przez hierarchię wiązań, więc nie muszę go jawnie ustawiać dla każdego elementu w ItemsControl. Wydaje mi się to trochę głupie.


Oto implementacja testowa, która demonstruje problem, na podstawie powyższego przykładu.

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>

The code-behind :

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

If you run that przykład, otrzymasz zachowanie, o którym mówię. Pole tekstowe aktualizuje swoje Wiązanie dobrze podczas edycji go, ale ComboBox nie. Bardzo mylące widząc, jak naprawdę jedyną rzeczą, jaką zrobiłem, jest wprowadzenie rodzica ViewModel.

Obecnie pracuję pod wrażeniem, że element związany z potomkiem DataContext ma to dziecko jako DataContext. Nie mogę znaleźć żadnej dokumentacji, która to wyjaśni.

Tzn.,

Window - > DataContext = MainWindowViewModel
..Items - > powiązane z DataContext.PhonebookEntries
....Item - > DataContext = PhonebookEntry (implicite associated)

Nie wiem, czy to lepiej wyjaśnia moje założenie(?).


Aby potwierdzić moje założenie, Zmień Wiązanie pola tekstowego na

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

I to pokaże, że głównym źródłem wiążącym pole tekstowe (które porównuję z DataContext) jest instancja ConnectionViewModel.

Author: Peter Mortensen, 2009-02-18

4 answers

Ustawiłeś DisplayMemberPath i SelectedValuePath na "Name", więc zakładam, że masz klasę PhoneBookEntry z nazwą własności publicznej.

Czy Ustawiłeś DataContext do obiektu ConnectionViewModel?

Skopiowałem Ci kod i wprowadziłem kilka drobnych modyfikacji, i wydaje się, że działa dobrze. Mogę ustawić właściwość ViewModels PhoneBookEnty I zmienić wybrany element w comboboxie, a także zmienić wybrany element w comboboxie i modele widoku Właściwość PhoneBookEntry jest ustawiona poprawnie.

Oto moja zawartość 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>

A oto mój kod-za:

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

Edit: Geoffs drugi przykład nie działa, co wydaje mi się nieco dziwne. Jeśli i zmieni właściwość PhonebookEntries na ConnectionViewModel na typ ReadOnlyCollection , Wiązanie TwoWay właściwości SelectedValue na comboboxie działa poprawnie.

Może jest problem z CollectionView? Zauważyłem ostrzeżenie w konsoli Wyjściowej:

System.Okna.Ostrzeżenie o DANYCH: 50: bezpośrednie korzystanie z CollectionView nie jest w pełni obsługiwane. Podstawowe funkcje działają, choć z pewnymi nieefektywnościami, ale zaawansowane funkcje mogą napotkać znane błędy. Rozważ użycie klasy pochodnej, aby uniknąć tych problemów.

Edit2 (. NET 4.5): Zawartość DropDownList może być oparta na ToString (), a nie na DisplayMemberPath, podczas gdy DisplayMemberPath określa element dla tylko wybrany i wyświetlony element.

 168
Author: Kjetil Watnedal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 13:25:26

Aby powiązać dane z Comboboxem

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 wygląda tak:

public class ComboData
{ 
  public int Id { get; set; } 
  public string Value { get; set; } 
}
 65
Author: Roy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 07:24:35

Miałem coś, co początkowo wydawało się identycznym problemem, ale okazało się, że jest to spowodowane problemem zgodności NHibernate/WPF. Problem był spowodowany sposobem, w jaki WPF sprawdza równość obiektów. Udało mi się uruchomić moje rzeczy za pomocą właściwości object ID we właściwościach SelectedValue i SelectedValuePath.

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

Zobacz wpis na blogu Chester, WPF ComboBox-SelectedItem, SelectedValue i SelectedValuePath za pomocą NHibernate, po szczegóły.

 22
Author: CyberMonk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 13:32:20

Miałem podobny problem, w którym SelectedItem nigdy nie został zaktualizowany.

Mój problem polegał na tym, że wybrany element nie był tą samą instancją co element zawarty na liście. Musiałem więc po prostu nadpisać metodę Equals () w moim MyCustomObject i porównać ID tych dwóch instancji, aby powiedzieć Comboboxowi, że to ten sam obiekt.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
 1
Author: phifi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-19 13:34:40