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