Разработка приложений на базе WPF и Silverlight

Разработка приложения на базе WPF

Показывать лекцию целиком

Цель

В процессе выполнения работы необходимо разработать страничное/оконное WPF приложение.

Задание 1. Создать проект в соответствии с шаблоном "приложение WPF" и разработать интерфейс пользователя – 1 час

Для разработки приложения необходимо создать WPF-проект (рис 6.1). В окне " ), выбрать шаблоны Windows (2 – на рис 6.1), а среди имеющихся шаблонов задать Приложение WPF и в поле " Имя " ввести имя проекта WpfApplProject.

(рис 6.1) Окно создания проекта

После нажатия кнопки "ОК" будет сформирован шаблон проекта. При этом инструментальная система сгенерирует следующий XAML-документ:

<Window x:Class="WpfApplProject.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        
    </Grid>
</Window>

Дизайнер проекта приведен на рис 6.2.

(рис 6.2) Окно дизайнера проекта

В приведенном XAML-документе имеется один элемент верхнего уровня <Window>. Дескриптор </Window> завершает весь документ. В XAML-документе приведено имя класса MainWindow

x:Class="WpfApplProject.MainWindow

два пространства имен

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x=http://schemas.microsoft.com/winfx/2006/xaml

и три свойства:

Title="MainWindow" Height="350" Width="525"

Каждый атрибут соответствует определенному свойству класса .

(рис 6.3) Окно MainWindow проекта

Когда выполняется компиляция приложения, XAML-файл, который определяет пользовательский интерфейс ( MainWindow.xaml ), транслируется в объявление типа CLR , которое объединяется с логикой приложения из файла класса отдельного кода ( MainWindow.xaml.cs ).

Метод InitializeComponent() генерируется во время компиляции приложения и в исходном коде не присутствует.

Для программного управления элементами управления, описанными в XAML-документе, необходимо задать XAML атрибут Name. Так для задания имени элементу Grid необходимо записать следующую разметку:

<Grid Name="grid">
        
</Grid>

