前回はウィンドウのドンガラをやりましたので、今回はボタン等のコントロールで呼び出されるイベント処理部分、特にイメージファイル形式の変換処理部分の解説をしましょう。

 

【ImageConverter.csの続き】

        //イメージを開く
        private void open_button_Click (object sender, System.EventArgs e)
        {
            //OpenFileDialogオブジェクトの生成
            OpenFileDialog openDlg = new OpenFileDialog();    //usingを使うと最後のDispose処理が不要になる
            openDlg.Filter = "pngファイル|*.png|jpeg/jpgファイル|*.jpeg;*.jpg|bmpファイル|*.bmp|gifファイル|*.gif|tiffファイル|*.tiff";   //解説:最初からGDI+で扱えるファイル種類で一般的なものに絞りました。
            openDlg.Multiselect = true;
            DialogResult result = openDlg.ShowDialog();
            if(result == DialogResult.OK)
            {
                //リストボックスにイメージファイル名を登録
                if(openDlg.FileNames != null)    //複数ファイル選択の場合
                {
                    listBox.BeginUpdate();
                    for(int i = 0; i < openDlg.FileNames.Length; i++ )
                        listBox.Items.Add(openDlg.FileNames[i]);
                    listBox.EndUpdate();
                }
                else                            //単一ファイル選択の場合
                    listBox.Items.Add(openDlg.FileName);
            }
            else if(result == DialogResult.Cancel)
                MessageBox.Show("キャンセルされました。", "キャンセル", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //オブジェクトを破棄する(解説:↑に書いたようにusingを使うと、これは不要となります。)
            openDlg.Dispose();
        }

        //リストボックスで指定したイメージの消去
        private void del_button_Click (object sender, System.EventArgs e)
        {
            if(listBox.SelectedIndex != -1)    //選択されている場合(-1は選択されていない場合)
            {
                listBox.Items.Remove(listBox.SelectedItem);            //リストボックスから消去
                pictureBox.Image = null;                            //pictureBoxの画面消去
                num_label.Text = "イメージ番号:";   //解説:ラベルの初期化もします。
            }
            else                            //-1は選択されていない場合
                MessageBox.Show("選択されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        //すべてのイメージの消去
        private void clr_button_Click (object sender, System.EventArgs e)
        {
            listBox.Items.Clear();                    //リストボックスから消去する
            pictureBox.Image = null;                //pictureBoxの画面消去
            num_label.Text = "イメージ番号:";
        }

        //イメージ型式変換
        private void cnvt_button_Click (object sender, System.EventArgs e)
        {
            //エラーチェック-変換対象イメージファイルない場合
            if(listBox.Items.Count == 0)
            {
                MessageBox.Show("変換対象ファイルがありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            //エラーチェック-「ファイルサイズを変更」がチェックされていて、有効な画像幅または画像高が入力されていない場合(解説:ここで同時に画像幅と高さを取得しておきます。)
            int new_width = 0, new_height = 0;        //入力画像幅と高さ
            if((resizing.CheckState == CheckState.Checked) &&
                !(Int32.TryParse(img_Width.Text, out new_width) && Int32.TryParse(img_Height.Text, out new_height)))
            {
                MessageBox.Show("画像幅または画像高の値が不正です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            //以下オプションに応じて指定画像ファイル形式で出力(同じ形式の場合、ファイル名を変更して出力))
            if(selected_only.CheckState == CheckState.Checked)    //単一ファイルの変換
            {
                //エラーチェック-変換対象ファイルが未選択の場合
                if(listBox.SelectedIndex == -1)
                {
                    MessageBox.Show("変換対象ファイルが選択されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return;
                }
                //出力ファイルパス・名の設定
                //1-ソースファイルパス・名の拡張子を変換対象のものに変更
                output_Name = Path.ChangeExtension(listBox.Text, img_ext[comBox.SelectedIndex]);
                //2-出力先指定があればそのパスと入れ替える
                if(output_Path != "")
                    output_Name = output_Path + "\\" + Path.GetFileName(output_Name);
                if(resizing.CheckState == CheckState.Checked)    //ファイルサイズを変更する
                {
                    //エラーチェック-ソースファイル拡張子と出力拡張子が同じ場合
                    if(Path.GetExtension(listBox.Text) == img_ext[comBox.SelectedIndex])
                    {   //解説:ファイルサイズを変更する場合にはエラーにせず、ファイル名に"_Rev"をつけて保存する。
                        output_Name = Path.GetDirectoryName(output_Name) + "\\" + Path.GetFileNameWithoutExtension(output_Name) + "_Rev" + img_ext[comBox.SelectedIndex];
                        MessageBox.Show("ソースと同じファイル形式の指定であり、ファイル名を変更しました。\r\n" + output_Name, "注意", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                    }

                    //解説:↓のImageConversion関数二つ(オーバーロードしています)を参照して下さい。
                    Image_Conversion(listBox.Text, output_Name, new_width, new_height, asp_ratio.Checked);
                }
                else                                            //ファイルサイズを変更しない
                {
                    //エラーチェック-ソースファイル拡張子と出力拡張子が同じ場合(解説:エラー扱い)
                    if(Path.GetExtension(listBox.Text) == img_ext[comBox.SelectedIndex])
                    {
                        MessageBox.Show("ソースと同じファイル形式の指定であり、変換を中止します。\r\n" + output_Name, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    Image_Conversion(listBox.Text, output_Name);
                }
            }
            else                                                //ListBox表示のすべてのファイルの変換
            {
                foreach(object file in listBox.Items)
                {
                    //出力ファイルパス・名の設定
                    //1-ソースファイルパス・名の拡張子を変換対象のものに変更
                    output_Name = Path.ChangeExtension(file.ToString(), img_ext[comBox.SelectedIndex]);

                    //解説:単一ファイルと同じ処理ですが、ソースファイルがlistBox.Textかforeachのobjectかの違いがあります。
                    //2-出力先指定があればそのパスと入れ替える
                    if(output_Path != "")
                        output_Name = output_Path + "\\" + Path.GetFileName(output_Name);
                    if(resizing.CheckState == CheckState.Checked)    //ファイルサイズを変更する
                    {
                        //エラーチェック-ソースファイル拡張子と出力拡張子が同じ場合
                        if(Path.GetExtension(file.ToString()) == img_ext[comBox.SelectedIndex])
                        {
                            output_Name = Path.GetDirectoryName(output_Name) + "\\" + Path.GetFileNameWithoutExtension(output_Name) + "_Rev" + img_ext[comBox.SelectedIndex];
                            MessageBox.Show("ソースと同じファイル形式の指定であり、ファイル名を変更しました。\r\n" + output_Name, "注意", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                        }
                        Image_Conversion(file.ToString(), output_Name, new_width, new_height, asp_ratio.Checked);
                    }
                    else                                            //ファイルサイズを変更しない
                    {
                        //エラーチェック-ソースファイル拡張子と出力拡張子が同じ場合
                        if(Path.GetExtension(file.ToString()) == img_ext[comBox.SelectedIndex])
                        {
                            MessageBox.Show("ソースと同じファイル形式の指定であり、変換を中止します。\r\n" + output_Name, "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                            continue;
                        }
                        Image_Conversion(file.ToString(), output_Name);
                    }
                }
            }
            output_Path = "";        //出力し終えたので、出力先パスを初期化する
            output_Name = "";        //再初期化
        }

        //出力先(フォールダー)の指定
        private void sel_button_Click(object sender, EventArgs e)
        {    //解説:ヘルプが無いのでメッセージボックスで注意喚起します。
            MessageBox.Show("出力先を指定せず、「キャンセル」すればオリジナル画像と同じフォールダーに保存されます。", "出力先設定上の注意", MessageBoxButtons.OK, MessageBoxIcon.Information);
            FolderBrowserDialog fbDlg = new FolderBrowserDialog();    //usingを使うと最後のDispose処理が不要になる
            // ダイアログの説明文
            fbDlg.Description = "変換したイメージファイルの出力先(フォールダー)を選択してください。";
            // デフォルトのフォルダ(output_Pathにデータがあればそこ、なければ"C:\")
            fbDlg.SelectedPath = (output_Path == "") ? @"C:\" : output_Path;    //解説:C由来の条件文です。
            // 「新しいフォルダーの作成する」ボタンを表示する(デフォルトはtrue)
            fbDlg.ShowNewFolderButton = true;
            //フォルダを選択するダイアログを表示する
            if(fbDlg.ShowDialog() == DialogResult.OK)
            {
                output_Path = fbDlg.SelectedPath;
                MessageBox.Show(output_Path, "指定された出力先", MessageBoxButtons.OK, MessageBoxIcon.Information);
                tooltip.SetToolTip(sel_button, output_Path);    //sel_buttonにツールチップをつける
            }
            else
                MessageBox.Show("キャンセルされました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            // オブジェクトを破棄する
            fbDlg.Dispose();
        }

        //フォームを閉じる
        private void exit_button_Click(object sender, EventArgs e)
        {
            Close();
        }

        //resizingチェックボックスの状態が変更された
        private void resizing_CheckedChanged(object sender, EventArgs e)
        {
            if(resizing.CheckState == CheckState.Checked)    //有効化
            {
                img_Width.Enabled = true;    //"画像幅"用ラベル
                img_Height.Enabled = true;    //"画像高"用ラベル
                asp_ratio.Enabled = true;    //"縦横比を維持"用ラベル
                asp_ratio.Checked = true;    //"縦横比を維持"用チェックボックス
            }
            else                                            //無効化
            {
                img_Width.Enabled = false;    //"画像幅"用ラベル
                img_Height.Enabled = false;    //"画像高"用ラベル
                asp_ratio.Enabled = false;    //"縦横比を維持"用ラベル
                asp_ratio.Checked = false;    //"縦横比を維持"用チェックボックス
            }
        }

        //同一サイズで変換
        private void Image_Conversion(string filename, string output_name)
        {
            //ソースImageオブジェクトの作成
            using(Image image = Image.FromFile(filename))    //自動的にDispose()が呼び出される
            {
                //変換コーデックのImageCodecInfoを作成
                ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
                ImageCodecInfo ici = null;
                for(int i = 0; i < encoders.Length; i++)
                    if(encoders[i].MimeType == comBox.SelectedItem.ToString())
                        ici = encoders[i];
                //画質パラメーターカテゴリー用EncoderオブジェクトをGUIDに基づいて作成
                //EncoderParameter配列であるEncoderParametersオブジェクトの作成

                EncoderParameters encoderParameters = new EncoderParameters(1);
                //出力用Imageをレベル100の画質で保存
                encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
                image.Save(output_name, ici, encoderParameters);
            }
        }

        //サイズを変更して変換(オーバーロード関数-既定値:アスペクト比を維持)
        private void Image_Conversion(string filename, string output_name, int width, int height, bool aspectRatio = true)
        {
            //ソースImageオブジェクトの作成
            float scale;
            int new_width = width, new_height = height;
            using(Image image = Image.FromFile(filename))            //自動的にDispose()が呼び出される
            {
                //変更倍率を取得する
                if(aspectRatio)
                {
                    scale = Math.Min((float)width / (float)image.Width,
                                    (float)height / (float)image.Height);
                    //変更サイズを取得
                    new_width = (int)(image.Width * scale);
                    new_height = (int)(image.Height * scale);
                }
                //変換用Bitmapオブジェクトの作成
                using(Bitmap bmp = new Bitmap(width, height))        //自動的にDispose()が呼び出される
                using(Graphics graphics = Graphics.FromImage(bmp))    //自動的にDispose()が呼び出される
                {
                    //背景色(黒)
                    using(SolidBrush solidBrush = new SolidBrush(Color.Black))
                    {    //自動的にDispose()が呼び出される
                        graphics.FillRectangle(solidBrush, new RectangleF(0, 0, width, height));
                    }
                    //左上起点で変更画像を描画
                    graphics.DrawImage(image, 0, 0, new_width, new_height);
                    //変換コーデックのImageCodecInfoを作成
                    ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
                    ImageCodecInfo ici = null;
                    for(int i = 0; i < encoders.Length; i++)
                        if(encoders[i].MimeType == comBox.SelectedItem.ToString())
                            ici = encoders[i];
                    //画質パラメーターカテゴリー用EncoderオブジェクトをGUIDに基づいて作成
                    //EncoderParameter配列であるEncoderParametersオブジェクトの作成

                    EncoderParameters encoderParameters = new EncoderParameters(1);
                    //出力用Imageをレベル100の画質で保存
                    encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
                    bmp.Save(output_name, ici, encoderParameters);
                }
            }
        }
    }
}

 

比較的短く、単純なプログラミングでしたが、出来上がりは結構使い勝手の良いツールとなりました。

 

前回の動作テストの後、特段の不具合もないので、コード解説に入ってゆきます。

解説は2回に分けて、最初にウィンドウデザイン等、ドンガラ部分を、次は動作の実装部分を解説します。

 

【ImageConverter.cs】

//////////////////////////////////////////
// ImageConverter.cs
// Copyright (c) by Ysama September, 2024
//////////////////////////////////////////

using System;
using System.Drawing;
using System.Drawing.Imaging;    //ImageCodecInfoを使う為
using System.IO;                //https://learn.microsoft.com/ja-jp/dotnet/api/system.io.path?view=net-8.0
using System.Linq;                //配列のWhere抽出を行う為
using System.Reflection;        //Assemblyを使う為
using System.Windows.Forms;

namespace ImageConverter
{
    public class ImgConverter : Form
    {
        //MimeTypeリスト(解説:img_extと同様です。選択リスト表示やMicrosoft内部データに合わせました。)
        string[] img_MimeType = new string [5] {"image/png", "image/jpeg", "image/bmp", "image/gif", "image/tiff"};
        //拡張子
        string[] img_ext = new string [5] {".png", ".jpg", ".bmp", ".gif", ".tiff"};
        //出力フォールダーパス(解説:ユーザー指定がある場合に使います。)
        string output_Path = "";
        //出力イメージファイルパス・名(解説:出力先のパスや拡張子を変更する為に使います。)
        string output_Name = "";
        //コントロール
        private ListBox listBox;            //変換対象ファイルのファイルパス、名のリスト表示用
        private Button open_button;            //変換対象ファイルの追加用
        private Button del_button;            //変換対象ファイルの削除用
        private Button clr_button;            //リストボックスクリア用
        private Button sel_button;            //変換ファイル出力先の指定用
        private Button cnvt_button;            //ファイル形式の変換用
        private Button exit_button;            //終了用
        private ToolTip tooltip;            //変換ファイル出力先の表示用
        private Label num_label;            //画像説明用
        private Label format_label;            //変換ファイル形式指定用
        private Label w_label;                //変更サイズ幅用
        private Label h_label;                //変更サイズ高用
        private PictureBox pictureBox;        //イメージファイル表示用
        private ComboBox comBox;            //変換ファイル形式指定用
        private CheckBox selected_only;        //選択ファイルのみ(falseの場合はリストボックスの全ファイル)変換
        private CheckBox resizing;            //変換時にサイズを変更する
        private CheckBox asp_ratio;            //縦横比(aspect ratio)維持有無
        private TextBox img_Width;            //変更サイズ幅
        private TextBox img_Height;            //変更サイズ高

        //エントリーポイント
        [STAThread]
        public static void Main() 
        {
            Application.Run(new ImgConverter());
        }

        //コンストラクター
        public ImgConverter()
        {
            this.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);    //プログラムアイコンをフォームにつける
            this.Size = new Size(496, 518);
            this.MinimumSize = new Size(496, 518);    //(解説:これ以上小さくすると見えないので)
            this.Text = "Image Converter";
            this.Load += new EventHandler(InitComponent);    //コントロール等の初期化((解説:WN_CREATE処理)
        }

        //コントロール等の初期化
        private void InitComponent(object sender, EventArgs e)
        {
            //表示停止
            SuspendLayout();
            //リストボックス
            listBox = new ListBox();
            listBox.Location = new Point(10, 10);
            listBox.Size = new Size(280, 148);
            listBox.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
            listBox.HorizontalScrollbar = true;
            listBox.TabIndex = 0;
            //listBox.SelectedIndexChanged += new EventHandler(listBox_SelectedIndexChanged);        listBox.SelectedIndexChanged += listBox_SelectedIndexChanged;    //解説:標記の違いだけで↑と変わらない。
            listBox.DragEnter += listBox_DragEnter;
            listBox.DragDrop += listBox_DragDrop;
            listBox.AllowDrop = true;
            //ボタン1
            open_button = new Button();
            open_button.Location = new Point(300, 10);
            open_button.Size = new Size(160, 24);
            open_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            open_button.Text = "イメージの選択・追加";
            open_button.Click += new EventHandler(open_button_Click);
            clr_button = new Button();
            clr_button.Location = new Point(300,  open_button.Height + 20);
            clr_button.Size = new Size(160, 24);
            clr_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            clr_button.Text = "リスト初期化";
            clr_button.Click += new EventHandler(clr_button_Click);
            del_button = new Button();
            del_button.Location = new Point(300, open_button.Height + clr_button.Height + 30);
            del_button.Size = new Size(160, 24);
            del_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            del_button.Text = "イメージの削除";
            del_button.Click += new EventHandler(del_button_Click);
            sel_button = new Button();
            sel_button.Location = new Point(300, open_button.Height + del_button.Height + clr_button.Height + 60);
            sel_button.Size = new Size(160, 24);
            sel_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            sel_button.Text = "出力先指定";
            sel_button.Click += new EventHandler(sel_button_Click);
            exit_button = new Button();
            exit_button.Location = new Point(300, this.ClientSize.Height - exit_button.Height - 10);
            exit_button.Size = new Size(160, 24);
            exit_button.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            exit_button.Text = "終了";
            exit_button.Click += new EventHandler(exit_button_Click);
            cnvt_button = new Button();
            cnvt_button.Location = new Point(300, this.ClientSize.Height - exit_button.Height - cnvt_button.Height - 20);
            cnvt_button.Size = new Size(160, 24);
            cnvt_button.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            cnvt_button.Text = "イメージ形式を変換";
            cnvt_button.Click += new EventHandler(cnvt_button_Click);
            //ツールチップ(解説:指定出力先を表示する為に追加しました。)
            tooltip = new ToolTip();
            tooltip.ShowAlways = true;
            //ラベル1
            num_label = new Label();
            num_label.AutoSize = true;
            num_label.Location = new Point(10, this.ClientSize.Height - num_label.Height);
            num_label.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            num_label.Text = "イメージ番号:";
            format_label = new Label();
            format_label.Size = new Size(160, 20);
            format_label.Location = new Point(300, open_button.Height + del_button.Height + clr_button.Height + sel_button.Height + 65);
            format_label.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            format_label.TextAlign = ContentAlignment.MiddleCenter;
            format_label.Text = "出力ファイル形式指定";
            //ピクチャーボックス
            pictureBox = new PictureBox();
            pictureBox.BorderStyle = BorderStyle.Fixed3D;
            pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
            pictureBox.Size = new Size(280, 280);
            pictureBox.Location = new Point(10, this.ClientSize.Height - num_label.Height - pictureBox.Height - 8);
            pictureBox.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            //コンボボックス
            comBox = new ComboBox();
            comBox.Location = new Point(300, open_button.Height + del_button.Height + clr_button.Height + sel_button.Height + format_label.Height + 65);
            comBox.Size = new Size(160, 24);
            comBox.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            comBox.DropDownStyle = ComboBoxStyle.DropDownList;
            comBox.Items.AddRange(img_MimeType);
            comBox.SelectedIndex = 0;        //初期値は"image/png"
            //チェックボックス1
            selected_only = new CheckBox();
            selected_only.TextAlign = ContentAlignment.MiddleLeft;
            selected_only.Location = new Point(10, 20);
            selected_only.Size = new Size(160, 24);
            selected_only.Text = "選択ファイルのみ変換";
            resizing = new CheckBox();
            resizing.TextAlign = ContentAlignment.MiddleLeft;
            resizing.Location = new Point(10, selected_only.Height + 25);
            resizing.Size = new Size(160, 24);
            resizing.Text = "ファイルサイズを変更";
            resizing.CheckedChanged += new EventHandler(resizing_CheckedChanged);
            //テキストボックス
            img_Width = new TextBox();
            img_Width.Location = new Point(60, selected_only.Height + resizing.Height + 30);
            img_Width.Size = new Size(100, 24);
            img_Width.Enabled = false;
            img_Height = new TextBox();
            img_Height.Location = new Point(60, selected_only.Height + resizing.Height + img_Width.Height + 35);
            img_Height.Size = new Size(100, 24);
            img_Height.Enabled = false;
            //チェックボックス2
            asp_ratio = new CheckBox();
            asp_ratio.TextAlign = ContentAlignment.MiddleLeft;
            asp_ratio.Location = new Point(60, selected_only.Height + resizing.Height + img_Width.Height + img_Height.Height + 35);
            asp_ratio.Size = new Size(100, 24);
            asp_ratio.Text = "縦横比を維持";
            asp_ratio.Enabled = false;
            //ラベル2
            w_label = new Label();
            w_label.Size = new Size(60, 20);
            w_label.Location = new Point(10, selected_only.Height + resizing.Height + 32);
            w_label.Text = "画像幅";
            h_label = new Label();
            h_label.Size = new Size(60, 20);
            h_label.Location = new Point(10, selected_only.Height + resizing.Height + img_Width.Height + 37);
            h_label.Text = "画像高";
            //オプショングループボックス(解説:サイズ変更関連コントロールは纏めました。)
            GroupBox groupBox = new GroupBox();    //解説:これは参照する必要が無いのでローカルにしました。
            groupBox.Location = new Point(296, open_button.Height + del_button.Height + clr_button.Height + sel_button.Height + format_label.Height + comBox.Height + 70);
            groupBox.Size = new Size(174, 150);
            groupBox.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            groupBox.Text = "Option";
            groupBox.Controls.Add(selected_only);
            groupBox.Controls.Add(resizing);
            groupBox.Controls.Add(img_Width);
            groupBox.Controls.Add(img_Height);
            groupBox.Controls.Add(w_label);
            groupBox.Controls.Add(h_label);
            groupBox.Controls.Add(asp_ratio);
            //フォームにコントロールを追加
            Controls.Add(listBox);
            Controls.Add(open_button);
            Controls.Add(clr_button);
            Controls.Add(del_button);
            Controls.Add(sel_button);
            Controls.Add(format_label);
            Controls.Add(comBox);
            Controls.Add(groupBox);
            Controls.Add(cnvt_button);
            Controls.Add(exit_button);
            Controls.Add(pictureBox);
            Controls.Add(num_label);
            //表示再開
            ResumeLayout(false);
        }

        //終了処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        //listBoxの選択項目の変更(解説:指定変更の際に呼び出されます。)
        private void listBox_SelectedIndexChanged(object sender, System.EventArgs e)
        {
            if(listBox.SelectedIndex != -1)    //選択されている場合(-1は選択されていない場合)
            {
                //イメージをPictureBox.に表示
                pictureBox.Image = Image.FromFile(listBox.Text);
                num_label.Text = "イメージ番号:" + listBox.SelectedIndex + " (ファイル名:" + Path.GetFileName(listBox.Text) + ")";
                Image img = Image.FromFile(listBox.Text);
                img_Width.Text = img.Width.ToString();
                img_Height.Text = img.Height.ToString();
            }
        }

        //listBoxのDragEnterイベントハンドラ(解説:リストボックスのドラッグ用)
        private void listBox_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
        {
            //コントロール内にドラッグされたとき実行される
            if(e.Data.GetDataPresent(DataFormats.FileDrop))
                //ドラッグされたデータ形式を調べ、ファイルのときはコピーとする
                e.Effect = DragDropEffects.Copy;
            else
                //ファイル以外は受け付けない
                e.Effect = DragDropEffects.None;
        }

        //listBoxのDragDropイベントハンドラ(解説:リストボックスのドロップ用)
        private void listBox_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
        {
            //コントロール内にドロップされたとき実行される
            //ドロップされたすべてのファイル名を取得する
            string[] fileNames = (string[]) e.Data.GetData(DataFormats.FileDrop, false);
            //filenameをimg_ext配列でフィルタリングする(解説:これラムダ式)はとてもC#的ですね。)
            string[] imgfiles = fileNames.Where(file => img_ext.Contains(Path.GetExtension(file).ToLower())).ToArray();

            /* ↑のラムダ式でやっていることを通常のC#コードにするとこんな感じでしょうか?

            string[] imgfiles = new string[0];
            foreach(string file in fileNmaes)
                foreach(string ext in img_ext)
                    if(Path.GetExtension(file).ToLower() == ext)
                    {
                        Array.Resize(ref imgfiles, imgfiles.Length + 1);
                        imgfiles[imgfiles.Length - 1] = file;
                    }

            //解説:実は私、ラムダ式は(昔の一行BASICの様で)本当はちょっと苦手でして...(笑;)

            */
            //listBoxに追加する
            listBox.Items.AddRange(imgfiles);
        }
<<< 続く >>>

次回は↑で呼び出されるコントロールイベントやメソッドの処理を解説します。

 

前回はイメージファイル形式の変換について少し書きましたが、ImageConverterは既に出来上がり、現在動作チェック中です。

 

Optionを変えて色々とやっていますが、何処から不具合が出るかもしれないので、まだ続けます。(というか、実際に使って問題があるかどうかチェックすることを継続します。)

 

取り敢えず、当初の目的であるファイル形式の種類の違う(gifとjpeg)トランプのカードイメージをファイル形式、サイズを同一とし、縦横比の異なる絵札(jpeg)に余白が出ないよう縦横比を変更してジョーカー込みで53枚のgifファイルセットを作成してみました。

 

きれいにそろったカードイメージは気持ちが良いものです。

 

これらを自作ResWriterでリソースファイルを作成しましたが、どうも*.gifファイルはファイルストリームリソースとして認識するようで、イメージリソースとして取り込めるよう、これらをもう一度*.bmpファイルに変換しました。

 

そして今度は難なくリソースファイルを作成できました。(しっかし、一本約20KBのファイル53本なので、でかいねっ!)

 

これを自作のResReaderで再確認してみます。

 

キチンと設計通り変換してくれるようですので、次回からはImageConverterのコード解説を始めます。

 

前回、UIの最終形と称して画像

を載せましたが、その直後「矢張り出力先を選択したら表示できないとなー」と考え、「出力先指定」ボタンにToolTipをつけて、

確認できるようにしました。

 

ところで、

 

画像のファイル形式を変更する際に画質の保全という問題があります。例としてMicrosoft Learnに載っていたプログラムを使ってチェックしてみましょう。(注)

注:実際には末尾に載せている、日本語プログラム化したものを使っています。

 

実行結果は次の通り。最初の画像がBMPで、以降0→25→50→75→100までの品質レベル(圧縮するので品質が落ちます)で変換したものを順に載せています。

実際の画像を子細に見ると、BMP画像が最も美しく、100品質のjpeg画像も色が少し変化していることが判ります。↑の画像はそれらを.docxファイルに重ねて貼り付け、スクリーンショットを取っただけなので正確な見た目は出ていません。

いずれにしても変換品質100で変換した方が良いようですね。

 

Microsoft Learnプログラムの日本語化プログラム】

//////////////////////////////////////////
//https://learn.microsoft.com/ja-jp/dotnet/api/system.drawing.image.save?view=windowsdesktop-8.0
//////////////////////////////////////////

using System;
using System.Drawing;
using System.Drawing.Imaging;

class Example_SetJPEGQuality
{
    public static void Main()
    {
        //Bitmapオブジェクトの作成
        Image myImage = Image.FromFile("Shapes.bmp");
        //JPEGコーデックのImageCodecInfoを作成
        ImageCodecInfo myImageCodecInfo = GetEncoderInfo("image/jpeg");
        //画質パラメーターカテゴリー用EncoderオブジェクトをGUIDに基づいて作成
        Encoder myEncoder = Encoder.Quality;
        //EncoderParameter配列であるEncoderParametersオブジェクトの作成
        //但し以下では配列要素は一つだけ

        EncoderParameters myEncoderParameters = new EncoderParameters(1);
        //BitmapをLPEGのレベル0の画質で保存
        EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 0L);
        myEncoderParameters.Param[0] = myEncoderParameter;
        myImage.Save("Shapes000.jpg", myImageCodecInfo, myEncoderParameters);
        //BitmapをLPEGのレベル25の画質で保存
        myEncoderParameter = new EncoderParameter(myEncoder, 25L);
        myEncoderParameters.Param[0] = myEncoderParameter;
        myImage.Save("Shapes025.jpg", myImageCodecInfo, myEncoderParameters);
        //BitmapをLPEGのレベル50の画質で保存
        myEncoderParameter = new EncoderParameter(myEncoder, 50L);
        myEncoderParameters.Param[0] = myEncoderParameter;
        myImage.Save("Shapes050.jpg", myImageCodecInfo, myEncoderParameters);
        //BitmapをJPEGファイルとしてレベル75の画質で保存
        myEncoderParameter = new EncoderParameter(myEncoder, 75L);
        myEncoderParameters.Param[0] = myEncoderParameter;
        myImage.Save("Shapes075.jpg", myImageCodecInfo, myEncoderParameters);
        //BitmapをJPEGファイルとしてレベル100の画質で保存
        myEncoderParameter = new EncoderParameter(myEncoder, 100L);
        myEncoderParameters.Param[0] = myEncoderParameter;
        myImage.Save("Shapes100.jpg", myImageCodecInfo, myEncoderParameters);
        Console.ReadLine();
    }

    private static ImageCodecInfo GetEncoderInfo(String mimeType)
    {
        ImageCodecInfo[] encoders;
        encoders = ImageCodecInfo.GetImageEncoders();
        for(int j = 0; j < encoders.Length; ++j)
        {
            Console.WriteLine(encoders[j].CodecName + " - " + encoders[j].MimeType + " (" + encoders[j].FilenameExtension + ") " + encoders[j].FormatDescription);
        }
        for(int j = 0; j < encoders.Length; ++j)
        {
            if(encoders[j].MimeType == mimeType)
                return encoders[j];
        }
        return null;
    }
}
 

前回触れたように、ImageListのサンプルプログラムを再利用して、GDI+()で扱う画像ファイル形式の相互変換が行えるツールを先ず作ってみようと考えています。

:16bit Windows時代のGraphics Device Interface(GDI)→32bit Windows XP時代からのGDI+→GPUとDirectXを使ったWindows Platform Foundation(WPF-GDI+との比較についてはこれが分かり易いかも)という発展の歴史がありますが、今もGDI+のWindows FormベースでC#プログラムを書いている私にはpng、jpeg/jpg、bmp、gif等(各型式のメリデメ等、詳しくはこれ...私は無智で、「pngよりもjpgの方が良い」と信じ切っていました<恥>)の相互変換だけでも結構ありがたいツールとなります。

 

その名もImageConverter!

 

では、「そのプログラムを、どのような仕様にするのか?」という最初の問いが生じますが、BCCForm and BCCSkeltonを開発した私らしく、UI(≒ウィンドウデザイン)を検討することと同義(というと不正確ですが...)、というか深い関連があるというか、

 

要するに、あーでもない、こーでもない

 

と色々と試行錯誤してみることでその答えを出してゆくわけです。と、いうことで先ずは前回お見せしたプロトタイプ

 

が、最終的にこの(↓)ようになりました。

(最小サイズ状態)

 

これを仕様面から説明すると、次のようになります。

 

(1)png、jpeg/jpg、bmp、gif等のファイルを左上のリストボックスに追加(「イメージの選択・追加」)し、編集は全ファイル(「リスト初期化」)または1ファイル(「イメージの削除」)ごと削除して行います。変換対象ファイルは同じ場所にまとまってあると思いますが、異なるフォールダー(ファイルパス)にあっても構いません。

 

(2)このままの状態で「イメージ形式を変換」ボタンを押すと、リストボックスに登録された全ファイルを、そのファイルがあるフォールダーに「出力ファイル形式指定」ドロップダウンリストで指定した形式に変換して保存します。(ソースファイルと変換ファイルが同じ形式(従って拡張子も同じ)である場合、サイズ変更する場合のみファイル名を変えて出力し、そうでなければエラー扱いにする予定です。)

 

(3)出力先はソースファイルと同じフォールダーを基本としますが、「出力先指定」(「フォールダーを参照」ダイアログを使います)を行えば、ソースファイルがどこにあっても出力先は一つに纏められます。

 

(4)出力形式はペイントツールを参考に"image/png"、 "image/jpeg"、 "image/bmp"、 "image/gif"。 "image/tiff"に絞りました。

 

(5)オプションとしては、複数リストボックスに入っていてもそのうちの一つだけを変換できるように「選択ファイルのみ変換」が可能とし、その他ファイルサイズを指定することが出来るようにします。この場合、選択した複数ファイルを同じファイル形式、同じサイズで纏めて出力することが可能になります。

 

(6)更にオプションの「サイズ変更」のオプションとしてソース画像の「縦横比」を維持する、しないの選択を可能にします。

 

まぁ、元々の目的がトランプカードのファイル形式とサイズを統一する為だったので、こんな風にしましたが、このままでも他のイメージファイルを変換したい場面で結構役に立つツールになると思います。(その為には、各ファイル形式がどういうメリデメがあるのかをよく理解しないとだけですね...私は怠って誤解していましたけど。)

 

現在開発は最終段階に入っており、「イメージ形式を変換」ボタンの処理のみを残して動作チェックも完了しています。動くようになったら、またコードを紹介してゆくつもりです。

 

実は

 

ネタ

 

が出ました。(ウヒヒ)

 

ネタが無くて呻吟していたら、「カードゲームのインフラ部分をDLLにしては?」()というアイデアが下りてきて、「(絵心のない私としては)先ずはリソースから」ということで、フリーのイメージ素材をwebで漁っていました。そして、一応それらしきものを集めたのですが、データ形式やサイズがバラバラで「これは何とか統一しないと不味だな」と感じた時に、

:私が扱うゲーム領域は、ボードゲーム系(MENACE、Nim、Othello)、パズル系(TowerOfHanoi)、シミュレーション系(LifeGame、LightCycle、Maze、Chinchirorin、GhostLeg)等ですが、その制作過程で可能な限りコア部分をdllに纏めています。(Maze_DLL.dll、Dice.dll等)今までトランプは扱ったことが無かったのですが、基本的なトランプカードのUIをクラスオブジェクトからdll化したら面白いかと考えました。

 

それじゃん!

 

と閃いた訳です。何かって?

 

GDI+で扱う画像のデータ型式やサイズを変換するツール

 

でんがな。

:正確にはこんな感じです。一般的には「ペイント」の「名前を付けて保存」で示されるデータ形式("png", "jpg", "bmp", "gif", "tiff", "emf")なんでしょう。なお、ご存じの通り、「でんがな、まんがな」は偽関西弁だ、とか死滅した関西弁だとかいいますが、大阪大学大学院文学研究科教授のこういう解説を見つけました。元々は旧い関西弁で、余り使われなくなったのを噺家が復活させたということらしいです。

 

何れにしても、天の采配か、この間暇つぶしでやったImageListのソースが結構再利用できそうなので、先ずはこれを先に開発してネタに使い、

 

(現在開発中のImageConverterのショット)

 

その後それを使ってリソースを整え、最初のアイデアに戻ってゆこうかな、と考えています。

 

なーんて、書いていたところで、

 

腰が...痛い

:松重豊さんの「孤独のグルメ」の、背景音楽と共につぶやかれる決め文句(「腹が..へった」)のようなイメージでしょうか?

 

私は結構な腰痛持ちで、最初は25歳の時にぎっくり腰(筋肉系)を、その後椎間板ヘルニア的(神経系)になり、一番ひどかったのは63歳で全く身動きが出来なくなり、神さんもギブアップして救急車で病院に連れていかれました。(その時のMRI画像では明白にヘルニアが視認できます。)幸いにしてその後(ダチョウ倶楽部の肥後克広さん著の「あきらめない腰痛」に出ていた)マッケンジー法がぴたりとツボに嵌り、20数年以上毎朝行っているストレッチプログラムにも取り入れて、なんとかかんとかやってきたのですが、今回は冷房で腰回りの筋肉が緊張して痛くなったようです。

 

まー、ぼちぼちやりまんがな。

 

 

 

巷に颱風10号(注1)の懸念や楽観が流れる中、気が付いたら(ネタがないので昨日の駄文を書いた、将にその日に)古希を迎えました。(注2

注1:元々が保険屋の事故係だったので、20代から初夏から11月までは颱風の損害調査を行ってきましたが、今度の颱風の盛衰と進路、見たことも聞いたこともないですね。段々と「過去の経験から将来を推し量る」ことが難しくなってきましたが、気象は特に著しいようです。

注2:私は毎晩Amazon Prime等を肴に2~3合晩酌するのですが、昨日はあの天気で外に出られず、(通常は私が自分の肴を調理するのですが)神さんが珍しくおつまみプレートを作ってくれました。「夫の誕生日=妻が年に一回優しくなる」という定理でしょうか?

 

流石に感慨深い...

 

ので、ちょっと振り返ってみましょう。

 

10歳-「本当の東京オリンピック」が開催されたときでした。私はややガキ大将でしたか?

 

20歳-大学に現役で入学して、当時の三種の神器(酒、麻雀、ゴルフ)を始めていた私は二番目に嵌り、1年目の単位が恐ろしく少なかったことから、二年目は頑張って勉強しましたが、合格単位まで一つ届かず「留年(暗;)」で心折れ、抑うつ的になっていましたね。

 

30歳-既に就職し、社内試験に受かって、(9.11で破壊されたので、今は無き-追加注)ニューヨークの保険大学に半年就学しました。日航機墜落事件をサンフランシスコで知り、衝撃を受けたことを覚えています。

追加注The College of Insurtance は9.11で破壊されたと思い込んでいましたが、本日チェックしたら、現在も物理的に残っていますね。(追加注のps又、他の大学に接収されて専門課程になっているようです。

追加注のps:もう一度読み返してみたら、Wikiの中に次の記述がありました。矢張り私の記憶で正しかったです。"Following the sale of the 101 Murray Street campus, the School of Risk Management including the Davis Library moved to St. John's new Manhattan location at 101 Astor Place, part of the Maki-designed office tower at 51 Astor. The previous building at Murray Street was demolished in May 2015."

 

40歳-仕事でニューヨークに駐在4年目でした。当時大騒ぎになっていた豊胸シリコン材のクラス訴訟対応でとんでもなく忙しかったことを覚えています。

 

50歳-5年のシンガポール駐在から帰国し、久々に購入した車で即Speedingで捕まり、次のコールド免許はいつになるか、違反者講習で考えていたら「自分もいつか60歳になるんだ!」と震えました。恐ろしいことに、それでは終わらないんですよね。

 

60歳-定年を迎え、(その気はなかったのですが、神さんに強く押されて)定年延長就労を開始しました。自分の記念の為に「初めての外車、初めてのオープンカー、初めてのPorche」を買って乗りまくったことを覚えています。(分からないでしょうが、60-65とそれ以降では全く世界が違うんですよ。)

 

70歳-65歳で本当にリタイアした時に新型コロナ騒動が始まり、「自宅蟄居」となったので、15年ぶりに趣味のプログラミングを復活させ、翌年このブログを公開しましたが、何とか4年以上継続でき、今これを書いています。

(ブログ第1号のBCCSkeltonプログラム、定番の"Hello, World"で6つも"Hello, World"を表示させているのが可笑しい!)

 

このブログも4年。ネタがなくなるのも仕様がないよね?

 

What Game Shall We Play Today?

 

又もやネタを使い果たしたので、WEBでネタ探しをしていましたが、あまり進展がなく、久々にAI-Powerを借りようか、と思い、Chat-GPT様にお伺いを立てました。

 

I'd like to make a tool program in C# that emulates day-to-day activities of a man. What do you think?

ChatGPT

That sounds like an intriguing project! Creating a tool that emulates day-to-day activities could be quite complex, depending on how detailed and interactive you want it to be. Here are some key considerations and ideas for such a program:

1. Define the Scope

  • Activities: What specific activities are you looking to emulate? Common daily tasks could include waking up, working, exercising, eating, socializing, and going to bed.
  • Level of Detail: How detailed should the simulation be? Are you looking to track every minute of the day, or just high-level activities?

2. Core Components

  • Scheduler: Implement a scheduler to manage and simulate time-based activities.
  • Activity Manager: Create a system to handle different types of activities and their durations.
  • State Management: Keep track of the current state of the simulation (e.g., time of day, current activity).

3. User Interaction

  • User Interface: Decide if you want a graphical user interface (GUI) or a command-line interface (CLI). A GUI could be more user-friendly, while a CLI might be simpler to implement.
  • Customization: Allow users to customize the activities and schedules to fit different scenarios.

4. Implementation Ideas

  • Time Simulation: Use a timer or a simulated clock to progress through the day.
  • Event Triggers: Implement events that trigger based on time or other conditions (e.g., reminders for breaks).
  • Logging: Keep logs of activities to provide feedback or track patterns.

5. Technology Choices

  • C# Libraries: Utilize libraries and frameworks in C# for GUI development (e.g., Windows Forms, WPF) or for handling scheduling (e.g., Quartz.NET).
  • Data Storage: Use a database or file system to store user preferences, schedules, and logs if needed.

6. Example Code Snippet

Here’s a simple example of how you might structure a basic day simulation in C#:(解説:面白くないので省略)


6. Testing and Feedback
  • Testing: Test the simulation under various scenarios to ensure it behaves as expected.
  • Feedback: Get feedback from users to improve the tool based on their needs and preferences.

Feel free to adapt and expand on these ideas based on your specific goals and requirements

 

Chances are, I made a big mistake to communicate you of what I really want. The phrase, "day-to-day activities" that I referred to is to mean "writing", "note recording", "viewing video", "listening to music" or something like those. Having said so, however, I have made all of those. What else do you suggest things that I should consider to program?

ChatGPT

Got it! It sounds like you're looking to simulate or manage digital activities related to productivity, entertainment, and information consumption. Here are some additional ideas and features you might consider adding to your program:

1. Task Management and Scheduling

  • To-Do Lists: Implement features for creating and managing to-do lists or tasks.
  • Calendar Integration: Allow users to schedule tasks and set reminders.
  • Pomodoro Timer: Integrate a Pomodoro timer to manage work sessions and breaks.

2. Goal Tracking

  • Habit Tracker: Track progress on personal goals or habits.
  • Progress Reports: Generate reports or summaries of activities and achievements over time.

3. Project Management Tools

  • Kanban Board: Create a visual board for managing tasks and projects.
  • Task Prioritization: Allow users to prioritize tasks and track their progress.

4. Focus and Distraction Management

  • Focus Mode: Implement features to block distracting websites or apps during work periods.
  • Distraction Tracking: Track time spent on distractions and provide feedback.

5. Content Consumption

  • Read Later List: Manage a list of articles or documents to read later.
  • Media Library: Organize and track media consumption like books, movies, or podcasts.

6. Notes and Knowledge Management

  • Tagging and Categorization: Allow users to tag and categorize notes for better organization.
  • Search Functionality: Implement search features to easily find notes or information.

7. Automation and Integration

  • Workflow Automation: Automate repetitive tasks or workflows.
  • Third-Party Integrations: Integrate with other apps or services like email, cloud storage, or productivity tools.

8. Personal Analytics

  • Activity Analysis: Provide insights into how users spend their time.
  • Usage Statistics: Show statistics or trends about activity patterns.

9. Customization

  • Themes and Layouts: Allow users to customize the look and feel of the application.
  • Personal Preferences: Let users set preferences for notifications, reminders, and other features.

10. Collaboration and Sharing

  • Shared Projects: Enable collaboration on tasks or projects with other users.
  • Feedback and Comments: Allow users to give and receive feedback on shared tasks or notes.

Example Code Snippets

Here's a simple example of how you might implement a task manager in C#:(解説:面白くないので省略)

Next Steps
  1. Identify Your Goals: Clarify the main goals of your program. Are you focusing on productivity, entertainment, or both?
  2. Design the User Experience: Think about how users will interact with your tool. What features will be most valuable to them?
  3. Develop and Test: Start with basic functionality and gradually add more features. Test each component thoroughly.

Feel free to mix and match these ideas based on your interests and goals for the program.

 

どうも私は"failed to identify my goal "なんで、どれも刺さりません。あ"ー、どうしよう。

 

前回、不敬にも総本山のサンプルプログラムに突っ込みを入れてしまいましたので、「私(わたくし)的」なコードを晒して、ご批判に供しましょう。

 

【ImageRotator.cs】

//////////////////////////
// Microsoft Learn Sample
// Modified by Ysama 2024
// https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.imagelist?view=windowsdesktop-8.0

//解説:コレ↑が出典です。
//////////////////////////

using System;
//using System.ComponentModel;    //解説:前回に書いた通り、Visual Studioを使わないので削除です。
using System.Drawing;
using System.Reflection;        //Assemblyを使う為(解説:ウィンドウアイコン欲しさに...です。)
using System.Windows.Forms;

namespace ImageRotator
{
    public class ImgRotator : Form    //解説:フォームから派生させているので、即ウィンドウが作れます。
    {
        //解説:外部変数(フィールド)です。アクセス修飾子はココを参照。

        private ListBox listBox;
        private Label num_label;
        private Button open_button;
        private Button disp_button;
        private Button del_button;
        private Button clr_button;
        private Button exit_button;
        private PictureBox pictureBox;
        private ImageList imageList;
        protected Graphics graphics;
        private Panel panel;
        private Label path_label;
        private int currentImage = 0;

        //エントリーポイント    //解説:別にエントリークラスを設けず、クラスの中に置きます。
        [STAThread]               //解説:お忘れなく。
        public static void Main() 
        {
            Application.Run(new ImgRotator());
        }

        //コンストラクター
        public ImgRotator()
        {
            this.Icon = Icon.ExtractAssociatedIcon(Assembly.GetEntryAssembly().Location);    //プログラムアイコンをフォームにつける
            this.ClientSize = new Size(480, 504);    //解説:結果的にこのサイズが一番でした。
            this.Text = "Image Rotator";
            InitComponent();        //コントロール等の初期化
        }

        //コントロール等の初期化
        private void InitComponent()
        {
            //表示停止
            SuspendLayout();
            //イメージリスト
            imageList = new ImageList ();
            imageList.ImageSize = new Size(128, 128);    //イメージリストのimage sizeの設定(image sizeの初期値は16 x 16)
            imageList.TransparentColor = Color.White;    //背景白地は透明となる
           //解説:以下コントロールにアンカーを使っていますので、ウィンドウサイズを変えて試してください。

            //リストボックス
            listBox = new ListBox();
            listBox.Location = new Point(10, 10);
            listBox.Size = new Size(280, 164);
            listBox.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
            listBox.HorizontalScrollbar = true;
            listBox.TabIndex = 0;
            //ボタン
            open_button = new Button();
            open_button.Location = new Point(300, 10);
            open_button.Size = new Size(160, 24);
            open_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            open_button.Text = "イメージを開く";
            open_button.Click += new System.EventHandler(open_button_Click);
            disp_button = new Button();
            disp_button.Location = new Point(300, open_button.Height + 20);
            disp_button.Size = new Size(160, 24);
            disp_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            disp_button.Text = "選択イメージを表示";
            disp_button.Click += new System.EventHandler(disp_button_Click);
            del_button = new Button();
            del_button.Location = new Point(300, open_button.Height + disp_button.Height + 30);
            del_button.Size = new Size(160, 24);
            del_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            del_button.Text = "イメージの削除";
            del_button.Click += new System.EventHandler(del_button_Click);
            clr_button = new Button();
            clr_button.Location = new Point(300,  open_button.Height + disp_button.Height + del_button.Height + 40);
            clr_button.Size = new Size(160, 24);
            clr_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            clr_button.Text = "リスト初期化";
            clr_button.Click += new System.EventHandler(clr_button_Click);
            exit_button = new Button();
            exit_button.Location = new Point(300,  open_button.Height + disp_button.Height + del_button.Height + clr_button.Height + 50);
            exit_button.Size = new Size(160, 24);
            exit_button.Anchor = (AnchorStyles.Top | AnchorStyles.Right);
            exit_button.Text = "終了";
            exit_button.Click += new System.EventHandler(exit_button_Click);
            //解説:オリジナルでパネルの描画とピクチャーボックスを二重に使っていますが、これは前に書いたように使い方の説明の為だと思います。

            //パネル
            panel = new Panel();
            panel.Location = new Point(10, 204);
            panel.Size = new Size(128, 128);
            panel.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            //描画で使うpanelのgraphicsオブジェクト
            graphics = Graphics.FromHwnd(panel.Handle);
            //ピクチャーボックス
            pictureBox = new PictureBox();
            pictureBox.Location = new Point(10, 364);
            pictureBox.Size = new Size(128, 128);
            pictureBox.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            //ラベル
            num_label = new Label();
            num_label.AutoSize = true;
            num_label.Location = new Point(10, 184);
            num_label.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            num_label.Text = "番号";
            path_label = new Label();
            path_label.AutoSize = true;
            path_label.Location = new Point(10, 342);
            path_label.Anchor = (AnchorStyles.Bottom | AnchorStyles.Left);
            path_label.Text = "ファイルパス";
            //フォームにコントロールを追加
            Controls.Add(path_label);
            Controls.Add(panel);
            Controls.Add(pictureBox);
            Controls.Add(open_button);
            Controls.Add(disp_button);
            Controls.Add(clr_button);
            Controls.Add(del_button);
            Controls.Add(exit_button);
            Controls.Add(num_label);
            Controls.Add(listBox);
            //表示再開
            ResumeLayout(false);
        }

        //終了処理
        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            base.OnFormClosing(e);
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.No)
            {
                e.Cancel = true;
            }
        }

        //イメージを開く
        private void open_button_Click (object sender, System.EventArgs e)
        {
            //OpenFileDialogオブジェクトの生成
            OpenFileDialog openDlg = new OpenFileDialog();
            openDlg.Multiselect = true;
            if(openDlg.ShowDialog() == DialogResult.OK)
            {
                if(openDlg.FileNames != null)    //複数ファイル選択の場合
                {
                    for(int i =0; i < openDlg.FileNames.Length; i++ )
                    {
                        addImage(openDlg.FileNames[i]);
                    }
                }
                else                            //単一ファイル選択の場合
                {
                    addImage(openDlg.FileName);
                }
            }
            else if(openDlg.ShowDialog() == DialogResult.Cancel)
                MessageBox.Show("キャンセルされました", "キャンセル", MessageBoxButtons.OK, MessageBoxIcon.Information);
            //オブジェクトを破棄する
            openDlg.Dispose();
        }

        //イメージの表示
        private void disp_button_Click (object sender, System.EventArgs e)
        {
            if(imageList.Images.Empty != true)    //イメージリストにイメージがあるか
            {
                panel.Refresh();
                //解説:何故同じ画像をパネルとピクチャーボックスに表示するのかは、オリジナルコードの作成者の「謎」です。

                //解説:恐らくイメージの表示の仕方を示す為、だったのじゃないかと思います。

                //イメージをパネルに描画
                imageList.Draw(graphics, 0, 0, currentImage);
                //イメージをPictureBox.に表示
                pictureBox.Image = imageList.Images[currentImage];
                num_label.Text = "イメージ番号:" + currentImage;
                listBox.SelectedIndex = currentImage;
                path_label.Text = "パス:" + listBox.Text;
                if(imageList.Images.Count - 1 > currentImage)
                {
                    currentImage++;
                }

                //解説:以下はエラー回避のために本来付け加えておくべきコードだと思います。
                else
                {
                    currentImage = 0;
                }
            }
            else
            {                                //イメージリストにイメージがない
                currentImage = 0;
                MessageBox.Show("イメージがありません。", "注意", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
        }

        //リストボックスで指定したイメージの消去
        private void del_button_Click (object sender, System.EventArgs e)
        {
            if(listBox.SelectedIndex > -1)    //選択されている場合(-1は選択されていない場合)
            {
                imageList.Images.RemoveAt(listBox.SelectedIndex);    //イメージリストから消去
                listBox.Items.Remove(listBox.SelectedItem);            //リストボックスから消去
                //解説:以下は選択イメージを消去したため、本来付け加えておくべきコードだと思います。
                graphics.Clear(SystemColors.Control);                //panel画面消去
                pictureBox.Image = null;                            //pictureBoxの画面消去
                num_label.Text = "イメージ番号:";
                path_label.Text = "パス:";
                if(currentImage > 0) {
                    currentImage--;                                    //currentImageが1以上であれば一つ減らす
                    //listBox.SelectedIndex = currentImage;
                    listBox.SetSelected (currentImage, true);        //リストボックスのcurrentImageを選択
                }
            }
            //解説:以下はエラー回避のために本来付け加えておくべきコードだと思います。
            else if(listBox.SelectedIndex == -1)
                MessageBox.Show("選択されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
 
        //すべてのイメージの消去
        private void clr_button_Click (object sender, System.EventArgs e)
        {
            imageList.Images.Clear();                //イメージリストから消去する
            listBox.Items.Clear();                    //リストボックスから消去する
            //解説:以下は選択イメージを消去したため、本来付け加えておくべきコードだと思います。
            graphics.Clear(SystemColors.Control);    //panel画面消去
            pictureBox.Image = null;                //pictureBoxの画面消去
            currentImage = 0;                        //初期化
            num_label.Text = "イメージ番号:";
            path_label.Text = "パス:";
        }
 
        //フォームを閉じる
        private void exit_button_Click (object sender, System.EventArgs e)
        {
            Close();
        }

        //イメージリストとリストボックスにイメージファイル名を登録
        private void addImage(string imageToLoad)
        {
            if(imageToLoad != "")
            {
                imageList.Images.Add(Image.FromFile(imageToLoad));
                listBox.BeginUpdate();
                listBox.Items.Add(imageToLoad);
                listBox.EndUpdate();
            }
        }
    }
}

 

いかがだったでしょうか?ImageListの使い方を紹介する簡単なプログラムで何もできませんが(笑;)、それなりにデモンストレーションとしての意味はあったかも?

少なくともオリジナルよりは見やすくなったと思います。

 

前回、本シリーズを始めるにあたってMicrosoft Learnのサンプルコード

について、

 

(1)不要なコンポーネントのDLLを参照し、宣言している。

(2)コントロールの宣言がコンストラクターに置かれたり、InitializeComponentに置かれたりしてバラバラ。

(3)コントロールの二重定義もある。

(4)コントロール名(label3、label5)をそのまま初期表示している。(↑参照)

(5)イメージを削除しても削除したイメージが表示され続ける。(表示に段差がありますね↓参照)

(6)イメージを削除し、リストボックスが未選択になって、再度削除ボタンを押すと落ちる。(↓参照)

(7)ウィンドウサイズは可変ですが、コントロールは微動だにしません。

 

と書きました。それを解説してゆきます。

 

(1)不要なコンポーネントのDLLを参照し、宣言している。

まず、

    using System.ComponentModel;

ComponentoModel名前空間を使い、

    private System.ComponentModel.IContainer components;

IContainerインターフェィス注1)のインスタンスとして宣言し、さらにVisual Studio定番のInitializeComponent()関数の中で、

    this.components = new System.ComponentModel.Container();

Containerクラス注2)としてインスタンスを生成しています。(注3

注1:「コンテナーの機能を提供します。 コンテナーは、論理的に 0 個以上のコンポーネントを格納するオブジェクトです。」(Microsoft Learn)

注2:「0 個以上のコンポーネントをカプセル化します。」「クラスはContainer、インターフェイスの既定のIContainer実装です。」(Microsoft Learn)

注3:Visual Studioを使っていると機械自動生成のコード、ファイルが他にあり、全体が見えないことがままあります。これはどうもこういうことのようです。この記事では「components』変数はクラス内で使用されるフィールド変数で、コンポーネントを管理するためのものだ(「コンテナ」と呼ばれる)。コンポーネントについては、改訂版 C#入門の「Column - コンポーネントとコントロール -」を参照してほしい。」と書かれており、Visual StudioによるウィンドウのUI定義、実装を行う肝の様です。

 

そして最終的には、

    this.imageList1 = new System.Windows.Forms.ImageList(this.components);
ImageListクラスインスタンスのコンストラクターに引数として渡されます。そしてコード全体のどこを見ても、このcomponentsが何なのかは全く分かりません。因みにImageListクラスを見ると、Containerクラスの引数付きコンストラクターがあり、「コンテナーに関連付けて、ImageListクラスの新しいインスタンスを初期化します。」と書かれていますが、このプログラムについては何も参照が無く寿限無寿限無です。

 

従って、components変数は意味がなくなる(Visual Studioではユーザー定義が無ければnullになるようです)為、ここは思い切ってcomponents関連コードはすべて削除しましょう。(読んでいただければわかりますが、本プログラムではImageLIstにイメージを読み込む仕様となっているので、引数無しコンストラクターでも全く差し支えありません。)

 

(2)コントロールの宣言がコンストラクターに置かれたり、InitializeComponentに置かれたりしてバラバラ。

(3)コントロールの二重定義もある。

これは何かの人為的間違いのようなので一遍にやりましょう。

先ず、クラス内で参照されるものはクラス内変数として宣言される点は問題ありません。しかし、例外としてImageListクラスインスタンスがコンストラクター内で、

            imageList1 = new ImageList () ;
            // The default image size is 16 x 16, which sets up a larger
            // image size. 
            imageList1.ImageSize = new Size(255,255);
            imageList1.TransparentColor = Color.White;

と実装、定義されているのに対して他のコントロールはInitializeComponent()関数内でなされています。

統一性がないな」と感じたのも束の間、よくよく見るとInitializeComponent()関数内でも

            this.imageList1 = new System.Windows.Forms.ImageList(this.components);    //二重生成
            .

            .

            .

            this.imageList1.ImageSize = new System.Drawing.Size(16, 16);    //二重設定
            this.imageList1.TransparentColor = System.Drawing.Color.Transparent;    //二重設定

になっています。

どうしてこんなことになったのでしょうか?私はVisual Studioを使っていませんが、内部チェック(シンタックスや文法、ロジックチェック)で引っかかるのじゃないかなと感じますが。いずれにしてもご多忙な様子、職業プログラマーは大変です。これはおそらく最初はVisual Studioで初め、InitializeComponent()関数が機械生成コードで、後から人間がそれと異なるコードをマニュアルで追加したのでしょう。これは纏めてInitComponent()関数()に収容しましょう。

:IContainerで使用される”InitializeComponent"という名称は避けました。

 

(4)コントロール名(label3、label5)をそのまま初期表示している。

マニュアルでプログラミングする際には可読性を上げるために、「それが何か」直感で分かるようにしました。以下に対比させます。

<Original>

        private System.ComponentModel.IContainer components;
        private System.Windows.Forms.ListBox listBox1;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Button button1;
        private System.Windows.Forms.Button button2;
        private System.Windows.Forms.Button button3;
        private System.Windows.Forms.Button button4;
        private System.Windows.Forms.PictureBox pictureBox1;
        private System.Windows.Forms.ImageList imageList1;
        private System.Windows.Forms.OpenFileDialog openFileDialog1;
        protected Graphics myGraphics;
        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.Label label5;
        private int currentImage = 0;

<Modified>

        private ListBox listBox;
        private Label num_label;
        private Button open_button;
        private Button disp_button;
        private Button del_button;
        private Button clr_button;
        private Button exit_button;
        private PictureBox pictureBox;
        private ImageList imageList;
        protected Graphics graphics;
        private Panel panel;
        private Label path_label;
        private int currentImage = 0;

 

(5)イメージを削除しても削除したイメージが表示され続ける。

これは

        private void button2_Click (object sender, System.EventArgs e)
        private void button3_Click (object sender, System.EventArgs e)

で「リストボックスとイメージリストの要素を削除」しかしていないからです。これに加えてパネルとピクチャーボックスの画像消去とラベルの文字列消去が欲しいですね。

        private void del_button_Click (object sender, System.EventArgs e)
        private void clr_button_Click (object sender, System.EventArgs e)

で修正しています。

 

(6)イメージを削除し、リストボックスが未選択になって、再度削除ボタンを押すと落ちる。

リストボックスとイメージリストの要素は0から始まる整数で、currentImage変数で管理しています。オリジナルコードは要素の削除を行ってもcurrentImage変数を変えていないので、イメージの削除や表示を行おうとすると「配列範囲外エラー」になるわけです。エラーハンドラーのコードを追加します。

 

(7)ウィンドウサイズは可変ですが、コントロールは微動だにしません。

オリジナルは機械作成のInitializeComponent関数でコントロールを設定していますが、余り熱心に配置していなかったので、アンカーなどはついていません。又英語表記で短くて済んだものが日本語表記にするとはみ出してしまうことが有ります。その為、UIレイアウト一から見直しし、アンカーも付けました。

 

次回は修正済のプログラム

のコードを紹介しましょう。