🖼️

PDFを画像に変換(C#+PdfiumViewer)

2023/11/15に公開

はじめに

手書きメモのPDF化と情報共有

業務で行った試験や測定についての記録を、手書きのメモとして、コメントや簡単な図で残しており、係内の職員で共有しています。このメモを、利便性や検索性を考え、ドキュメントスキャナを使用して、PDFファイルとして共有フォルダに保存しました。フォルダ構造やファイル名を工夫することで、PCでの利便性は大幅に向上しました。

さらに、タブレット等からのアクセスを容易にするため、Webブラウザでの閲覧にも挑戦し、目的とするPDFファイルが決まっている場合には、タブレット等からも容易に閲覧が可能となりました。

PDFを画像変換する必要性

しかし、目的とするファイルが決まってなく、複数のメモ(PDFファイル)を何度も閲覧しながら、必要とする情報を探す際に、問題が発生しました。

Webブラウザにて、PDFファイルを閲覧する際、ほんの数秒ですが待ち時間が生じます。そして、必要な情報を探すときには、いくつものPDFファイルを連続して閲覧しますので、数秒の待ちがファイル数分かかってくるため、回を重ねるごとに作業は苦痛となってきます。

ところで、Webブラウザにて、画像を表示したときの待ち時間が、PDFファイルの表示に比べてほとんど無いことは、ご存じのことかと思います。(表示の仕組みや元々の解像度の違いなどが理由でしょうか。)ですので、PDFファイルを目で見て読める程度に低い解像度の画像ファイルに変換して、

  • 必要な情報を探すとき → 低解像度の画像ファイル
  • 見つかった情報を閲覧するとき → 高解像度のPDFファイル

のようにすれば、ストレスなくファイルを探すことができそうです。

このような経緯で、PDFファイルから画像へ変換する必要が生じました。このためのアプリケーションは、フリーソフトウエア等で対応できるかもしれませんが、技術的興味やノウハウ蓄積などから、自前アプリケーションで対応することとしました。

アプリケーションの仕様

必要な機能

手書きメモは日々作成されていますので、それをスキャンしたPDFファイルも日々増えていきます。このため、直ちに画像変換する必要はないものの、新規作成あるいは更新されたPDFファイルを1日に1回程度まとめて変換するアプリケーションが必要です。

ここから、今回開発するアプリケーションは、以下のような機能が必要となります。

  • PDFファイルを探し、画像ファイルへ変換する
  • 指定フォルダ以下を再帰的に探す
  • 既存PDFファイルから画像ファイルへ変換する必要があるか判断し、必要なら変換する

このうち「画像ファイルへの変換が必要かどうか」については、次のような条件のときに変換が必要となります。

  • 画像ファイルが存在しない(PDFファイルが新規に作成された)
  • 画像ファイルが存在するが、PDFファイルの方がファイル日時が新しい(PDFファイルが更新された)

なお、今回生成する画像は、上にも書いたとおり、必要な情報を探す際に利用する画像であり、またブラウザが読み込む際にもストレスなく読み込めるように、以下のような方針とします。

  • 目で見て読める程度に低解像度
  • ファイルサイズはなるべく小さく

PdfiumViewerライブラリ

PDFファイルを扱うことができるライブラリはいくつかあるようですが、今回は「PdfiumViewer」というライブラリを使用しました。

このライブラリは、Google社のPDFiumプロジェクトをベースとしており、GitHubにてソースコードも公開されています。

参考リンク1:
pdfium - Git at Google

参考リンク2:
GitHub - pvginkel/PdfiumViewer - PDF viewer based on Google's PDFium.

このPdfiumViewerライブラリは、Visual StudioのNuGetを使用することで簡単にインストールできるため、今回はC#によるアプリケーションとしました。

参考サイト

今回のアプリケーション開発に当たり、以下のサイトを参考にさせて頂きました。

  • PDFium ViewerによるPDFレンダリングがイケてる - CAD日記
  • PDFium第2弾はPDFを画像化するWindowsフォームアプリケーションなり - CAD日記
  • 画像をJpegファイルで保存する (Jpegファイルへの変換) (C#プログラミング) - iPentec

その他

今回の最終目的は、Webブラウザで、情報探すときに画像ファイルを閲覧し、必要な情報が見つかったらPDFファイルを閲覧する、ことです。このため、WebサーバやWebページを動的に生成するプログラムなどが必要となります。

ただ、これらは全く別の技術となりますので、今回の記事では省略します。

PdfiumViewerの使い方

以下のコード1に、PDFファイルから画像ファイルへ変換するプログラムを示します。これは、一番基本的な使い方です。

コード1:PDFファイルから画像ファイルへ変換する基本プログラム

//...

//■■■ (1) ■■■
using PdfiumViewer;
using System.IO;

namespace PdfToImgTiny
{
    public partial class Form1 : Form
    {
        //...

        private const int IMG_DPI = 100;

        private void pdfFileToImg(string pdf_path)
        {
            //PDFを開く ■■■ (2) ■■■
            PdfDocument pdf_doc = PdfDocument.Load(pdf_path);

            //画像変換 ■■■ (3) ■■■
            Image img = pdf_doc.Render(0, IMG_DPI, IMG_DPI, PdfRenderFlags.CorrectFromDpi);

            //保存先ファイル名(元ファイル名の拡張子を ".jpg" へ)
            string fn_img_full = Path.ChangeExtension(pdf_path, ".jpg");

            //保存 ■■■ (4) ■■■
            img.Save(fn_img_full, ImageFormat.Jpeg);
        }
    }
}

コメント(1)にてPdfiumViewer名前空間をデフォルト指定しています。

コメント(2)は、指定したPDFファイルを開き、PDFドキュメント(PdfDocumentクラスのインスタンス)として変数名pdf_docに代入しています。PDFドキュメントは、PdfiumViewerライブラリが、PDFファイルを扱えるようにするためのインスタンスです。これ以降、pdf_docのメソッドやプロパティを使用して、開いているPDFを操作します。

コメント(3)は、RenderメソッドによりPDFから画像をレンダリングする(描画する)メソッドです。つまり、PDFから画像への変換は、このメソッドを呼び出せば完了します。

Renderメソッドの戻り値は、Image型でPictureBoxへの表示等も可能です。今回は、コメント(4)にてJPEG形式で保存しています。

Renderメソッドの引数ですが、第1引数は0始まりのページ番号です。コメント(3)では0を指定していますので、PDFの1ページ目が画像として生成されます。第2引数と第3引数は、横方向および縦方向のDPIの値です。DPI(Dots Per Inch)とは、1インチの幅の中に何ドット(点)あるかを示す値で、解像度(画像の細かさ)となります。1インチという実際の長さが出てきていますが、DPIは印刷業界でよく使われる単位だそうです。(画面上での表示は、ピクセルという単位がよく使われます。)

Renderメソッドの第4引数は、画像変換時のオプション指定で、PdfRenderFlags.CorrectFromDpiは、第1引数で指定したPDFのページ(1ページ目)の横幅と高さと、第2/第3引数のDPIから、生成する画像の横幅と高さを自動計算させるオプションです。これにより、戻り値であるImage型の画像オブジェクトは、適切な横幅と高さで生成されます。

DPIとJPEG画質

A4サイズの手書きメモを300DPIとしてスキャンしたPDFファイルから、上記のコード1を基にしたアプリケーションにて、実験的にDPIとJPEG画質を変えて画像ファイル変換しました。その比較を下表に示します。

なお、画像サンプル1および2は、生成された画像から部分的に切り取ったものとなります。画像サンプル1はワードプロセッサにより印刷した部分、画像サンプル2は手書き文字の部分となります。

DPI 画質 画像の横幅x高さ ファイルサイズ 画像サンプル1 画像サンプル2
50 80 412x584 15 KB 画像サンプル1 画像サンプル5
100 50 825x1169 33 KB 画像サンプル1 画像サンプル5
100 80 825x1169 44 KB 画像サンプル2 画像サンプル6
200 50 1650x2338 100 KB 画像サンプル3 画像サンプル7
200 80 1650x2338 123 KB 画像サンプル4 画像サンプル8

ここから、見やすさは、JPEG画質よりもDPIに左右されることが分かります。今回は、ファイルサイズは小さく、目で見て読み取れる、といった条件であるため、

  • DPI:100
  • JPEG画質:50

を採用します。

サンプルプログラム

作成したプロジェクト

サンプルプログラムとして、Visual Studio 2019 を使用して、以下のプロジェクトを作成しました。

項目 内容
開発ツール Visual Studio 2019 Community
言語 C#
テンプレート Windowsフォームアプリケーション
フレームワーク .NET Framework 4.7.2
プロジェクト名 PdfToImgTiny
実際のコード
(掲載しているコード)
Form1.cs、Form1.Designer.cs
追加パッケージ NuGetにて
PdfiumViewer
PdfiumViewer.Native.x86.v8-xfa (32bitのとき)
PdfiumViewer.Native.x86_64.v8-xfa (64bitのとき)

概要

アプリケーションの実行画面例を以下に示します。

図1.アプリケーションの実行画面例
図1.アプリケーションの実行画面例

ラベルが2つと、テキストボックスと「実行」ボタンが1つずつのシンプルな画面です。

「実行」ボタンをクリックすると、テキストボックスで指定されたフォルダ以下からPDFファイルを探し、PDFファイルと同じフォルダに画像ファイルを出力します。画像ファイル名は、
 <PDFファイル名> + "_" + <ページ番号3桁> + ".jpg"
となります。

たとえば、
 20230101scan.pdf
というPDFファイルがあり、ページ数が3ページだった場合、
 20230101scan_001.jpg
 20230101scan_002.jpg
 20230101scan_003.jpg
となります。

簡単な解説

コード2:PDFファイルを画像変換

PDFファイルを画像変換するpdfFileToImgメソッドを以下に示します。上述したコード1を機能拡張したメソッドです。

コード1との大きな機能の違いは、画像ファイルへの変換をすべきかどうかの判定があることです。今回のアプリケーションでは、以下のいずれかのときに画像変換を行うこととしましたので、それに合わせた機能拡張となっています。

  • 画像ファイルが存在しない(PDFファイルが新規に作成された)
  • 画像ファイルが存在するが、PDFファイルの方がファイル日時が新しい(PDFファイルが更新された)
private const string IMG_PATH_SEP = "_";
private const string IMG_PATH_EXT = ".jpg";

private const int IMG_DPI = 100;
private const long JPEG_QUALITY = 50;

//PDFファイルを画像変換
private void pdfFileToImg(string pdf_path)
{
    //PDFのフォルダ名
    string img_dir = Path.GetDirectoryName(pdf_path);
    //PDFの拡張子を除いたファイル名+区切り文字
    string img_fn_head = Path.GetFileNameWithoutExtension(pdf_path) + IMG_PATH_SEP;
    //■■■ (1) ■■■
    //保存先画像ファイル名(保存時は、この後ろに連番と拡張子が追加される)
    string img_path_head = Path.Combine(img_dir, img_fn_head);

    //■■■ (2) ■■■
    //1ページ目の画像ファイル名
    string img_path_001 = this.getImgPath(img_path_head, 1);

    //■■■ (3) ■■■
    //1ページ目の画像ファイルが存在する?
    if (File.Exists(img_path_001))
    {
        //■■■ (4) ■■■
        //PDFファイルの最終更新日時
        DateTime dt_pdf = File.GetLastWriteTime(pdf_path);
        //1ページ目画像ファイルの最終更新日時
        DateTime dt_img = File.GetLastWriteTime(img_path_001);

        //■■■ (5) ■■■
        //画像の方が大きい?(新しい?)
        if (dt_pdf < dt_img)
        {
            return;
        }
        //画像の方が古い
        else
        {
            //...
        }
    }

    //■■■ (6) ■■■
    //PDFを開く
    using (PdfDocument pdf_doc = PdfDocument.Load(pdf_path))
    {
        //■■■ (7) ■■■
        //ページ毎にループ
        for (int page = 0; page < pdf_doc.PageCount; page++)
        {
            //■■■ (8) ■■■
            //画像変換
            using (Image img_obj = pdf_doc.Render(page, IMG_DPI, IMG_DPI,
	                PdfRenderFlags.CorrectFromDpi))
            {
                //保存先ファイル名(連番挿入後)
                string img_path_full = this.getImgPath(img_path_head, page + 1);

                //保存
                this.saveJpeg(img_obj, img_path_full, JPEG_QUALITY);
            }
        }
    }
}

コメント(1)にて、引数で受け取ったPDFファイル名から、保存先画像ファイル名の連番より前の部分までを文字列生成し、img_path_headという変数に代入しています。たとえば「20230101scan.pdf」というPDFファイル名だったら、"20230101scan_"という文字列が生成されます。(実際にはフォルダ名も含まれます。)

コメント(2)は、存在チェックを行う画像ファイルのファイル名文字列をページ番号1で生成しています。上の例なら "20230101scan_001.jpg" という文字列が生成されます。(やはり、実際にはフォルダ名が含まれます。)

コメント(3)は、画像ファイルが存在するかを確認しています。存在する場合、if文内(コメント(4)~コメント(5)部分)を実行します。

コメント(4)の2つの文は、PDFファイルと画像ファイルの最終更新日時を取得しています。そして、コメント(5)にて、両者の日時を比較し、画像ファイルの方が新しければ、これ以降処理せず、return文でpdfFileToImgメソッドは終了します。

つまり、コメント(6)まで辿り着くのは、コメント(3)で画像ファイルが存在しなかった場合と、コメント(5)で画像ファイルの方が古かった(PDFファイルの方が新しかった)場合です。上記の箇条書きの2つの条件に合致します。

コメント(6)以降は、コード1で示したコードに似ています。コメント(6)でPDFファイルを開き、PDFドキュメントインスタンスを生成しています。

コメント(7)はPDFドキュメントインスタンスが持つPageCountプロパティを使用してforループを実行しています。PageCountプロパティは、開いているPDFドキュメントのページ数を表します。つまり、ページ数分のループとなります。

コメント(8)は画像変換を実行しています。続くブロック内部では、ページ番号に見合った保存先ファイル名文字列を生成し、そのファイル名で保存を行っています。

コメント(6)や(8)で使用しているusingですが、続く括弧内で生成したインスタンスが持つメモリ等のリソースを、usingのブロックから抜けたときに自動的に開放してくれる機能となります。厳密には、IDisposableインターフェースを実装しているクラスに使用でき、usingブロックを抜けるときに自動的にDispose()メソッドが呼ばれる機能です。

なお、ここで使用したコード2は、説明上、記載の無いメソッドや省略した部分があります。省略のないすべてのコードは「実際のコード」で示した「Form1.cs」を参照してください。

コード3:PDFフォルダを再帰的に処理

以下にフォルダを再帰的に辿って処理するpdfDirToImgRecursiveメソッドを示します。

//PDFフォルダを再帰的に処理
private void pdfDirToImgRecursive(string pdf_dir)
{
    //■■■ (1) ■■■
    //サブフォルダ毎に処理
    foreach (string dd in Directory.GetDirectories(pdf_dir))
    {
        //■■■ (2) ■■■
        //サブフォルダを引数に、自分自身を再帰呼び出し
        this.pdfDirToImgRecursive(dd);
    }

    //■■■ (3) ■■■
    //PDFファイル毎に処理
    foreach (string ff in Directory.GetFiles(pdf_dir, "*.pdf"))
    {
        //...

        //■■■ (4) ■■■
        //PDFファイルを画像変換
        this.pdfFileToImg(ff);
    }
}

コメント(1)は、引数で受け取ったフォルダ名以下のサブフォルダの一覧を取得し、ループします。

コメント(2)は、サブフォルダの1つずつを引数として、自分自身を呼び出しています。これにより、再帰的にフォルダを辿ることができます。

サブフォルダが処理し終わったら、コメント(3)により引数フォルダ内のすべてのPDFファイル(拡張子 ".pdf" のファイル)の一覧を取得し、ループします。

コメント(4)は、1つのPDFファイルを引数に、上記コード2で示したpdfFileToImgメソッドを呼び出しています。これにより、フォルダ内のすべてのPDFファイルが処理されます。

まとめると、引数で受け取った pdf_dir のフォルダに対して、

  • コメント(1)、(2)で、pdf_dirフォルダ内のサブフォルダをすべて処理(自身を再帰呼び出し)
  • コメント(3)、(4)で、pdf_dirフォルダ内のPDFファイルをすべて処理(画像変換)

としています。

実際のコード

C# サンプルプログラムの試し方

実際のコードの動作確認方法は、以下を参照してください。

C# サンプルプログラムの試し方

このうち、今回示すサンプルプログラムでは、以下の手順を実行します。

2.掲載コードをファイル保存 【Form1.cs、Form1.Designer.cs】
3.Visual Studio で、プロジェクト(ソリューション)作成 【プロジェクト名:PdfToImgTiny】
5.すべてのタブを閉じる
6.プロジェクトへファイルを追加 【手順2の2つのファイル】
7.NuGetによるパッケージの追加 【PdfiumViewer、PdfiumViewer.Native.x86.v8-xfa、PdfiumViewer.Native.x86_64.v8-xfa】
8.試しに実行

コード4:Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.IO;
using PdfiumViewer;
using System.Drawing.Imaging;

namespace PdfToImgTiny
{
    public partial class Form1 : Form
    {
        private const string IMG_PATH_SEP = "_";
        private const string IMG_PATH_EXT = ".jpg";

        private const int IMG_DPI = 100;
        private const long JPEG_QUALITY = 50;

        private const string NODIR_MESS = "(フォルダをドラッグ&ドロップ)";

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //最小サイズ
            this.MinimumSize = this.Size;

            //ドラッグ&ドロップを許可
            this.AllowDrop = true;

            //イベントハンドラ登録
            this.DragDrop += new System.Windows.Forms.DragEventHandler(this.form1_DragDrop);
            this.DragEnter += new System.Windows.Forms.DragEventHandler(this.form1_DragEnter);

            this.getImageCodecInfo_Jpeg();
            this.dispDir("");
        }

        //DragEnterイベントハンドラ
        private void form1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
            else e.Effect = DragDropEffects.None;
        }

        //DragDropイベントハンドラ
        private void form1_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] files = (string[])(e.Data.GetData(DataFormats.FileDrop));
                this.dispDir(files[0]);
            }
        }

        //実行ボタンクリック
        private void button1_Click(object sender, EventArgs e)
        {
            this.pdfToImg(this.textBox1.Text);
        }

        //テキストボックスにフォルダ名を表示、ボタンの有効/無効
        private void dispDir(string dir)
        {
            if (string.IsNullOrEmpty(dir) || !Directory.Exists(dir))
            {
                this.textBox1.Text = NODIR_MESS;
                this.button1.Enabled = false;
            }
            else
            {
                this.textBox1.Text = dir;
                this.button1.Enabled = true;
            }

            this.fileCount = 0;
        }

        //現在処理中のファイル数
        private int _fileCount;
        private int fileCount
        {
            get { return this._fileCount; }
            set
            {
                this._fileCount = value;
                this.dispFileCount();
            }
        }

        //PDFフォルダを画像変換
        private async void pdfToImg(string pdf_dir)
        {
            //画像変換前の初期設定
            this.fileCount = 0;
            this.button1.Enabled = false;

            //実行するTaskを生成して実行(別スレッドで)
            Task task = Task.Run(() => this.pdfDirToImgRecursive(pdf_dir));
            await task;

            //画像変換後の処理
            this.button1.Enabled = true;
        }

        //PDFフォルダを再帰的に処理
        private void pdfDirToImgRecursive(string pdf_dir)
        {
            //サブフォルダ毎に処理
            foreach (string dd in Directory.GetDirectories(pdf_dir))
            {
                //サブフォルダを引数に、自分自身を再帰呼び出し
                this.pdfDirToImgRecursive(dd);
            }

            //PDFファイル毎に処理
            foreach (string ff in Directory.GetFiles(pdf_dir, "*.pdf"))
            {
                //現在処理中のファイル数を1増加
                this.fileCount++;
                //PDFファイルを画像変換
                this.pdfFileToImg(ff);
            }
        }

        //PDFファイルを画像変換
        private void pdfFileToImg(string pdf_path)
        {
            //PDFのフォルダ名
            string img_dir = Path.GetDirectoryName(pdf_path);
            //PDFの拡張子を除いたファイル名+区切り文字
            string img_fn_head = Path.GetFileNameWithoutExtension(pdf_path) + IMG_PATH_SEP;
            //保存先画像ファイル名(保存時は、この後ろに連番と拡張子が追加される)
            string img_path_head = Path.Combine(img_dir, img_fn_head);

            //1ページ目の画像ファイル名
            string img_path_001 = this.getImgPath(img_path_head, 1);

            //1ページ目の画像ファイルが存在する?
            if (File.Exists(img_path_001))
            {
                //PDFファイルの最終更新日時
                DateTime dt_pdf = File.GetLastWriteTime(pdf_path);
                //1ページ目画像ファイルの最終更新日時
                DateTime dt_img = File.GetLastWriteTime(img_path_001);

                //画像の方が大きい?(新しい?)
                if (dt_pdf < dt_img)
                {
                    return;
                }
                //画像の方が古い
                else
                {
                    //既存画像ファイルを削除
                    string del_img_fn = this.getImgPath(img_fn_head, "*");
                    string[] del_files = Directory.GetFiles(img_dir, del_img_fn);
                    foreach (string ff in del_files)
                    {
                        File.Delete(ff);
                    }
                }
            }

            //PDFを開く
            using (PdfDocument pdf_doc = PdfDocument.Load(pdf_path))
            {
                //ページ毎にループ
                for (int page = 0; page < pdf_doc.PageCount; page++)
                {
                    //画像変換
                    using (Image img_obj = pdf_doc.Render(page, IMG_DPI, IMG_DPI, PdfRenderFlags.CorrectFromDpi))
                    {
                        //保存先ファイル名(連番挿入後)
                        string img_path_full = this.getImgPath(img_path_head, page + 1);

                        //保存
                        this.saveJpeg(img_obj, img_path_full, JPEG_QUALITY);
                    }
                }
            }
        }

        //ファイル名先頭部と末尾部からファイル名文字列を生成する
        private string getImgPath(string head, int tail_num)
        {
            return this.getImgPath(head, tail_num.ToString("D3"));
        }
        private string getImgPath(string head, string tail)
        {
            return head + tail + IMG_PATH_EXT;
        }

        //現在処理中のファイル数を画面上に表示する
        private delegate void dispFileCountDelegate();
        private void dispFileCount()
        {
            //別スレッド?
            if (this.InvokeRequired)
            {
                //メインスレッドの自分自身を呼び出し
                dispFileCountDelegate func = new dispFileCountDelegate(dispFileCount);
                this.Invoke(func);
            }
            //メインスレッド
            else
            {
                //ラベルに表示
                this.label2.Text = $"処理済み: {this.fileCount}";
            }
        }

        //JPEG保存(画質指定)
        private void saveJpeg(Image img_obj, string path, long quality)
        {
            //JPEGエンコーダ取得失敗?
            if (this.jpegEncoder == null)
            {
                //Imageオブジェクトにお任せで保存
                img_obj.Save(path, ImageFormat.Jpeg);
            }
            //JPEGエンコーダあり
            else
            {
                //画質パラメータ生成
                EncoderParameter encParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
                EncoderParameters encParams = new EncoderParameters(1);
                encParams.Param[0] = encParam;
                
                //パラメータを指定して保存
                img_obj.Save(path, this.jpegEncoder, encParams);
            }
        }

        //JpegEncoderを取得
        private ImageCodecInfo jpegEncoder = null;
        private void getImageCodecInfo_Jpeg()
        {
            this.jpegEncoder = null;
            foreach (ImageCodecInfo ici in ImageCodecInfo.GetImageEncoders())
            {
                if (ici.FormatID == ImageFormat.Jpeg.Guid)
                {
                    this.jpegEncoder = ici;
                    break;
                }
            }
        }

    }
}

コード5:Form1.Designer.cs


namespace PdfToImgTiny
{
    partial class Form1
    {
        /// <summary>
        /// 必要なデザイナー変数です。
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// 使用中のリソースをすべてクリーンアップします。
        /// </summary>
        /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows フォーム デザイナーで生成されたコード

        /// <summary>
        /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディターで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.label2 = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
            this.button1.Location = new System.Drawing.Point(197, 56);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(95, 23);
            this.button1.TabIndex = 3;
            this.button1.Text = "実行";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(10, 17);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(46, 12);
            this.label1.TabIndex = 0;
            this.label1.Text = "フォルダ:";
            // 
            // textBox1
            // 
            this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.textBox1.Location = new System.Drawing.Point(62, 14);
            this.textBox1.Name = "textBox1";
            this.textBox1.ReadOnly = true;
            this.textBox1.Size = new System.Drawing.Size(230, 19);
            this.textBox1.TabIndex = 1;
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(60, 36);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(11, 12);
            this.label2.TabIndex = 2;
            this.label2.Text = "0";
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(304, 91);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.TextBox textBox1;
        private System.Windows.Forms.Label label2;
    }
}

Discussion