it-swarm.com.de

Binden einer WPF-ComboBox an eine benutzerdefinierte Liste

Ich habe eine ComboBox, die das SelectedItem/SelectedValue nicht zu aktualisieren scheint.

Die ComboBox ItemsSource ist an eine Eigenschaft einer ViewModel-Klasse gebunden, in der eine Reihe von RAS Telefonbucheinträgen als CollectionView aufgeführt sind. Dann habe ich (zu unterschiedlichen Zeiten) sowohl die SelectedItem als auch die SelectedValue an eine andere Eigenschaft des ViewModel gebunden. Ich habe dem Befehl save eine MessageBox hinzugefügt, um die durch die Datenbindung festgelegten Werte zu debuggen, aber die Bindung SelectedItem/SelectedValue wird nicht festgelegt.

Die ViewModel-Klasse sieht ungefähr so ​​aus:

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

Die _phonebookEntries-Auflistung wird im Konstruktor von einem Geschäftsobjekt aus initialisiert. Die ComboBox XAML sieht ungefähr so ​​aus:

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

Ich interessiere mich nur für den tatsächlichen String-Wert, der in der ComboBox angezeigt wird, nicht für andere Eigenschaften des Objekts, da dies der Wert ist, den ich an RAS weitergeben muss, wenn ich die VPN-Verbindung herstellen möchte, daher DisplayMemberPath und SelectedValuePath sind beide die Name-Eigenschaft des ConnectionViewModel. Die ComboBox befindet sich in einer DataTemplate, die auf eine ItemsControl in einem Fenster angewendet wird, dessen DataContext auf eine ViewModel-Instanz festgelegt wurde.

Die ComboBox zeigt die Liste der Elemente korrekt an, und ich kann problemlos eine in der Benutzeroberfläche auswählen. Wenn ich jedoch das Meldungsfeld aus dem Befehl anzeige, enthält die PhonebookEntry-Eigenschaft immer noch den Anfangswert und nicht den in der ComboBox ausgewählten Wert. Andere TextBox-Instanzen werden problemlos aktualisiert und in der MessageBox angezeigt.

Was fehlt mir bei der Datenbindung der ComboBox? Ich habe viel gesucht und kann anscheinend nichts finden, was ich falsch mache.


Dies ist das Verhalten, das ich sehe, aber es funktioniert aus irgendeinem Grund in meinem speziellen Kontext nicht.

Ich habe ein MainWindowViewModel, das eine CollectionView von ConnectionViewModels hat. In der Datei MainWindowView.xaml habe ich den DataContext auf das MainWindowViewModel gesetzt. In der Datei MainWindowView.xaml ist eine ItemsControl an die Auflistung von ConnectionViewModels gebunden. Ich habe ein DataTemplate, das die ComboBox sowie einige andere TextBoxen enthält. Die TextBoxen werden mit Text="{Binding Path=ConnectionName}" Direkt an die Eigenschaften des ConnectionViewModel gebunden.

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

Der XAML-Code-Behind:

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

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

Die TextBoxen werden alle korrekt gebunden, und die Daten werden problemlos zwischen ihnen und dem ViewModel verschoben. Es ist nur die ComboBox, die nicht funktioniert.

Sie sind in Ihrer Annahme in Bezug auf die Klasse PhonebookEntry korrekt.

Ich gehe davon aus, dass der von meinem DataTemplate verwendete DataContext automatisch über die Bindungshierarchie festgelegt wird, damit ich ihn nicht explizit für jedes Element in ItemsControl festlegen muss. Das kommt mir ein bisschen albern vor.


Hier ist eine Testimplementierung, die das Problem anhand des obigen Beispiels demonstriert.

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>

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

Wenn Sie dieses Beispiel ausführen, erhalten Sie das Verhalten, über das ich spreche. Die TextBox aktualisiert ihre Bindung, wenn Sie sie bearbeiten, die ComboBox jedoch nicht. Sehr verwirrend, da das einzige, was ich getan habe, ist, ein übergeordnetes ViewModel vorzustellen.

