WPF ComboBox öğesini özel bir listeye bağlama


183

SelectedItem / SelectedValue güncelleştirmek görünmüyor bir ComboBox var.

ComboBox ItemsSource, ViewModel sınıfında bir grup RAS telefon defteri girdisini CollectionView olarak listeleyen bir özelliğe bağlıdır. Sonra (ayrı zamanlarda) hem SelectedItemveya SelectedValueViewModel başka bir özelliğine bağlı ettik . Veritabanının ayarladığı değerlerde hata ayıklamak için save komutuna bir MessageBox ekledim, ancak SelectedItem/ SelectedValuebağlama ayarlanmadı.

ViewModel sınıfı şuna benzer:

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 koleksiyonu yapıcıda bir iş nesnesinden başlatılıyor. ComboBox XAML şuna benzer:

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

Ben sadece ComboBox görüntülenen gerçek dize değeri, bu VPN bağlantısı yapmak istediğinizde RAS geçmesi gereken değer DisplayMemberPathve bu nedenle SelectedValuePathhem Name özelliği olduğu gibi nesnenin diğer özellikleri ilgileniyorum ConnectionViewModel. ComboBox, DataContext öğesinin ViewModel örneğine ayarlanmış olan bir Pencere üzerindeki bir DataTemplateuygulamasındadır ItemsControl.

ComboBox öğelerin listesini doğru görüntüler ve kullanıcı arayüzünde sorunsuz bir tane seçebilirim. Ancak komuttan ileti kutusunu görüntülediğimde, PhonebookEntry özelliğinin hala ilk değeri var, ComboBox öğesinden seçilen değer değil. Diğer TextBox örnekleri iyi güncelleniyor ve MessageBox içinde görüntüleniyor.

ComboBox veritabanında eksik olan ne? Çok fazla arama yaptım ve yanlış yaptığım hiçbir şey bulamıyorum.


Gördüğüm davranış bu, ancak benim belirli bağlamımda bir sebepten dolayı çalışmıyor.

CollectionViewConnectionViewModels olan bir MainWindowViewModel var . Arkadaki MainWindowView.xaml dosyasında, DataContext'i MainWindowViewModel olarak ayarladım. MainWindowView.xaml, ItemsControlConnectionViewModels koleksiyonuna bağlıdır. ComboBox yanı sıra diğer bazı TextBox tutan bir DataTemplate var. TextBox'lar doğrudan kullanarak ConnectionViewModel özelliklerine bağlıdır 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; }
}

Arkasındaki XAML kodu:

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

Sonra 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}" />

TextBox'ların tümü doğru bir şekilde bağlanır ve veriler ve ViewModel arasında sorunsuz bir şekilde hareket eder. Sadece ComboBox çalışmıyor.

PhonebookEntry sınıfı ile ilgili varsayımlarınız doğrudur.

Yaptığım varsayım, benim DataTemplate tarafından kullanılan DataContext otomatik olarak bağlama hiyerarşisi ile ayarlanır, böylece açıkça her öğe için ayarlamak zorunda değilsiniz ItemsControl. Bu bana biraz aptalca geliyor.


Yukarıdaki örneği temel alarak sorunu gösteren bir test uygulaması.

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>

Arka plan kod :

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

Bu örneği çalıştırırsanız, bahsettiğim davranışı elde edersiniz. TextBox, ciltleme düzenini düzenlediğinizde güncelleştirir ancak ComboBox değiştirmez. Gerçekten yaptığım tek şey olarak görmek çok kafa karıştırıcı bir üst ViewModel tanıtmaktır.

Şu anda bir DataContext alt öğeye bağlı bir öğenin DataContext olarak bu çocuğa sahip olduğu izlenimi altında çalışıyorum. Bunu şu ya da bu şekilde temizleyen herhangi bir belge bulamıyorum.

yani,

Pencere -> DataContext = MainWindowViewModel
..Items -> DataContext.PhonebookEntries'e Bağlanın
.... Öğe -> DataContext = PhonebookEntry (örtük olarak ilişkili)

Bu varsayımı daha iyi açıklıyor mu bilmiyorum (?).


Varsayımı onaylamak için, TextBox'ın bağlayıcılığını

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

Ve bu TextBox bağlama kökü gösterecektir (ki ben DataContext ile karşılaştırıyorum) ConnectionViewModel örneğidir.

