ちょっと時間があったので、WPFで画像ビューワ作ってみました。
実行結果は下の通り。
開くボタンで、フォルダを指定すると、その中の画像のサムネイルを表示する。
表示されたサムネイルの中から、適当な画像を選択すると、その画像が画面の中央部分に表示される。
拡大縮小・回転もすることが出来る。
画像は一応jpg, bmp, png, tiff, gifあたりが見えるはず。
この見た目を作るためにバックで保持している情報は
public class ImageInfo
{
public string Path { get; set; }
}
というstring型のプロパティを1つもつだけのクラスのリストだけ。
後はXAMLでサムネイル表示や、選択されたものを中心に表示したり、拡大縮小・回転をやってる。
XAMLが、どれだけ表現力があるかというのがわかると思う。
というわけで、これから、これとなるべく同じものをもう一度作りながらBlog記事を書いていってみようと思う。
プロジェクトの作成
とりあえずWPFアプリケーションの形式のプロジェクトを作成します。
名前は、WpfImageViewerApplicationにしました。
プロジェクトを作成したら、以下の手順でメインウィンドウをViewerWindowに変更します。
- Window1.xamlを削除
- プロジェクトの右クリックで表示されるメニューから追加→ウィンドウで、ViewerWindow.xamlを新規作成
- App.xamlのStartupUriをViewerWindow.xamlにする
ここまでの手順でソリューションエクスプローラは下のような感じになります。
App.xamlは、下のようになります。
<Application x:Class="WpfImageViewerApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="ViewerWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
指定したフォルダから画像だけ取得する処理
WPFとは直接関係無い部分をさくっと作っていきます。
今回は、bmp, jpg, png, tiff, gifあたりのファイルを、特定のフォルダから抜き出したいので、その処理を書いていきます。
Commonというフォルダを作って、そこにImageUtilsというクラスを作成します。
WPFとはかすってもいない処理なので、さくっとコードを書きます。
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace WpfImageViewerApplication.Common
{
/// <summary>
/// 画像ビューワの便利メソッド集
/// </summary>
public static class ImageUtils
{
/// <summary>
/// 指定したフォルダから、指定した拡張子のファイルを
/// ImageInfo型のリストにして返す。
/// </summary>
/// <param name="directory">ファイルを探すディレクトリへのパス</param>
/// <param name="supportExts">探す拡張子</param>
/// <returns>引数で指定した条件に合致する画像の情報</returns>
public static IList<ImageInfo> GetImages(string directory, string[] supportExts)
{
if (!Directory.Exists(directory))
{
// ディレクトリが無い時は空のリストを返す
return new List<ImageInfo>();
}
var dirInfo = new DirectoryInfo(directory);
// ディレクトリからファイルを拡張子で絞り込んで返す
return dirInfo.GetFiles().
Where(f => supportExts.Contains(f.Extension)).
Select(f => new ImageInfo { Path = f.FullName }).
ToList();
}
}
/// <summary>
/// 画像に関する情報を持たせるクラス
/// </summary>
public class ImageInfo
{
/// <summary>
/// 画像ファイルへのパス
/// </summary>
public string Path { get; set; }
}
}
裏の肝となる処理がこれでできました。
次は、画面に表示するためのモデルクラスを作成していきます。
モデルクラスの作成
ViewerWindowのDataContextに設定するモデルクラスを作成します。
ViewerWindowModelという名前のクラスを作成して、INotifyPropertyChangedを実装させます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfImageViewerApplication
{
/// <summary>
/// ViewerWindowのモデルクラス
/// </summary>
public class ViewerWindowModel : INotifyPropertyChanged
{
#region コンストラクタ
public ViewerWindowModel()
{
// OnPropertyChangedでnullチェックするのがめんどいので
// 空の処理をあらかじめ1つ追加しておく。
PropertyChanged += (sender, e) => { };
}
#endregion
#region INotifyPropertyChanged メンバ
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}
そして、画面に表示するデータを保持するためのプロパティを定義します。
プロパティの型はさっき定義したImageInfoのIListになります。
#region プロパティ
private IList<ImageInfo> _images;
/// <summary>
/// ビューワーで表示する画像の情報を取得または設定します。
/// </summary>
public IList<ImageInfo> Images
{
get
{
return _images;
}
set
{
_images = value;
OnPropertyChanged("Images");
}
}
#endregion
次に、指定したフォルダの画像を読み込むという処理を書きます。
処理自体は、先ほど作成したImageUtilsクラスのメソッドを呼ぶだけなのですが、フォルダをユーザに選択してもらう処理を書く必要があります。そのためにSystem.Windows.Forms.FolderBrowserDialogを使います。
System.Windows.Forms.FolderBrowserDialogは、System.Windows.Formsを参照に追加します。
若干前後しますが、Windows Formのダイアログの見た目をモダンっぽくするためにAppクラスのStartupイベントにEnableVisualStylesを呼び出す処理を書きます。
さらに、アプリケーションでサポートする拡張子を取得するプロパティと、現在のアプリケーションのインスタンスをApp型で返すプロパティも追加しておきます。
App.xaml
<Application x:Class="WpfImageViewerApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup"
StartupUri="ViewerWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
App.xaml.cs
using System.Windows;
namespace WpfImageViewerApplication
{
/// <summary>
/// App.xaml の相互作用ロジック
/// </summary>
public partial class App : Application
{
private string[] _supportExts = { ".jpg", ".bmp", ".png", ".tiff", ".gif" };
/// <summary>
/// アプリケーションでサポートするファイルの拡張子を取得する。
/// </summary>
public string[] SupportExts
{
get { return _supportExts; }
}
/// <summary>
/// 現在のAppクラスのインスタンスを取得する
/// </summary>
public static new App Current
{
get { return Application.Current as App; }
}
private void Application_Startup(object sender, StartupEventArgs e)
{
// モダンな見た目にするために、ここで呼び出しておく。
System.Windows.Forms.Application.EnableVisualStyles();
}
}
}
下準備が終わったので、ViewerWindowModelクラスにOpenDirectoryメソッドを追加して、以下の内容を書きます。
FolderBrowserDialogでディレクトリを選択したら、その中から画像ファイルを取得してImagesプロパティを更新するような内容になっています。
#region 公開メソッド
public void OpenDirectory()
{
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
{
// OK以外は何もしない
return;
}
// Imagesプロパティを、選択された画像のリストに更新する
this.Images = ImageUtils.GetImages(
dialog.SelectedPath, App.Current.SupportExts);
}
}
#endregion
これで、裏方の処理が実装完了。
続いてXAMLを書いていきます。
画面を作成
まずは、名前空間の定義を行います。
ImageInfoクラスと、ViewerWindowModelクラスは確実にXAML内で使うので、Windowタグの部分に以下の二行の定義を足します。
xmlns:local="clr-namespace:WpfImageViewerApplication"
xmlns:common="clr-namespace:WpfImageViewerApplication.Common"
ついでに、WidthとHeightの定義を消して、Windowの大きさが、環境に合わせた大きさになるようにしておきます。
そして、タイトルをWPF画像ビューワーに変更します。DataContextにViewerWindowModelクラスも設定しておきます。
ここまでで、XAMLは以下のようになります。
<Window x:Class="WpfImageViewerApplication.ViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfImageViewerApplication"
xmlns:common="clr-namespace:WpfImageViewerApplication.Common"
Title="WPF画像ビューワー">
<Window.DataContext>
<local:ViewerWindowModel />
</Window.DataContext>
<Grid>
</Grid>
</Window>
全体のラフなレイアウトの決定
XAMLで、全体のラフなコントロールの配置を考えていきます。
今回は、画面上部にボタンや、スライダーなどのアプリケーションを操作する系のコントロールを配置して、その下に、サムネイルを表示する領域を設けて、残りを画像を表示する部分として利用します。
こんなレイアウトをするのにぴったりなのはDockPanelということで、Windowの直下をDockPanelにします。
Top部分にStackPanelとListBoxを順番に配置して、CenterにImageを含んだScrollViewerを配置します。
Top部分のStackPanelには、ボタンやスライダーの入ったGroupBoxを配置しておきます。
<Window x:Class="WpfImageViewerApplication.ViewerWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfImageViewerApplication"
xmlns:common="clr-namespace:WpfImageViewerApplication.Common"
Title="WPF画像ビューワー">
<Window.DataContext>
<local:ViewerWindowModel />
</Window.DataContext>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<GroupBox Header="操作">
<Button Name="buttonOpen" Content="開く" />
</GroupBox>
<GroupBox Header="拡大/縮小">
<StackPanel Orientation="Horizontal">
<TextBlock Text="X.X倍:" />
<Slider Name="sliderZoom" Minimum="0.1" Maximum="5.0" Width="75"/>
</StackPanel>
</GroupBox>
<GroupBox Header="回転">
<StackPanel Orientation="Horizontal">
<TextBlock Text="XXX度:" />
<Slider Name="sliderRotate" Minimum="0" Maximum="360" Width="75" SmallChange="1" LargeChange="90" />
</StackPanel>
</GroupBox>
</StackPanel>
<ListBox DockPanel.Dock="Top">
</ListBox>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Image />
</ScrollViewer>
</DockPanel>
</Window>
ここまで実行すると、下のようになります。
開くボタンの実装
続いて開くボタンを実装します。
コマンドを使おうかと思いましたが、今回はお手軽なイベントで実装しました。
ButtonタグにClickイベントを登録して、以下の処理を書きます。
using System.Windows;
namespace WpfImageViewerApplication
{
public partial class ViewerWindow : Window
{
public ViewerWindow()
{
InitializeComponent();
}
/// <summary>
/// このウィンドウに関連付けられたモデルを取得します。
/// </summary>
public ViewerWindowModel Model
{
get { return DataContext as ViewerWindowModel; }
}
private void buttonOpen_Click(object sender, RoutedEventArgs e)
{
// Modelに委譲する
this.Model.OpenDirectory();
}
}
}
これでボタンを押すと、実際にフォルダにある画像が読み込まれますが、このままだと何も実行結果がわからないので、ListBoxにバインドしてみます。
<ListBox DockPanel.Dock="Top"
ItemsSource="{Binding Images}">
</ListBox>
これで実行して結果を確認します。
起動して開くボタンを押下
ダイアログが出てくるのでサンプルピクチャを選択
サンプルピクチャ内の画像?がきっとListBoxに出てると思われる
ここまでで、基本的にC#のコードを書くのは終わりです。(多分)
ここからは、ガリガリとXAMLを書いて実行結果を確認して仕上げていくという感じになります。
でも、今日は眠いのでここまでzzz
続く!