「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