При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. |
Разработка приложения на базе WPF
Задание 4. Произвести привязку данных к элементам контроля – 2 часа.
Для взаимодействия приложения с базой данных необходимо в коде класса страницы объявить статическое свойство контекста данных сформированной EDM-модели. Это свойство целесообразно объявлять статическим.
Например:
public static TitlePresonalEntities DataEntitiesEmployee { get; set; }
Также необходимо объявить обобщенную коллекцию типа ObservableCollection<Employee> для работы приложения с коллекцией объектов базы данных. Этот тип представляет коллекцию динамических данных, обеспечивающих выдачу уведомления при получении и удалении элементов или при обновлении всего списка. Тип ObservableCollection<Т> находится в пространстве имен System.Collections.ObjectModel, ссылку на которое нужно добавить в объявлении класса приложения.
using System.Collections.ObjectModel;
Экземпляры свойств контекста данных и коллекции необходимо создать в конструкторе класса страницы.
Например:
public PageEmployee() { InitializeComponent(); DataEntitiesEmployee = new TitlePresonalEntities(); ListEmployee = new ObservableCollection<Employee>(); }
Формирование данных для приложения, которые должны предоставляться из базы данных, буде проводиться при загрузке страницы приложения. Для этого в XAML-документ Page добавьте свойство Loaded.
Loaded="Page_Loaded"
В код класса страницы приложения включаем обработчик Page_Loaded.
private void Page_Loaded(object sender, RoutedEventArgs e) { ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees; var queryEmployee = from employee in employees orderby employee.Surname select employee; foreach (Employee emp in queryEmployee) { ListEmployee.Add(emp); } DataGridEmployee.ItemsSource = ListEmployee; }
Поле employees имеет тип ObjectQuery<Employee>. Класс ObjectQuery<Т> представляет запрос, возвращающий коллекцию типизированных сущностей с любым количеством элементов. Запрос сформируем с помощью технологии LINQ.
var queryEmployee = from employee in employees orderby employee.Surname select employee;
Результаты запроса целесообразно отсортировать, например по фамилии сотрудника ( orderby employee.Surname ). Далее формируем коллекцию объектов базы данных и источник данных для сетки DataGrid.
foreach (Employee emp in queryEmployee) { ListEmployee.Add(emp); } DataGridEmployee.ItemsSource = ListEmployee;
В результате проектирования в приложении сформирована коллекция с данными из таблицы базы данных.
Необходимо настроить сетку DataGrid для корректного отображения данных.
Модифицируйте общее описание DataGrid.
- Определите привязку для источника данных.
ItemsSource="{Binding}"
- Отмените автоматическую генерацию столбцов.
AutoGenerateColumns="False"
- Установите привязку к левому краю страницы.
HorizontalAlignment="Left"
- Определите максимальные размеры сетки.
MaxWidth="1000" MaxHeight="295"
- Установите основной и альтернативный цвета заливки сетки.
RowBackground="#FFE6D3EF" AlternatingRowBackground="#FC96CFD4"
- Определите цвет заливки и толщину линии для рамки сетки.
BorderBrush="#FF1F33EB" BorderThickness="3"
- Определите высоту строк сетки.
RowHeight="25"
- Переопределите форму курсора при наведении указателя мыши на таблицу DataGridEmployee.
Модифицированное XAML-описание сетки DataGrid:
Cursor="Hand" <DataGrid Name="DataGridEmployee" ItemsSource="{Binding}" AutoGenerateColumns="False" HorizontalAlignment="Left" MaxWidth="1000" MaxHeight="295" RowBackground="#FFE6D3EF" AlternatingRowBackground="#FC96CFD4" BorderBrush="#FF1F33EB" BorderThickness="3" IsReadOnly="True" RowHeight="25" Cursor="Hand">
Привязка текстового поля
Для каждого текстового столбца задайте свойство привязки Binding. В расширении разметки привязки данного свойства определите свойство класса источника даннных, к которому привязывается колонка. Далее укажите режим двусторонней привязки ( Mode=TwoWay ), который определяет синхронное обновление как источника, так и приемника привязки. Способ обновления источника привязки задается свойством UpdateSourceTrigger, которое принимает значение PropertyChanged, что соответствует немедленному обновлению источника привязки каждый раз при изменении свойства цели привязки.
Например:
<DataGridTextColumn Header="Фамилия" Binding="{Binding Surname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Привязка выпадающего списка
Привязка данных к колонке типа DataGridComboBoxColumn требует определенной подготовительной работы. Если в таблице модели данных хранится не текстовое значение поля, а внешний ключ для другой таблицы, где находится данные, то в EDM-модели можно получить значение атрибута из связанных таблиц. Для этого используется атрибут связи в таблице EDM-модели.
Например, для обеспечения возможности работы с коллекцией таблицы Title в приложении добавте в проект папку Model и в ней создайте класс ListTitle.
public class ListTitle: ObservableCollection<Title> { public ListTitle() { ObjectQuery<Title> titles = PageEmployee.DataEntitiesEmployee.Titles; var queryTitle = from title in titles select title; foreach (Title titl in queryTitle) { this.Add(titl); } } }
Класс ListTitle наследуется от класса обобщенной коллекции ObservableCollection<T> и его назначение создавать коллекцию объектов Title. Поле titles является запросом типа ObjectQuery<Title>. Данному полю присваивается свойство Titles контекста данных DataEntitiesEmployee, который определен в классе приложения.
Запрос LINQ получает данные из базы данных в поле queryTitle и затем в цикле foreach формируется коллекция класса ListTitle.
Для использования класса ListTitle в XAML-документе класса приложения необходимо объявить данный класс как ресурс. При этом нужно подключить пространство имен WpfApplProject.Model.
xmlns:core ="clr-namespace:WpfApplProject.Model"
Затем определите ресурс страницы с ключом listTitle.
<Page.Resources> <core:ListTitle x:Key="listTitle" /> </Page.Resources>
Далее модифицируйте XAML-описание для типа DataGridComboBoxColumn.
<DataGridComboBoxColumn Header="Должность" ItemsSource="{Binding Source={StaticResource listTitle}}" DisplayMemberPath="Title1" SelectedValueBinding="{Binding Path=TitleID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="ID" />
Источник выпадающего списка задается как статический ресурс { StaticResource listTitle }. Выводимое в ячейки колонки поле должно соответствовать полю Title1 таблицы Title EDM-модели ( DisplayMemberPath="Title1" ). Выбираемый в списке параметр ( SelectedValueBinding ) должен быть привязан к полю TitleID таблицы Employee.
SelectedValueBinding="{Binding Path=TitleID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Выбор в свойства SelectedValueBinding производится по пути определенному свойством SelectedValuePath (SelectedValuePath="ID").
Привязка даты
При привязке даты ставится задача для невыделенной ячейки представлять дату в виде цифр дня, месяца и года, разделенных точками, а для выделенной ячейки – использовать класс для выбора даты.
Решение данной задачи осуществлено с помощью разработки двух шаблонов данных DataTemplate.
В первом шаблоне, для которого задан ключ DateTemplate, используется элемент управления TextBlock, свойство Text которого привязывается к атрибуту BirstDate таблицы Employee. Данные в привязке форматируются свойством StringFormat и строковые данные выравниваются по центру. Этот шаблон используется для визуализации данных в режиме их просмотра.
<DataTemplate x:Key="DateTemplate" > <TextBlock Text="{Binding BirstDate, StringFormat={}{0:dd\.}{0:MM\.}{0:yyyy}}" VerticalAlignment="Center" HorizontalAlignment="Center" /> </DataTemplate>
Второй шаблон с ключом EditingDateTemplate использует класс DatePicker. Этот шаблон используется в режиме редактирования данных.
<DataTemplate x:Key="EditingDateTemplate"> <DatePicker SelectedDate="{Binding BirstDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </DataTemplate>
Разработанные шаблоны необходимо добавить к ресурсам в XAML-документ страницы PageEmployee.
Для настройки колонки "Дата рождения" модифицируем её XAML-описание.
<DataGridTemplateColumn Header="Дата рождения" CellTemplate="{StaticResource DateTemplate}" CellEditingTemplate="{StaticResource EditingDateTemplate}" />
Невыделенную ячейку колонки ( CellTemplate ) свяжите с ресурсом DateTemplate, а редактируемую ячейку ( CellEditingTemplate ) – с ресурсом EditingDateTemplate.
Ячейка столбца для отображения даты типа DataGridTemplateColumn имеет три представления ( рис. 6.17). Если ячейка не выделена, то представление обычное текстовое ( рис. 6.17а). При выделении ячейки прорисовывается свернутое содержание класса DatePicker ( рис. 6.17б). При щелчке на ячейке появляется календарь ( рис. 6.17в).
Задание 5. Разработать методы манипулирования данными – 4 часа.
Для редактирования данных в приложении модифицируйте метод EditCommandBinding_Executed
private void EditCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { DataGridEmployee.IsReadOnly = false; DataGridEmployee.BeginEdit(); isDirty = false;
Свойству IsReadOnly сетки DataGridEmployee присвойте значение false, что обеспечит возможность редактирования строк и ячеек сетки. Далее используйте метод BeginEdit для начала редактирования ячейки, на которую наведен указатель мыши.
Результаты редактирования необходимо сохранить в базе данных. Для этого необходимо вызвать метод SaveCommandBinding_Executed.
private void SaveCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { dataEntitiesEmployee.SaveChanges(); isDirty = true; DataGridEmployee.IsReadOnly = true; }
В методе SaveCommandBinding_Executed вызывается метод SaveChanges класса dataEntitiesEmployee. Метод SaveChanges сохраняет все обновления в источнике данных.
Для создания новых данных модифицируйте метод NewCommandBinding_Executed.
Например:
private void NewCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { Employee employee = Employee.CreateEmployee(-1, "не задано", "не задано", "не задано", 0); employee.Telephone = "не задано"; employee.Email = "не задано"; try { DataEntitiesEmployee.Employees.AddObject(employee); ListEmployee.Add(employee); isDirty = false; } catch (DataServiceRequestException ex) { throw new ApplicationException( "Ошибка добавления данных" + ex.ToString()); } }
В методе NewCommandBinding_Executed объект employee класса Employee создается с помощью метода CreateEmployee. В блоке try ... catch созданный объект employee добавляется в контекст данных методом AddObject сущности Employees EDM-модели DataEntitiesEmployee, а также в коллекцию ListEmployee.
При инициализации команды создания данных в сетке формируется первоначальная строка с данными "по умолчанию". В сформированную строку необходимо ввести реальные данные сохранить их, вызвав команду "Сохранить".
Для удаления данных по сотруднику используем метод DeleteCommandBinding_Executed.
private void DeleteCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { Employee emp = DataGridEmployee.SelectedItem as Employee; if (emp != null) { MessageBoxResult result = MessageBox.Show("Удалить данные ", "Предупреждение", MessageBoxButton.OKCancel); if (result == MessageBoxResult.OK) { DataEntitiesEmployee.DeleteObject(emp); DataGridEmployee.SelectedIndex = DataGridEmployee.SelectedIndex == 0 ? 1 : DataGridEmployee.SelectedIndex - 1; ListEmployee.Remove(emp); DataEntitiesEmployee.SaveChanges(); } } else { MessageBox.Show("Выберите строку для удаления"); } }
В методе определяется объект emp класса Employee, который выделен в сетке DataGridEmployee.
Если объект emp найден, то с помощью диалогового окна класса MessageBoxResult выводится сообщение с данными, которые предполагается удалить.
При подтверждении операции удаления объект emp удаляется из контекста методом DeleteObject.
DataEntitiesEmployee.DeleteObject(emp);
Объект emp удаляется из коллекции ListEmployee.
ListEmployee.Remove(emp);
Изменения вносятся в базу данных.
DataEntitiesEmployee.SaveChanges();
Метод UndoCommandBinding_Executed реализует отмену редактирования последних элементов сетки DataGridEmployee и переводит её в режим просмотра.
Для реализации данной функциональности необходимо провести изменения в коде приложения. Для метода Page_Loaded основной код выделите в метод GetEmployees.
private void GetEmployees() { ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees; var queryEmployee = from employee in employees orderby employee.Surname select employee; foreach (Employee emp in queryEmployee) { ListEmployee.Add(emp); } DataGridEmployee.ItemsSource = ListEmployee; }
С учетом созданного метода GetEmployees метод Page_Loaded будет иметь следующий вид.
private void Page_Loaded(object sender, RoutedEventArgs e) { GetEmployees(); DataGridEmployee.SelectedIndex = 0; }
Создайте метод RewriteEmployee, который будет обновлять контекст данных и коллекцию ListEmployee.
private void RewriteEmployee() { DataEntitiesEmployee = new TitlePresonalEntities(); ListEmployee.Clear(); GetEmployees(); }
С учетом проведенных модификаций кода метод UndoCommandBinding_Executed будет иметь следующий вид.
private void UndoCommandBinding_Executed(object sender, ExecutedRoutedEventArgs e) { RewriteEmployee(); DataGridEmployee.IsReadOnly = true; isDirty = true; }
При запуске команды Отмена будет отменено редактирование в текущей ячейке сетки, а если редактировались несколько ячеек, то при повторном запуске команды будет обновлена вся строка.