Страницы приложения можно размещать внутри окон и внутри других страниц. В WPF при создании страничных приложений контейнером наивысшего уровня могут быть следующие объекты:

  • NavigationWindow, который представляет собой несколько видоизмененную версию класса Window ;
  • Frame, находящийся внутри другого окна или другой страницы;
  • Frame, обслуживаемый непосредственно в Internet Explorer.
  • Для вставки страницы внутрь окна будем использовать класс ). Автоматически будет сгенерирован экземпляр класса ).

    (рис 6.5) Выбор класса Frame в панели инструментов(рис 6.4) Фрейм с фиксированными границами

    В XAML-документ проекта будет добавлена следующая строка:

    <Frame Height="100" HorizontalAlignment="Left" 
    Name="frame1" VerticalAlignment="Top" Width="200" />

    С учетом того, что создается страничное приложение размеры фрейма не нужно фиксировать, поэтому изменим описание свойств фрейма:

    <Frame Name="frame1" Margin="3" />

    В результате фрейм заполнит всё окно (рис 6.6):

    (рис 6.6) Фрейм, заполняющий всё окно

    Созданный фрейм необходим для размещения в нем страницы WPF-приложения. Класс Page допускает наличие только одного вложенного элемента. Он не является элементом управления содержимым и наследуется от класса FrameworkElement. Класс Page имеет небольшой набор дополнительных свойств, которые позволяют настраивать его внешний вид, взаимодействовать с контейнером и использовать навигацию. Для перехода на другую страницу необходимо использовать навигацию.

    Добавьте в проект начальную страницу. Для этого в ), а в раскрывающимся меню - пункт ).

    В окне ).

    (рис 6.8) Меню создания страницы(рис 6.7) Окно Добавления нового элемента

    В дизайнере проекта сгенерируется страница ).

    (рис 6.9) Дизайнер страницы PageMain.xaml

    В сгенерированной странице в качестве контейнера верхнего уровня используется Grid. Замените Grid на контейнер StackPanel.

    Главная страница приложения в дизайнере представлена на рис 6.10.

    (рис 6.10) Главная страница WPF-приложения в дизайнере

    Измените свойство Title окна класса Window, в соответствии с вариантом лабораторной работы.

    Основная страница должна обеспечивать переход на другие страницы, обеспечивающие инферфейс для отдельных функций и выход из системы. Для перехода на другие страцицы используйте гиперссылки. Гиперссылки позволяют пользователю перемещаться с одной страницы на другую. Элемент гиперссылки, соответствующий объекту класса Hyperlink, определяется следующей строкой:

    <Hyperlink >Текст Гиперссылки</Hyperlink>

    Класс Hyperlink имеет свойство NavigateUri. Данное свойство определяет на какую страницу будет переходить приложение при щелчке на соответствующей гиперссылке. Например, NavigateUri="Page2.xaml".

    В WPF гиперссылки являются не отдельными, а внутристроковыми потоковыми элементами, которые должны размещаться внутри другого поддерживающего их элемента. Это можно сделать, например в элементе TextBlock, который для гиперссылки является контейнером.

    На первой странице создайте гиперссылки для перехода на страницы приложния, в соответствии в заданием.

    Создание основного меню. Создайте основное меню с помощью класса Menu, который представляет Windows элементы управления меню, позволяющие иерархически организовать элементы, связанные с командами и обработчиками событий. Меню формируют из объектов MenuItem (имя пункта меню) и Separator (разделитель). Класс MenuItem имеет свойство Header, которое определяет текст элемента меню. Данный класс может хранить коллекцию объектов MenuItem, которая соответствует подпунктам меню. Класс Separator отображает горизонтальную линию, разделяющую пункты меню.

    Добавьте в StackPanel страницы приложения XAML- описание меню, которое на верхнем уровне будет содержать, например два пункта Действие и Отчет. Пункт Действие включает подпункты Отменить, Создать, Редактировать, Сохранить, Найти и Удалить. Между пунктами Отменить, Создать и пунктами Найти, Удалить добавьте разделительные линии.

    <Menu>
        <MenuItem Header="Действие" >
            <MenuItem Header="Отменить" ></MenuItem>
            <Separator></Separator>
            <MenuItem Header="Создать" ></MenuItem>
            <MenuItem Header="Редактировать" ></MenuItem>
            <MenuItem Header="Сохранить" ></MenuItem>
            <MenuItem Header="Найти" />
            <Separator></Separator>
            <MenuItem Header="Удалить" ></MenuItem>
        </MenuItem>
        <MenuItem Header="Отчет"></MenuItem>
    </Menu>

    Создание панели инструментов. Панель инструментов представляет специализированный контейнер для хранения коллекции элементов, обычно кнопок. Расположите в панели инструментов кнопки, функциональное назначение которых будет соответствовать подпунктам меню Действие, то есть Отменить, Создать, Редактировать, Сохранить, Найти и Удалить.

    На лицевой стороне кнопок поместите графическое изображение соответствующего действия. Для этого добавьте в файл проекта папку Images и в неё включите графические объекты, которые можно найти в библиотеке графических объектов Visual Studio (папка VS2010ImageLibrary).

    После добавления графических файлов в проект они будут отображены в обозревателе решений (рис 6.11).

    (рис 6.11) Включение в проект папки Images с файлами рисунков

    Для каждой кнопки задайте свойство Name – имя объекта в программе и свойство ToolTip с текстом всплывающей подсказки при наведении указателя мыши на кнопку.

    Свойство Margin определяет внешние отступы для кнопки. Задание графического объекта для кнопки осуществляется определением для объекта Image источника Source, который должен соответствовать полному пути к графическому файлу.

    <ToolBar Name="ToolBar1" Margin="3">
    	<Button Name="Undo" ToolTip="Отменить редактирование/создание" 
    		Margin="5,2,5,2">
    		<Image Source="Images/Undo.jpg" />
    	</Button>
    	...
    </ToolBar>

    Проектирование интерфейсных элементов. При проектировании интерфейсных элементов для страницы приложения используются элементов контроля ListBox, ListView, TextBox, TextBlock, ComboBox, DataGrid и других.

    Класс DataGrid представляет элемент управления, отображающий данные в настраиваемой сетке строк и столбцов. По умолчанию DataGrid автоматически создает столбцы, на основе источника данных. При этом генерируются следующие типы столбцов:

  • DataGridTextColomn – для отображения в ячейках столбцов текстового содержимого;
  • DataGridCheckBoxColomn – для отображения в ячейках столбцов логических данных;
  • DataGridComboBoxColomn – для отображения в ячейках столбцов данных, когда имеется набор элементов для выбора;
  • DataGridHyperlinkColomn – для отображения в ячейках столбцов элементов Uri.
  • Если разработчика не устраивает автоматическая генерация столбцов DataGrid можно её отключить. Для этого свойству AutoGenerateColumns необходимо присвоить значение false. Далее можно создать собственный набор столбцов ( Columns ), используя существующие типы столбцов или создать новый тип столбца с помощью шаблона DataGridTemplateColumn.

    DataGrid поддерживает множество способов настройки отображения данных.

    Задание 2. Разработать бизнес-логику приложения – 2 часа.

    Для реализации функциональности приложения необходимо для страницы приложения установить механизм запуска задач при выборе соответствующих пунктов меню и нажатии на кнопки панели инструментов. Технология WPF предлагает модель команд для выполнения такой привязки.

    Модель команд обеспечивает делегирование событий определенным командам и управление доступностью элементов управления в зависимости от состояния соответствующей команды. В WPF команда представляет собой задачу приложения и механизм слежения за тем, когда она может быть выполнена. В то же время сама команда не содержит конкретного кода выполнения задачи. Одна и та же команда может быть привязана к одному или нескольким интерфейсным элементам приложения. Инициируют команду источники, которые могут быть различными элементами управления, например пункты меню MenuItem или кнопки – Button. Целевым объектом команды является элемент, для которого предназначена эта команда.

    Классы, реализующие команды должны поддерживать интерфейс ICommand. В этом интерфейсе определены два метода Execute, CanExecute и событие CanExecuteChanged.

    public interface ICommand1
        {
            void Execute(object par);
            bool CanExecute(object par);
            event EventHandler CanExecuteChanged;
        }

    Метод Execute может содержать код, реализующий бизнес-логику приложения. Метод CanExecute возвращает информацию о состоянии команды. Событие CanExecuteChanged вызывается при изменении состояния команды.

    В WPF имеется библиотека базовых команд. Команды доступны через статические свойства следующих статических классов:

  • ApplicationCommands ;
  • NavigationCommands ;
  • EditingCommands ;
  • MediaCommands.
  • Для создания пользовательских команд целесообразно использовать классы RoutedCommand, который имеет реализацию интерфейса ICommand.

    Для разработки пользовательских команд добавьте в проект папку 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 задать комбинацию клавиш для вызова команды.

    Для использования страницей приложения пользовательских команд в XAML-документе необходимо добавить пространство имен, где расположен класс DataCommands. Данному пространству имен присвоим ссылку command.

    xmlns:command="clr-namespace:WpfApplProject.Commands"

    Теперь для страницы приложения сформируйте коллекцию объектов CommandBinding, которая осуществляет привязку команд для данного элемента и объявляет связь между командой, ее событиями и обработчиками.

    <Page.CommandBindings>
    	<CommandBinding Command="Undo" 
    	 Executed="UndoCommandBinding_Executed" 
            CanExecute="SaveCommandBinding_CanExecute" />
    		... 
    </Page.CommandBindings>

    Для класса CommandBinding свойство Command определяет ссылку на соответствующую команду, а свойства Executed и CanExecute задают обработчики событий при выполнении команды.

    На странице приложения используются следующие команды: Отменить, Создать, Редактировать, Поиск, Сохранить и Удалить. Команды могут быть доступны или недоступны пользователю при работе приложения. Это проверяет метод CanExecute при генерации события CanExecuteChanged, которое вызывается при изменении состояния команды. Доступность команд определяется состоянием, в котором находится приложение. В тоже время выполнение какой-либо команды переводит, как правило, приложение в какое-либо другое состояние. Для проектируемого приложения можно определить следующие состояния:

  • первоначальная загрузка страницы;
  • просмотр данных;
  • редактирование данных;
  • создание новой записи.
  • В код программы класса страницы приложения введите логическое поле isDirty для управления доступностью команд.

    private bool isDirty = true;

    В код класса добавьте обработчики (реализация метода Executed ), определяющие бизнес-логику приложения. На данном этапе проектирования системы обработчики будут содержать только вывод сообщений о вызове команды и изменение поля isDirty. Код обработчика команды Удалить приведен ниже.

    private void UndoCommandBinding_Executed(object sender, 
        ExecutedRoutedEventArgs e)
            {
                MessageBox.Show("Отмена");
                isDirty = true;
            }

    В дальнейшем в обработчики необходима будет добавить код для обеспечения требуемой функциональности.

    В коде класса необходимо добавить обработчики (реализация метода CanExecute ), которые управляют доступностью команд.

    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>

    Задание 3. Создать EDM-модель данных – 1 час.

    Для создания EDM-модели данных необходимо добавить в проект новый элемент – модель ADO.NET EDM, присвоив файлу модели имя с соответствии с заданием, в рассматриваемом примере модель имеет имя ).

    (рис 6.12) Добавление в проект модели EDM

    В мастере создания EDM-модели выберем опцию "Создать из базы данных" (рис 6.13). Для создания соединения с базой данных в окне "Выбор подключения к данным" укажем соединение с базой данных (имя_сервера.имя_базы данных, в рассматриваемом примере – алексей-пк.TitlePerson) и зададим имя модели данных - ).

    (рис 6.14) Создание модели EDM из базы данных(рис 6.13) Создание соединения с базой данных

    В окне "Выбор объектов базы данных" отметим необходимые для приложения таблицы (в нашем случае это таблицы ).

    (рис 6.15) Выбор таблиц базы данных

    При завершении работы мастера создания EDM-модели в проект будет добавлен файл (в рассматриваемом примере ), ссыки на необходимые библиотеки и конфигурационный файл.

    (рис 6.16) Выбор таблиц базы данных

    Автоматически сгенерированный класс TitlePresonalEntities, который наследуется от класса ObjectContext, представляет сущности базы данных TitlePerson, содержит свойства, моделирующие таблицы Employee и Title, связи между таблицами.

    При создании модели данных в проекте автоматически был сгенерирован конфигурационный файл App.Config, который содержит строку соединения с базой данных.

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <connectionStrings>
        <add name="TitlePresonalEntities" connectionString="metadata=res://*/TitlePersonal.csdl|res://*/TitlePersonal.ssdl|res://*
        /TitlePersonal.msl;provider=System.Data.SqlClient;provider connection string="Data Source=АЛЕКСЕЙ-ПК;
        Initial Catalog=TitlePresonal;Integrated Security=True;MultipleActiveResultSets=True""
       providerName="System.Data.EntityClient" />
      </connectionStrings>
    </configuration>

    Задание 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.

    Ячейка столбца для отображения даты типа ). Если ячейка не выделена, то представление обычное текстовое (рис 6.17а). При выделении ячейки прорисовывается свернутое содержание класса б). При щелчке на ячейке появляется календарь (рис 6.17в).

    (рис 6.17) Отображение даты в сетке DataGrid

    Задание 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;
    }

    При запуске команды Отмена будет отменено редактирование в текущей ячейке сетки, а если редактировались несколько ячеек, то при повторном запуске команды будет обновлена вся строка.

    Задание 6. Реализовать валидацию данных – 1 час.

    Разработку пользовательского правила валидации данных рассмотрим на примере проверки шаблона при вводе свойства Email, то есть адреса электронной почты. В строке ввода должны присутствовать символы @ и точка.

    Спроектируйте правило проверки для привязки, которое будет использоваться со столбцом "Электронная почта". Для этого необходимо создать класс правила проверки EmailRule, который должен наследоваться от базового класса ValidationRule. Класс EmailRule поместите во вновь созданную папку ValidationRules проекта.

    public class EmailRule: ValidationRule
    {
        public override ValidationResult Validate(object value,
                        System.Globalization.CultureInfo cultureInfo)
        {
            string email = string.Empty;
            if (value != null)
            {
                email = value.ToString();
            }
            else
                return new ValidationResult(false, " Адрес электронной почты не задан! ");
            if (email.Contains("@")  email.Contains("."))
            {
                return new ValidationResult(true, null);
            }
            else
            {
                return new ValidationResult(false, 
    "Адрес электронной почты должен содержать 
    символы @ и (.) точки \n Шаблон адреса:   adres@mymail.com");
            }
           
        }
    }

    В классе правил проверки необходимо переопределить метод Validate, который возвращает результат проверки – экземпляр класса ValidationResult. Если проверка проходит успешно, тогда возвращаемым значением является объект класса ValidationResult, который создается с параметрами true и null.

    return new ValidationResult(true, null);

    Если проверка приводит к выявлению несоответствия, то класс ValidationResult, создается с параметрами false и строка сообщения об ошибке.

    return new ValidationResult(false, "Адрес электронной почты должен содержать 
    символы @ и (.) точки \n Шаблон адреса:   adres@mymail.com");

    Для использования объекта EmailRule в XAML-документе страницы приложения добавьте в её описание пространство имен WpfApplProject.ValidationRules.

    xmlns:rule="clr-namespace:WpfApplProject.ValidationRules"

    Внесите изменения в XAML-описание колонки Электронная почта.

    <DataGridTextColumn  Header="Электронная почта" EditingElementStyle="{StaticResource errorStyle}">
        <DataGridTextColumn.Binding >
            <Binding Path="Email" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"
    ValidatesOnExceptions ="True" >
                <Binding.ValidationRules>
                     <rule:EmailRule />
              </Binding.ValidationRules>
            </Binding>
        </DataGridTextColumn.Binding>
    </DataGridTextColumn>

    Для привязки Binding свойству ValidatesOnExceptions задайте значение true. Задание данного свойства обеспечивает использование элемента ExceptionValidationRule. ExceptionValidationRule предоставляет встроенное правило проверки, проверяющее возникновение исключений при обновлении свойства источника. В случае возникновения ошибки механизм привязки создает ValidationError с исключением и добавляет его в коллекцию Validation.Errors привязанного элемента. Отсутствие ошибок сбрасывает это состояние обратной связи проверки, если другое правило не вызовет событие проверки.

    Правило проверки задается для свойства ValidationRules класса Binding.

    <Binding.ValidationRules>
    	<rule:EmailRule />
    </Binding.ValidationRules>

    Модель привязки данных WPF позволяет связывать ValidationRules с объектом Binding, используя пользовательские правила. Подсистема привязки проверяет каждое из ValidationRule, связанных с привязкой, каждый раз, когда вводимое значение (значение свойства цель привязки) переносится в свойство источник привязки.

    В WPF имеются стандартные стили для отображения ячеек сетки при возникновении ошибки ввода, но они являются недостаточно информативными. Для более эффектного выделения ячейки сетки при ошибке ввода создайте специальный стиль errorStyle.

    <Style x:Key="errorStyle" TargetType="{x:Type TextBox}">
        <Setter Property="Padding" Value="-2"/>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="Background" Value="Red"/>
                <Setter Property="BorderThickness" Value="1" />
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={RelativeSource Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>

    В стиле errorStyle триггер срабатывает, когда свойство Validation.HasError принимает значение true. При этом цвет заливки ячейки становится красным и при наведении на ячейку указателя мыши выводится сообщение-подсказка (свойство ToolTip ), текст которого определяется в правиле проверки EmailRule при обнаружении ошибки ( Validation.Errors )[0] .ErrorContent ).

    Стандартные средства WPF при обнаружении ошибки ввода в ячейке помечают строку сетки красным восклицательным знаком. Создадим шаблон ControlTemplate для визуального указания ошибки при проверке строки. Данный шаблон задается для свойства RowValidationErrorTemplate сетки DataGrid. Проектируемый шаблон должен обеспечить вывод красного круга с белым восклицательным знаком слева от строки сетки с обнаруженной ошибкой ввода. При наведении указателя мыши на круг должна выводиться подсказка, сформированная в классе правил проверки ввода.

    <DataGrid.RowValidationErrorTemplate>
        <ControlTemplate>
            <Grid Margin="0,-2,0,-2"
                  ToolTip="{Binding RelativeSource={RelativeSource FindAncestor,
                                               AncestorType={x:Type DataGridRow}},
                Path=(Validation.Errors)[0].ErrorContent}">
                <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}"
                                                     Height="{TemplateBinding FontSize}" />
                <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold"
                                        Foreground="White" HorizontalAlignment="Center"  />
            </Grid>
        </ControlTemplate>
    </DataGrid.RowValidationErrorTemplate>

    Задание 7. Разработать методы поиска данных – 1 час.

    Функцию поиска данных в приложений необходимо выполнить на основе данных, имеющихся в базе данных варианта лабораторной работы. параметры поиска необходимо согласовать с преподавателем.

    Общий подход к поиску реализации поиска данных, получаемых из базы данных, рассмотрим на примере поиска сотрудника по фамилии и должности. Для реализации функций поиска добавьте на страницу приложения элементы контроля в соответствии с рис 6.18.

    (рис 6.18) Дизайн страницы с элементами контроля для поиска

    XAML-описание элементов контроля, поддерживающих поиск имеет следующий вид.

    <TextBlock Name="TextBlockSurname" Text="Фамилия" />
    <TextBlock Name="TextBlockTitle" Text="Должность" />
    <TextBox Name="TextBoxSurname" TextChanged="TextBoxSurname_TextChanged"/>
    <ComboBox Name="ComboBoxTitle
    ItemsSource="{Binding Source={StaticResource listTitle}}"
    DisplayMemberPath="Title1"
    SelectionChanged="ComboBoxTitle_SelectionChanged"/>
    <Button Name="ButtonFindSurname" ToolTip="Поиск по фамилии"
    IsEnabled="False" Click="ButtonFindSurname_Click">
    <Image Source="Images/Search.jpg"/>
    </Button>
    <Button Name="ButtonFindTitle" ToolTip="Поиск по должности"
    IsEnabled="False" Click="ButtonFindTitle_Click">
    <Image Source="Images/Search.jpg" />
    </Button>

    Элементы контроля размещаем в рамке BorderFind. В рамке располагаем сетку gridFind с тремя колонками и строками. Первая строка в объединенных колонках содержит текстовый блок find с текстом "Поиск". Во второй и третьей строках первой колонки размещены текстовые блоки TextBlockSurname и TextBlockTitle соответственно. Во второй колонке размещаются элементы контроля для ввода информации: текстовый блок TextBoxSurname (вторая строка) и выпадающий список ComboBoxTitle (третья строка). В третьей колонке размещаются кнопки: для поиска по фамилии ButtonFindSurname (вторая строка) и для поиска по должности ButtonFindTitle (третья строка).

    При загрузке страницы PageEmployee рамка BorderFind невидима, так как свойству Visibility задано значение "Hidden". При активизации команды Find из меню или панели инструментов запускается обработчик FindCommandBinding_Executed, который делает рамку ButtonFindTitle видимой.

    private void FindCommandBinding_Executed(object sender, 
    ExecutedRoutedEventArgs e)
            {
                BorderFind.Visibility = System.Windows.Visibility.Visible;
            }

    Если в текстовом блоке TextBoxSurname не введена информация или в выпадающем списке ComboBoxTitle не сделан выбор, то кнопки ButtonFindSurname и ButtonFindTitle будут недоступны.

    При вводе информации в текстовый блок TextBoxSurname запускается обработчик TextBoxSurname_TextChanged, который делает доступной кнопку ButtonFindSurname.

    private void TextBoxSurname_TextChanged(object sender, TextChangedEventArgs e)
    {
        ButtonFindSurname.IsEnabled = true;
        ButtonFindTitle.IsEnabled = false;
        ComboBoxTitle.SelectedIndex = -1;
    }

    При нажатии кнопки ButtonFindSurname запускается обработчик ButtonFindSurname_Click.

    private void ButtonFindSurname_Click(object sender, RoutedEventArgs e)
    {
        string surname = TextBoxSurname.Text;
        DataEntitiesEmployee = new TitlePresonalEntities();
        ListEmployee.Clear();
        ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;
        var queryEmployee = from employee in employees
                                where employee.Surname == surname
                                select employee;
        foreach (Employee emp in queryEmployee)
        {
            ListEmployee.Add(emp);
        }
        if (ListEmployee.Count > 0)
        {
            DataGridEmployee.ItemsSource = ListEmployee;
            ButtonFindSurname.IsEnabled = true;
            ButtonFindTitle.IsEnabled = false;
        }
        else
            MessageBox.Show("Сотрудник с фамилией \n"+surname+"\n не найдан",
                 "Предупреждение", MessageBoxButton.OK, MessageBoxImage.Warning);
    }

    В данном обработчике события Click выполняется LINQ запрос на получение данных из базы TitlePersonal в соответствии с заданной фамилией сотрудника.

    var queryEmployee = from employee in employees
                            where employee.Surname == surname
                            select employee;

    При выполнении запроса по фамилии страница

    (рис 6.19) Поиск по фамилии

    Найденные данные по сотруднику можно редактировать и удалять.

    Если поиск в базе данных не привел к нахождению информации, то выдается информационное сообщение (рис 6.20).

    (рис 6.20) Неудачный поиск по фамилии

    Для поиска информации из базы данных по должности сотрудников необходимо сделать выбор из выпадающего списка ComboBoxTitle. В результате выбора срабатывает обработчик ComboBoxTitle_SelectionChanged, который делает доступной кнопку ButtonFindTitle.

    private void ComboBoxTitle_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        ButtonFindTitle.IsEnabled = true;
        ButtonFindSurname.IsEnabled = false;
        TextBoxSurname.Text = "";
    }

    При нажатии на кнопку ButtonFindTitle срабатывает обработчик ButtonFindTitle_Click.

    private void ButtonFindTitle_Click(object sender, RoutedEventArgs e)
    {
        DataEntitiesEmployee = new TitlePresonalEntities();
        ListEmployee.Clear();
        
        Title title = ComboBoxTitle.SelectedItem as Title;
        ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;
        var queryEmployee = from employee in employees
                            where employee.TitleID == title.ID
                            orderby employee.Surname
                            select employee;
        foreach (Employee emp in queryEmployee)
        {
            ListEmployee.Add(emp);
        }
        DataGridEmployee.ItemsSource = ListEmployee;
    }

    При запуске данного обработчика выполняется LINQ-запрос к базе данных TitlePersonal для выборки данных по сотрудникам, имеющим заданную должность.

    Для обновления выводимого списка сотрудников в приложение добавлена функция обновления, которая реализуется командой Refresh. Данная команда добавлена в коллекцию команд страницы PageEmployee.

    <Page.CommandBindings>
    ....
            <CommandBinding Command="Refresh" Executed="RefreshCommandBinding_Executed" />
    ... 
        </Page.CommandBindings>

    Вызов команды добавлен в меню

    <MenuItem Header="Обновить" Command="Refresh"/>

    и панель инструментов

    <Button Name="Refresh" Margin="5,2,5,2"  Command="Refresh"
            ToolTip="обновить данные по сотрудникам">
        <Image Source="Images/Refresh.jpg" ></Image>
    </Button>

    Основное назначение обработчика команды RefreshCommandBinding_Executed – обновление списка ListEmployee.

    private void RefreshCommandBinding_Executed(object sender, 
    ExecutedRoutedEventArgs e)
    {
        RewriteEmployee();
        DataGridEmployee.IsReadOnly = false;
        isDirty = false;
        SetUnvisibilityFind();
    }

    Команда "Обновить" обычно используется после поиска данных или ввода данных по новому сотруднику.

    Вернуться к учебному плану