В процессе выполнения работы необходимо разработать браузерное/внебраузерное
Создайте новое
(рис 9.1) Создание новое веб-приложение ASP.NETВ проекте целесообразно использовать порт 12345. Для этого откройтве окно свойств проекта и на вкладке ).
(рис 9.2) Создание новое веб-приложение ASP.NETДобавьте в проект новый элемент – модель
При завершении работы мастера создания
).
(рис 9.3) Добавление в проект службы данныхВ объявлении сгенерированного класса 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;
}
}
Результаты запуска службы данных приведены на рис 9.4.
(рис 9.4) Тестирование службы данныхСоздайте для имеющегося решения новый проект
(рис 9.5) Создание нового проекта – приложения SilverlightВ окне мастера создания приложения
(рис 9.6) Окно мастера создания приложения SilverlightВ обозревателе решений будет добавлен новый проект ).
(рис 9.7) Дизайнер решения с приложением SilverlightДля проекта ).
(рис 9.8) Добавление ссылки на службу WcfDataServicePersonВ обозревателе решений будет добавлена ссылка на службу (рис 9.9).
(рис 9.9) Ссылка на службу в обозревателе решенийДобавление в проект нового файла кода позволяет приложению оперировать с классами данных, которые используются для доступа к ресурсам служб данных, и взаимодействовать с ними, как с объектами. Классы данных создаются в стандартном пространстве имен клиентского приложения, которое в нашем случае имеет имя Personal.
Для обеспечения функциональности необходимо добавить интерфейсные элементы на
Сервисы WCF Data Services позволяют выполнять запросы к службе данных из клиентского приложения на основе библиотеки .NET Framework с использованием сформированных клиентских классов службы данных. Клиентская библиотека преобразует запрос, определенный на клиенте как экземпляр класса DataServiceQuery.
При выполнении запросов можно явно вызывать метод асинхронного вызова BeginExecute с объектом DataServiceQuery<T> или выполнить запрос LINQ к именованному объекту DataServiceQuery<T>, который получен из контекста DataServiceContext.
Использование метода асинхронного вызова BeginExecute или выполнение запроса LINQ в лабораторной работе необходимо согласовать с преподаватемем.
Подход к применению метода асинхронного вызова BeginExecute рассмотрим не примере базы данных Personal. В лабораторной работе необходимо использовать базу данных в соответствии с вариантом задания.
Для загрузки в приложение таблицы Employee базы данных Personal добавьте в коде класса MainPage ссылки на библиотеку Data.Services.Client и созданный сервис данных Personal
using System.Data.Services.Client; using SilverlightAppPersonal.Personal;
а также контекст сущностей context сервиса
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> представляет коллекцию
При загрузке
Добавьте в
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;
}
<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 для
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;
}
}
Подход к выполнению запрос LINQрассмотрим не примере базы данных Personal. В лабораторной работе необходимо использовать базу данных в соответствии с вариантом задания.
Класс 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> для приложения
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);
}
}
Следует отметить, что по сравнению с
Привязка данных к элементу списка ListBox. При использовании в приложении списка ListBox необходимо выполнить привязку к нему данных. Методика
Для привязки используем шаблон 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>
В списке должны отображаться фамилия имя и отчества сотрудника.
Для привязки отображения отдельных данные из списка к детальным данным по сотруднику необходимо для контента основной 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}">
Для элемента управления TextBox привязка присоединяемого свойства Text к текущему выделению списка listBox будет иметь следующий вид:
Text="{Binding Path= EmployeeSurname, Mode=TwoWay}"
Для объекта привязки Binding задается только привязываемое свойство ( Path=EmployeeSurname ), а элемент привязки был ранее определен в контенте основной Grid (свойство DataContext ). Свойство Mode задается значение TwoWay, что определяет двустороннюю привязку.
Привязка данных к элементу ComboBox. При использовании в приложении выпадающего списка ComboBox необходимо выполнить привязку к нему данных. Методика привязуки данных к выпадающего списка ComboBox рассматривается на примере списка должностей сотрудников базы данных Personal.
Для обеспечения работы с ограниченным списком добавьте в приложение специальный класс 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 создайте экземпляр коллекции 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;
}
После формирования коллекции accesses её значение присваивается источнику данных выпадающего списка comboBoxAccess.
comboBoxAccess.ItemsSource = accesses;
В
<ComboBox Height="25" Name="comboBoxAccess" Margin="5,2,2,2"
SelectedValue="{Binding Path=Access, Mode=TwoWay}"
SelectedValuePath="AccessRole" DisplayMemberPath="AccessRole " />
Привязка данных из связанных таблиц. При использовании в приложении связанных таблиц необходимо выполнить привязку к ним данных. Методика привязуки данных к связанным таблицам рассматривается на примере списка сотрудников базы данных Personal.
Должности сотрудников хранятся с отдельной таблице 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();
}
В
<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" />
Для редактирования данных необходимо обеспечить возможность изменения детальных данных, что реализуется нажатием кнопки Редактировать. С учетом того, что для элементов управления детальными данными выполнена двусторонняя
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;
}
Проведенные изменения обеспечивают для разрабатываемого приложения возможность редактирования данных и сохранение изменений в базе данных.
Создание новых записей в базе данных рассматривается на примере базы Personal.
Для создания новых записей в базе данных изменим код метода 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;
Для удаления данных в базе данных необходимо определить экземпляр удаляемого объекта, например из списка listBox.
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);
}
}
Для реализации привязки графических данных модели с элементом управления Image необходимо создание класса преобразователя значений. Класс конвертора должен реализовывать интерфейс IValueConverter и содержать два метода Convert() и ConvertBack() для прямого и
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
BitmapImage bi = new BitmapImage();
if (value != null)
{
if (value != null)
bi.SetSource(new MemoryStream((Byte[])value));
return bi;
}
return bi;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException("Отсутствует ConvertBack");
}
}
Метод Convert() осуществляет прямое преобразование массива байт Byte[] в объект класса BitmapImage, то есть изменяет источник данных перед их передачей целевому объекту для отображения в пользовательском интерфейсе. Параметрами метода является:
Возвращаемое значение метода Convert() – object, которое в нашем случае является объектом bi класса BitmapImage.
В методе Convert() вначале создается экземпляр bi класса BitmapImage.
BitmapImage bi = new BitmapImage();
Если входное значение параметра value определено, то задается источник для объекта bi класса BitmapImage на основании потока MemoryStream из массива байт.
if (value != null)
{
if (value != null)
bi.SetSource(new MemoryStream((Byte[])value));
return bi;
}
Для отображения фотографии в списке listBox необходимо модифицировать шаблон DataTemplate этого списка. Добавим в шаблон сетку с двумя колонками.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
.
</Grid>
В первую колонку поместим интерфейсный элемент Image для размещения фотографии.
<Image Grid.Column="0"
Source="{Binding Path=Picture, Mode=OneWay,
Converter={StaticResource ImageConverter}}"
Height="87" Width="60" Margin="5"
Name="Image"
Stretch="Uniform" />
В качестве источника для интерфейсного элемента Image зададим привязку к полю Picture объекта Employee, режим односторонней привязки ( Mode=OneWay ), а также конвертор, ссылающийся на статический ресурс. Предварительно необходимо определить пространство имен, где расположен класс конвертора
xmlns:converter="clr-namespace:DataBindingPersonal.Converters"
и затем определить ресурс для основного окна приложения
<UserControl.Resources>
<converter:ImageConverter x:Key="ImageConverter" />
</UserControl.Resources>
Во вторую колонку сетки поместим текстовые поля с данными по сотруднику.
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock Text="{Binding Path=EmployeeSurname}" Margin="5,5,5,5" />
<TextBlock Text="{Binding Path=EmployeeName}" Margin="5" />
<TextBlock Text="{Binding Path=EmployeePatronymic}" Margin="5" />
</StackPanel>
Модифицированная
<ListBox Grid.Row="1" Name="listBoxEmployees" HorizontalAlignment="Center"
Margin="11,2,29,9" Padding="3" Width="300" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=Picture, Mode=OneWay,
Converter={StaticResource ImageConverter}}"
Height="87" Width="60" Margin="5"
Name="Image" Stretch="Uniform" />
<StackPanel Grid.Column="1" Orientation="Vertical">
<TextBlock Text="{Binding Path=EmployeeSurname}" Margin="5,5,5,5" />
<TextBlock Text="{Binding Path=EmployeeName}" Margin="5" />
<TextBlock Text="{Binding Path=EmployeePatronymic}" Margin="5" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Для элементов пользовательского
Для примера поставим задачу создания следующего визуального представления и поведения элемента управления ListBox – listBoxEmployees:
Перед изменением представления интерфейсного элемента ListBox целесообразно подготовить объектные ресурсы для задания цветов и кистей рисования.
Для реализации требуемого визуального представления и поведения элемента управления ListBox спроектируем три стиля:
Кроме того, необходимо выделить в отдельный ресурс шаблон используемый для формирования данных отдельного элемента списка ListBox – ListBoxItemPhotoDataTemplate.
Стиль ListBoxItemStyle создается для типа ListBoxItem (TargetType="ListBoxItem") и в нем определяются четыре свойства: Padding, VerticalAlignment, HorizontalAlignment и Template.
Style x:Key="ListBoxItemStyle" TargetType="ListBoxItem">
<Setter Property="Padding" Value="1"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="Template">
<Setter.Value>
...
<!—Определение вложенного свойства ControlTemplate -->
...
</Setter.Value>
</Setter>
</Style>
Основная задача при формировании визуального представления и поведения элемента управления ListBoxItem сводится к заданию вложенного свойства ControlTemplate. Это определяется тем, что визуальная структура и визуальное поведение интерфейсного элемента задается в его шаблоне ControlTemplate.
<ControlTemplate TargetType="ListBoxItem">
<Border
Name="Border"
Padding="5"
Width="200" Height="126"
CornerRadius="5"
Background="{StaticResource ListBoxItemPhotoActiveBGSolidBrush}">
<!—Определение визуального поведения в различных состояниях -->. . .
<Border
BorderBrush="{StaticResource PhotoSelectedBGSolidBrush}"
BorderThickness="1"
CornerRadius="5">
<ContentPresenter x:Name="contentPresenter" />
</Border>
</Border>
</ControlTemplate>
Шаблон ControlTemplate содержит две рамки. Внешняя рамка имеет имя Border, а внутренняя - задана без имени. Свойства заливки фона Background, для внешней рамки, и контура BorderBrush, для внутренней рамки, заданы в расширенной разметке ссылкой на статические ресурсы.
Свойство ContentPresenter внутренней рамки интерфейсного элемента ListBoxItem определяет где должно отображаться содержание ( Content ) данного элемента.
Визуальное поведение задает способ отображения элемента управления в определенных состояниях. Для
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
...
<!—Описание состояний и поведения в этих состояниях -->...
...
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates">
...
<!—Описание состояний и поведения в этих состояниях -->...
...
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Каждый объект VisualStateGroup содержит коллекцию взаимоисключающих объектов VisualState. Элемент управления всегда находится в точности одном состоянии в каждой группе VisualStateGroup.
Для группы CommonStates определим функциональность для состояний Normal и MouseOver. При наведении указателя мыши на элемент списка ListBoxItem (состояние MouseOver ) свойство ColorAnimation определяет процесс анимации значения свойства Background (Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)") для объекта Border (Storyboard.TargetName="Border") от исходного значения к целевому (To="{StaticResource PhotoSelectedBGColor}") в указанном интервале Duration (Duration="0:0:0").
При переходе в состояние Normal, то есть, когда указатель мыши покидает элемент списка, анимация прерывается и внешняя рамка элемента списка принимаем исходный вид.
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0"
To="{StaticResource PhotoSelectedBGColor}"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
Storyboard.TargetName="Border" />
</Storyboard>
</VisualState>
Для группы SelectionStates определим функциональность для состояний Unselected и Selected.
<VisualState x:Name="Unselected" />
<VisualState x:Name="Selected">
<Storyboard>
<ColorAnimation Duration="0:0:0"
To="{StaticResource ItemSelectedBGColor}"
Storyboard.TargetProperty= (Border.Background).(SolidColorBrush.Color)"
Storyboard.TargetName="Border" />
</Storyboard>
</VisualState>
Стиль ListBoxPhotoStyle задается для типа ListBox и определяет элемент прокрутки ScrollViewer. Стиль элемент прокрутки задается ссылкой на статический ресурс ScrollViewerPhotoStyle.
Стиль ScrollViewerPhotoStyle задается для типа ScrollViewer (TargetType="ScrollViewer") и в нем определяются четыре свойства: HorizontalScrollBarVisibility, VerticalScrollBarVisibility, BorderThickness и Template.
<Style x:Key="ScrollViewerPhotoStyle" TargetType="ScrollViewer">
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<!—Определение вложенного свойства ControlTemplate -->
</Setter.Value>
</Setter>
</Style>
Шаблон ControlTemplate содержит два контейнера Grid. Для внешней сетки (Grid) задается свойство ScrollContentPresenter, определяющее где и как будет отображаться содержимое элемента управления ScrollViewer. Вложенная сетка имеет две колонки.
<ControlTemplate TargetType="ScrollViewer">
<Grid>
<ScrollContentPresenter
x:Name="ScrollContentPresenter"
Cursor="{TemplateBinding Cursor}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Grid.Column="1" Grid.Row="1"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!—Определение свойства ScrollBar -->
</Grid>
</Grid>
</ControlTemplate>
Элемент управления ScrollBar предоставляет полосу прокрутки с перемещаемым элементом, позиция которого соответствует значению.
<ScrollBar
x:Name="VerticalScrollBar"
Grid.Row="0" Grid.Column="1"
IsTabStop="False"
Maximum="{TemplateBinding ScrollableHeight}"
Height="250" Minimum="0"
Orientation="Vertical"
VerticalAlignment="Center"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"
Value="{TemplateBinding VerticalOffset}"
ViewportSize="{TemplateBinding ViewportHeight}"
Width="18" Margin="10" HorizontalAlignment="Center"
Opacity="0.5"/>
Шаблон данных ListBoxItemPhotoDataTemplate практически не отличается от ранее определенного шаблона для элемента listBoxEmployees, за исключением внешней и внутренней рамок. Полный код
Стили и шаблон для интерфейсного элемента ListBox поместим в словарь ресурсов StylesAndTemplates.xaml. Так как созданные стили ссылаются на ресурсы из файла ColorsAndBrushesRD.xaml, то данные ресурсы необходимо сделать доступными в файле StylesAndTemplates.xaml с помощью свойства MergedDictionaries словаря ResourceDictionary. Свойство MergedDictionaries представляет различные словари ресурсов в объединенных словарях.
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ColorsAndBrushesRD.xaml"/>
</ResourceDictionary.MergedDictionaries>
После создания стилей и шаблон для интерфейсного элемента ListBox необходимо модифицировать
<ListBox Grid.Row="1" Name="listBoxEmployees" HorizontalAlignment="Center"
Margin="45,2,76,9" Padding="3" Width="263"
ItemContainerStyle="{StaticResource ListBoxItemStyle}"
Style="{StaticResource ListBoxPhotoStyle}"
ItemTemplate="{StaticResource ListBoxItemPhotoDataTemplate}"
Background="{x:Null}" />
Создадим стиль ButtonStyle для типа Button (TargetType="Button") и в нем определяются шесть свойств: Background, Foreground, Padding, BorderThickness, BorderBrush и Template. Для свойств Background, Foreground, Padding и BorderThickness задаются конкретные значения, а для свойства BorderBrush определяется кисть градиентной заливки.
Вложенное свойство ControlTemplate определено для типа Button (TargetType="Button"). Оно содержит контейнер Grid, для которого задается свойства Cursor (Cursor="Hand"). Свойство Cursor будет определять изменение вида курсора при наведении указателя мыши на кнопку. Вложенное свойство зависимостей VisualStateManager.VisualStateGroups элемента управления определяет визуальное представление элемента управления в различных состояниях. Рамка ( Border ) с именем Background определяет визуальное представление, которое будет изменяеться в различных состояниях кнопки. Свойство ContentPresenter определяет, что и как будет формироваться на панели кнопки. Три прямоугольника используются для представления внутреннего содержания кнопки в режимах получения фокуса ( FocusVisualElement ), наведения указателя мыши ( MouseOverVisualElement ) и в недоступном состоянии ( DisabledVisualElement ).
Для проектируемого стиля необходимо задать визуальное представление для состояний CommonStates и FocusStates.
Для группы состояний CommonStates в состоянии Normal применяется визуальное представление по умолчанию, а для состояний MouseOver, Pressed и Disabled задается как цветовая ( ColorAnimation ), так числовая ( DoubleAnimation ) анимация свойств кнопки.
Для группы состояний FocusStates в состоянии Unfocused применяется визуальное представление по умолчанию, а для состояния Focused задается как цветовая анимация свойств кнопки – изменяется прозрачность.
Стиль для интерфейсного элемента Buttun добавим в словарь ресурсов StylesAndTemplates.xaml. далее необходимо модифицировать
<Button Name="ButtonNew"
Content="Создать"
Margin="5"
Padding="10,5,10,5"
Height="25"
Click="New_Click"
ToolTipService.ToolTip="Создать новую запись"
IsEnabled="False"
Style="{StaticResource ButtonStyle}" />
Визуальное представление кнопки на основе спроектированного стиля .
(рис 9.10) Визуальное представление кнопки в различных состояниях
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.