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++開発に必要となる以下のコンポーネントのみをインストールした。

なお、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を利用するSHA1CryptoServiceProviderSHA1Cngクラスが使えるが、基本的にはSHA1Managedを使えばよい(参考)。

コードを保存した後「Start」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。

f:id:inaz2:20160502010405p:plain

進捗を表す文字列がビューにリアルタイムで表示され、ハッシュ値が計算できていることが確認できる。

実行可能なexeファイルを作成する

ビルド設定を「Debug」から「Release」に変えてビルドすることで、プロジェクトフォルダのSHA1CalculatorWPF\bin\Release以下にデバッグ情報を含まないexeファイルが生成される。 また、メニューの「Build」→「Publish SHA1CalculatorWPF」を選ぶことで、ClickOnceインストーラを作成することもできる。

生成されたexeファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。

関連リンク