C#+WPFでGUIアプリケーションを作ってみる
「JavaFX+FXMLでGUIアプリケーションを作ってみる」では、JavaFX+FXMLを使ってJavaランタイム(JRE)が入っている環境向けのGUIアプリケーションを作った。 ここでは、C#+WPFを使い、.NET Frameworkが入っている環境向けのGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版
>systeminfo
OS 名: Microsoft Windows 8.1 Pro
OS バージョン: 6.3.9600 N/A ビルド 9600
OS ビルドの種類: Multiprocessor Free
システムの種類: x64-based PC
プロセッサ: 1 プロセッサインストール済みです。
[01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
Visual Studio Community 2015のインストール
まず、C# 6.0を使うために、Visual Studio Community 2015をインストールする。
HDD容量を節約するため、ここではインストール時のオプションとして「Custom」を選択し、C++開発に必要となる以下のコンポーネントのみをインストールした。
- 「Programming Languages」→「Visual C++」→「Common Tools for Visual C++ 2015」
- 「Windows and Web Development」→「Windows 8.1 and Windows Phone 8.0/8.1 Tools」→「Tools and Windows SDKs」
なお、C#を使うだけであればこれらも不要である。
インストールが終わったらOSを再起動し、アプリケーションメニューからVisual Studioを起動する。 初期設定はデフォルトで進めればよい。
GUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」と同様に、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
「File」→「New」→「Project」を開き、「Templates」→「Visual C#」→「Windows」から「WPF Application」を選択する。 さらに、名前を「SHA1CalculatorWPF」としてOKを押し、プロジェクトを作成する。
まず、MainWindow.xamlについて、ToolBoxからDataGridを選択し配置する。
また、WindowのTitle属性を「MainWindow」から「SHA-1 Calculator WPF」に書き換える。
さらに他のコードを書きながら編集していくと、最終的には次のようなコードとなる。
<Window x:Class="SHA1CalculatorWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SHA1CalculatorWPF"
mc:Ignorable="d"
Title="SHA-1 Calculator WPF" Height="350" Width="640">
<Grid AllowDrop="True" DragOver="Grid_DragOver" Drop="Grid_Drop">
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" CanUserResizeRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="File Path" Binding="{Binding Path}" Width="*"/>
<DataGridTextColumn Header="sha1sum" Binding="{Binding Sha1sum}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
GridのDragOver、Drop属性が、次のコードで定義しているハンドラ関数に対応する。 また、DataGridTextColumnのBinding属性で指定した変数が、FileInfoクラスのメンバ変数に対応する。
MainWindow.xaml.csを開き、動作を記述するコードを書くと次のようになる。
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Runtime.CompilerServices;
using System.Windows;
namespace SHA1CalculatorWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ObservableCollection<FileInfo> fileList;
public class FileInfo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string Path { get; set; }
private string _sha1sum;
public string Sha1sum
{
get { return _sha1sum; }
set
{
if (value != _sha1sum)
{
_sha1sum = value;
NotifyPropertyChanged();
}
}
}
}
public MainWindow()
{
InitializeComponent();
fileList = new ObservableCollection<FileInfo>();
dataGrid.ItemsSource = fileList;
}
private void Grid_DragOver(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Copy;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = false;
}
private void Grid_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var paths = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (var path in paths)
{
var fileinfo = new FileInfo { Path = path, Sha1sum = "" };
fileList.Add(fileinfo);
var worker = new BackgroundWorker();
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync(fileinfo);
}
}
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// update digest with each 8MB chunk
var fileinfo = (FileInfo)e.Argument;
var fs = new FileStream(fileinfo.Path, FileMode.Open, FileAccess.Read);
var sha1 = new System.Security.Cryptography.SHA1Managed();
var buf = new byte[8 * 1024 * 1024];
int n = fs.Read(buf, 0, buf.Length);
while (fs.Position < fs.Length)
{
sha1.TransformBlock(buf, 0, n, buf, 0);
fileinfo.Sha1sum = string.Format("Calculating {0}%...", 100L * fs.Position / fs.Length);
n = fs.Read(buf, 0, buf.Length);
}
sha1.TransformFinalBlock(buf, 0, n);
fileinfo.Sha1sum = BitConverter.ToString(sha1.Hash).Replace("-", "").ToLower();
}
}
}
上のコードでは、ObservableCollectionをDataGridのItemsSourceにセットすることで、データバインディングを行っている。
ただし、FileInfoクラスのメンバ変数の変更までは即座にビューに反映されないため、FileInfoクラスにもINotifyPropertyChangedインタフェースを実装する必要がある。
そして、プロパティのsetterの中でPropertyChangedEventHandlerを呼ぶことにより、更新をビューに通知する。
また、バッググラウンドスレッドを生成するには、BackgroundWorkerを使う。
SHA-1ハッシュの計算には.NET基盤(CLR)上で計算するSHA1ManagedクラスとWindows APIを利用するSHA1CryptoServiceProvider、SHA1Cngクラスが使えるが、基本的にはSHA1Managedを使えばよい(参考)。
コードを保存した後「Start」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。

進捗を表す文字列がビューにリアルタイムで表示され、ハッシュ値が計算できていることが確認できる。
実行可能なexeファイルを作成する
ビルド設定を「Debug」から「Release」に変えてビルドすることで、プロジェクトフォルダのSHA1CalculatorWPF\bin\Release以下にデバッグ情報を含まないexeファイルが生成される。
また、メニューの「Build」→「Publish SHA1CalculatorWPF」を選ぶことで、ClickOnceインストーラを作成することもできる。
生成されたexeファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。
関連リンク
- WPF4.5入門 その62「まとめ」 - かずきのBlog@hatena
- [WPF]DataGridへのBindingに関する基本設計 | OITA: Oika's Information Technological Activities
- INotifyPropertyChanged, The .NET 4.6 Way | DanRigby.com
- [MVVM] 便利で間違えのないプロパティ変更通知を行うBindableBaseの実装 – Tech.D-ITlab | Denso IT Laboratory researcher's blog sites
- How to: Open a File That is Dropped on a RichTextBox Control