Eu tenho uma caixa de combinação que parece não atualizar o SelectedItem / SelectedValue.
O ComboBox ItemsSource está vinculado a uma propriedade em uma classe ViewModel que lista um monte de entradas da agenda telefônica do RAS como um CollectionView. Em seguida, vinculei (em momentos separados) a SelectedItem
ou SelectedValue
a outra propriedade do ViewModel. Eu adicionei uma MessageBox ao comando save para depurar os valores definidos pela ligação de dados, mas a ligação SelectedItem
/ SelectedValue
não está sendo definida.
A classe ViewModel se parece com isso:
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");
}
}
}
A coleção _phonebookEntries está sendo inicializada no construtor a partir de um objeto de negócios. O ComboBox XAML é mais ou menos assim:
<ComboBox ItemsSource="{Binding Path=PhonebookEntries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=PhonebookEntry}" />
Eu só estou interessado no valor de cadeia real exibido na caixa de combinação, e não quaisquer outras propriedades do objeto como este é o valor que eu preciso para passar através de RAS quando eu quero fazer a conexão VPN, portanto, DisplayMemberPath
e SelectedValuePath
estão ambos a propriedade Name o ConnectionViewModel. O ComboBox é DataTemplate
aplicado a um ItemsControl
em uma janela cujo DataContext foi definido como uma instância do ViewModel.
O ComboBox exibe a lista de itens corretamente e eu posso selecionar um na interface do usuário sem nenhum problema. No entanto, quando eu exibir a caixa de mensagem do comando, a propriedade PhonebookEntry ainda possui o valor inicial, não o valor selecionado na ComboBox. Outras instâncias do TextBox estão sendo atualizadas corretamente e exibidas na MessageBox.
O que estou perdendo com a ligação de dados da ComboBox? Pesquisei bastante e não consigo encontrar nada do que estou fazendo de errado.
Esse é o comportamento que estou vendo, no entanto, não está funcionando por algum motivo no meu contexto particular.
Eu tenho um MainWindowViewModel que tem um CollectionView
de ConnectionViewModels. No arquivo MainWindowView.xaml, code-behind, defino o DataContext como MainWindowViewModel. O MainWindowView.xaml tem um ItemsControl
limite para a coleção de ConnectionViewModels. Eu tenho um DataTemplate que contém o ComboBox, bem como alguns outros TextBoxes. Os TextBoxes são vinculados diretamente às propriedades do ConnectionViewModel usando 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; }
}
O código XAML por trás:
public partial class Window1
{
public Window1()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
Então 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}" />
Todos os TextBoxes se vinculam corretamente e os dados são movidos entre eles e o ViewModel sem problemas. É apenas a ComboBox que não está funcionando.
Você está correto em sua suposição sobre a classe PhonebookEntry.
A suposição que estou assumindo é que o DataContext usado pelo meu DataTemplate é definido automaticamente através da hierarquia de ligação, para que eu não precise defini-lo explicitamente para cada item no ItemsControl
. Isso me pareceria um pouco bobo.
Aqui está uma implementação de teste que demonstra o problema, com base no exemplo acima.
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>
O 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; }
}
}
}
Se você executar esse exemplo, obterá o comportamento de que estou falando. O TextBox atualiza bem sua ligação quando você o edita, mas o ComboBox não. Muito confuso, vendo que realmente a única coisa que fiz foi apresentar um ViewModel pai.
Atualmente, estou trabalhando com a impressão de que um item vinculado ao filho de um DataContext tem esse filho como seu DataContext. Não consigo encontrar nenhuma documentação que esclareça isso de uma maneira ou de outra.
Ou seja,
Janela -> DataContext = MainWindowViewModel
..Items -> Vinculado a DataContext.PhonebookEntries
.... Item -> DataContext = PhonebookEntry (implicitamente associado)
Não sei se isso explica melhor minha suposição (?).
Para confirmar minha suposição, altere a ligação do TextBox para
<TextBox Text="{Binding Mode=OneWay}" Width="50" />
E isso mostrará que a raiz de ligação do TextBox (que estou comparando com o DataContext) é a instância do ConnectionViewModel.
<
T>
e no getter de propriedades usando _list.AsReadOnly () semelhante à maneira como você mencionou. Está funcionando como eu esperava que o método original funcionasse. Além disso, me ocorreu que, enquanto a ligação ItemsSource estava funcionando bem, eu poderia ter usado a propriedade Current no ViewModel para acessar o item selecionado na ComboBox. Ainda assim, não parece tão natural quanto vincular a propriedade ComboBoxes SelectedValue / SelectedItem.