Yanıtlar:


189

DisplayMemberPath ve SelectedValuePath "Name" olarak ayarlayın, bu nedenle ortak özellik Name ile bir sınıf PhoneBookEntry olduğunu varsayalım.

DataContext'i ConnectionViewModel nesnenize ayarladınız mı?

Kodunuzu kopyaladım ve bazı küçük değişiklikler yaptım ve iyi çalışıyor gibi görünüyor. Viewmodels PhoneBookEnty özelliğini ve açılan kutudaki seçili öğeyi değiştirebilirim ve açılan kutudaki seçili öğeyi değiştirebilirim ve PhoneBookEntry özellikli görünüm modelleri doğru ayarlanmış.

İşte benim XAML içeriğim:

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

Ve işte arkamdaki kod:

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 ikinci örnek bana biraz garip gibi görünüyor, iş gibi görünmüyor. Ben ise tip ReadOnlyCollection olması ConnectionViewModel üzerinde PhonebookEntries özelliğini değiştirmek , combobox SelectedValue özelliğinin bağlayıcı TwoWay cezası çalışır.

Belki CollectionView ile ilgili bir sorun var mı? Çıkış konsolunda bir uyarı fark ettim:

System.Windows.Data Uyarı: 50: CollectionView uygulamasını doğrudan kullanmak tam olarak desteklenmemektedir. Temel özellikler, bazı verimsizliklere rağmen çalışır, ancak gelişmiş özellikler bilinen hatalarla karşılaşabilir. Bu sorunlardan kaçınmak için türetilmiş bir sınıf kullanmayı düşünün.

Edit2 (.NET 4.5): DropDownList içeriği, DisplayMemberPath yerine ToString () yöntemini temel alırken, DisplayMemberPath yalnızca seçilen ve görüntülenen öğenin üyesini belirtir.


1
Ben mesajın bunu da fark ettiniz ama ne farz edildi temel veri bağlama olurdu kaplı. Sanırım hayır. :) Şimdi IList <T >ve _list.AsReadOnly () kullanarak özellik getter özellikleri belirttiğiniz şekilde benzer maruz bırakıyorum. Orijinal yöntemin olmasını umduğum gibi çalışıyor. Ayrıca, ItemsSource bağlama iyi çalışırken, ben sadece ComboBox seçili öğeye erişmek için ViewModel Current özelliğini kullanabileceğini aklımdan geçti. Yine de, ComboBoxes SelectedValue / SelectedItem özelliğini bağlamak kadar doğal gelmiyor.
Geoff Bennett

3
ItemsSourceÖzelliğin bağlı olduğu koleksiyonu değiştirmenin salt okunur bir koleksiyona değiştirilmesinin işe yaradığını onaylayabilirim . Benim durumumda ben onu değiştirmek zorunda ObservableCollectioniçin ReadOnlyObservableCollection. Fındık. Bu .NET 3.5
4.0'da

74

Verileri ComboBox'a bağlamak için

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 şöyle görünür:

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

Bu çözüm benim için işe yaramıyor. ItemsSource gayet iyi çalışıyor, ancak Path özellikleri ComboData değerlerine doğru bir şekilde yönlendirmiyor.
Coneone

3
Idve sınıf alanı değil, özelliklerValue olmalıdır :public class ComboData { public int Id { get; set; } public string Value { get; set; } }
Edgar

23

İlk başta aynı bir sorun gibi görünüyordu, ama bir NHibernate / WPF uyumluluk sorunu nedeniyle ortaya çıktı. Sorun WPF'nin nesne eşitliğini kontrol etme yönteminden kaynaklandı. SelectedValue ve SelectedValuePath özelliklerinde object ID özelliğini kullanarak işlerimi alabildim.

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

Ayrıntılar için Chester, The WPF ComboBox - SelectedItem, SelectedValue ve NHibernate ile SelectedValuePath blog yayınına bakın.


1

SelectedItem hiç güncellenmedi benzer bir sorun vardı.

Sorunum, seçilen öğenin listedeki öğeyle aynı örnek olmamasıydı. Bu yüzden sadece MyCustomObject içinde Equals () yöntemini geçersiz kılmak ve ComboBox'a aynı nesne olduğunu söylemek için bu iki örneğin kimliğini karşılaştırmak zorunda kaldım.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.