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

Разработка Silverlight-приложения

< Лекция 7 || Самостоятельная работа 2: 12345 || Лекция 8 >

Задание 6. С помощью стилей и шаблонов модифицировать представление интерфейсных элементов – 2 часа.

Конвертор графических данных

Для реализации привязки графических данных модели с элементом управления 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() определяет только исключение, если к нему будет обращение.

Модификация шаблона данных списка ListBox

Для отображения фотографии в списке 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>

Модифицированная 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>

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

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

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

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

Перед изменением представления интерфейсного элемента ListBox целесообразно подготовить объектные ресурсы для задания цветов и кистей рисования.

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

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

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

Стиль 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, за исключением внешней и внутренней рамок. Полный код 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}" />

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

Создадим стиль 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. далее необходимо модифицировать 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 представлено на рис. 9.9.

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

Рис. 9.10. Визуальное представление кнопки в различных состояниях
< Лекция 7 || Самостоятельная работа 2: 12345 || Лекция 8 >
Александр Петров
Александр Петров

При загрузке данных из БД возникает исключение 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 прописал все как положено, здесь похоже именно с преобразованием типов проблемы