Mam ComboBox, który nie wydaje się aktualizować SelectedItem / SelectedValue.
ComboBox ItemsSource jest powiązany z właściwością klasy ViewModel, która zawiera listę wpisów w książce telefonicznej RAS jako CollectionView. Następnie związałem (w oddzielnych momentach) obie właściwości SelectedItem
lub SelectedValue
inną właściwość ViewModel. Dodałem MessageBox do komendy save, aby debugować wartości ustawione przez wiązanie danych , ale nie ustawiono wiązania SelectedItem
/ SelectedValue
.
Klasa ViewModel 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 inicjowana 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ść ciągu wyświetlana w ComboBox, 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ą zarówno właściwością Name ConnectionViewModel. ComboBox jest DataTemplate
stosowany ItemsControl
w oknie, dla którego DataContext ustawiono na instancję ViewModel.
ComboBox wyświetla listę elementów poprawnie i bez problemu mogę wybrać jeden w interfejsie użytkownika. Jednak gdy wyświetlam okno komunikatu z polecenia, właściwość PhonebookEntry nadal ma w sobie wartość początkową, a nie wybraną wartość z ComboBox. Inne instancje TextBox aktualizują się dobrze i wyświetlają się w MessageBox.
Czego mi brakuje w wiązaniu danych w ComboBox? Przeprowadziłem wiele poszukiwań i nie mogę znaleźć niczego, co robię źle.
Takie zachowanie widzę, jednak z jakiegoś powodu nie działa w moim szczególnym kontekście.
Mam MainWindowViewModel, który ma CollectionView
ConnectionViewModels. W pliku MainWindowView.xaml z tyłu kodu ustawiłem DataContext na MainWindowViewModel. MainWindowView.xaml ma ItemsControl
powiązanie z kolekcją ConnectionViewModels. Mam DataTemplate, który przechowuje ComboBox, a także niektóre inne TextBoxy. TextBox 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 TextBoxy wiążą się poprawnie, a dane bez problemu przemieszczają się między nimi a ViewModel. Tylko ComboBox nie działa.
Masz rację w swoich założeniach dotyczących klasy PhonebookEntry.
Zakładam, że DataContext używany przez mój DataTemplate jest automatycznie ustawiany przez hierarchię wiązania, więc nie muszę jawnie ustawiać go dla każdego elementu w ItemsControl
. Wydałoby 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>
Kod z opóźnieniem :
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; }
}
}
}
Jeśli uruchomisz ten przykład, uzyskasz zachowanie, o którym mówię. TextBox aktualizuje swoje wiązanie w porządku podczas edycji, ale ComboBox nie. Bardzo mylące, ponieważ naprawdę jedyne, co zrobiłem, to wprowadzenie nadrzędnego ViewModel.
Obecnie pracuję pod wrażeniem, że element związany z dzieckiem DataContext ma to dziecko jako DataContext. Nie mogę znaleźć żadnej dokumentacji, która wyjaśnia to w ten czy inny sposób.
To znaczy,
Window -> DataContext = MainWindowViewModel
..Items -> Bound to DataContext.PhonebookEntries
.... Pozycja -> DataContext = PhonebookEntry (domyślnie powiązany)
Nie wiem, czy to lepiej tłumaczy moje założenie (?).
Aby potwierdzić moje założenie, zmień powiązanie TextBox na
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
Pokaże to korzeń wiązania TextBox (który porównuję z DataContext) to instancja ConnectionViewModel.
<
T.>
ujawniam oraz w narzędziu do pobierania właściwości za pomocą _list.AsReadOnly () podobnie jak wspomniano. Działa tak, jak chciałbym mieć oryginalną metodę. Przyszło mi też do głowy, że gdy powiązanie ItemsSource działa dobrze, mogłem po prostu użyć właściwości Current w ViewModel, aby uzyskać dostęp do wybranego elementu w ComboBox. Mimo to nie wydaje się tak naturalny, jak wiązanie właściwości ComboBoxes SelectedValue / SelectedItem.