При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. |
Модификация клиентского Silverlight приложения
Цель
Освоить основные приемы разработки эффективного интерфейса пользователя с помощью стилей и шаблонов.
Модификация серверной части и приложения
В корпоративных базах данных часто возникает необходимость хранения изображений, в частности, фотографий сотрудников. Для хранения фотографий в базе данных SQL Server 2008 можно использовать тип данных больших значений varbinary(max), который обеспечивает хранение до 231-1 байт данных.
Модифицируем базу данных Personal, используемую в "Разработка приложения на базе WPF" . Переименуем её, задав имя Personal_Picture, и добавим в таблицу Employee поле Picture типа varbinary(max). Тип данных varbinary(max) обеспечивает хранение двоичных данных в строке.
Модификация серверной части PersonDataService сводится к построению модели данных для обновленной базы данных Personal_Picture. Имя модели данных зададим Person.edmx, а построенная структура модели будет иметь вид, приведенный на рис. 8.1.
В серверной части необходимо построить заново службу WCF Data Services - Person.svc, а на клиенте – обновить ссылку на службу данных.
Для реализации функциональности отображения фотографий на странице приложения необходимо:
- разработать конвертор для преобразования данных из базы в данные, которые могут отображаться в элементах управления WPF;
- изменить шаблон данных списка listBoxEmployees, обеспечив возможность отображения фотографий;
- добавить в детальных данных по сотруднику элемент управления Image для отображения большой фотографии сотрудника;
- добавить кнопку для изменения фотографии сотрудника.
Конвертор графических данных
В базе данных Personal_Picture данные о фотографии сотрудника хранятся в поле Picture, которое имеет тип varbinary(max), а в модели данных Person представляется массивом байт Byte[]. На странице Silverlight-приложения фотографии могут отображаться с помощью элемента контроля Image, для которого необходимо сформировать экземпляр класса BitmapImage. Таким образом, для реализации привязки данных поля Picture модели данных с элементом управления Image необходимо создание класса преобразователя значений. Класс конвертора должен реализовывать интерфейс IValueConverter и содержать два метода Convert() и ConvertBack() для прямого и обратного преобразования данных. Код класса ImageConverter конвертора двоичных данных в графический формат имеет следующий вид.
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, то есть изменяет источник данных перед их передачей целевому объекту для отображения в пользовательском интерфейсе. Параметрами метода является:
- value типа object – исходные данные, передаваемые целевому объекту, в нашем случае массива байт;
- targetType – тип данных, ожидаемый целевым свойством зависимостей;
- parameter – необязательный параметр для использования в логике преобразователя;
- culture – язык и региональные параметры преобразования.
Возвращаемое значение метода 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; }
Обратное преобразование по логике программы не требуется, поэтому метод ConvertBack() определяет только исключение, если к нему будет обращение.
Модификация шаблона данных списка listBoxEmployees
Для отображения фотографии в списке listBoxEmployees необходимо модифицировать шаблон 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>
Модифицированная XAML-разметка для интерфейсного элемента listBoxEmployees примет следующий вид.
<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>
Отображение и редактирование фотографии сотрудника
Для отображения фотографии в детальных данных по сотруднику добавим в StackPanel интерфейсный элемент Image.
<Image Name="ImageEmployee" Source="{Binding Path=Picture, Mode=OneWay, Converter={StaticResource ImageConverter}}" Margin="5" Width="210" Height="225" Stretch="UniformToFill" />
Задание источника данных для элемента ImageEmployee аналогично тому, как это было сделано для списка listBoxEmployees.
Реализация функциональности изменения фотографии сотрудника осуществлена введением дополнительной кнопки и обработчика события Click - ChangeFoto_Click.
<Button Content="Изменить фото" Margin="5,100,5,5" Height="23" Name="ButtonChangeFoto" Width="117" Click="ChangeFoto_Click" IsEnabled="False" />
В обработчике события ChangeFoto_Click используется экземпляр класса диалогового окна OpenFileDialog, с помощью которого осуществляется считывание графического (*.jpg) файла и формируется поток байтов.
Stream stream = (Stream)openFileDialog.File.OpenRead();
На основе потока stream формируется массив байтов.
byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length);
Полученный массив байтов присваивается выделенному элементу списка listBoxEmployees.
(listBoxEmployees.SelectedItem as Employee).Picture = bytes;
Полный код обработчика ChangeFoto_Click приведен ниже.
private void ChangeFoto_Click(object sender, RoutedEventArgs e) { OpenFileDialog openFileDialog = new OpenFileDialog { Filter = "JPEG files|*.jpg" }; if (openFileDialog.ShowDialog() == true) { using (Stream stream = (Stream)openFileDialog.File.OpenRead()) { byte[] bytes = new byte[stream.Length]; stream.Read(bytes, 0, (int)stream.Length); (listBoxEmployees.SelectedItem as Employee).Picture = bytes; } } }
Модифицированная страница приложения приведена на рис. 8.2.
Созданные ранее методы (см. "Разработка приложения на базе WPF" ), обеспечивающие функциональность приложения (сохранить данные – Save_Click, отмерить редактирование – Undo_Click, создать новую запись – New_Click, загрузить данные из базы – Open_Click, редактировать данные по сотруднику – Edit_Click, удалить данные по сотруднику – Delete_Click ) не требуют модификации.
После компиляции и запуска страница приложения имеет вид, приведенный на рис. 8.3.