Ich habe eine ComboBox, die das SelectedItem / SelectedValue nicht zu aktualisieren scheint.
Die ComboBox ItemsSource ist an eine Eigenschaft in 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
oder SelectedValue
eine andere Eigenschaft des ViewModel gebunden . Ich habe dem Befehl save eine MessageBox hinzugefügt, um die von der Datenbindung festgelegten Werte zu debuggen, aber die SelectedItem
/ SelectedValue
-Bindung 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 Zeichenfolgenwert, der in der ComboBox angezeigt wird, nicht für andere Eigenschaften des Objekts, da dies der Wert ist, den ich an RAS übergeben muss, wenn ich die VPN-Verbindung herstellen möchte, DisplayMemberPath
und daher SelectedValuePath
beide die Eigenschaft Name von das ConnectionViewModel. Die ComboBox wird in einem Fenster DataTemplate
angewendet, dessen DataContext ItemsControl
auf eine ViewModel-Instanz festgelegt wurde.
Die ComboBox zeigt die Liste der Elemente korrekt an, und ich kann problemlos eines in der Benutzeroberfläche auswählen. Wenn ich jedoch das Meldungsfeld über den Befehl anzeige, enthält die PhonebookEntry-Eigenschaft weiterhin den Anfangswert und nicht den in der ComboBox ausgewählten Wert. Andere TextBox-Instanzen werden ordnungsgemäß 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 mit einem CollectionView
ConnectionViewModel. In der Code-Behind-Datei MainWindowView.xaml habe ich den DataContext auf das MainWindowViewModel gesetzt. Die Datei MainWindowView.xaml ist ItemsControl
an die Sammlung von ConnectionViewModels gebunden. Ich habe eine DataTemplate, die die ComboBox sowie einige andere TextBoxen enthält. Die TextBoxen werden mit direkt an Eigenschaften des ConnectionViewModel gebunden 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; }
}
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.
Ihre Annahme bezüglich der PhonebookEntry-Klasse ist richtig.
Ich gehe davon aus, dass der von meiner DataTemplate verwendete DataContext automatisch über die Bindungshierarchie festgelegt wird, sodass ich ihn nicht für jedes Element in der explizit festlegen muss ItemsControl
. 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>
Der 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 in Ordnung, wenn Sie sie bearbeiten, die ComboBox jedoch nicht. Sehr verwirrend, da ich wirklich nur ein übergeordnetes ViewModel vorgestellt habe.
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.
Dh
Fenster -> DataContext = MainWindowViewModel
..Items -> An DataContext.PhonebookEntries gebunden
.... Item -> DataContext = PhonebookEntry (implizit zugeordnet)
Ich weiß nicht, ob das meine Annahme besser erklärt (?).
Um meine Annahme zu bestätigen, ändern Sie die Bindung der TextBox in
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
Dies zeigt, dass der TextBox-Bindungsstamm (den ich mit dem DataContext vergleiche) die ConnectionViewModel-Instanz ist.
<
T>
und im Property Getter mit _list.AsReadOnly () verfügbar, ähnlich wie Sie es erwähnt haben. Es funktioniert so, wie ich es mir von der ursprünglichen Methode erhofft hätte. Außerdem fiel mir ein, dass ich, während die ItemsSource-Bindung einwandfrei funktionierte, einfach die Current-Eigenschaft im ViewModel hätte verwenden können, um auf das ausgewählte Element in der ComboBox zuzugreifen. Es fühlt sich jedoch nicht so natürlich an wie das Binden der ComboBoxes SelectedValue / SelectedItem-Eigenschaft.