В моем проекте имеется достаточно большое количество объектов данных по различной аппаратуре, с которыми нужно обеспечить работу. Данные мои хранятся в иерархической структуре: родительский класс содержит общую для всех типов аппаратуры информацию, а наследники добавляют свою специфику.
Вот такая вот картинка... получилась у меня, после полутора дней шаманства и чтения форумов. Фотография сделана до чашки чая и зачистки решения от излишества в интерфейсах.
Да, оно работает. Но если вернуться к картинке, то, между нами, все это не очень красиво. Почему бы просто не унаследовать интерфейс?.. Потому, что в самых первых моих опытах компилятор предупредил меня (в ультимативной форме), что иниировать событие я могу только в классе хозяина. Наследники таковыми не являются. Но есть выход: обернуть инициацию события в защищенный метод, и использовать его в наследниках. И не надо дублировать описание интерфейса, события и вообще.
class BDModelClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string update_args)
{
PropertyChangedEventHandler TmpThreadHandl = PropertyChanged;
if (TmpThreadHandl != null)
TmpThreadHandl(this, new PropertyChangedEventArgs(update_args));
}
public string ConfName
{
set
{
_ConfName = value;
OnPropertyChanged("ConfName");
}
get { return _ConfName; }
}
}
class FotoConf : BDModelClass
{
public FotoConf(...)
{
....
OnPropertyChanged(string.Empty);
}
}
Вот такая вот картинка... получилась у меня, после полутора дней шаманства и чтения форумов. Фотография сделана до чашки чая и зачистки решения от излишества в интерфейсах.
Данные схожие, а мест их ввода и редактирования в различных комбинациях - очень много. Ввод приходится постоянно обрабатывать.
Чтобы собрать все эти обработки по каждому параметру в одно место, я решила рассмотреть возможность привязывать мои данные к элементам управления.
Шаг 1. Прямая привязка дело не хитрое.
textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName");
- предсказуемо приводит к отображению в текстовом поле данных.
Шаг 2. На уровень глубже
textbox1.DataBindings.Add("Text", CurrentFotoConf, "AppObj.ConfName");
- так наша привязка делать, как выяснилось не умеет. Зато умеет так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName");
- в самом деле - объекту источнику довольно все равно, из каких глубин вы его извлечете. Главное, чтобы был доступ на чтение.
Шаг 3. Обновление отображения при изменении источника.
Приведенная выше привязка работает только в одну сторону: изменяем значение в интерфейсе, изменяется объект. А как обновить отображение автоматически, при изменении источника?
Для этого нам понадобятся две составляющих:
- Класс, который мы привязываем, должен поддерживать интерфейс INotifyPropertyChanged. Т.е. у него есть событие, например PropertyChanged, на которое сможет подписаться наш биндинг, и в случае изменения полей это событие вызывается. Подробнее про интерфейс и как выглядит его интеграция можно найти в конце поста. Using для волшебного интерфейса: System.ComponentModel .
- Надо подписать наш биндинг на это событие. Например, так:
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- или другим, более симпатичным вам образом.
Шаг 4. Опаньки: подводный камень.
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "Alpha", true, DataSourceUpdateMode.OnPropertyChanged)
- работает
textbox1.DataBindings.Add("Text", CurrentFotoConf, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает
textbox1.DataBindings.Add("Text", CurrentFotoConf.AppObj, "ConfName", true, DataSourceUpdateMode.OnPropertyChanged)
- не работает.
Как так?...
Очень просто. Свойство "ConfName" - унаследованное. C# считает, что оно принадлежит не классу Objective (или FotoConf), откуда я его беру для привязки, а классу родителю, где оно объявлено - BDModelClass.
Поэтому, конструктор новой привязки, т.е. биндинга, берет свойство, лезет в класс, с намерением привязать к нему свой обработчик событий изменения... и не находит там этого события. И интерфейса тоже не находит.
Поэтому...
... Если вы привязываете унаследованное свойство - класс родитель, где это свойство объявлено, тоже должен поддерживать интерфейс INotifyPropertyChanged. А метод set нашего свойства, должен инициировать событие после изменения.
Ура!
Все работает. Задача решена, теперь я могу выносить свои обработки, сообщения и ограничения на значения в одно место: в методы set для соответствующего поля.
Шаг 5. Зачистка.
Да, оно работает. Но если вернуться к картинке, то, между нами, все это не очень красиво. Почему бы просто не унаследовать интерфейс?.. Потому, что в самых первых моих опытах компилятор предупредил меня (в ультимативной форме), что иниировать событие я могу только в классе хозяина. Наследники таковыми не являются. Но есть выход: обернуть инициацию события в защищенный метод, и использовать его в наследниках. И не надо дублировать описание интерфейса, события и вообще.
class BDModelClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string update_args)
{
PropertyChangedEventHandler TmpThreadHandl = PropertyChanged;
if (TmpThreadHandl != null)
TmpThreadHandl(this, new PropertyChangedEventArgs(update_args));
}
public string ConfName
{
set
{
_ConfName = value;
OnPropertyChanged("ConfName");
}
get { return _ConfName; }
}
}
class FotoConf : BDModelClass
{
public FotoConf(...)
{
....
OnPropertyChanged(string.Empty);
}
}
Примечание.
Этого нет здесь, потому что я узнала об этом раньше, но это частые нюансы, поэтому я их упомяну.
- Привязывать можно только свойства. Не поля. Публичные, открытые, хотя бы на чтение, свойства.
- Это относится к сложным привязкам, т.е. привязкам списков ко всяким DataGridView - но я оставлю это тут: не все списки одинаково съедобны. Т.е. не все поддерживают интерфейс INotifyPropertyChanged при изменении коллекции.
List<T>, кстати, не поддерживает.
System.Collections.ObjectModel.ObservableCollection<T> - точно поддерживает. Обычно я использую его, хотя никак не могу это мотивировать.
BindingList<T> - не пробовала. Но я бы очень надеялась, что класс с таким названием все-таки это делает. - Подробнее про интерфейс, способы привязки и вообще, например, здесь: http://rsdn.ru/article/dotnet/Data_Binding_Basics.xml .

Комментариев нет:
Отправить комментарий