|
При загрузке данных из БД возникает исключение InvalidOperationException с сообщением: Элемент коллекции должен быть пустым перед использованием ItemsSource. Знаю, что для заполнения DataGrid можно использовать коллекции Items или ItemsSource, но одновременно их использовать нельзя: если задано значение для свойства ItemsSource и в коде C# добавляется элемент в Items, возникает исключение. |
Разработка Silverlight-приложения
Задание 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.
