Использование такого компонента как BusyIndicator привнесит в наше приложение приятные (индикация процесса) и полезные (блокировка элемента управления) качества. Однако до последнего времени я редко использовал его, т.к. при асинхронном получении источника данных приходилось постоянно писать дополнительный код для включения/выключения. При синхронной работе ситуация вроде как упрощается, но использование MVVM-модели всё-равно требует дополнительных телодвижений. Особенно, если BusyIndicator
добавляется в самом конце разработки формы.
Поэтому, я решил максимально автоматизировать этот процесс, чтобы мне не приходилось писать ни строчки кода. Итак:
Постановка задачи:
- Обеспечить автоматическую индикацию для любых списков, использующих в качестве источника данных значение свойства
ItemsSource
. - Признаком, что данные получены будем считать не пустое значение свойства
ItemsSource
. - В качестве
BusyIndicator
'а можно использовать любой элемент управления только бы он имелboolean
-свойствоIsBusy
. - Всё дополнительное кодирование должно быть реализовано в представлении (View) формы и XAML-код должен иметь такой шаблон:
<BusyIndicator ...> <ListBox ItemsSource="{Binding DataList, IsAsync=true}" ...> ... </ListBox> <BusyIndicator>
Свойство биндинга IsAsync=true
в примере можно опустить, в этом случае рассуждения особо не изменяться, но в данной статье я буду приводить примеры именно асихронного получения данных, т.к. если требуется индикация процесса, то получение данных занимает ощутимое время, а раз так, то мы же не хотим, чтобы наше приложение зависало при этом, а раз так — то асинхронный биндинг наше всё. Тем более, что реализовать его нам ничего не стоит: IsAsync=true в XAML-е и в коде ViewModel'и:
private IEnumerable _dataList = null;
public IEnumerable DataList
{
get
{
if (_dataList == null)
_dataList = Model.GetDataList(...);
return _dataList;
}
private set
{
if (_dataList == value) return;
_dataList = value;
NotifyPropertyChanged("DataList");
}
}
public void RefreshDataList()
{
DataList = null;
}
Первое, что мне пришло в голову (и это даже визуально заработало) — это написать нечто наподобие такого:
<BusyIndicator IsBusy="{Binding DataList, IsAsync=true, Converter={StaticResource NullToBool}">
<ListBox ItemsSource="{Binding DataList, IsAsync=true}" ...>
...
</ListBox>
<BusyIndicator>
А ну, кто сможет сразу раскритиковать этот код?
Вот и мне он тоже сразу не понравился. Отладка подтвердила мои опасения: получение списка происходило дважды — для BusyIndicatorа
и для списка.
«Не беда!» — сказал я и чуть изменил метод получения списка:
private object _dataListSync = new nbject();
private IEnumerable _dataList = null;
public IEnumerable DataList
{
get
{
lock (_dataListSync)
{
if (_dataList == null)
_dataList = Model.GetDataList(...);
return _dataList;
}
}
}
Теперь получение данных происходило один раз и всё работало так как и задумывалось, но, этот метод мне всё-равно не нравился.
Во-первых, нужно регистрировать конвертер, во-вторых, писать довольно много одинаковых букафф, не забывая синхронизировать биндинги списка и индикатора, если вдруг что-то изменится, в-третьих, я не уверен, что получение DataList
для индикатора всегда вызовется до получения DataList
для списка.
Следующей идеей было использование attached property
.
До сих пор мне не выдавалась возможность глубоко в этом разобраться на реальном интересном примере, так что в следующей статье Создание Attached Property для BusyIndicator шаг за шагом я расскажу, что у меня получилось.
Спасибо тем кто дочитал до конца.
Автор: LionSoft