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

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

Разбить на страницы
Показывать лекцию целиком

Цель

Освоить основные приемы разработки эффективного интерфейса пользователя с помощью стилей и шаблонов.

Модификация серверной части и приложения

В корпоративных базах данных часто возникает необходимость хранения изображений, в частности, фотографий сотрудников. Для хранения фотографий в базе данных SQL Server 2008 можно использовать тип данных больших значений varbinary(max), который обеспечивает хранение до 231-1 байт данных.

Модифицируем базу данных Personal, используемую в лекции 6. Переименуем её, задав имя Personal_Picture, и добавим в таблицу Employee поле Picture типа varbinary(max). Тип данных varbinary(max) обеспечивает хранение двоичных данных в строке.

Модификация серверной части .

(рис 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) Страница приложения в дизайнере

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

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

    (рис 8.3) Страница приложения

    Улучшение пользовательского интерфейса

    Разработанное Silverlight-приложение является работоспособным и может использоваться для обработки информации о сотрудниках предприятия. Однако вопросы эффективности интерфейса или взаимодействия с пользователем ( ].

    Для улучшения UX пользовательского интерфейса разработанного Silverlight-приложения поставим следующие задачи:

  • улучшить представление элементов списка listBoxEmployees в различных состояниях (не выбранный элемент, выбранный элемент, элемент, на который наведен указатель мыши);
  • улучшить представление кнопок в различных состояниях (доступная, недоступная, наведен указатель мыши).
  • Изменение визуального поведения элемента ListBox

    Для элементов пользовательского интерфейса объект ControlTemplate задает визуальную структуру и визуальное поведение элемента управления. Можно настраивать внешний вид элемента управления, предоставляя ему новый шаблон ControlTemplate без изменения его функциональности.

    Для нашего приложения ставится задача создания следующего визуального представления и поведения элемента управления ListBox – listBoxEmployees:

  • список не должен иметь внешней рамки;
  • элементы списка должны представляться в двойной рамке со скругленными углами;
  • выбранный элемент списка должен иметь жирное выделение для внешней рамки, например светло-голубым цветом;
  • элемент списка, на который наведен указатель мыши, должен иметь жирное выделение для внешней рамки, например светло-фиолетовым цветом
  • полоса прокрутки должна находиться справа и отдельно от элементов списка.
  • Перед изменением представления интерфейсного элемента ListBox целесообразно подготовить объектные ресурсы для задания цветов и кистей рисования. Ресурсы цветов и кистей организуем с помощью словаря ресурсов в файле ColorsAndBrushesRD.xaml (XAML-описание приведено в приложении).

    Для реализации требуемого визуального представления и поведения элемента управления ListBox спроектируем три стиля:

  • стиль, используемый при визуализации контейнеров элементов ListBox – ListBoxItemStyle;
  • стиль, используемый для элемента контроля ListBox - ListBoxPhotoStyle ;
  • стиль, используемый для прокручиваемой области, в которой могут содержаться другие видимые элементы – ScrollViewerPhotoStyle.
  • Кроме того, необходимо выделить в отдельный ресурс шаблон используемый для формирования данных отдельного элемента списка 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, для внутренней рамки, заданы в расширенной разметке ссылкой на статические ресурсы, определенные в файле ColorsAndBrushesRD.xaml.

    Свойство ContentPresenter внутренней рамки интерфейсного элемента ListBoxItem определяет где должно отображаться содержание ( Content ) данного элемента.

    Визуальное поведение задает способ отображения элемента управления в определенных состояниях. Для элемента управления ListBoxItem имеются следующие состояния, которые приведены в табл.7.1

    Визуальные состояния класса ListBoxItem
    VisualState VisualStateGroup Описание
    Normal CommonStates Состояние по умолчанию.
    MouseOver CommonStates Указатель мыши расположен в элементе управления.
    Disabled CommonStates Элемент отключен.
    Focused FocusStates На элементе установлен фокус.
    Unfocused FocusStates Элемент не имеет фокус.
    Selected SelectionStates Элемент выбран.
    Unselected SelectionStates Элемент не выбран.
    SelectedUnfocused SelectionStates Элемент выбран, но не имеет фокус. Это состояние отсутствует в шаблоне элемента управления ListBoxItem по умолчанию. Однако можно изменить ControlTemplate объекта ListBoxItem для использования этого состояния.
    BeforeLoaded LayoutStates ListBoxItem еще не загружен.
    AfterLoaded LayoutStates ListBoxItem загружен.
    BeforeUnloaded LayoutStates ListBoxItem еще не выгружен.

    Для управления состояниями и логикой переходов между состояниями элементов управления используется класс VisualStateManager, который в XAML-разметке стиля представлен одноименным свойством. Данное свойство позволяет указывать состояния для элемента управления, его внешний вид в определенном состоянии, и порядок изменения состояния элемента управления. Внешний вид элемента управления, находящегося в некотором состоянии, определяется классом VisualState. Данный класс содержит коллекцию объектов Storyboard, указывающих, как изменяется внешний вид элемента управления в определенном состоянии. Состояния просмотра добавляются в элемент управления посредством вложенного свойства зависимостей VisualStateManager.VisualStateGroups элемента управления. Для проектируемого стиля необходимо задать визуальное представление для состояний CommonStates и SelectionStates.

    <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, то есть, когда указатель мыши покидает элемент списка, анимация прерывается и внешняя рамка элемента списка принимаем исходный вид. XAML-разметка управления визуальным представлением элемент списка ListBoxItem для группы CommonStates приведена ниже.

    <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. XAML-разметка управления визуальным представлением элемент списка ListBoxItem для группы SelectionStates спроектирована аналогично приведенной выше для группы CommonStates и имеет следующий вид.

    <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>

    Полный код XAML-разметки стиля ListBoxItemStyle приведен в приложении.

    Стиль ListBoxPhotoStyle задается для типа ListBox и определяет элемент прокрутки ScrollViewer. Стиль элемент прокрутки задается ссылкой на статический ресурс ScrollViewerPhotoStyle. Полный код XAML-разметки стиля приведен в приложении.

    Стиль 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"/>

    Полный код XAML-разметки стиля ScrollViewerPhotoStyle приведен в приложении.

    Шаблон данных ListBoxItemPhotoDataTemplate практически не отличается от ранее определенного шаблона для элемента listBoxEmployees, за исключением внешней и внутренней рамок. Полный код XAML-разметки шаблона данных ListBoxItemPhotoDataTemplate приведен в приложении.

    Стили и шаблон для интерфейсного элемента ListBox поместим в словарь ресурсов StylesAndTemplates.xaml. Так как созданные стили ссылаются на ресурсы из файла ColorsAndBrushesRD.xaml, то данные ресурсы необходимо сделать доступными в файле StylesAndTemplates.xaml с помощью свойства MergedDictionaries словаря ResourceDictionary. Свойство MergedDictionaries представляет различные словари ресурсов в объединенных словарях.

    <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ColorsAndBrushesRD.xaml"/>
        </ResourceDictionary.MergedDictionaries>

    После создания стилей и шаблон для интерфейсного элемента ListBox необходимо модифицировать XAML-разметку для элемента listBoxEmployees главного окна приложения.

    <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}" />

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

    (рис 8.4) Страница приложения с модифицированным интерфейсным элементом списка

    Изменение визуального поведения элемента Button

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

    Стиль ButtonStyle создается для типа Button (TargetType="Button") и в нем определяются шесть свойств: Background, Foreground, Padding, BorderThickness, BorderBrush и Template. Для свойств Background, Foreground, Padding и BorderThickness задаются конкретные значения, а для свойства BorderBrush определяется кисть градиентной заливки. Полный код XAML-разметки стиля ButtonStyle приведен в приложении.

    Вложенное свойство ControlTemplate определено для типа Button (TargetType="Button"). Оно содержит контейнер Grid, для которого задается свойства Cursor (Cursor="Hand"). Свойство Cursor будет определять изменение вида курсора при наведении указателя мыши на кнопку. Вложенное свойство зависимостей VisualStateManager.VisualStateGroups элемента управления определяет визуальное представление элемента управления в различных состояниях. Рамка ( Border ) с именем Background определяет визуальное представление, которое будет изменяеться в различных состояниях кнопки. Свойство ContentPresenter определяет, что и как будет формироваться на панели кнопки. Три прямоугольника используются для представления внутреннего содержания кнопки в режимах получения фокуса ( FocusVisualElement ), наведения указателя мыши ( MouseOverVisualElement ) и в недоступном состоянии ( DisabledVisualElement ). Полный код XAML-разметки шаблона ControlTemplate для Button приведен в приложении.

    Для элемента управления Button имеются следующие состояния, которые приведены в табл.7.2

    Визуальные состояния класса Button
    Имя VisualState Имя VisualStateGroup Описание
    Normal CommonStates Состояние по умолчанию.
    MouseOver CommonStates Указатель мыши расположен в элементе управления.
    Pressed CommonStates Элемент управления нажат.
    Disabled CommonStates Элемент управления отключен.
    Focused FocusStates Элемент управления имеет фокус.
    Unfocused FocusStates Элемент управления не имеет фокуса.

    Для проектируемого стиля необходимо задать визуальное представление для состояний CommonStates и FocusStates.

    Для группы состояний CommonStates в состоянии Normal применяется визуальное представление по умолчанию, а для состояний MouseOver, Pressed и Disabled задается как цветовая ( ColorAnimation ), так числовая ( DoubleAnimation ) анимация свойств кнопки.

    Для группы состояний FocusStates в состоянии Unfocused применяется визуальное представление по умолчанию, а для состояния Focused задается как цветовая анимация свойств кнопки – изменяется прозрачность.

    Полный код XAML-разметки стиля ButtonStyle приведен в приложении.

    Стиль для интерфейсного элемента Buttun добавим в словарь ресурсов StylesAndTemplates.xaml. далее необходимо модифицировать XAML-разметку для элементов Buttun главного окна приложения. Задание стиля производится аналогично для всех кнопок главного окна приложения. Так для кнопки "Создать" ( ButtonNew ) XAML-разметка будет иметь следующий вид:

    <Button Name="ButtonNew" 
                   Content="Создать"  
                  Margin="5" 
                  Padding="10,5,10,5" 
                  Height="25"
                  Click="New_Click" 
                  ToolTipService.ToolTip="Создать новую запись"
                  IsEnabled="False" 
             	Style="{StaticResource ButtonStyle}" />

    Визуальное представление кнопки на основе спроектированного стиля ButtonStyle представлено на рис 8.5.

    (рис 8.5) Визуальное представление кнопки в различных состояниях

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

    (рис 8.6) Страница приложения с модифицированным интерфейсным элементом кнопки

    Ключевые термины

    Конвертор данных, шаблон данных, стиль, объектный ресурс, пользовательский интерфейс, элемент пользовательского интерфейса, словарь ресурсов, визуальное состояние класса,

    IValueConverter, Convert, ConvertBack, DataTemplate, OpenFileDialog, stream, ControlTemplate, User Experience, UX, ResourceDictionary, MergedDictionaries , ContentPresenter, VisualStateManager, VisualState

    Краткие итоги

    Рассмотрены подходы по включению в элементы управления Silverlight- приложения графических объектов, построение сложных стилей и шаблонов для списков и кнопок с целью повышения качества интерфейса пользователя.

    Ресурсы для углубленного изучения

  • Silverlight // http://msdn.microsoft.com/ru-ru/library/cc838158(v=VS.95).aspx.
  • , стр. 193 – 272.
  • , стр. 385 – 440.
  • , стр. 134 – 139.
  • , стр. 1137 – 1212.
  • Вопросы для самопроверки

  • В каком формате можно хранить изображения с SQL Server 2008?
  • Какие требования предъявляются к классу конвертора данных?
  • Поясните, как можно использовать конвертор данных в XAML-разметке элемента управления.
  • Поясните, что означает визуальная структура и поведение элемента управления.
  • Для чего использутся словарь ресурсов?
  • Поясните назначение свойства MergedDictionaries.
  • Что определяет визуальное состояние элемента контроля?
  • Поясните назначение класса VisualState.
  • Поясните назначение класса VisualStateManager.
  • Поясните назначение свойства VisualStateGroups.
  • Поясните назначение коллекции Storyboard.
  • Страницы:

    Цель

    Освоить основные приемы разработки эффективного интерфейса пользователя с помощью стилей и шаблонов.

    Модификация серверной части и приложения

    В корпоративных базах данных часто возникает необходимость хранения изображений, в частности, фотографий сотрудников. Для хранения фотографий в базе данных SQL Server 2008 можно использовать тип данных больших значений varbinary(max), который обеспечивает хранение до 231-1 байт данных.

    Модифицируем базу данных Personal, используемую в лекции 6. Переименуем её, задав имя Personal_Picture, и добавим в таблицу Employee поле Picture типа varbinary(max). Тип данных varbinary(max) обеспечивает хранение двоичных данных в строке.

    Модификация серверной части .

    (рис 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) Страница приложения в дизайнере

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

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

    (рис 8.3) Страница приложения

    Улучшение пользовательского интерфейса

    Разработанное Silverlight-приложение является работоспособным и может использоваться для обработки информации о сотрудниках предприятия. Однако вопросы эффективности интерфейса или взаимодействия с пользователем ( ].

    Для улучшения UX пользовательского интерфейса разработанного Silverlight-приложения поставим следующие задачи:

  • улучшить представление элементов списка listBoxEmployees в различных состояниях (не выбранный элемент, выбранный элемент, элемент, на который наведен указатель мыши);
  • улучшить представление кнопок в различных состояниях (доступная, недоступная, наведен указатель мыши).
  • Изменение визуального поведения элемента ListBox

    Для элементов пользовательского интерфейса объект ControlTemplate задает визуальную структуру и визуальное поведение элемента управления. Можно настраивать внешний вид элемента управления, предоставляя ему новый шаблон ControlTemplate без изменения его функциональности.

    Для нашего приложения ставится задача создания следующего визуального представления и поведения элемента управления ListBox – listBoxEmployees:

  • список не должен иметь внешней рамки;
  • элементы списка должны представляться в двойной рамке со скругленными углами;
  • выбранный элемент списка должен иметь жирное выделение для внешней рамки, например светло-голубым цветом;
  • элемент списка, на который наведен указатель мыши, должен иметь жирное выделение для внешней рамки, например светло-фиолетовым цветом
  • полоса прокрутки должна находиться справа и отдельно от элементов списка.
  • Перед изменением представления интерфейсного элемента ListBox целесообразно подготовить объектные ресурсы для задания цветов и кистей рисования. Ресурсы цветов и кистей организуем с помощью словаря ресурсов в файле ColorsAndBrushesRD.xaml (XAML-описание приведено в приложении).

    Для реализации требуемого визуального представления и поведения элемента управления ListBox спроектируем три стиля:

  • стиль, используемый при визуализации контейнеров элементов ListBox – ListBoxItemStyle;
  • стиль, используемый для элемента контроля ListBox - ListBoxPhotoStyle ;
  • стиль, используемый для прокручиваемой области, в которой могут содержаться другие видимые элементы – ScrollViewerPhotoStyle.
  • Кроме того, необходимо выделить в отдельный ресурс шаблон используемый для формирования данных отдельного элемента списка 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, для внутренней рамки, заданы в расширенной разметке ссылкой на статические ресурсы, определенные в файле ColorsAndBrushesRD.xaml.

    Свойство ContentPresenter внутренней рамки интерфейсного элемента ListBoxItem определяет где должно отображаться содержание ( Content ) данного элемента.

    Визуальное поведение задает способ отображения элемента управления в определенных состояниях. Для элемента управления ListBoxItem имеются следующие состояния, которые приведены в табл.7.1

    Визуальные состояния класса ListBoxItem
    VisualState VisualStateGroup Описание
    Normal CommonStates Состояние по умолчанию.
    MouseOver CommonStates Указатель мыши расположен в элементе управления.
    Disabled CommonStates Элемент отключен.
    Focused FocusStates На элементе установлен фокус.
    Unfocused FocusStates Элемент не имеет фокус.
    Selected SelectionStates Элемент выбран.
    Unselected SelectionStates Элемент не выбран.
    SelectedUnfocused SelectionStates Элемент выбран, но не имеет фокус. Это состояние отсутствует в шаблоне элемента управления ListBoxItem по умолчанию. Однако можно изменить ControlTemplate объекта ListBoxItem для использования этого состояния.
    BeforeLoaded LayoutStates ListBoxItem еще не загружен.
    AfterLoaded LayoutStates ListBoxItem загружен.
    BeforeUnloaded LayoutStates ListBoxItem еще не выгружен.

    Для управления состояниями и логикой переходов между состояниями элементов управления используется класс VisualStateManager, который в XAML-разметке стиля представлен одноименным свойством. Данное свойство позволяет указывать состояния для элемента управления, его внешний вид в определенном состоянии, и порядок изменения состояния элемента управления. Внешний вид элемента управления, находящегося в некотором состоянии, определяется классом VisualState. Данный класс содержит коллекцию объектов Storyboard, указывающих, как изменяется внешний вид элемента управления в определенном состоянии. Состояния просмотра добавляются в элемент управления посредством вложенного свойства зависимостей VisualStateManager.VisualStateGroups элемента управления. Для проектируемого стиля необходимо задать визуальное представление для состояний CommonStates и SelectionStates.

    <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, то есть, когда указатель мыши покидает элемент списка, анимация прерывается и внешняя рамка элемента списка принимаем исходный вид. XAML-разметка управления визуальным представлением элемент списка ListBoxItem для группы CommonStates приведена ниже.

    <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. XAML-разметка управления визуальным представлением элемент списка ListBoxItem для группы SelectionStates спроектирована аналогично приведенной выше для группы CommonStates и имеет следующий вид.

    <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>

    Полный код XAML-разметки стиля ListBoxItemStyle приведен в приложении.

    Стиль ListBoxPhotoStyle задается для типа ListBox и определяет элемент прокрутки ScrollViewer. Стиль элемент прокрутки задается ссылкой на статический ресурс ScrollViewerPhotoStyle. Полный код XAML-разметки стиля приведен в приложении.

    Стиль 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"/>

    Полный код XAML-разметки стиля ScrollViewerPhotoStyle приведен в приложении.

    Шаблон данных ListBoxItemPhotoDataTemplate практически не отличается от ранее определенного шаблона для элемента listBoxEmployees, за исключением внешней и внутренней рамок. Полный код XAML-разметки шаблона данных ListBoxItemPhotoDataTemplate приведен в приложении.

    Стили и шаблон для интерфейсного элемента ListBox поместим в словарь ресурсов StylesAndTemplates.xaml. Так как созданные стили ссылаются на ресурсы из файла ColorsAndBrushesRD.xaml, то данные ресурсы необходимо сделать доступными в файле StylesAndTemplates.xaml с помощью свойства MergedDictionaries словаря ResourceDictionary. Свойство MergedDictionaries представляет различные словари ресурсов в объединенных словарях.

    <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="ColorsAndBrushesRD.xaml"/>
        </ResourceDictionary.MergedDictionaries>

    После создания стилей и шаблон для интерфейсного элемента ListBox необходимо модифицировать XAML-разметку для элемента listBoxEmployees главного окна приложения.

    <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}" />

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

    (рис 8.4) Страница приложения с модифицированным интерфейсным элементом списка

    Изменение визуального поведения элемента Button

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

    Стиль ButtonStyle создается для типа Button (TargetType="Button") и в нем определяются шесть свойств: Background, Foreground, Padding, BorderThickness, BorderBrush и Template. Для свойств Background, Foreground, Padding и BorderThickness задаются конкретные значения, а для свойства BorderBrush определяется кисть градиентной заливки. Полный код XAML-разметки стиля ButtonStyle приведен в приложении.

    Вложенное свойство ControlTemplate определено для типа Button (TargetType="Button"). Оно содержит контейнер Grid, для которого задается свойства Cursor (Cursor="Hand"). Свойство Cursor будет определять изменение вида курсора при наведении указателя мыши на кнопку. Вложенное свойство зависимостей VisualStateManager.VisualStateGroups элемента управления определяет визуальное представление элемента управления в различных состояниях. Рамка ( Border ) с именем Background определяет визуальное представление, которое будет изменяеться в различных состояниях кнопки. Свойство ContentPresenter определяет, что и как будет формироваться на панели кнопки. Три прямоугольника используются для представления внутреннего содержания кнопки в режимах получения фокуса ( FocusVisualElement ), наведения указателя мыши ( MouseOverVisualElement ) и в недоступном состоянии ( DisabledVisualElement ). Полный код XAML-разметки шаблона ControlTemplate для Button приведен в приложении.

    Для элемента управления Button имеются следующие состояния, которые приведены в табл.7.2

    Визуальные состояния класса Button
    Имя VisualState Имя VisualStateGroup Описание
    Normal CommonStates Состояние по умолчанию.
    MouseOver CommonStates Указатель мыши расположен в элементе управления.
    Pressed CommonStates Элемент управления нажат.
    Disabled CommonStates Элемент управления отключен.
    Focused FocusStates Элемент управления имеет фокус.
    Unfocused FocusStates Элемент управления не имеет фокуса.

    Для проектируемого стиля необходимо задать визуальное представление для состояний CommonStates и FocusStates.

    Для группы состояний CommonStates в состоянии Normal применяется визуальное представление по умолчанию, а для состояний MouseOver, Pressed и Disabled задается как цветовая ( ColorAnimation ), так числовая ( DoubleAnimation ) анимация свойств кнопки.

    Для группы состояний FocusStates в состоянии Unfocused применяется визуальное представление по умолчанию, а для состояния Focused задается как цветовая анимация свойств кнопки – изменяется прозрачность.

    Полный код XAML-разметки стиля ButtonStyle приведен в приложении.

    Стиль для интерфейсного элемента Buttun добавим в словарь ресурсов StylesAndTemplates.xaml. далее необходимо модифицировать XAML-разметку для элементов Buttun главного окна приложения. Задание стиля производится аналогично для всех кнопок главного окна приложения. Так для кнопки "Создать" ( ButtonNew ) XAML-разметка будет иметь следующий вид:

    <Button Name="ButtonNew" 
                   Content="Создать"  
                  Margin="5" 
                  Padding="10,5,10,5" 
                  Height="25"
                  Click="New_Click" 
                  ToolTipService.ToolTip="Создать новую запись"
                  IsEnabled="False" 
             	Style="{StaticResource ButtonStyle}" />

    Визуальное представление кнопки на основе спроектированного стиля ButtonStyle представлено на рис 8.5.

    (рис 8.5) Визуальное представление кнопки в различных состояниях

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

    (рис 8.6) Страница приложения с модифицированным интерфейсным элементом кнопки

    Ключевые термины

    Конвертор данных, шаблон данных, стиль, объектный ресурс, пользовательский интерфейс, элемент пользовательского интерфейса, словарь ресурсов, визуальное состояние класса,

    IValueConverter, Convert, ConvertBack, DataTemplate, OpenFileDialog, stream, ControlTemplate, User Experience, UX, ResourceDictionary, MergedDictionaries , ContentPresenter, VisualStateManager, VisualState

    Краткие итоги

    Рассмотрены подходы по включению в элементы управления Silverlight- приложения графических объектов, построение сложных стилей и шаблонов для списков и кнопок с целью повышения качества интерфейса пользователя.

    Ресурсы для углубленного изучения

  • Silverlight // http://msdn.microsoft.com/ru-ru/library/cc838158(v=VS.95).aspx.
  • , стр. 193 – 272.
  • , стр. 385 – 440.
  • , стр. 134 – 139.
  • , стр. 1137 – 1212.
  • Вопросы для самопроверки

  • В каком формате можно хранить изображения с SQL Server 2008?
  • Какие требования предъявляются к классу конвертора данных?
  • Поясните, как можно использовать конвертор данных в XAML-разметке элемента управления.
  • Поясните, что означает визуальная структура и поведение элемента управления.
  • Для чего использутся словарь ресурсов?
  • Поясните назначение свойства MergedDictionaries.
  • Что определяет визуальное состояние элемента контроля?
  • Поясните назначение класса VisualState.
  • Поясните назначение класса VisualStateManager.
  • Поясните назначение свойства VisualStateGroups.
  • Поясните назначение коллекции Storyboard.
  • Вернуться к учебному плану