Теперь, когда элементы пользовательского интерфейса находятся на своих местах, мы можем перейти к вопросам привязки данных (к шлифовке пользовательского интерфейса мы вернемся чуть позже). Первая задача — сохранить файлы в некоей коллекции. Оказывается, в System. Windows .Media есть класс BitmapSource, который подходит для наших целей. В качестве коллекции подойдет объект List<BitmapSource>, но нам нужен способ заполнения списка. Давайте создадим класс-оболочку, который загружает список и предоставляет его как свойство. Добавим в проект новый класс при помощи следующего кода:
public class DirectorylmageList {
private string _path;
private List<BitmapSource> images = new List<BitmapSource>();
public DirectoryImageList(string path)
{
_jpath = path;
Loadlmages();
}
public List<BitmapSource> Images
{
get { return _images; }
\
set { _images = value; }
}
public string Path
{
get { return _path; } set
{
_path = value;
Loadlmages();
}
}
private void Loadlmages()
{
_images.Clear();
Bitmap Image img;
foreach (string file in Directory.GetFiles(_path))
/
{
try
{
img = new BitmapImage(new Uri(file));
_images.Add(img);
}
catch
{
// empty catch; ignore any files that won't load as // an image...
}
i
Метод Loadlmages в предшествующем коде— это то место, где сосредоточена большая часть самой важной программной логики; он пересчитывает файлы внутри заданного каталога и пытается загрузить их в объект Bitmapimage. Если это получается, то мы знаем, что это файл изображения. Если не получается, то мы просто игнорируем исключительное состояние и продолжаем дальше.
В нашем классе windowl нам нужно создать несколько закрытых (private) полей для экземпляра этого нового класса, а также для текущего выбранного пути. Именно это может изменить пользователь при помощи диалога, запускаемого через пункт меню Folder | Open.
Вот эти поля:
private DirectoryImageList _imgList; private string _path =
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
Обратите внимание, что в качестве пути по умолчанию мы приняли каталог Pictures. Для загрузки объекта списка мы напишем метод ResetList в нашем классе Windows 1 (в файле выделенного кода _imgList объявлен как наш локальный экземпляр DirectorylmageList):
private void ResetList()
Рис. 16.23. Начальная привязка
Вспоминая наше более раннее обсуждение привязки данных, мы добавим несколько строк кода в конструктор Windowl: начальный вызов ResetList, а также вызов для присваивания контекста данных свойству Images экземпляра DirectorylmageList.
public Windowl()
{
InitializeComponent();
ResetList();
this.DataContext = _imgList.Images;
}
Если мы теперь запустим это приложение, то увидим знакомую картину (рис. 16.23): привязка данных работает, но не совсем в том формате, который нам нужен для представления.
Привязка к изображениям
Поскольку наш предыдущий фокус с перекрытием Tostring не дает нам нужного представления данных (потому что изображение — это не строка), то нам надо применить шаблоны данных. Класс DataTemplate используется для того, чтобы указать элементу управления, как именно вы хотите отображать его данные. При помощи шаблона данных внутри дерева элементов вы имеете полную свободу в представлении привязанных данных.
В нашем приложении мы ищем изображения в ListBox. Это очень легко. Создайте в XAML элемент Windowl.Resources и создайте шаблон DataTemplate, который настраивает нужную нам визуализацию:
<Window.Resources>
<DataTemplate х:Кеу="ImageDataTemplate">
<Image Source="{Binding UriSource.LocalPath}" Width="125" Height="125" /> </DataTemplate>
</Window.Resources>
Затем присвойте ListBox шаблрн DataTemplate: '
CListBox Grid.Row="l" Name=MlistBoxlM ItemsSource="{Binding}"
ItemTemplate="{StaticResource ImageDataTemplate}"/>
В нашем шаблоне данных элемент Image ждет указания URI для каждого изображения. Поэтому мы используем UriSource. LocalPath объекта BitmapSource.
Щелчок по контрольному изображению в списке должен привести к показу выбранного изображения в центральном элементе управления Image. Создав обработчик события SelectionChanged и подключив его к ListBox, мы можем соответствующим образом обновлять свойство Image. Source.
Событие объявляется в элементе ListBox вполне ожидаемым образом:
CListBox SelectionChanged=HlistBoxl_SelectionChanged" Grid.Row=f,l" Name="listBoxl" ItemsSource="{Binding}"
ItemTemplate="{StaticResource ImageDataTemplate}"/>
А для обработчика события мы приведем тип Selectedltem из ListBox к его родному представлению BitmapSource и присвоим его нашему элементу управления:
private void listBoxl_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
imagel.Source - (BitmapSource)((sender as ListBox).Selectedltem);
}
Обработчики событий кнопки и эффекты изображения
После того как изображение успешно загружено в список и показано в центральном элементе управления Image, мы можем заняться нашими четырьмя эффектами (опциями редактирования):
□ черно-белый фильтр;
□ размытость;
□ вращение;
□ переворот.
Поскольку эти четыре функции управляются четырьмя кнопками, то нам нужно будет добавить соответствующие изображения кнопок и события; мы не будем описывать здесь стилизацию кнопок, поскольку для нее необходимы внешние графические ресурсы, но вы можете взглянуть на их окончательный вид на моментальном снимке экрана в конце данной главы (или скачать исходный код с Web-сайта этой книги).
Теперь — код для событий. Во-первых, XAML-объявления событий каждой кнопки:
<Button Click="buttonBandW_Click" Margin="20,0,0,0" Height="23" Name="buttonBandW" Width="30">Button</Button>
<Button Click="buttonBlur_Click" Margin="20,0,0,0" Height="23" Name="buttonBlur" Width="30">Button</Button>
'<Button Click="buttonRotate_Click" Margin="20,0,0,0" Height="23" Name="buttonRotate" Width="30">Button</Button>
<Button Click="buttonFlip_Click" Margin="20,0,20,0" Height="23" Name="buttonFlip" Width="30">Button</Button>
Обратите внимание, что по мере того как вы вводите эти события щелчков в панели XAML, редактор XAML выдает вам подсказки IntelliSense, которые не только дописывают наши объявления Click, но и создают соответствующие обработчики события в нашем классе выделенного кода (рис. 16.24)!
Рис. 16.24. Технология IntelliSense в действии внутри редактора XAML
Для манипулирования изображением мы используем преобразование: манипуляцию двумерной поверхностью для вращения, перекашивания и прочих изменений внешнего вида поверхности.
Мы можем выполнить нашу функцию вращения непосредственно при помощи RotateTransform следующим образом:
private void buttonRotate_Click(object sender, RoutedEventArgs e)
{
CachedBitmap cache = new CachedBitmap((BitmapSource)imagel.Source,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad); imagel.Source = new TransformedBitmap(cache, new RotateTransform(90));
}
Наша функция переворота так же просто реализуется при помощи преобразования ScaleTransform:
private void buttonFlip_Click(object sender, RoutedEventArgs e)
{
CachedBitmap cache = new CachedBitmap((BitmapSource)imagel.Source,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad); ScaleTransform scale = new ScaleTransform(-1,-1,imagel.Source.Width/2,
imagel.Source.Height / 2); imagel.Source = new TransformedBitmap(cache, scale);
)
Размывание изображения достигается при помощи другого механизма, известного как растровый эффект. При помощи создания нового экземпляра BlurBitmapEffeet и присваивания его нашему элементу управления изображением WPF применит соответствующий алгоритм к растровому изображению для размывания картинки:
imagel.BitmapEffeet = new BlurBitmapEffeet();
Выбор пути в обычном диалоговом окне
Последнее, что нам надо сделать— это дать пользователю возможность изменить путь к файлам картинок. Сама WPF не имеет для этого никаких встроенных классов, но пространство имен System, windows. Forms обладает как раз тем, что нам нужно,— классом FolderBrowse г Dialog. Он запускается из обработчика нашего события щелчка
Folde rOpenMenuItem:
private void FolderOpenMenuItem_Click(object sender, RoutedEventArgs e)
{
SetPath();
}
private void SetPathO {
FolderBrowserDialog dig = new FolderBrowserDialog();
dig.ShowDialog();
path = dig.SelectedPath;
ResetList();
}
На рис. 16.25 показано диалоговое окно в действии.
Когда пользователь выбирает каталог, мы соответственно обновляем наше внутреннее поле, перезагружаем новый путь в класс DirectorylmageList, а затем сбрасываем свойство DataContext нашего окна. Это превосходный пример того, насколько просто использовать внутри WPF другие технологии и библиотеки классов .NET. При помощи добавления в наш проект соответствующих пространств имен и ссылок мы просто создаем экземпляр этого класса точно так же, как и любого другого класса в нашем решении.
Совет
Поскольку в WPF и WinForms существует большое количество элементов управления, имеющих одинаковые имена (один такой пример— это ListBox), то (если вы будете использовать классы из библиотек System, windows, controls и System.windows.Forms) вам придется указывать полностью квалифицированные имена некоторых объектов (во избежание использования неверного класса).
Теперь наше приложение функционально завершено. Текущее состояние кода XAML и файла выделенного кода мы приводим в листингах 16.3 и 16.4 соответственно. Однако если вы действительно хотите подробно исследовать это приложение, то вам следует скачать его исходный код с Web-сайта данной книги. Это позволит вам увидеть все улучшения, сделанные при помощи графических ресурсов, которые привели к окончательной версии, показанной на рис. 16.26.
<Window х:Class="ImageViewer.Windowl"
xmlns="http: //schemas .microsoft. com/winfx/2006/xaml/presentation" xmlns :x="http: //schemas .microsoft. com/winfx/2006/xaml" Title="Windowl" Height="464" Width="545">
<Window.Resources>
<DataTemplate x:Key="ImageDataTemplate">
dmage Source="{Binding UriSource.LocalPath}" Width="125M
Height="125fl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width^'lSO" />
<ColumnDefinition Width="378*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="352*" />
<RowDefinition Height=H45n />
</Grid.RowDefinitions>
<Menu Grid.Row="0" Grid.ColumnSpan=H2" Height="22" Name="menul"
VerticalAlignment="Top">
<MenuItem Header="_Folder">
<MenuItem Header=M_Open..." Click=,,FolderOpenMenuItem_Click" /> </MenuItem>
</Menu>
<ListBox SelectionChanged=MlistBoxl_SelectionChangedH Grid.Row="l" Name="listBoxl" ItemsSource="{Binding}"
ItemTemplate="{StaticResource ImageDataTemplate}"/>
cimage Grid.Column="l" Grid.Row="l" Name="image!" Stretch="Fill" />
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal"
Grid.Column="l" Grid.Row="2" Name="stackPanell">
<Button BorderThickness="0" Click="buttonBandW_Click"
Margin="20,0,0,0" Height="23" Name="buttonBandW" Width="30"> Button </Button>
<Button Click="buttonBlur_Click" Margin="20,0,0,0" Height="23"
Name="buttonBlur" Width="30">Button</Button> <Button Click="buttonRotate_Click" Margin="20,0,0,0" Height="23"
Name="buttonRotate" Width="30">Button</Button> <Button Click="buttonFlip_Click" Margin="20,0,20,0" Height="23"
Name="buttonFlip" Width="30">Button</Button>
</StackPanel>
</Grid>
</Window>
Cusing System;
using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes;
namespace ImageViewer
III <summary>
III Логика взаимодействия для Windowl.xaml III </summary>
public partial class Windowl : Window {
private DirectoryImageList _imgList; private string _path =
Environment.GetFolderPath(Environment.SpecialFolder.MyPictures);
public Windowl()
{
InitializeComponent();
ResetList();
this.DataContext = _imgList.Images;
\
private void FolderOpenMenuItem_Click(object sender, RoutedEventArgs e)
{
SetPath();
}
private void SetPath()
I
FolderBrowserDialog dig = new FolderBrowserDialog();
dig.ShowDialog() ;
path = dig.SelectedPath;
ResetList();
}
private void ResetList ()
{
if (IsValidPath(_path))
{
imgList = new DirectorylmageList(_path);
}
this.DataContext = _imgList.Images;
}
private bool IsValidPath(string path)
{
try
{
string folder = System.10.Path.GetFullPath(path); return true;
}
catch
{
return false;
}
}
private void buttonBandW_Click(object sender, RoutedEventArgs e)
{
BitmapSource img = (BitmapSource)image1.Source;
imagel.Source = new FormatConvertedBitmap(img, PixelFormats.Grayl6,
BitmapPalettes.Gray256, 1.0);
}
private void buttonBlur_Click(object sender, RoutedEventArgs e)
{
if (imagel.BitmapEffeet != null)
{
// Если текущий эффект — размытие, удалить imagel.BitmapEffeet = null;
}
else
{
// В противном случае добавить в изображение размытие imagel.BitmapEffeet = new BlurBitmapEffeet();
}
private void buttonRotate_Click(object sender, RoutedEventArgs e)
{
CachedBitmap cache = new CachedBitmap ( (BitmapSource) imagel. Source,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad); imagel.Source = new TransformedBitmap(cache, new RotateTransform(90) ) ;
}
private void buttonFlip_Click(object sender, RoutedEventArgs e)
{
CachedBitmap cache = new CachedBitmap((BitmapSource)imagel.Source,
BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
ScaleTransform scale = new ScaleTransform(-1, -1,
imagel.Source.Width / 2, imagel.Source.Height / 2);
imagel.Source = new TransformedBitmap(cache, scale);
}
private void listBoxl_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
imagel.Source = (BitmapSource)
((sender as System.Windows.Controls.ListBox).Selectedltem) ;
)
)
}