При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. |
Разработка приложений на базе WPF
Разработка бизнес-логики
Для реализации функциональности приложения в части обработки информации о сотрудниках предприятия необходимо для страницы PageEmployee установить механизм запуска задач ( Отменить, Создать, Редактировать, Сохранить, Найти и Удалить ) при выборе соответствующих пунктов меню и нажатии на кнопки панели инструментов. Технология WPF предлагает модель команд для выполнения такой привязки.
Модель команд обеспечивает делегирование событий определенным командам и управление доступностью элементов управления в зависимости от состояния соответствующей команды. В WPF команда представляет собой задачу приложения и механизм слежения за тем, когда она может быть выполнена. В то же время сама команда не содержит конкретного кода выполнения задачи. Одна и та же команда может быть привязана к одному или нескольким интерфейсным элементам приложения. Инициируют команду источники, которые могут быть различными элементами управления, например пункты меню MenuItem или кнопки – Button. Целевым объектом команды является элемент, для которого предназначена эта команда.
Классы, реализующие команды должны поддерживать интерфейс ICommand. В этом интерфейсе определены два метода Execute, CanExecute и событие CanExecuteChanged.
В WPF имеется библиотека базовых команд. Команды доступны через статические свойства следующих статических классов:
- ApplicationCommands ;
- NavigationCommands ;
- EditingCommands ;
- MediaCommands.
Для создания пользовательских команд целесообразно использовать классы RoutedCommand, который имеет реализацию интерфейса ICommand.
В разрабатываемом приложении для функций Отменить, Создать, Сохранить и Найти будем использовать команды и библиотеки WPF – статический класс ApplicationCommands, а для функций Редактировать и Удалить спроектируем пользовательские команды.
Для разработки пользовательских команд добавим в проект папку Commands и в ней создадим класс DataCommands.
public class DataCommands { public static RoutedCommand Delete { get; set;} public static RoutedCommand Edit { get; set; } static DataCommands() { InputGestureCollection inputs = new InputGestureCollection(); inputs.Add(new KeyGesture(Key.E, ModifierKeys.Control, "Ctrl+E")); Edit = new RoutedCommand("Edit", typeof(DataCommands), inputs); inputs = new InputGestureCollection(); inputs.Add(new KeyGesture(Key.D, ModifierKeys.Control, "Ctrl+D")); Delete = new RoutedCommand("Delete", typeof(DataCommands), inputs); } }
В классе DataCommands объявлены два свойства Delete и Edit типа RoutedCommand. Класс RoutedCommand определяет команду, реализующую ICommand. В конструкторе данного класса определяется объект inputs типа InputGestureCollection. Класс InputGestureCollection представляет упорядоченную коллекцию объектов InputGesture, которые позволяют с помощью класса KeyGesture задать комбинацию клавиш для вызова команды.
Для использования страницей PageEmployee пользовательских команд в XAML-документе необходимо добавить пространство имен, где расположен класс DataCommands. Данному пространству имен присвоим ссылку command.
xmlns:command="clr-namespace:WpfApplProject.Commands"
Теперь для страницы PageEmployee сформируем коллекцию объектов CommandBinding, которая осуществляет привязку команд для данного элемента и объявляет связь между командой, ее событиями и обработчиками.
<Page.CommandBindings> <CommandBinding Command="Undo" Executed="UndoCommandBinding_Executed" CanExecute="UndoCommandBinding_CanExecute" /> . . . </Page.CommandBindings>
Для класса CommandBinding свойство Command определяет ссылку на соответствующую команду, а свойства Executed и CanExecute задают обработчики событий при выполнении команды.
На странице приложения используются следующие команды: Отменить, Создать, Редактировать, Поиск, Сохранить и Удалить. Команды могут быть доступны или недоступны пользователю при работе приложения. Это проверяет метод CanExecute при генерации события CanExecuteChanged, которое вызывается при изменении состояния команды. Доступность команд определяется состоянием, в котором находится приложение. В тоже время выполнение какой-либо команды переводит, как правило, приложение в какое-либо другое состояние. Для проектируемого приложения можно определить следующие состояния:
- первоначальная загрузка страницы (1);
- просмотр данных по всем сотрудникам (2);
- редактирование данных по отдельному сотруднику (3);
- создание новой записи по сотруднику в базе данных (4).
На рис. 4.19 приведена диаграмма состояний приложения, на которой кружками обозначены состояния, а дуги соответствуют переходам при выполнении определенной команды.
На основе диаграммы состояний построим таблицу доступности команд в различных состояниях (табл. 4.2).
Состояние | Доступность команд | |||||
---|---|---|---|---|---|---|
Сохранить | Отменить | Создать | Поиск | Редактировать | Удалить | |
1 | false | false | true | true | true | true |
2 | false | false | true | true | true | true |
3 | true | true | false | false | false | false |
4 | true | true | false | false | false | false |
Из табл. 4.2 видно, что в приложении режим доступности команд в состояниях 1 и 2 противоположно режиму доступности команд в состояниях 3 и 4. Фактически для приложения имеются два режима (один объединяет состояния 1 и 2, а второй – состояния 3 и 4), управлять которыми можно с помощью логической переменной.
В код программы класса PageEmployee введем логическое поле isDirty для управления доступностью команд.
private bool isDirty = true;
В код класса добавим обработчики (реализация метода Executed ), определяющие бизнес-логику приложения. На данном этапе проектирования системы обработчики будут содержать только вывод сообщений о вызове команды и изменение поля isDirty. Код обработчика команды Удалить приведен ниже.
private void UndoCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("Отмена"); isDirty = true; }
В дальнейшем в обработчики добавим код для обеспечения требуемой функциональности.
В коде класса остается добавить обработчики (реализация метода CanExecute ), которые управляют доступностью команд. Так как при анализе табл. 4.2 было выявлено, что для приложения различимо только два состояния доступности команд, то и обработчиков тоже достаточно иметь в программе два.
private void EditCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = isDirty; } private void SaveCommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = !isDirty; }
Теперь необходимо модифицировать XAML-документ в части задания свойства Command при описании пунктов меню и панели инструментов для привязки команд.
При описании меню (Menu) XAML-документ модифицирован следующим образом.
<Menu> <MenuItem Header="Действие" BorderThickness="3" > <MenuItem Header="Отменить" Command="Undo"></MenuItem> . . . </Menu>
Соответствующие изменения XAML-документа необходимо провести и для панели инструментов ToolBar.
<ToolBar Name="ToolBar1" Margin="3"> <Button Name="Undo" Margin="5,2,5,2" Command="Undo" ToolTip="Отменить редактирование/создание" > <Image Source="Images/Undo.jpg"> </Image> </Button> ... </ToolBar>
При выполнении приложения различные состояния доступности пунктов меню приведены на рис. 4.20 и рис. 4.21.
При выборе какого-либо пункта меню, например пункта Создать, система выдает сообщение ( рис. 4.22).
Следующим этапом разработки приложения является создание источника данных, который обеспечит взаимодействие приложения с базой данных.
Ключевые термины
Приложение WPF, пространство имен, атрибут, частичный класс, простое свойство, сложное свойство, фрейм, гиперссылка, меню, панель инструментов, команда, привязка команд.
MainWindow, xmlns, partial, InitializeComponent, NavigationWindow, Frame, FrameworkElement, Page, Hyperlink, NavigateUri, Menu, MenuItem, DataGrid, DataGridTextColomn, DataGridCheckBoxColomn, DataGridComboBoxColomn, DataGridHyperlinkColomn, DataGridTemplateColumn, ICommand, Execute, CanExecute, CanExecuteChanged, CommandBinding.
Краткие итоги
В данной теме были рассмотрены вопросы разработки страничного WPF-приложения, организации перехода по страницам, создания системы меню, панели команд, табличного представления информации для просмотра и редактирования данных, системы универсальных команд многократного использования.
Ресурсы для углубленного изучения
- Application Development // http://msdn.microsoft.com/ru-ru/library/ms754032.aspx.
- Система макета // http://msdn.microsoft.com/ru-ru/library/ms745058.aspx.
- Библиотека классов // http://msdn.microsoft.com/ru-ru/library/ms753307.aspx.
- [ 4 ] , стр. 82 – 174, 288 – 362.
- [ 6 ] , стр. 1171 - 1212
- [ 8 ] , стр. 297 – 351,565 – 625.
- [ 9 ] , стр. 1048 – 1070.
Вопросы для самопроверки
- Какой дескриптор верхнего уровня используется в WPF-проектах?
- Какое назначение метода InitializeComponent() в коде класса?
- Сколько можно вложить элементов в класс Page?
- Поясните назначение свойства Content класса Page.
- В каких контейнерах можно размещать страницы WPF?
- Какие элементы контроля используются для перехода между страницами приложения?
- Из каких объектов можно сформировать меню приложения?
- Какие типы столбцов автоматически генерируются для элемента управления DataGrid?
- Поясните назначение модели команд WPF.
- Какие в WPF имеется библиотеки базовых команд?
- Как можно управлять доступностью команд в приложении WPF?