Освоить основные приемы разработки корпоративных Silverlight- приложений, обеспечивающих взаимодействие клиентской части с базой данных.
В отмечается, что Silverlight имеет следующие преимущества:
В технологии Silverlight используются надстройки браузера, которые обеспечивают следующие средства создания интерактивных приложений:
Технология Silverlight базируется на подмножестве модели WPF. Это обстоятельство определяет следующие общие для Silverlight и WPF особенности:
Для изучения технологии проектирования Silverlight- приложения будем испльзовать учебный пример с базой данных Person и приложением, которое реализует стандартные фунции:
В общем случае корпоративное приложение состоит из клиентской и серверной частей. Серверная часть должна обеспечивать взаимодействие клиента с базой данных.
Основой серверной части Silverlight- приложения является веб-сервер. В рамках веб-сервера создаются источники данных и службы, предоставляющие доступ к данным.
Взаимодействие приложения Silverlight с источником данных базируется на службе данных WCF и модели "
Концептуальная модель является специфическим представлением структуры некоторых данных в виде сущностей и связей. Одним из способов
(рис 7.1) Концептуальная модель базы данныхТаблица Employee содержит данные по сотруднику. Атрибутами таблицы являются:
Таблица JobTitle является справочником должностей, имеющихся на предприятии, и включает следующие атрибуты:
Разработку Silverlight-приложения, взаимодействующего с базой данных, начнем с конструирования
(рис 7.2) Создание новое веб-приложение ASP.NETВ проекте будем использовать порт 12345. Для этого откроем окно свойств проекта и на вкладке ).
(рис 7.3) Создание новое веб-приложение ASP.NETДобавим в проект новый элемент – модель
При завершении работы мастера создания
(рис 7.4) Проект с моделью EDMСлужбы WCF Data Services входят в состав библиотеки .NET Framework и состоят из набора шаблонов и библиотек. Они позволяют создавать службы данных и обращаться к ним в Интернете, интрасети или в приложении. Службы WCF Data Services предоставляют данные как ресурсы, адресуемые с помощью URI. Это позволяет обращаться к данным и изменять их с использованием семантики
Службы WCF Data Services взаимодействуют с сущностей модели
В состав служб WCF Data Services включен набор клиентских библиотек:
Клиентские библиотеки предоставляют возможности объектно-ориентированного программирования для доступа к службам WCF Data Services из таких платформ, как .NET Framework и Silverlight.
Создание службы данных начнем с добавления в проект нового элемента – службы данных WCF, файл которой назовем ).
(рис 7.5) Добавление в проект службы данныхВ объявлении сгенерированного класса WcfDataServicePerson необходимо указать обобщенный тип PersonalEntities (
public class WcfDataServicePerson : DataService<PersonalEntities>
и права при работе с таблицами базы данных:
config.SetEntitySetAccessRule("Employees", EntitySetRights.All);
config.SetEntitySetAccessRule("JobTitles", EntitySetRights.All);
Это обеспечивает авторизованным клиентам доступ к ресурсам указанных наборов сущностей.
Отредактированный класс службы данных будет иметь следующий вид:
public class WcfDataServicePerson: DataService<PersonalEntities >
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("Employees", EntitySetRights.All);
config.SetEntitySetAccessRule("JobTitles", EntitySetRights.All);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
}
}
Для тестирования запустим службу. Результаты выполнения приведены на рис 7.6.
(рис 7.6) Тестирование службы данныхНа этом создание службы данных завершено.
Разрабатываемое клиентское приложение должно обеспечивать чтение информации по сотруднику из базы данных, просмотр, редактирование, обновление, добавление и удаление данных. В качестве клиентского приложения будем использовать Silverlight. Silverlight объединяет возможности сервера, Интернета и персонального компьютера; возможности
Silverlight позволяет создавать приложения, используя любой поддерживаемый платформой .NET Framework язык (включая Visual Basic, C# и JavaScript). Visual Studio и Expression Blend предоставляют возможности разработки приложений Silverlight. Можно создавать веб-страницы, использующие как элементы HTML, так и элементы WPF с Silverlight. Подобно HTML, XAML позволяет создать пользовательский интерфейс для
Создадим для нашего решения новый проект Silverlight и назовем его SilverlightAppPersonal (рис 7.7).
(рис 7.7) Создание нового проекта – приложения SilverlightВ окне мастера создания приложения Silverlight проверьте размещение приложения и версию Silverlight (рис 7.8). В нашем проекте приложение Silverlight должно размещаться на существующем сайте в решении PersonService, а версия Silverlight должна быть – 4.
(рис 7.8) Окно мастера создания приложения SilverlightВ обозревателе решений будет добавлен новый проект ).
(рис 7.9) Дизайнер решения с приложением SilverlightДля проекта ).
(рис 7.10) Добавление ссылки на службу WcfDataServicePersonВ обозревателе решений будет добавлена ссылка на службу (рис 7.11).
(рис 7.11) Ссылка на службу в обозревателе решенийДобавление в проект нового файла кода позволяет приложению оперировать с классами данных, которые используются для доступа к ресурсам служб данных, и взаимодействовать с ними, как с объектами. Классы данных создаются в стандартном пространстве имен клиентского приложения, которое в нашем случае имеет имя Personal.
Для обеспечения функциональности добавим интерфейсные элементы на главную страницу приложения в соответствии с рис 7.12.
(рис 7.12) Главная страница приложенияXAML-код
Созданная страница клиентского приложения имеет все необходимые интерфейсные элементы для обеспечения требуемой функциональности. В XAML-коде описания
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Следующим шагом разработки является последовательное добавление функциональности приложения.
Сервисы WCF Data Services позволяют выполнять запросы к службе данных из клиентского приложения на основе библиотеки .NET Framework с использованием сформированных клиентских классов службы данных. Клиентская библиотека преобразует запрос, определенный на клиенте как экземпляр класса DataServiceQuery, в сообщение запроса HTTP GET. Библиотека получает ответное сообщение и преобразует его в экземпляры классов клиентской службы данных. Эти классы отслеживаются экземпляром DataServiceContext, которому принадлежит объект DataServiceQuery.
При выполнении запросов можно явно вызывать метод асинхронного вызова BeginExecute с объектом DataServiceQuery<T> или выполнить запрос LINQ к именованному объекту DataServiceQuery<T>, который получен из контекста DataServiceContext.
Для загрузки в приложение таблицы Employee базы данных Personal добавим в коде класса MainPage ссылки на библиотеку Data.Services.Client и созданный сервис данных Personal
using System.Data.Services.Client; using SilverlightAppPersonal.Personal;
а также контекст сущностей context сервиса модели данных EmployeeEntities и экземпляр коллекции employees класса DataServiceCollection<T>
EmployeeEntities context; DataServiceCollection<Employee> employees;
После внесенных изменений код класса MainPage будет следующим:
using System.Windows.Controls;
using System.Data.Services.Client;
using SilverlightAppPersonal.Personal;
namespace SilverlightAppPersonal
{
public partial class MainPage : UserControl
{
EmployeeEntities context;
DataServiceCollection<Employee> employees;
public MainPage()
{
InitializeComponent();
}
}
}
Класс DataServiceCollection<T> представляет коллекцию
При загрузке
Добавим в XAML-код описание события Loaded класса MainPage со ссылкой на обработчик MainPage_Loaded:
Loaded="MainPage_Loaded"
В коде класса MainPage добавим обработчик MainPage_Loaded:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
context = new EmployeeEntities(new Uri(
"WfcDataServicePerson.svc", UriKind.Relative));
employees = new DataServiceCollection<Employee>();
employees.LoadCompleted += new
EventHandler<LoadCompletedEventArgs>(employees_LoadCompleted);
ButtonOpen.IsEnabled = true;
}
С учетом того, что при генерации страницы все кнопки создаются как недоступные, то после загрузки необходимо кнопку
ButtonOpen.IsEnabled = true;
<Button Content="Загрузить" Height="25" Name="ButtonOpen" Margin="35,0,0,0"
Padding="10,5,10,5" Click="Open_Click" />
В код класса MainPage добавим метод Open_Click обработчика события Click кнопки ButtonOpen.
private void Open_Click(object sender, RoutedEventArgs e)
{
ResetBindingData();
DataServiceQuery<Employee> queryEmployee =
context.Employees.AddQueryOption("$orderby", "EmployeeSurname");
try
{
queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Метод ResetBindingData() предназначен для очистка коллекции данных employees, списков сотрудников listBoxEmployees и должностей comboBoxTitle:
private void ResetBindingData()
{
employees.Clear();
listBoxEmployees.ItemsSource = null;
comboBoxTitle.ItemsSource = null;
LayoutRoot.UpdateLayout();
}
Далее формируется запрос queryEmployee на получение данных, при этом задается режим сортировки данных по фамилии сотрудника ( EmployeeSurname ):
DataServiceQuery<Employee> queryEmployee =
context.Employees.AddQueryOption("$orderby", "EmployeeSurname");
С помощью метода AddQueryOption в запрос добавляются параметры. Службы данных WCF поддерживают следующие параметры запросов, приведенные в табл. 7.1.
| Параметр запроса | Описание |
|---|---|
| $orderby | Определяет порядок сортировки по умолчанию для сущностей в возвращенном канале |
| $top | Указывает количество сущностей, которые необходимо включить в возвращаемый канал |
| $skip | Указывает количество сущностей, которые необходимо пропустить перед возвратом сущностей в канал |
| $filter | Определяет выражение, фильтрующее сущности, которые возвращаются в канал на основе определенного критерия. Этот параметр запроса поддерживает набор операторов логического сравнения, |
| $expand | Указываются связанные сущности, возвращаемые запросом. Связанные сущности включаются либо в качестве канала, либо в качестве записи, встроенной в сущность, возвращаемую запросом |
| $format | Указывает формат возвращаемого канала. По умолчанию для каналов задается формат |
| $select | Указывает проекцию, определяющую свойства сущности, возвращаемые в проекции. По умолчанию в канале возвращаются все свойства сущности |
| $inlinecount | Запрашивает включение в поток количества сущностей, возвращаемых в канале |
Запросы службы данных WCF для Silverlight выполняются асинхронно. Это реализуется методом BeginExecute(), который использует в качестве параметров делегат OnEmployeeQueryComplete и запрос queryEmployee.
queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);
Делегат OnEmployeeQueryComplete использует класс Dispatcher для того, чтобы обеспечить асинхронное получение результата в правильном потоке.
private void OnEmployeeQueryComplete(IAsyncResult result)
{
Dispatcher.BeginInvoke(() =>
{
DataServiceQuery<Employee> queryEmployee =
result.AsyncState as DataServiceQuery<Employee>;
try
{
employees.LoadAsync(queryEmployee);
}
catch (DataServiceQueryException ex)
{
MessageBox.Show(string.Format("Ошибка запроса в БД: {0} - {1}",
ex.Response.StatusCode.ToString(), ex.Response.Error.Message));
}
});
}
На данном этапе нам осталось добавить в код класса MainPage делегат employees_LoadCompleted для события LoadCompleted коллекции employees, в котором после завершения формирования коллекции employees задается источник данных для списка listBoxEmployees и производится выделение первого элемента списка.
private void employees_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
if (e.Error == null)
{
if (employees.Continuation != null)
{
employees.LoadNextPartialSetAsync();
}
else
{
listBoxEmployees.ItemsSource = employees;
listBoxEmployees.UpdateLayout();
if (listBoxEmployees.Items.Count > 0)
listBoxEmployees.SelectedIndex = 0;
ButtonOpen.IsEnabled = false;
}
}
else
{
MessageBox.Show(string.Format("Ошибка формирования коллекции: {0}",
e.Error.Message));
ButtonOpen.IsEnabled = true;
}
}
Класс DataServiceQuery реализует интерфейс IQueryable, определяемый языком LINQ. Клиентская библиотека службы WCF Data Services может преобразовывать запросы LINQ к данным набора сущностей в URI, который представляет выражение запроса, вычисляемое для ресурса службы данных.
Сформируем запрос LINQ для получения приложением данных сущности Employee.
var allEmpoyees = from emp in context.Employees
orderby emp.EmployeeSurname
select emp;
Формирование коллекции employees класса DataServiceCollection<Employee> для приложения Silverlight может осуществляться только асинхронно, что определяет необходимость использования метода LoadAsync, который требует приведения параметра метода к типу DataServiceQuery<T>, в нашем случае к типу DataServiceQuery<Employee>.
employees.LoadAsync(allEmpoyees as DataServiceQuery<Employee>);
При асинхронной загрузке коллекции employees необходимо сформировать и обработать события загрузки коллекции типа employees.LoadCompleted для сущности Employee и события titles.LoadCompleted для сущности JobTitle.
Для случая использования запроса LINQ метод Open_Click() будет представлен следующим кодом.
private void Open_Click(object sender, RoutedEventArgs e)
{
ResetBindingData();
listBoxEmployees.ItemsSource = null;
comboBoxTitle.ItemsSource = null;
LayoutRoot.UpdateLayout();
try
{
var allEmpoyees = from emp in context.Employees
orderby emp.EmployeeSurname
select emp;
employees.LoadAsync(allEmpoyees as DataServiceQuery<Employee>);
var allTitle = from titl in context.JobTitles
select titl;
titles.LoadAsync(allTitle as DataServiceQuery<JobTitle>);
}
catch (DataServiceClientException ex)
{
MessageBox.Show(ex.Message);
}
}
Следует отметить, что по сравнению с
Для тестирования нашего приложения необходимо сделать привязку списка listBoxEmployees к данным. Для этого используем шаблон DataTemplate.
<ListBox Grid.Row="1" Name="listBoxEmployees" HorizontalAlignment="Center"
Margin="5,2,15,9" Padding="3" Width="364" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=EmployeeSurname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Path=EmployeeName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding Path=EmployeePatronymic}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
В списке будем отображать фамилию, имя и отчества сотрудника.
(рис 7.13) Фрагмент главной страницы с заполненным спискомДля привязки отображения отдельных данные из списка к детальным данным по сотруднику необходимо для контента основной Grid (LayoutRoot) выполнить привязку к текущему выделению списка listBoxEmployees:
<Grid x:Name="LayoutRoot" Background="White" Margin="10"
VerticalAlignment="Top" Height="590" Width="779"
DataContext="{Binding ElementName=listBoxEmployees, Path=SelectedItem, Mode=TwoWay}">
Начнем привязку детальных данных по сотруднику с текстовых полей: LastName, FirstName, SecondtName и NetName. Для элемента управления TextBox LastName привязка присоединяемого свойства Text к текущему выделению списка listBoxEmployees будет иметь следующий вид:
Text="{Binding Path= EmployeeSurname, Mode=TwoWay}"
Для объекта привязки Binding задается только привязываемое свойство ( Path=EmployeeSurname ), а элемент привязки был ранее определен в контенте основной Grid (свойство DataContext ). Свойство Mode задается значение TwoWay, что определяет двустороннюю привязку. Измененный XAML-код для интерфейсных TextBox примет следующий вид:
<TextBox Name="LastName" Height="25" Margin="5,2,2,2"
Text="{Binding Path=EmployeeSurname, Mode=TwoWay}"></TextBox>
<TextBox Name="FirstName" Height="25" Margin="2"
Text="{Binding Path=EmployeeName, Mode=TwoWay}"></TextBox>
<TextBox Name="SecondtName" Height="25" Margin="2"
Text="{Binding Path= EmployeePatronymic, Mode=TwoWay}"></TextBox>
<TextBox Name="NetName" Height="25" Margin="2"
Text="{Binding Path=NetName, Mode=TwoWay}"></TextBox>
После запуска приложения и данных страница примет вид, приведенный на рис 7.14.
(рис 7.14) Фрагмент главной страницы с детальными даннымиВ базе данных Person атрибут EmployeeStatus является целым числом, значение которого соответствует индексу списка ComboBox comboBoxStatus приложения. Функциональность приложения определяет следующий список статуса сотрудника: не задано, активен, выходной, в отпуске, болеет, не работает. Привязка для данного элемента управления выполняется для свойства SelectedIndex.
<ComboBox Name="comboBoxStatus" Height="25" Margin="2"
SelectedIndex = "{Binding Path=EmployeeStatus, Mode=TwoWay, BindsDirectlyToSource=True}">
<ComboBoxItem Content="не задано" />
<ComboBoxItem Content="активен" />
<ComboBoxItem Content="выходной" />
<ComboBoxItem Content="в отпуске" />
<ComboBoxItem Content="болеет" />
<ComboBoxItem Content="не работает" />
<ComboBoxItem Content="помечен как удаленный" />
</ComboBox>
Уровень доступа сотрудника к ресурсам информационной системы – Access в базе данных хранится в виде символьной строки. Однако перечень уровней доступа в системе фиксирован и меняется редко. Для обеспечения работы с ограниченным списком уровней доступа пользователей введем в приложение специальный класс Access. Для этого добавим в приложение SilverlightAppPersonal класс Access, имеющий свойство AccessRole, характеризующее уровень доступа сотрудника к ресурсам сети.
public class Access
{
public string AccessRole { set; get; }
public Access(string accessRole)
{
AccessRole = accessRole;
}
}
В коде класса MainPage добавим ссылку на библиотеку
System.Collections.ObjectModel.
using System.Collections.ObjectModel;
Затем определим коллекцию уровней доступа accesses.
ObservableCollection<Access> accesses;
В коде класса MainPage добавим ссылку на библиотеку System.Collections.ObjectModel.
using System.Collections.ObjectModel;
Затем определим коллекцию уровней доступа accesses.
ObservableCollection<Access> accesses;
В конструкторе класса MainPage создадим экземпляр коллекции accesses и сформируем её из следующего списка: не задано, оператор, старший оператор, начальник смены, администратор, аналитик. Измененный код конструктора класса MainPage приведен ниже.
public MainPage()
{
InitializeComponent();
accesses = new ObservableCollection<Access>();
accesses.Add(new Access("не задано"));
accesses.Add(new Access("оператор"));
accesses.Add(new Access("старший оператор"));
accesses.Add(new Access("начальник смены"));
accesses.Add(new Access("администратор"));
accesses.Add(new Access("аналитик"));
comboBoxAccess.ItemsSource = accesses;
}
После формирования коллекции её значение присваивается источнику данных выпадающего списка comboBoxAccess.
comboBoxAccess.ItemsSource = accesses;
В XAML-коде класса MainPage для выпадающего списка comboBoxAccess добавим привязку для свойства SelectedValue и зададим значение SelectedValuePath для вывода в ComboBox.
<ComboBox Height="25" Name="comboBoxAccess" Margin="5,2,2,2"
SelectedValue="{Binding Path=Access, Mode=TwoWay}"
SelectedValuePath="AccessRole" DisplayMemberPath="AccessRole " />
Должности сотрудников хранятся с отдельной таблице JobTitle базы данных Person. Перед загрузкой в приложение таблицы JobTitle немного усовершенствуем созданный ранее код. В методе Open_Click() всё, что связано с загрузкой в приложение данных таблицы Employee выделим в отдельный метод LoadEmployees():
private void LoadEmployees()
{
DataServiceQuery<Employee> queryEmployee =
context.Employees.AddQueryOption("$orderby", "EmployeeSurname");
try
{
queryEmployee.BeginExecute(OnEmployeeQueryComplete, queryEmployee);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
С учетом введения метода LoadEmployees() метод Open_Click() примет следующий вид:
private void Open_Click(object sender, RoutedEventArgs e)
{
LoadEmployees();
}
Определим в классе MainPage коллекцию titles.
DataServiceCollection<JobTitle> titles;
В методе MainPage_Loaded добавим код создания экземпляра titles коллекции
DataServiceCollection< JobTitle >: titles = new DataServiceCollection<JobTitle>();
и зарегистрируем для коллекции событие LoadCompleted с делегатом titles_LoadCompleted:
titles.LoadCompleted +=new EventHandler<LoadCompletedEventArgs>(titles_LoadCompleted);
Измененный метод MainPage_Loaded будет иметь следующий вид:
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
context =
new PersonalEntities(new Uri("WfcDataServicePerson.svc", UriKind.Relative));
employees = new DataServiceCollection<Employee>();
titles = new DataServiceCollection<JobTitle>();
employees.LoadCompleted +=
new EventHandler<LoadCompletedEventArgs>(employees_LoadCompleted);
titles.LoadCompleted +=
new EventHandler<LoadCompletedEventArgs>(titles_LoadCompleted);
ButtonOpen.IsEnabled = true;
}
По аналогии с методом LoadEmployees() создадим метод LoadTitles().
private void LoadTitles()
{
DataServiceQuery<JobTitle> queryTitle = context.JobTitles;
try
{
queryTitle.BeginExecute(OnTitleQueryComplete, queryTitle);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Далее спроектируем код делегата асинхронного запроса к службе данных.
private void OnTitleQueryComplete(IAsyncResult result)
{
Dispatcher.BeginInvoke(() =>
{
DataServiceQuery<JobTitle> queryTitle = result.AsyncState as DataServiceQuery<JobTitle>;
try
{
titles.LoadAsync(queryTitle);
}
catch (DataServiceQueryException ex)
{
MessageBox.Show(string.Format("Ошибка запроса в БД: {0} - {1}",
ex.Response.StatusCode.ToString(), ex.Response.Error.Message));
}
}
);
}
Делегат обработки события формирования коллекции типа DataServiceCollection<JobTitle> будет иметь следующий вид:
private void titles_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
if (e.Error == null)
{
if (titles.Continuation != null)
{
titles.LoadNextPartialSetAsync();
}
else
{
comboBoxTitle.ItemsSource = titles;
comboBoxTitle.UpdateLayout();
}
}
else
{
MessageBox.Show(string.Format("An error has occured: {0}", e.Error.Message));
}
}
Добавим в код метода Open_Click() вызов метода LoadTitles().
private void Open_Click(object sender, RoutedEventArgs e)
{
LoadEmployees();
LoadTitles();
}
В XAML-коде класса MainPage для выпадающего списка comboBoxTitle добавим привязку для свойства SelectedValue, SelectedValuePath и зададим значение DisplayMemberPath для вывода в ComboBox, а также обработчик события SelectionChanged:
<ComboBox Height="25" Name="comboBoxTitle" Margin="5,2,2,2"
SelectedValue="{Binding Path=JobRoleID, Mode=TwoWay}"
SelectedValuePath="ID" DisplayMemberPath="Title" />
Следует пояснить, что привязка SelectedValue="{Binding Path=JobRoleID, Mode=TwoWay}" и задание свойства для привязки SelectedValuePath="ID" используют поля JobRoleID и ID таблицы Employee через контент Grid, а выводимое в ComboBox значение задается как DisplayMemberPath="Title" и при этом используется атрибут Title таблицы JobTitle коллекции titles, являющейся источником данных для comboBoxTitle.
Для календаря DatePicker необходимо построить привязку для свойства SelectedDate.
<controls:DatePicker Name="datePickerFirstDate"
SelectedDate="{Binding Path=FirstDate, Mode=TwoWay}" Margin="5,2,2,2"/>
<controls:DatePicker Name="datePickerLastDate"
SelectedDate="{Binding Path=LastDate, Mode=TwoWay}" Margin="5,2,2,2" />
На этом
(рис 7.15) Фрагмент главной страницы с привязанными даннымиSilverlight не предлагает встроенную поддержку для модели команд WPF поэтому в разрабатываемом приложении функциональность, связанная с кнопками, должна реализовываться обработчиками событий, а доступность тех или иных кнопок управляться программно.
На странице приложения размещены шесть кнопок: Сохранить, Отменить, Создать, Загрузить, Редактировать и Удалить. Кнопки могут быть доступны или недоступны пользователю при работе приложения. Это задается свойством IsEnable. Доступность кнопок определяется состоянием, в котором находится приложение. В тоже время нажатие какой-либо кнопки переводит, как правило, приложение в какое-либо другое состояние. Для проектируемого приложения можно определить следующие состояния:
На рис 7.16 приведена диаграмма состояний приложения, на которой кружками обозначены состояния, а дуги соответствуют переходам при нажатии определенной кнопки.
(рис 7.16) Диаграмма состояний приложенияНа основе
| Состояние | Доступность кнопок - IsEnable | |||||
|---|---|---|---|---|---|---|
| Сохранить | Отменить | Создать | Загрузить | Редактировать | Удалить | |
| 1 | false | false | false | true | false | false |
| 2 | false | false | true | false | true | true |
| 3 | true | true | false | false | false | false |
| 4 | true | true | false | false | false | false |
В XAML-коде класса MainPage все кнопки определены как недоступные, то есть свойство IsEnable –false. После загрузки страницы (состояние 1) кнопка Загрузить становится доступной, а после
private void ButtonIsEnabled(bool enabele)
{
ButtonEdit.IsEnabled = enabele;
ButtonSave.IsEnabled = !enabele;
ButtonNew.IsEnabled = enabele;
ButtonUndo.IsEnabled = !enabele;
ButtonDelete.IsEnabled = enabele;
}
Кроме управления доступностью кнопок в приложении целесообразно управлять и возможностью редактирования детальных данных по сотруднику. Детальные данные должны быть доступны для редактирования в состояниях редактирования (3) и создания (4) новой записи, а в режиме просмотра – должны быть доступны только для чтения. Для управления доступностью детальных данных по сотруднику добавим в код класса MainPage метод ReadOnly().
private void ReadOnly(bool readOnly)
{
LastName1.IsReadOnly = !readOnly;
FirstName1.IsReadOnly = !readOnly;
SecondtName1.IsReadOnly = !readOnly;
NetName1.IsReadOnly = !readOnly;
comboBoxTitle.IsEnabled = readOnly;
comboBoxAccess.IsEnabled = readOnly;
comboBoxStatus.IsEnabled = readOnly;
datePickerFirstDate.IsEnabled = readOnly;
datePickerLastDate.IsEnabled = readOnly;
}
В XAML-коде класса MainPage для кнопок Сохранить, Отменить, Создать, Редактировать и Удалить добавим событие Click и обработчики событий Save_Click(), Undo_Click(), New_Click(), Edit_Click() и Delete_Click().
<Button Name="ButtonSave" Content="Сохранить" Margin="5" Padding="5" Height="25" IsEnabled="False" Click="Save_Click" /> <Button Name="ButtonUndo" Content="Отменить" Margin="5" Padding="5" Height="25" IsEnabled="False" Click="Undo_Click"/> <Button Name="ButtonNew" Content="Создать" Margin="5" Padding="5" Height="25" IsEnabled="False" Click="New_Click"/> <Button Name="ButtonEdit" Content="Редактировать" Margin="5" Padding="5" Height="25" IsEnabled="False" Click="Edit_Click"/> <Button Name="ButtonDelete" Content="Удалить" Padding="5" Margin="35,0,0,0" Height="25" IsEnabled="False" Click="Delete_Click" />
При нажатии кнопки Удалить не изменяется состояние приложения – это видно из рис 7.16, где действию Удалить соответствует петля для состояния 2, поэтому на текущем этапе разработки приложения не будем определять код метода и Delete_Click(), а для остальных созданных методов добавим код для управления доступности кнопок и элементов управления.
private void Save_Click(object sender, RoutedEventArgs e)
{
ReadOnly(false);
ButtonIsEnabled(true);
listBoxEmployees.IsEnabled = true;
}
private void Undo_Click(object sender, RoutedEventArgs e)
{
ReadOnly(false);
ButtonIsEnabled(true);
listBoxEmployees.IsEnabled = true;
}
private void Edit_Click(object sender, RoutedEventArgs e)
{
ButtonIsEnabled(false);
listBoxEmployees.IsEnabled = false;
ReadOnly(true);
}
private void New_Click(object sender, RoutedEventArgs e)
{
ButtonIsEnabled(false);
ReadOnly(true);
listBoxEmployees.IsEnabled = false;
}
private void Delete_Click(object sender, RoutedEventArgs e)
{
MessageBoxResult result = MessageBox.Show("Удалить сотрудника: " +
emp.EmployeeSurname + " " + emp.EmployeeName + " " + emp.EmployeePatronymic,
"Предупреждение", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
// Удалить выбранного сотрудника из коллекции данных
}
}
Кроме обработчиков событий кнопок необходимо внести изменения в код метода employees_LoadCompleted(), который завершает
private void employees_LoadCompleted(object sender, LoadCompletedEventArgs e)
{
if (e.Error == null)
{
if (employees.Continuation != null)
{
employees.LoadNextPartialSetAsync();
}
else
{
listBoxEmployees.ItemsSource = employees;
listBoxEmployees.UpdateLayout();
if (listBoxEmployees.Items.Count > 0)
listBoxEmployees.SelectedIndex = 0;
ButtonIsEnabled(true);
ReadOnly(false);
ButtonOpen.IsEnabled = false;
}
}
else
{
MessageBox.Show(string.Format("An error has occured: {0}", e.Error.Message));
ButtonOpen.IsEnabled = true;
}
}
Для редактирования данных по сотруднику необходимо обеспечить возможность изменения детальных данных, что реализуется нажатием кнопки Редактировать, которая задает свойству IsReadOnly элементов управления TextBox и свойству IsEnabled элементов управления ComboBox и DatePicker значение true. С учетом того, что для элементов управления детальными данными выполнена двусторонняя
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
Измененный код метода Save_Click будет иметь следующий вид:
private void Save_Click(object sender, RoutedEventArgs e)
{
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
ReadOnly(false);
ButtonIsEnabled(true);
listBoxEmployees.IsEnabled = true;
}
Метод BeginSaveChanges() класса DataServiceContent запускает асинхронную операцию сохранения данных, изменение которых были проведены в контексте сущностей PersonalEntities. Перечисление SaveChangesOptions.Batch определяет, что все проведенные изменения сохраняются в составе одного пакетного запроса. Вторым параметром метода BeginSaveChanges() является делегат, который ссылается на метод OnChangesSaved(). Метод OnChangesSaved() обрабатывает результат этой асинхронной операции путем вызова метода EndSaveChanges(), анализа возвращаемых объектов OperationResponse и печати кодов состояния ответа.
private void OnChangesSaved(IAsyncResult result)
{
Dispatcher.BeginInvoke(() =>
{
context = result.AsyncState as PersonalEntities;
try
{
WriteOperationResponse(context.EndSaveChanges(result));
}
catch (DataServiceRequestException ex)
{
MessageBox.Show( WriteOperationResponse(ex.Response));
}
catch (InvalidOperationException ex)
{
MessageBox.Show(ex.Message);
}
}
);
}
Метод WriteOperationResponse() обеспечивает формирование информации при сохранении данных.
private string WriteOperationResponse(DataServiceResponse response)
{
string mes = string.Empty;
int i = 1;
if (response.IsBatchResponse)
{
mes = string.Format("Коды серии операций сохранения: {0}\n",
response.BatchStatusCode);
}
foreach (ChangeOperationResponse change in response)
{
mes +=
string.Format("\tИзменение {0} code: {1}\n",
i.ToString(), change.StatusCode.ToString());
if (change.Error != null)
{
string.Format("\tИзменение {0} error: {1}\n",
i.ToString(), change.Error.Message);
}
i++;
}
return mes;
}
Проведенные изменения обеспечивают для разрабатываемого приложения возможность редактирования данных и сохранение изменений в базе данных.
Для создания новых записей по сотрудникам в базе данных изменим код метода New_Click().
private void New_Click(object sender, RoutedEventArgs e)
{
Employee newEmployee =
Employee.CreateEmployee(0, "не задано", "не задано", 0, "не задано");
newEmployee.Access = "не задано";
newEmployee.EmployeeStatus = 0;
newEmployee.NetName = "guest";
newEmployee.FirstDate = DateTime.Now;
try
{
context.AddToEmployees(newEmployee);
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
employees.Add(newEmployee);
int count = listBoxEmployees.Items.Count;
listBoxEmployees.SelectedIndex = count - 1;
ButtonIsEnabled(false);
ReadOnly(true);
listBoxEmployees.IsEnabled = false;
}
catch (DataServiceRequestException ex)
{
MessageBox.Show("Ошибка добавления нового сотрудника в базу данных");
}
}
Вначале для класса Employee модели сущностей Personal создаем новый экземпляр newEmployee с помощью метода CreateEmployee():
Employee newEmployee=Employee.CreateEmployee(0,
"не задано","не задано",0,"не задано");
Метод CreateEmployee() автоматически сгенерирован при создании
newEmployee.Access = "не задано"; newEmployee.EmployeeStatus = 0; newEmployee.NetName = "guest"; newEmployee.FirstDate = DateTime.Now;
Добавление новой сущности newEmployee в модель Personal производится следующей строкой кода:
context.AddToEmployees(newEmployee);
при этом используется метод AddToEmployees().
Затем посредством метода BeginSaveChanges() класса DataServiceContent запускаем сохранение данных в контексте сущностей PersonalEntities.
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
Для продолжения работы приложения необходимо внести изменения в коллекцию employees.
employees.Add(newEmployee);
и установить фокус в списке listBoxEmployees на созданной записи:
int count = listBoxEmployees.Items.Count; listBoxEmployees.SelectedIndex = count - 1;
Для удаления данных по сотруднику в базе данных необходимо определить экземпляр удаляемого объекта из списка listBoxEmployees.
Employee emp = (Employee)listBoxEmployees.SelectedItem;
Затем удалим экземпляр класса Employee из контекста сущностей PersonalEntities с помощью метода DeleteObject().
context.DeleteObject(emp);
После изменения индекса выделения в списке listBoxEmployees необходими удалить экземпляр
listBoxEmployees.SelectedIndex =
listBoxEmployees.SelectedIndex == 0 ? 1 : listBoxEmployees.SelectedIndex - 1;
employees.Remove(emp);
Посредством метода BeginSaveChanges() класса DataServiceContent запускаем сохранение изменений при удалении данных из контекста сущностей PersonalEntities.
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
Окончательный код метода Delete_Click() будет иметь следующий вид.
private void Delete_Click(object sender, RoutedEventArgs e)
{
Employee emp = (Employee)listBoxEmployees.SelectedItem;
MessageBoxResult result = MessageBox.Show("Удалить сотрудника: " + emp.EmployeeSurname + " "
+ emp.EmployeeName + " " + emp.EmployeePatronymic,
"Предупреждение", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
context.DeleteObject(emp);
listBoxEmployees.SelectedIndex =
listBoxEmployees.SelectedIndex == 0 ? 1 : listBoxEmployees.SelectedIndex - 1;
employees.Remove(emp);
context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context);
}
}
Процесс разработки Silverlight-приложения, проведенный в лекциях 5 и 6 настоящего пособия, отражает основные этапы создания корпоративных
Технология Silverlight , браузер, веб-сервер, модель "
Silverlight, Entity
Проиллюстрированы возможности построения корпоративного Silverlight-приложения, включающего серверную часть на базе веб-приложение ASP.NET, источник данных в виде базы данных для СУБД SQL Server, службу данных, клиентскую часть системы, позволяющую просматривать, редактировать, удалять и создать данные по сотрудникам предприятия.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.