#c# #wpf #xaml #mvvm #combobox
#c# #wpf #xaml #mvvm #combobox
Вопрос:
Я новичок в WPF и MVVM, и я разрабатываю тестовое приложение WPF, следуя шаблону проектирования MVVM. В моей базе данных есть 2 объекта, карточки и отделы. У любой карты может быть только 1 отдел, так что это отношение «один ко многим».
Я создал следующую ViewModel для привязки к представлению:
public class CardViewModel : INotifyPropertyChanged
{
public CardViewModel(Card card)
{
this.Card = card;
SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = ".\SQLExpress";
builder.InitialCatalog = "TESTDB";
builder.IntegratedSecurity = true;
SybaseDatabaseContext myDB = new SybaseDatabaseContext(builder.ConnectionString);
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
private Card _Card;
private ObservableCollection<Department> _Departments;
public Card Card
{
get { return _Card; }
set
{
if (value != this._Card)
{
this._Card = value;
SendPropertyChanged("Card");
}
}
}
public ObservableCollection<Department> Departments
{
get { return _Departments; }
set
{
this._Departments = value;
SendPropertyChanged("Departments");
}
}
#region INPC
// Logic for INotify interfaces that nootify WPF when change happens
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void SendPropertyChanged(String propertyName)
{
if ((this.PropertyChanged != null))
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
DataContext для CardForms в настоящее время устанавливается в экземпляр CardViewModel в коде, в котором создается экземпляр CardForm, но я собираюсь создать контейнер IoC или инъекции зависимостей в дальнейшем.
Все привязывается правильно, за исключением выпадающего списка, который должен содержать все отделы и в котором выбран текущий отдел в экземпляре Card (card.Отдел). Вот XAML для ComboBox:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="350,64,0,0"
Name="comboBoxDepartment" VerticalAlignment="Top" Width="120"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Departments}"
DisplayMemberPath="DepartmentName"
SelectedItem="{Binding Path=Card.Department, Mode=TwoWay}" />
Отделы отображаются в выпадающем списке, но текущего отдела карты нет, и если я попытаюсь его изменить, я получаю сообщение об ошибке «Невозможно добавить объект с ключом, который уже используется».
Итак, мой вопрос в том, как мне правильно привязать этот combobox к моей ViewModel?
PS Я знаю, что заполнение ObservableCollection<Department>
в ViewModel, вероятно, неправильный способ сделать это, но в то время я не мог придумать лучшего способа. Если у вас есть какие-либо предложения и по этому поводу, пожалуйста, дайте мне знать.
Кроме того, это модель Card:
[Table(Name = "Card")]
public class Card : INotifyPropertyChanged, INotifyPropertyChanging
{
private string _CardID;
private string _Holder;
private Int16? _DepartmentNo;
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string CardID
{
get
{
return this._CardID;
}
set
{
if (value != this._CardID)
{
SendPropertyChanging();
this._CardID = value;
SendPropertyChanged("CardID");
}
}
}
[Column(UpdateCheck = UpdateCheck.WhenChanged)]
public string Holder
{
get
{
return this._Holder;
}
set
{
if (value != this._Holder)
{
SendPropertyChanging();
this._Holder = value;
SendPropertyChanged("Holder");
}
}
}
[Column(CanBeNull = true, UpdateCheck = UpdateCheck.WhenChanged)]
public Int16? DepartmentNo
{
get
{
return this._DepartmentNo;
}
set
{
if (value != this._DepartmentNo)
{
SendPropertyChanging();
this._DepartmentNo = value;
SendPropertyChanged("DepartmentNo");
}
}
}
private EntityRef<Department> department;
[Association(Storage = "department", ThisKey = "DepartmentNo", OtherKey = "DepartmentNo", IsForeignKey = true)]
public Department Department
{
get
{
return this.department.Entity;
}
set
{
Department previousValue = this.department.Entity;
if (((previousValue != value)
|| (this.department.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this.department.Entity = null;
previousValue.Cards.Remove(this);
}
this.department.Entity = value;
if ((value != null))
{
value.Cards.Add(this);
this._DepartmentNo = value.DepartmentNo;
}
else
{
this._DepartmentNo = default(Nullable<short>);
}
this.SendPropertyChanged("Department");
}
}
}
Комментарии:
1. вы можете вставить
Card
код?2. @kmatyaszek Конечно, но это просто свойства с геттерами и сеттерами. И он реализует INotifyPropertyChanged .
3. @Sheridan Я отредактировал вопрос, чтобы объяснить разницу, пожалуйста, снимите пометку, чтобы люди могли на него ответить.
4. Возможно, это потому, что я создаю 2x DataContexts. Один для моделей, а другой для извлечения всех отделов из базы данных. Я попытаюсь извлечь все отделы, которые должны быть в ComboBox, из одного и того же.
5. Да, так оно и было. Не может быть 2 разных экземпляра DataContext. Я удалил объявление одного из
ViewModel
них и просто передал его в качестве ссылки.
Ответ №1:
Я отредактировал конструктор в CardViewModel
, чтобы принять в DataContext
качестве параметра, и это сделало это. Это новый CardViewModel
конструктор:
public CardViewModel(Card card, SybaseDatabaseContext myDB)
{
this.Card = card;
var query = from d in myDB.Departments
select d;
this.Departments = new ObservableCollection<Department>(query);
}
Ответ №2:
Пришлось самому провести небольшое исследование по этому вопросу. Думал, что я внесу свой вклад в ответ на вопрос, но нашел этот открытый текущий вопрос…
Он ComboBox
разработан как своего рода текстовое поле, которое ограничивает его возможные значения содержимым данного списка. Список предоставляется ItemsSource
атрибутом. Текущее значение ComboBox
— это SelectedValue
свойство. Обычно эти атрибуты привязаны к соответствующим свойствам соответствующей ViewModel.
В следующем примере показан проводной ComboBox вместе с TextBox
элементом управления, используемым для избыточного просмотра текущего значения свойства ComboBox
путем совместного использования свойства view model . (Интересно отметить, что когда TextBox изменяет разделяемое свойство на значение, выходящее за рамки ComboBox
списка значений, ComboBox
ничего не отображается.)
Примечание: следующий пример WPF / C # использует скрытый код и поэтому представляет ViewModel просто как datacontext представления, а не partial class
как его часть, текущее ограничение реализации при использовании WPF с F #.
WPF XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<m:MainWindowVM />
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding SelectedString}" />
<ComboBox ItemsSource="{Binding MyList}" SelectedValue="{Binding SelectedString}" />
</StackPanel>
</Window>
C # ViewModel
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication1
{
public class MainWindowVM : INotifyPropertyChanged
{
string selectedString;
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged == null) return;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public string SelectedString
{
get { return selectedString; }
set
{
selectedString = value;
NotifyPropertyChanged("SelectedString");
}
}
public List<string> MyList
{
get { return new List<string> { "The", "Quick", "Brown", "Fox" }; }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
По умолчанию toString() используется для интерпретации объектов в списке. Однако ComboBox
предложения DisplayMemberPath
и SelectedValuePath
атрибуты для указания путей к определенным свойствам объекта для соответствующих отображаемых и сохраненных значений. Эти пути относятся к элементу объекта списка, поэтому путь «Name» относится к имени элемента объекта списка.
Раздел «Примечания» этой ссылки MSDN объясняет интерпретации свойств IsEditable
и IsReadOnly
ComboBox
.
Комментарии:
1.
List<T>
не очень дружелюбен к WPF. Он не будет отправлять уведомления в представление при добавлении или удалении элемента. Вы должны использоватьObservableCollection<T>
. Кроме того, это общий пример привязки combobox к списку строк вместо конкретного, где он привязан к коллекции объектов.2. Да, я знаю. Я удалю уведомление об изменении из самого списка. В своем поиске я нашел много информации о крайних случаях и хитростях, но очень мало об основах. Это сложный элемент управления буквально с пятью атрибутами, которые начинаются, например, с «Select», и дополнительными сложностями, связанными с настройками IsEditable и IsReadOnly.