Ich arbeite derzeit unter dem Eindruck, dass ein Element, das an das untergeordnete Element eines DataContext gebunden ist, dieses untergeordnete Element als DataContext hat. Ich kann keine Dokumentation finden, die dies auf die eine oder andere Weise klärt.

Das heißt,

Fenster -> DataContext = MainWindowViewModel
.. Elemente -> An DataContext.PhonebookEntries gebunden
.... Item -> DataContext = PhonebookEntry (implizit zugeordnet)

Ich weiß nicht, ob das meine Vermutung besser erklärt (?).


Ändern Sie die Bindung der TextBox, um meine Annahme zu bestätigen

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

Und dies zeigt, dass der TextBox-Bindungsstamm (der mit dem DataContext verglichen wird) die ConnectionViewModel-Instanz ist.

173
Geoff Bennett

Sie setzen den DisplayMemberPath und den SelectedValuePath auf "Name", sodass ich davon ausgehe, dass Sie eine Klasse PhoneBookEntry mit einer öffentlichen Eigenschaft Name haben.

Haben Sie den DataContext auf Ihr ConnectionViewModel-Objekt festgelegt?

Ich habe Sie Code kopiert und einige kleinere Änderungen vorgenommen, und es scheint gut zu funktionieren. Ich kann die PhoneBookEnty-Eigenschaft für Ansichtsmodelle festlegen und das ausgewählte Element in der Combobox ändern. Ich kann das ausgewählte Element in der Combobox ändern und die PhoneBookEntry-Eigenschaft für Ansichtsmodelle ist korrekt festgelegt.

Hier ist mein XAML-Inhalt:

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

Und hier ist mein Code-Behind:

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 zweites Beispiel scheint nicht zu funktionieren, was mir etwas seltsam vorkommt. Wenn I ändern Sie die PhonebookEntries-Eigenschaft im ConnectionViewModel in ReadOnlyCollection, funktioniert die TwoWay-Bindung der SelectedValue-Eigenschaft im Kombinationsfeld einwandfrei.

Vielleicht liegt ein Problem mit der CollectionView vor? Ich habe eine Warnung in der Ausgabekonsole bemerkt:

System.Windows.Data Warnung: 50: Die direkte Verwendung von CollectionView wird nicht vollständig unterstützt. Die grundlegenden Funktionen funktionieren zwar mit einigen Ineffizienzen, aber erweiterte Funktionen können auf bekannte Fehler stoßen. Ziehen Sie die Verwendung einer abgeleiteten Klasse in Betracht, um diese Probleme zu vermeiden.

Edit2 (.NET 4.5): Der Inhalt der DropDownList kann auf ToString () und nicht auf DisplayMemberPath basieren, während DisplayMemberPath den Member nur für das ausgewählte und angezeigte Element angibt.

180
Kjetil Watnedal

So binden Sie die Daten an die 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 sieht folgendermaßen aus:

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

Ich hatte ein anfangs identisches Problem, das sich jedoch als Folge eines NHibernate/WPF-Kompatibilitätsproblems herausstellte. Das Problem wurde durch die Art und Weise verursacht, in der WPF die Objektgleichheit überprüft. Ich konnte meine Sachen zum Laufen bringen, indem ich die Objekt-ID-Eigenschaft in den SelectedValue- und SelectedValuePath-Eigenschaften verwendete.

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

Weitere Informationen finden Sie im Blogbeitrag aus Chester Die WPF-Kombinationsfeld - SelectedItem, SelectedValue und SelectedValuePath mit NHibernate.

22
CyberMonk

Ich hatte ein ähnliches Problem, bei dem das SelectedItem nie aktualisiert wurde.

Mein Problem war, dass das ausgewählte Element nicht dieselbe Instanz wie das in der Liste enthaltene Element war. Also musste ich einfach die Equals () -Methode in meinem MyCustomObject überschreiben und die IDs dieser beiden Instanzen vergleichen, um der ComboBox mitzuteilen, dass es sich um dasselbe Objekt handelt.

public override bool Equals(object obj)
{
    return this.Id == (obj as MyCustomObject).Id;
}
1
phifi