Опубликован: 08.07.2011 | Доступ: свободный | Студентов: 1772 / 93 | Оценка: 4.15 / 4.08 | Длительность: 15:28:00
Лекция 7:

Модификация клиентского Silverlight приложения

Аннотация: Рассматриваются вопросы использования в элементах управления 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.

Модель данных Personal_Picture

Рис. 8.1. Модель данных Personal_Picture

В серверной части необходимо построить заново службу 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.

Страница приложения в дизайнере

увеличить изображение
Рис. 8.2. Страница приложения в дизайнере

Созданные ранее методы (см. "Разработка приложения на базе WPF" ), обеспечивающие функциональность приложения (сохранить данные – Save_Click, отмерить редактирование – Undo_Click, создать новую записьNew_Click, загрузить данные из базы – Open_Click, редактировать данные по сотруднику – Edit_Click, удалить данные по сотруднику – Delete_Click ) не требуют модификации.

После компиляции и запуска страница приложения имеет вид, приведенный на рис. 8.3.

Страница приложения

увеличить изображение
Рис. 8.3. Страница приложения
Александр Петров
Александр Петров

При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. 
Вопрос, как отследить и отключить добавление элемента в Items?

Максим Спиридонов
Максим Спиридонов

В пятой лекции на второй странице в компиляторе выскакивает ошибка в строчке :

ObjectQuery<Employee> employees = DataEntitiesEmployee.Employees;

Ошибка CS0029

Не удается неявно преобразовать тип "System.Data.Entity.DbSet<WpfApplProject.Employee>" в "System.Data.Entity.Core.Objects.ObjectQuery<WpfApplProject.Employee>".

в using прописал все как положено, здесь похоже именно с преобразованием типов проблемы