私は28歳からプログラミングを始めましたが、当時はSharpのポケコンBASICMSX-BASICでプログラミングしていました。しかし当時のBASIC言語は全ての変数がグローバル変数で、スタックを使う局所変数がないために構造的なプログラムや再帰呼び出しができない()という問題がありました。そんな僕がC言語にひかれたのは、グローバル変数とローカル(局所、auto)変数が使える為でした。

:「不可能」ではないのですが、自分で「スタックルーチン」を組んで、同じ変数をpushで退避させたり、popで復帰させたりすることが必要で、実用にはなりませんでした。

 

そんな時代に「Basic言語でも、グローバルとローカル変数が使えて、構造化プログラミングができる」というTrue Basicに出会ったのは、私が米国ニューヨークへ赴任する直前でした。(

:もちろん購入して持ってゆきました。しかし、その年にはDOS 5とWIndows 3.1が発売されており、MicrosoftもQBasicをリリースし、専らそっちを使っていましたが。(爆)

 

IDListの移植を終了し、プログラムネタを探して、Basicの聖地、ダートマス大学True Basicのサイトを訪れると、

 

 

いよいよ私の時代も終わりに近づいている、という実感

 

が押し寄せてきました。

 

前回でオリジナルのIDListを移植したのC#版の解説を完了しました。今回は先ずそのコンパイルの方法と完成版の動作試験(現在のところ申し分ありません)における印象をお伝えしてお開きにしましょうか。

 

先ずはコンパイル方法です。前に触れたとおり、IDListのC#版を動かすには次のファイル()が必要です。

BCCForm and BCCSkeltonパッケージの".\SampleBCCSkelton\MSCompAss\Debug\Samples\IDList_CS\"フォールダーの模様です。

 

MSCompAssを使う場合は、

 

(1)IDList_CS.csファイルをOptionダイアログを次のように「出力ファイル」を"/target:libray"(dll)してコンパイルし、

 

(2)次にIDList.csファイルをOptionダイアログを次のように「出力ファイル」を"/target:winexe"、リソースを"IDList.resources"に指定、プログラムアイコンを"IDList.ico"に指定、「DLL参照」に今作った"IDList_CS.dll"を指定してコンパイルします。

 

そんな面倒なことはイヤっ!」と言う人もいようかと、上記の操作をバッチ処理したIDList_CS.batを作っておきましたので、それをダブルクリックすれば全て一瞬で終わります。

 

これでC#版のIDList.exeが出来上がりますので、起動した後は(最初はおっかなびっくりでしょうから)当たり障りのないwebサイトで使うようなIDとパスワードを入れてみてください。

 

さて、今までC++とBCCSkeltonで作ったオリジナルのIDListを使ってきた私が、実際に(そのデータをUTF-8に変え)C#版のものを使ってみて感じたことを紹介しましょう。

 

オリジナルのC++版と移植したC#版の一番大きな違いはオリジナル版で使っていたDPAPIのダイアログ

 

 

が.NETベースでは使えなくなったことでしょうか?しかし、パスワード保護は使えなくともデータはDPAPIによる暗号化はなされていますので影響は非常に大きくはないでしょう。

 

二番目に挙げる違いは(矢張りDataGricView-BindingSource-Listの同期処理によるのか、)表示等実行スピードではC++のネイティブプログラムの方が勝るように感じられました。

 

【2021年-C++版】

(解説:よくよく見ると未選択の場合、メニューとツールバーボタンが殺されていますね。小技が細かい。)

 

しかしC#版は、逆にプログラムが64bitベースになり、プログラム構造が明確化され、可読性が高くなったことでプログラミング的には勝っているとも言えます。

 

【2026年-C#版】

 

私自身から言えば、矢張りEmbarcadero C++(32bit版ですが...)+BCCForm and BCCSkeltonで作ったIDListを今も愛用しており、今後も(OSがゆるすかぎりは )使い続けるんじゃないかな()、と思いますが、

:既に金融機関を中心として「ID、パスワード方式」から「パスキー方式」への移行が始まっているので、IDList自体の必要性は今後低くなってゆきますが...爆!

 

C#(Even with one of the version 5.0)+ DataGridView + BindingSource + List

 

での表型アプリの開発、

 

とても簡便で作りやすく、お勧め

 

というのが結論です。

 

(2)の続き。

 

    ///////////////////////
    //入力・編集ダイアログ
    ///////////////////////

    class AddEditDlg : Form
    {
        //追加データの受け渡し用idItemインスタンス
        public idItem iditem;
        //編集対象となるDataGridViewRow
        DataGridViewRow target;
        //ダイアログのコントロール
        Button BtnOK, BtnCancel;
        Label MsLabel, IdLabel, PwLabel, RmLabel;
        TextBox MsBox, IdBox, PwBox, RmBox;

        public AddEditDlg(DataGridViewRow dgvr = null)
        {
            //対象行を保存(解説:呼び出したidListクラスの行情報です。)
            target = dgvr;
            //ダイアログの属性設定
            this.Text = ReferenceEquals(target, null) ? "データの追加" : "データの編集";    //解説:行情報がnullであれば追加、データがあれば編集になります。
            this.ClientSize = new Size(420, 240);
            this.ControlBox = false;        //システムボタン無効
            this.ShowInTaskbar = false;        //タスクバー上非表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        //境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += Form_Load;
        }

        //OnLoad処理
        private void Form_Load(object sender, EventArgs e)
        {
            BtnOK = new Button();    //解説:OKボタンです。
            BtnOK.Location = new Point(ClientSize.Width - BtnOK.Width - 30, ClientSize.Height - BtnOK.Height - 10);
            BtnOK.Text = "OK";
            BtnOK.AutoSize = true;
            BtnOK.DialogResult = DialogResult.OK;
            BtnOK.Click += new EventHandler(OnOK_Click);
            this.AcceptButton = BtnOK;
            this.Controls.Add(BtnOK);

            BtnCancel = new Button();    //解説:キャンセルボタンです。
            BtnCancel.Location = new Point(30, ClientSize.Height - BtnCancel.Height - 10);
            BtnCancel.Text = "Cancel";
            BtnCancel.AutoSize = true;
            BtnCancel.DialogResult = DialogResult.Cancel;
            BtnCancel.Click += new EventHandler(OnCancel_Click);
            this.CancelButton = BtnCancel;
            this.Controls.Add(BtnCancel);

            //解説:以下入力用のラベルとTextBoxの対になっています。
            MsLabel = new Label();
            MsLabel.Size = new Size(120, 20);
            MsLabel.Location = new Point(10, 10);
            MsLabel.BorderStyle = BorderStyle.Fixed3D;
            MsLabel.TextAlign = ContentAlignment.MiddleCenter;
            MsLabel.Text = "メンバーシップ";
            this.Controls.Add(MsLabel);

            MsBox = new TextBox();
            MsBox.Size = new Size(270, 24);
            MsBox.Location = new Point(140, 10);
            MsBox.Text = ReferenceEquals(target, null) ? String.Empty : target.Cells[0].Value.ToString();    //解説:行情報がnullであれば空文字列、行情報があればCells[0]の値を文字列として表示します。以下同じ。
            this.Controls.Add(MsBox);

            IdLabel = new Label();
            IdLabel.Size = new Size(120, 20);
            IdLabel.Location = new Point(10, MsLabel.Height + 15);
            IdLabel.BorderStyle = BorderStyle.Fixed3D;
            IdLabel.TextAlign = ContentAlignment.MiddleCenter;
            IdLabel.Text = "ログインID";
            this.Controls.Add(IdLabel);

            IdBox = new TextBox();
            IdBox.Size = new Size(270, 24);
            IdBox.Location = new Point(140, MsLabel.Height + 15);
            IdBox.Text = ReferenceEquals(target, null) ? String.Empty : target.Cells[1].Value.ToString();    //解説:Ditto.
            this.Controls.Add(IdBox);

            PwLabel = new Label();
            PwLabel.Size = new Size(120, 20);
            PwLabel.Location = new Point(10, MsLabel.Height + IdLabel.Height + 20);
            PwLabel.BorderStyle = BorderStyle.Fixed3D;
            PwLabel.TextAlign = ContentAlignment.MiddleCenter;
            PwLabel.Text = "パスワード";
            this.Controls.Add(PwLabel);

            PwBox = new TextBox();
            PwBox.Size = new Size(270, 24);
            PwBox.Location = new Point(140, MsLabel.Height + IdLabel.Height + 20);
            PwBox.Text = ReferenceEquals(target, null) ? String.Empty : target.Cells[2].Value.ToString();    //解説:Ditto.
            this.Controls.Add(PwBox);

            RmLabel = new Label();
            RmLabel.Size = new Size(120, 110);
            RmLabel.Location = new Point(10, MsLabel.Height + IdLabel.Height + PwLabel.Height + 25);
            RmLabel.BorderStyle = BorderStyle.Fixed3D;
            RmLabel.TextAlign = ContentAlignment.MiddleCenter;
            RmLabel.Text = "備考";
            this.Controls.Add(RmLabel);

            RmBox = new TextBox();
            RmBox.Size = new Size(270, 114);
            RmBox.Location = new Point(140, MsLabel.Height + IdLabel.Height + PwLabel.Height + 25);
            RmBox.Multiline = true;
            RmBox.AcceptsReturn = true;
            RmBox.Text = ReferenceEquals(target, null) ? String.Empty : target.Cells[3].Value.ToString();    //解説:Ditto.
            this.Controls.Add(RmBox);
        }

        //OKボタン処理
        protected void OnOK_Click(object sender, EventArgs e)
        {
            if(ReferenceEquals(target, null))    //追加処理
            {    //解説:戻り値用のiditemクラスインスタンスに4つのTextBoxの文字列の値を代入します。
                iditem = new idItem() {Membership = MsBox.Text, ID = IdBox.Text, Password = PwBox.Text, Remark = RmBox.Text};
            }
            else                                //編集処理
            {    //解説:引数の行情報からそのCellsの値に、4つのTextBoxの文字列の値を代入して編集します。
                target.Cells[0].Value = MsBox.Text;
                target.Cells[1].Value = IdBox.Text;
                target.Cells[2].Value = PwBox.Text;
                target.Cells[3].Value = RmBox.Text;
            }
            Close();
        }

        //Cancelボタン処理
        protected void OnCancel_Click(object sender, EventArgs e)
        {
            Close();
        }
    }

    /////////////////
    //検索ダイアログ
    /////////////////

    class FindDlg : Form
    {
        //対象となるDataGridView
        DataGridView target;
        //ダイアログのコントロール
        Button BtnOK;
        Label label;
        TextBox tBox;

        public FindDlg(DataGridView dgv)
        {
            //ダイアログの属性設定
            this.Text = "検索";
            this.ClientSize = new Size(160, 100);
            this.ControlBox = false;        //システムボタン無効
            this.ShowInTaskbar = false;        //タスクバー上非表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        //境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += Form_Load;
            target = dgv;                    //DataGridViewを記録(解説:呼び出したidList(DataGridView)の行を選択する必要があるので、引数で取得します。)
        }

        //OnLoad処理
        private void Form_Load(object sender, EventArgs e)
        {
            //コントロールの属性設定
            tBox = new TextBox();
            tBox.Size = new Size(140, 24);
            tBox.Location = new Point((ClientSize.Width - tBox.Width) / 2, 30);
            this.Controls.Add(tBox);

            BtnOK = new Button();
            BtnOK.Location = new Point((ClientSize.Width - BtnOK.Width) / 2, ClientSize.Height - BtnOK.Height - 10);
            BtnOK.Text = "OK";
            BtnOK.AutoSize = true;
            BtnOK.DialogResult = DialogResult.OK;
            BtnOK.Click += new EventHandler(OnOK_Click);
            this.Controls.Add(BtnOK);

            label = new Label();
            label.Size = new Size(140, 20);
            label.Location = new Point((ClientSize.Width - label.Width) / 2, 4);
            label.Text = "検索する文字列";
            this.Controls.Add(label);

            this.AcceptButton = this.BtnOK;
            this.KeyPreview = true;
        }

        //OKボタン処理
        protected void OnOK_Click(object sender, EventArgs e)
        {
            if(tBox.Text == String.Empty)    //解説:エラーメッセージは出しますが、終了することにしました。
                MessageBox.Show("検索文字列が入力されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
            {
                //選択を一旦クリア
                target.ClearSelection();    //解説:引数として取得したDataGridViewの現選択を一旦解除します。
                bool found = false;
                foreach(DataGridViewRow row in target.Rows)    //解説:行配列を進めます。
                {
                    foreach(DataGridViewCell cell in row.Cells)    //解説:行のCells配列を進めます。
                    {
                        if(cell.Value != null && cell.Value.ToString().Contains(tBox.Text))    //解説:特定行の特定セルに検索文字列があれば
                        {
                            //セルを選択状態にする(解説:一行モードにしているので、実際にはセルを選択すると行が選択されます。)
                            cell.Selected = true;
                            //最初に見つかったセルにフォーカスを合わせる
                            if(!found)
                            {
                                target.CurrentCell = cell;
                                found = true;
                            }
                        }
                    }
                }
                Close();
            }
        }
    }

    ///////////////////
    //ソートダイアログ
    ///////////////////

    class SortDlg : Form
    {
        //ダイアログのフィールド
        public int ColIndex;            //解説:ソートの対象となる列番号です。
        public bool IsAscending;    //解説:昇順、降順のフラグです。

        //ダイアログのコントロール
        Button BtnOK;
        ComboBox ColList;        //DropDownStyleプロパティをDropDownList
        ComboBox ADList;        //Ascending|Decending(昇順、降順指定)

        public SortDlg()
        {
            //ダイアログの属性設定
            this.Text = "データの並び替え";
            this.ClientSize = new Size(160, 100);
            this.ControlBox = false;        //システムボタン無効
            this.ShowInTaskbar = false;        //タスクバー上非表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        //境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親ダイアログ中央に配置
            this.Load += Form_Load;
        }

        //OnLoad処理
        private void Form_Load(object sender, EventArgs e)
        {
            //コントロールの属性設定
            BtnOK = new Button();
            BtnOK.Location = new Point((ClientSize.Width - BtnOK.Width) / 2, ClientSize.Height - BtnOK.Height - 10);
            BtnOK.Text = "完了";
            BtnOK.AutoSize = true;
            BtnOK.Click += new EventHandler(OnOK_Click);
            this.Controls.Add(BtnOK);

            ColList = new ComboBox();
            ColList.DropDownStyle = ComboBoxStyle.DropDownList;
            ColList.Location = new Point((ClientSize.Width - ColList.Width) / 2, 10);
            //解説:列数4で"メンバーシップ"、"ログインID"、"パスワード"、"備考"に合わせてitemsに登録します。

            ColList.MaxDropDownItems = 4;
            ColList.Items.Add("メンバーシップ");
            ColList.Items.Add("ログインID");
            ColList.Items.Add("パスワード");
            ColList.Items.Add("備考");
            ColList.SelectedIndex = 0;
            this.Controls.Add(ColList);

            ADList = new ComboBox();
            ADList.DropDownStyle = ComboBoxStyle.DropDownList;
            ADList.Location = new Point((ClientSize.Width - ADList.Width) / 2, 40);
            //解説:列数2で"昇順"、"降順"を登録します。

            ADList.MaxDropDownItems = 2;
            ADList.Items.Add("昇順");
            ADList.Items.Add("降順");
            ADList.SelectedIndex = 0;
            this.Controls.Add(ADList);
        }

        //OKボタン処理
        protected void OnOK_Click(object sender, EventArgs e)
        {
            if(ColList.SelectedIndex == -1)
                MessageBox.Show("並び替えが選択されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            else
            {
                //解説:戻り値を設定します。

                ColIndex = ColList.SelectedIndex;
                IsAscending = (ADList.SelectedIndex == 0);
                Close();
            }
        }
    }
}

 

このidListコントロールは以下のウィンドウイメージのクライアントエリア一杯に貼っている表となります。

 

 

最終回はBCCForm and BCCSkeltonパッケージのファイルのコンパイルの方法と作成および動作試験の講評で締めましょうか。

 

(1)の続き。

 

        //データ入力
        public bool DataRead(string fn, bool crypt)
        {
          
 //ファイル入出力用データ
            string fData = String.Empty;
            
//ファイル読み込み(入力UTF-8)
            try
            {
                using(StreamReader file = new StreamReader(fn, Encoding.UTF8))
                {
                    if(crypt)                    
//暗号化ファイルを読む
                        fData = DecryptData(file.ReadToEnd());
                    else                        
//平文ファイルを読む
                        fData = file.ReadToEnd();
                    file.Close();                
//ファイルを閉じる
                }
            }
            catch(Exception e)                  
 //エラー表示
            {
                MessageBox.Show("エラーが発生しました。\r\n" + e.Message , "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
          
 //既存のidItemsデータを"IDList.dbk"(暗号化、非隠しファイル)で保存
            if(idItems.Count > 0)                //起動時の自動読み込みでは実行しない
                DataWrite(".\\IDListFiles\\IDList.dbk", true, false);
          
 //平文のCSVデータ文字列(fData)をidItemsへ登録
            idItems.Clear();                    //idItemsを初期化する
            while(fData.Length > 0)                //fDataは切り取られてゆく
            {
                idItem item = new idItem();        
//入力用局所変数
                for(int i = 0; i < 4; i++)
                {
                    string token = GetDelimitStr(ref fData);   
//解説:↓の自作メソッドで、fData(参照)からカンマや改行ごとに文字列を切り出してゆきます。
                    switch(i)
                    {
                        case 0:
                            item.Membership = token;
                            break;
                        case 1:
                            item.ID = token;
                            break;
                        case 2:
                            item.Password = token;
                            break;
                        case 3:
                            item.Remark = token;
                            break;
                    }
                    if(fData.Length == 0)      
 //不完全idItemの場合(解説:IDListが出力したファイルではありえません。)
                        break;
                }
                idItems.Add(item);                
//idItemをidItemsへ登録
            }
            
//idItemsの内容をDataGridViewに表示する
            bs.ResetBindings(false);
            return true;
        }

        
//文字列の復号化(解説:↑noDataReadメソッドで使用します。)
        static string DecryptData(string data)
        {   
//解説:以下は定番のDPAPIの使用方法です。
            byte[] dataBytes = Convert.FromBase64String(data);
            byte[] decryptedBytes = ProtectedData.Unprotect(dataBytes,  null, DataProtectionScope.CurrentUser);
            return Encoding.UTF8.GetString(decryptedBytes);
        }


        /////////////////////////////////////
        //カンマ区切り文字列切り出しメソッド
        // str : 切り出すcsvファイル
        //★strは切り出した文字列分短縮され、
        //(ループ条件:str.Length > 0)
        /////////////////////////////////////

        //解説:C++で書いたGetDelimitSrtをC#の特性(null終端がない、ポインターがない、文字数は.Length)に合わせて改造したものを、更にカンマ区切り文字列切り出しループを外側に出して再構成したもの

        public string GetDelimitStr(ref string str)
        {
            
//空文字列チェック
            if(String.IsNullOrEmpty(str))
                return "Error: Empty data.";        
//本来str.Lengthが0で呼ばれない
            //フィールド宣言
            char[] cArr = str.ToCharArray();        //長さcArr.Lengthのヌル端末無しの配列
            int cPtr = 0;                            //文字列ポインター
            string buff = String.Empty;                //文字列バッファ
            bool dblQuo = false;                    //「"~"内」フラグ
            //カンマ区切り文字列切り出しループ
            while(cArr[cPtr] != ',' && cArr[cPtr] != '\r' && cArr[cPtr] != '\n')    //解説:区切り文字でなければ
            {
                if(cArr[cPtr] == '"')              
 //「"」があった場合
                {
                  
 //「\"」を除き「"~"」内モードになる(解説:例外は「"... "..."」という文字列です。)
                    if(cPtr == 0 || cArr[cPtr - 1] != '\\')
                    {
                        dblQuo = !dblQuo;                
//dblQuoフラグを立てる/下す
                        cPtr++;                            //'\"'の次に進める
                        if(!dblQuo)                        //既に「"~"」内ならば
                            break;                        //ループ終了
                        else                            //「"~"」内に入ったら
                            continue;                    //仕切り直し
                    }
                }
                buff += cArr[cPtr];                    
//cArrデータをbuffにコピーし
                cPtr++;                                //ポインターを進める
                if(cPtr < cArr.Length && dblQuo)    //既に「"~"」内ならば
                {    //区切り文字があってもコピーし、パスする
                    while(cArr[cPtr] == ',' || cArr[cPtr] == '\r' || cArr[cPtr] == '\n')    //解説:区切り文字が続く限り
                    {
                        buff += cArr[cPtr];          
 //cArrデータをbuffにコピーし
                        cPtr++;                        //ポインターを進める
                        if(cPtr == cArr.Length)        //括弧が閉じられず、文字列が終了した場合
                        {
                            buff = "Error: Double quotation marked string not closed.";
                            break;
                        }
                    }
                }
                
//文字列終了チェック
                if(cPtr == cArr.Length)                //文字列が終了した場合
                        break;                        //ループ終了
            }
          
 //文字列が終了していない場合、区切り文字をスキップする
            if(cPtr < cArr.Length)
            {
                
//','、'\r'、'\n'があれば(カンマ、CR、LF単体)
                if(cArr[cPtr] == ',' || cArr[cPtr] == '\r' || cArr[cPtr] == '\n')
                    cPtr++;      
 //スキップする
                //更に文字があり、改行文字(CR、LF)の片割れがあれば
                if(cPtr < cArr.Length && (cArr[cPtr] == '\n' || cArr[cPtr] == '\r'))
                    cPtr++;        
//もう一文字スキップする
            }
          
 //strを残余の文字列に切り詰める
            str = new string(cArr, cPtr, cArr.Length - cPtr);
            
//切り取った文字列またはエラーメッセージを返す
            return buff;
        }

        
//データ出力
        public bool DataWrite(string fn, bool crypt, bool hide)
        {   
//解説:ファイル属性管理の為のFileinfoの使い方に注目してください。インスタンスのfiにしています。
            FileInfo fi = new FileInfo(fn);                        //FileInfoはfnが存在しなくても例外にならない
            if(fi.Exists &&                                        //fnが存在し且つ
                fi.Attributes.HasFlag(FileAttributes.Hidden))    //隠しファイル属性があれば解説:単純に "fi.Attributes & FileAttributes.Hidden"<ビット論理式>とはできないようです。)
                fi.Attributes &= ~FileAttributes.Hidden;        //隠しファイル属性を外す(解説:ここではビット論理式にしてますが...)
            //(C++のように)if((fi.Attributes & FileAttributes.Hidden))だけではbool値を返してくれない。
            //C++のように数値の0を偽(false)、それ以外を真(true)とみなす自動変換(暗黙の型変換)がC#にはない。
            //C#ではif文の条件式に必ずbool型(true または false)を渡す必要があり、エラーとなる。
            //ファイル入出力用データ

            string fData = String.Empty;
            
//idItemsデータのCSV化
            foreach(idItem item in idItems)
            {
                
//最終列(「備考」)はコントロールコードが入るかもしれないので""で括る

                //解説:データ フォーマットは「(Membership),(ID),(Password),"(Remark)"CRLF」です 
                fData += item.Membership + "," + item.ID + "," + item.Password + ","
                        + "\"" + item.Remark + "\"" + Environment.NewLine;
            }
            fData = fData.TrimEnd();                            //最後のCRLFを削除
            //ファイル書き込み(出力UTF-8)
            try
            {
                using (StreamWriter file = new StreamWriter(fn, false, Encoding.UTF8))
                {
                    if(crypt)
                        file.Write(EncryptData(fData));        
//暗号化ファイルを書く
                    else
                        file.Write(fData);                    
//平文ファイルを書く
                    file.Close();                            //ファイルを閉じる
                    if(hide)                                //隠しファイル属性を追加する
                        fi.Attributes |= FileAttributes.Hidden;        //解説:HiddenのビットをAND(|)で立てています。
                }
            }
            catch(Exception e)                    
//エラー表示
            {
                MessageBox.Show("エラーが発生しました。\r\n" + e.Message , "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
            return true;
        }

        
//文字列の暗号化(解説:DataWriteで使います。)
        static string EncryptData(string data)
        {   
//解説:以下は定番のDPAPIの使用方法です。
            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
            byte[] encryptedBytes = ProtectedData.Protect(dataBytes, null, DataProtectionScope.CurrentUser);
            return Convert.ToBase64String(encryptedBytes);
        }
    }

 

(3)に続く。

前回で「DataGridViewから派生させた、idListクラスidListコントロール)のアプリにおける使用の実際」を紹介しましたので、今回はそれが入っているIDList_CS.dllのソースコードであるIDList_CS.csを紹介します。(何れ字数オーバーになるのだろうと、最初から(1)にしてしまいました。)適宜解説文を入れておきますので、深堀りは今までのブログを振り返ってください。

 

【IDList_CS.cs】

///////////////////////////////
//        IDList_CS.cs
// Copyright (c) 2026 by ysama
// https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.datagridview?view=windowsdesktop-10.0
///////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;        //[DisplayName()]使用の為
using System.IO;                    //StreamReader使用の為
using System.Text;                  //Encoding.GetEncoding使用の為
using System.Security.Cryptography;    //データの暗号化の為

namespace IDList_CS
{
    ///////////////////////////////////
    //IDListで使用するIDデータのクラス
    ///////////////////////////////////

    public class idItem
    {
        //解説:これについては既に「【DataGridView】IDList for CS写真集」の「3.編集操作」で触れていますので省略します。

    }

    /////////////////////////////////////
    //idListクラス(DataGridViewから派生)
    /////////////////////////////////////

    public class idList : DataGridView;    //解説:これが今回の主役(心臓部)です。
    {
        //idItemのデータリスト(解説:idItemクラスインスタンスをListに入れて使います。)
        List <idItem> idItems = new List <idItem> ();
        //DataGridViewのバインディングデータソース(解説:今回初めて知りました。DataGridViewとListをつなぐインターフェースです。)
        BindingSource bs = new BindingSource();
        //システムデータファイルパス(解説:セキュリティ上はユーザーの好きなところに作らせ、そのパスも暗号化すべきでしょうが...)
        const string dfPath = ".\\IDListFiles\\IDList.dat";    //解説:相対パスはChat-GPTに批判されましたが、これでいいです。
        //コンテキストメニュー(解説:右クリックでポップアップさせるメニューです。)
        ContextMenuStrip cms = new ContextMenuStrip();
        //並び替え順(true - 昇順、false - 降順-解説:ソートの為にこれに記憶させておきます。)
        bool sortOrder = true;

        //コンストラクター
        public idList()
        {
            //DataGridViewの設定
            this.ColumnHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
            this.EditMode = DataGridViewEditMode.EditProgrammatically;        //ダブルクリック編集不可(解説:セキュリティ目的で殺しました。)
            this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;    //一行選択(飽くまでidItem単位です。)
            this.AllowUserToAddRows = false;                                //末尾の空行禁止
            bs.DataSource = idItems;    //bsのデータソースにidItemsをセット(解説:上記参照)
            this.DataSource = bs;        //DataGridViewのデータソースにbsをセット(解説:Ditto)
            this.DataBindingComplete += On_DataBindingComplete;
            this.ColumnHeaderMouseClick += On_ColumnHeaderMouseClick;
            this.CellDoubleClick += On_CellDoubleClick;
            //コンテキストメニュー設定
            cms.Items.Add("追加", null, AddItem_Click);
            cms.Items.Add("検索", null, FindItem_Click);
            cms.Items.Add("編集", null, EditItem_Click);
            cms.Items.Add("削除", null, DeleteItem_Click);
            this.ContextMenuStrip = cms;
            //*.datファイル用ディレクトリーの作成(初回のみ-解説:既にある場合もエラーにはなりません。)
            Directory.CreateDirectory(".\\IDListFiles");
            //dfPathファイルのデータを読み込む
            DataRead(dfPath, true);        //dfPathを暗号化で読む
        }

        //サイズ変更時処理
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            //起動時はColumnCount == 0でエラーになる為(解説:利便性の反面、バインディングが遅いのが弱点で、タイミングを合わせることが大事です。)
            if(this.ColumnCount > 3 && this.ClientSize.Width >= 560)
                this.Columns[3].Width = this.ClientSize.Width - 560;
        }

        //終了時ファイル保存処理(解説:ウィンドウならOnClosingですが、コントロールには無いのでこれにしました。)
        protected override void OnHandleDestroyed(EventArgs e)
        {
            if (this.IsCurrentCellInEditMode)
            {
                this.EndEdit();                //編集中データを確定させる
            }
            DataWrite(dfPath, true, true);    //dfPathを暗号化、隠しファイルで保存
            base.OnHandleDestroyed(e);
         }

        //DataGridViewのデータバインディング完了イベント(解説:これもデータバインディングが遅いので、タイミングを計る為に最適なイベントを探しました。)
        private void On_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            //列幅の設定(解説:列が生成される迄、列数や列幅は設定できない<エラーが出ます>ので。)
            this.Columns[0].Width = 240;
            this.Columns[1].Width = 160;
            this.Columns[2].Width = 160;
            if(this.Width < 560)    //「備考」幅は560以下にはしない(解説:Resizeイベントと同じです。)
                this.Columns[3].Width = 560;
            else
                this.Columns[3].Width = this.ClientSize.Width - 560;
            //第2、3列は中央揃えにする
            this.Columns[1].DefaultCellStyle.Alignment =DataGridViewContentAlignment.MiddleCenter;
            this.Columns[2].DefaultCellStyle.Alignment =DataGridViewContentAlignment.MiddleCenter;
        }

        //DataGridViewの列ヘッダークリックイベント(解説:これはソート目的です。)
        private void On_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            //クリックされた列の番号を取得
            int colIndex = e.ColumnIndex;
            string colName = (sender as DataGridView).Columns[colIndex].Name;
            switch(colName)
            {    //解説:列のIDを調べ、それによってListのSortメソッドにidItemの比較メソッドのデリゲートを与えます。
                case "Membership":
                    if(sortOrder)
                        idItems.Sort((x, y) => x.CompareMembershipTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareMembershipTo(x));
                    break;
                case "ID":
                    if(sortOrder)
                        idItems.Sort((x, y) => x.CompareIDTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareIDTo(x));
                    break;
                case "Password":
                    if(sortOrder)
                        idItems.Sort((x, y) => x.ComparePasswordTo(y));
                    else
                        idItems.Sort((x, y) => y.ComparePasswordTo(x));
                    break;
                case "Remark":
                    if(sortOrder)
                        idItems.Sort((x, y) => x.CompareRemarkTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareRemarkTo(x));
                    break;
            }
            bs.ResetBindings(false);
            sortOrder = !sortOrder;        //トグル変更(昇順←→降順)解説:この為にフラグを設けました。)
        }

        //DataGridViewのダブルクリックイベント(解説:デフォルトではセルの編集モードに入りますが、このプログラムではセキュリティを考え、idItemの編集ダイアログを呼ぶことにしています。)
        private void On_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            DataGridViewRow target = this.Rows[e.RowIndex];
            //編集処理
            AddEditDlg aeDlg = new AddEditDlg(target);
            aeDlg.ShowDialog();                        //AddEditDlg表示
            bs.EndEdit();                            //編集データのListとの同期
            aeDlg.Dispose();
        }

        //解説:↓以降同じように「追加の公開メソッドと内部イベントメソッド」と書いていますが、「追加」を例にとって解説します。idListコントロールのメソッドとして公開(public)の引数の無いAdd()メソッドが使えますが、それは単にprivateのイベントメソッドを呼んでいるだけです。

        //追加の公開メソッドと内部イベントメソッド
        public void Add()        //解説:コントロール外部から利用できる公開メソッド


        {
            AddItem_Click(cms, null);        //解説:実際は↓のイベントメソッドを呼んでいるだけ。
        }

        private void AddItem_Click(object sender, EventArgs e)
        {
            //追加処理
            AddEditDlg aeDlg = new AddEditDlg();    //AddEditDlg表示(解説:コンストラクターに引数がないと追加になります。)
            aeDlg.ShowDialog();
            if(!ReferenceEquals(aeDlg.iditem, null))
            {
                idItems.Add(aeDlg.iditem);            //追加データのListへの追加
                bs.ResetBindings(false);            //idItems変更をDataGridViewへ反映する
            }
            else
                MessageBox.Show("新データは追加されませんでした。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            aeDlg.Dispose();
        }

        //編集の公開メソッドと内部イベントメソッド
        public void Edit()
        {
            EditItem_Click(cms, null);
        }

        private void EditItem_Click(object sender, EventArgs e)
        {
            //選択がなければその旨確認して何もしない
            if(this.SelectedRows.Count == 0)
            {
                MessageBox.Show("選択された行がありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            //編集処理(複数行可)
            foreach (DataGridViewRow sel in this.SelectedRows)
            {
                //編集処理
                AddEditDlg aeDlg = new AddEditDlg(sel);    //AddEditDlg表示(解説:編集の場合は、引数に行情報を入れます。)
                aeDlg.ShowDialog();
                bs.EndEdit();                            //編集データのListとの同期
                aeDlg.Dispose();
            }
        }

        //検索の公開メソッドと内部イベントメソッド
        public void Find()
        {
            FindItem_Click(cms, null);
        }

        private void FindItem_Click(object sender, EventArgs e)
        {
            //検索処理(検索結果の行をSelectedRowとして表示する)
            FindDlg findDlg = new FindDlg(this);    //FindDlg表示
            findDlg.ShowDialog();                    //List(idItems)の検索は行わない(解説:表の列を選択するだけです。)
            findDlg.Dispose();
        }

        //削除の公開メソッドと内部イベントメソッド
        public void Delete()
        {
            DeleteItem_Click(cms, null);
        }

        private void DeleteItem_Click(object sender, EventArgs e)
        {
            //選択がなければその旨確認して何もしない
            if(this.SelectedRows.Count == 0)
            {
                MessageBox.Show("選択された行がありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
                return;
            }
            //削除処理(複数行可)-コントロールの選択行を基にidItemsの要素を削除
            if(MessageBox.Show("本当に削除しますか?", "確認", MessageBoxButtons.YesNo,
                MessageBoxIcon.Question) == DialogResult.Yes)
            {
                foreach (DataGridViewRow sel in this.SelectedRows)
                {
                    idItems.RemoveAt(sel.Index);
                }
            }
            bs.ResetBindings(false);    //idItems変更をDataGridViewへ反映する
            this.ClearSelection();        //選択を解除する(解説:これがないと、削除後も「選択番号(Index)」で選択し続けます。)
        }

        //データの並び替え
        public void Sort()
        {
            SortDlg sortDlg = new SortDlg();    //SortDlg表示
            sortDlg.ShowDialog();
            //列番号に基づいて並び変える(解説:↑の列ヘッダーのソートと比較してください。)
            switch(sortDlg.ColIndex)
            {   //解説:列番号(ColIndex)によってListのSortメソッドにデリゲートを与えます。
                case 0:
                    if(sortDlg.IsAscending)
                        idItems.Sort((x, y) => x.CompareMembershipTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareMembershipTo(x));
                    break;
                case 1:
                    if(sortDlg.IsAscending)
                        idItems.Sort((x, y) => x.CompareIDTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareIDTo(x));
                    break;
                case 2:
                    if(sortDlg.IsAscending)
                        idItems.Sort((x, y) => x.ComparePasswordTo(y));
                    else
                        idItems.Sort((x, y) => y.ComparePasswordTo(x));
                    break;
                case 3:
                    if(sortDlg.IsAscending)
                        idItems.Sort((x, y) => x.CompareRemarkTo(y));
                    else
                        idItems.Sort((x, y) => y.CompareRemarkTo(x));
                    break;
            }
            bs.ResetBindings(false);
            sortDlg.Dispose();
        }

 

(2)に続く。

(1)からの続き


        //ツールバーとステータスバーの作成
        private void MakeBars()
        {
            //ToolStripクラスインスタンスの生成
            this.toolStrip = new ToolStrip();
            //ツールバーのレイアウトを一時停止
            this.toolStrip.SuspendLayout();
            //ToolStripButton配列を作成
            this.tsButton = new ToolStripButton[10];
            //本プログラムの埋め込みリソースのリソースマネージャーを作成
            ResourceManager rm = new ResourceManager("IDList", myOwn);
            //ツールバービットマップの読み込み
            ImageList imgList = new ImageList();
            imgList.ImageSize = new Size(16, 15);
            imgList.Images.AddStrip((Bitmap)rm.GetObject("ToolBar"));
            imgList.TransparentColor = Color.FromArgb(0xFF, 0xFF, 0xFF);    //ToolBar.bmpの背景色

            //ToolStripButton[0]を作成
            this.tsButton[0] = new ToolStripButton();
            this.tsButton[0].Text = "データを開く";                            //テキスト設定
            this.tsButton[0].Image = (Bitmap)imgList.Images[0];                //画像設定
            this.tsButton[0].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.tsButton[0].Click += OnOpen_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[0]);                        //ボタンを追加
            //ToolStripButton[1]を作成
            this.tsButton[1] = new ToolStripButton();
            this.tsButton[1].Text = "データを保存";                            //テキスト設定
            this.tsButton[1].Image = (Bitmap)imgList.Images[1];                //画像設定
            this.tsButton[1].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[1].Click += OnSave_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[1]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[2]を作成
            this.tsButton[2] = new ToolStripButton();
            this.tsButton[2].Text = "終了";                    //テキスト設定
            this.tsButton[2].Image = (Bitmap)imgList.Images[2];                //画像設定
            this.tsButton[2].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[2].Click += OnExit_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[2]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[3]を作成
            this.tsButton[3] = new ToolStripButton();
            this.tsButton[3].Text = "追加";                                    //テキスト設定
            this.tsButton[3].Image = (Bitmap)imgList.Images[3];                //画像設定
            this.tsButton[3].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[3].Click += OnAdd_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[3]);                        //ボタンを追加
            //ToolStripButton[4]を作成
            this.tsButton[4] = new ToolStripButton();
            this.tsButton[4].Text = "検索";                                    //テキスト設定
            this.tsButton[4].Image = (Bitmap)imgList.Images[4];                //画像設定
            this.tsButton[4].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[4].Click += OnSearch_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[4]);                        //ボタンを追加
            //ToolStripButton[5]を作成
            this.tsButton[5] = new ToolStripButton();
            this.tsButton[5].Text = "編集";                                    //テキスト設定
            this.tsButton[5].Image = (Bitmap)imgList.Images[5];                //画像設定
            this.tsButton[5].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[5].Click += OnEdItem_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[5]);                        //ボタンを追加
            //ToolStripButton[6]を作成
            this.tsButton[6] = new ToolStripButton();
            this.tsButton[6].Text = "削除";                                    //テキスト設定
            this.tsButton[6].Image = (Bitmap)imgList.Images[6];                //画像設定
            this.tsButton[6].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[6].Click += OnDelete_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[6]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[7]を作成
            this.tsButton[7] = new ToolStripButton();
            this.tsButton[7].Text = "並び替え";                                //テキスト設定
            this.tsButton[7].Image = (Bitmap)imgList.Images[7];                //画像設定
            this.tsButton[7].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[7].Click += OnSort_Click;                            //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[7]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[8]を作成
            this.tsButton[8] = new ToolStripButton();
            this.tsButton[8].Text = "使い方";                                //テキスト設定
            this.tsButton[8].Image = (Bitmap)imgList.Images[8];                //画像設定
            this.tsButton[8].DisplayStyle = ToolStripItemDisplayStyle.Image;//画像表示のみ
            this.tsButton[8].Click += OnHowtoUse_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[8]);                        //ボタンを追加
            //セパレーターを挿入
            this.toolStrip.Items.Add(new ToolStripSeparator());
            //ToolStripButton[9]を作成
            this.tsButton[9] = new ToolStripButton();
            this.tsButton[9].Text = "バージョン情報";                        //テキスト設定
            this.tsButton[9].Image = (Bitmap)imgList.Images[9];                //画像設定
            this.tsButton[9].DisplayStyle = ToolStripItemDisplayStyle.Image;    //画像表示のみ
            this.tsButton[9].Click += OnVersion_Click;                        //Clickイベントハンドラ追加
            this.toolStrip.Items.Add(this.tsButton[9]);                        //ボタンを追加
            //ツールバーの設定
            this.Controls.Add(this.toolStrip);
            //ツールバーのレイアウトを再開
            this.toolStrip.ResumeLayout(false);
            this.toolStrip.PerformLayout();

            //StatusStripクラスインスタンスの生成
            this.statusStrip = new StatusStrip();
            //ステータスバーにパネルとテキストを追加
            tssl = new ToolStripStatusLabel[2];
            tssl[0] = new ToolStripStatusLabel();
            tssl[0].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[0].BorderStyle = Border3DStyle.SunkenInner;
            tssl[0].BackColor = SystemColors.Control;
            tssl[0].Text = "IDList for C# Ver. 1.0";
            tssl[0].AutoSize = true;
            tssl[0].TextAlign = ContentAlignment.MiddleLeft;
            tssl[1] = new ToolStripStatusLabel();
            tssl[1].BorderSides = ToolStripStatusLabelBorderSides.All;
            tssl[1].BorderStyle = Border3DStyle.SunkenInner;
            tssl[1].BackColor = SystemColors.Control;
            tssl[1].TextAlign = ContentAlignment.MiddleLeft;
            tssl[1].Text = "(ファイル名)";
            tssl[1].ToolTipText = "(ファイル名)";                            //ToolTip設定
            tssl[1].Spring = true;
            statusStrip.Items.AddRange(tssl);
            statusStrip.ShowItemToolTips = true;                            //ToolTip表示
            this.Controls.Add(this.statusStrip);                            //StatusStrip(ステータスバー)を追加
        }

        //終了処理
        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 OnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofDlg = new OpenFileDialog();
            //ファイルフィルターの指定
            ofDlg.Filter = "データファイル|*.dat";
            ofDlg.FilterIndex = 0;
            ofDlg.RestoreDirectory = true;    //初期ディレクトリへ復帰
            ofDlg.CheckPathExists = true;    //ファイルパスの存在チェック
            ofDlg.InitialDirectory = ".\\IDListFiles";        // デフォルトのフォルダーの指定
            ofDlg.Title = "ファイルを開く";    //ダイアログのタイトルを指定する
            if(ofDlg.ShowDialog() == DialogResult.Cancel)    //ダイアログを表示する
            {
                MessageBox.Show("キャンセルされました。", "キャンセル", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
            else
            {
                bool flag;
                if(MessageBox.Show("暗号化データファイルですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    flag = true;
                else
                    flag = false;
                IDList.DataRead(ofDlg.FileName, flag);    //解説:ファイル読み込み処理自体はIDListインスタンスが行います。
                tssl[1].Text = ofDlg.FileName;            //ファイル名を記録
                tssl[1].ToolTipText = ofDlg.FileName;    //ToolTip設定
            }
            //オブジェクトを破棄する
            ofDlg.Dispose();
        }

        //「保存」処理
        private void OnSave_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfDlg = new SaveFileDialog();
            //ファイルフィルターの指定
            sfDlg.AddExtension = true;        //拡張子自動付加
            sfDlg.DefaultExt = ".dat";
            sfDlg.Filter = "データファイル|*.dat";
            sfDlg.FilterIndex = 0;
            sfDlg.RestoreDirectory = true;    //初期ディレクトリへ復帰
            sfDlg.CheckPathExists = true;    //ファイルパスの存在チェック
            sfDlg.InitialDirectory = ".\\IDListFiles";        // デフォルトのフォルダーの指定
            sfDlg.Title = "ファイルを保存";    //ダイアログのタイトルを指定する
            sfDlg.OverwritePrompt = true;    //上書き確認
            if(sfDlg.ShowDialog() == DialogResult.Cancel)    //ダイアログを表示する
            {
                MessageBox.Show("キャンセルされました。", "キャンセル", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
            }
            else
            {
                bool crptflag, hideflag;    //解説:順に暗号化フラグ、隠しファイルフラグです。
                if(MessageBox.Show("暗号化データファイルですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    crptflag = true;
                else
                    crptflag = false;
                if(MessageBox.Show("隠しファイルにしますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    hideflag = true;
                else
                    hideflag = false;
                IDList.DataWrite(sfDlg.FileName, crptflag, hideflag);
                //ファイル名を記録
                tssl[1].Text = sfDlg.FileName;
                tssl[1].ToolTipText = sfDlg.FileName;    //ToolTip設定
            }
            //オブジェクトを破棄する
            sfDlg.Dispose();
        }

        //「終了」処理
        private void OnExit_Click(object sender, EventArgs e)
        {
            Close();    //終了確認はOnClosingメソッドで行う
        }

        //「追加」処理
        private void OnAdd_Click(object sender, EventArgs e)
        {
            IDList.Add();    //解説:実際の処理は全てIDListインスタンスでやります。
        }

        //「検索」処理
        private void OnSearch_Click(object sender, EventArgs e)
        {
            IDList.Find();    //解説:実際の処理は全てIDListインスタンスでやります。
        }

        //「編集」処理
        private void OnEdItem_Click(object sender, EventArgs e)
        {
            IDList.Edit();    //解説:実際の処理は全てIDListインスタンスでやります。
        }

        //「削除」処理
        private void OnDelete_Click(object sender, EventArgs e)
        {
            IDList.Delete();    //解説:実際の処理は全てIDListインスタンスでやります。
        }

        //「並び替え」処理
        private void OnSort_Click(object sender, EventArgs e)
        {
            IDList.Sort();    //解説:実際の処理は全てIDListインスタンスでやります。
        }

        //「使い方」処理
        private void OnHowtoUse_Click(object sender, EventArgs e)
        {
            string str = ".\\HowToUse.txt";
            using(StreamReader file = new StreamReader(str, Encoding.UTF8))
            {
                str = file.ReadToEnd();
                file.Close();                //ファイルを閉じる
            }
            MessageBox.Show(str, "簡単な説明", MessageBoxButtons.OK, MessageBoxIcon.Information);    //解説:メッセージボックス表示
            Help.ShowHelp(this, "IDListHelp.chm");    //解説:HTMLHelpファイルの表示
            System.Diagnostics.Process.Start(".\\IDList_CS使い方.html");    //解説:HTMLファイルの表示
        }

        //「バージョン」処理
        private void OnVersion_Click(object sender, EventArgs e)
        {
            VersionDlg verDlg = new VersionDlg(this.Icon);    //Versionダイアログ表示
            verDlg.ShowDialog();
            verDlg.Dispose();
        }
    }

    /////////////////////
    //Version ダイアログ
    /////////////////////

    class VersionDlg : Form
    {
        public VersionDlg(Icon ico)
        {
            //ダイアログの属性設定
            this.Text = "バーション情報";
            this.ClientSize = new Size(320, 100);
            this.MaximizeBox = false;        // 最大化ボタン
            this.MinimizeBox = false;        // 最小化ボタン
            this.ShowInTaskbar = false;        //タスクバー上表示
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        // 境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    // 親フォームの中央に配置
            //コントロールの属性設定
            Button btnOK = new Button();
            btnOK.Size = new Size(40, 28);
            btnOK.Location = new Point(ClientSize.Width - btnOK.Width - 10, (ClientSize.Height - btnOK.Height) / 2);
            btnOK.Text = "OK";
            btnOK.Click += new EventHandler(OnOK_Click);
            Label imglabel = new Label();
            imglabel.Size = new Size(40, 40);
            imglabel.Location = new Point(10, (ClientSize.Height - imglabel.Height) / 2);
            imglabel.BorderStyle = BorderStyle.Fixed3D;
            imglabel.Image = ico.ToBitmap();                        //親のシステムアイコン
            Label label = new Label();
            label.Size = new Size(ClientSize.Width - imglabel.Width - btnOK.Width - 40, ClientSize.Height- 20);
            label.Location = new Point(imglabel.Width + 20, (ClientSize.Height - label.Height) / 2);
            label.BorderStyle = BorderStyle.Fixed3D;
            label.Text = "IDList for C# Version 1.0\r\nCopyright (c) 2026 by Ysama\r\n(written in Microsoft C#)";
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.Font = new Font("Times New Roman", 10, FontStyle.Bold);
            this.Controls.Add(btnOK);
            this.Controls.Add(imglabel);
            this.Controls.Add(label);
        }

        private void OnOK_Click(object sender, EventArgs e)
        {
            Close();
        }
    }
}

 

 

後はいよいよIDList_CS.cs(IDList_CS.dll)の解説だけとなりました。

 

前回の【DataGridView】IDList for CS写真集でアプリの具体的な全容がお分かりだと思いますので、今回は「このプログラムの中核である機能を持つIDList_CS.dllをコントロールとして持つ器のウィンドウプログラム」の(一応)すべてのコードを掲載するとともに、IDList_CS.dllに係る部分)の解説を行います。

idListクラスコントロールのインスタンスIDListの作成、そのメソッドの実行とIDList関連の情報についてです。

 

なお、このプログラムはアイコンとツールバービットマップのファイルがないとコンパイルできません()のでご注意ください。

:必要なファイル、およびDLLと器のコンパイル用のバッチファイルを纏めたものが近々BCCForm and BCCSkeltonパッケージにアップされますので、ご興味がある方はそちらの方へ。

 

【IDList.cs】

///////////////////////////////
//        IDList.cs
// Copyright (c) 2026 by ysama
///////////////////////////////

using System;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;                //Assemblyを使う為
using System.Resources;                    //リソース関係クラス等の使用の為
using System.IO;                        //StreamReader使用の為
using System.Text;                        //Encoding.GetEncoding使用の為
using IDList_CS;                        //IDList.dll(idList使用の為)

namespace IDList
{
    public class testIDList : Form
    {
        //クラスメンバーフィールド
        Assembly myOwn = Assembly.GetEntryAssembly();
        //クラスメンバーコントロール
        MenuStrip menuStrip;                        //メニュー
        ToolStrip toolStrip;                        //ツールバー
        ToolStripButton[] tsButton;                    //ツールバーボタン
        StatusStrip statusStrip;                    //ステータスバー
        ToolStripStatusLabel[] tssl;                //ステータスバーラベル
        idList IDList;                                //idListコントロール

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

        public testIDList()                        //コンストラクター
        {
            this.Icon = Icon.ExtractAssociatedIcon(myOwn.Location);    //プログラムアイコン
            this.Size = new Size(1280, 640);
            this.MinimumSize = new Size(640, 480);
            this.Text = "IDList";
            //起動時処理
            this.Load += testIDList_Load;
        }

        private void testIDList_Load(object sender, EventArgs e)
        {
            //フォームのレイアウトを一時停止
            this.SuspendLayout();
            //ツールバーとステータスバーの設定
            MakeBars();
            //メニューの作成(MenuStripはコントロールであり、上からの並びはDock投入のFILO順となる)
            MakeMenu();
            //idListの追加(解説:IDList_CS.dllのidListクラスインスタンスを作ります。)
            IDList = new idList();
            IDList.Location = new Point(0, menuStrip.Height + toolStrip.Height);
            IDList.Size = new Size(this.ClientSize.Width, this.ClientSize.Height - (menuStrip.Height + toolStrip.Height + statusStrip.Height));
            IDList.Anchor = (AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom | AnchorStyles.Left);
            this.Controls.Add(IDList);

            //フォームのレイアウトを再開
            this.ResumeLayout(false);
            this.PerformLayout();
            //起動時の引数をチェックし、引数にファイル名があればそれを読み込む
            string[] arg = System.Environment.GetCommandLineArgs();
            if(arg.Length > 1)
            {
                if(Path.GetExtension(arg[arg.Length - 1]) == ".dat")
                {
                    bool crptflag;
                    if(MessageBox.Show("暗号化データファイルですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                        crptflag = true;
                    else
                        crptflag = false;
                    IDList.DataRead(arg[arg.Length - 1], crptflag);
                    //(複数投入であれば最後の)ファイル名を記録
                    tssl[1].Text = arg[arg.Length - 1];
                    tssl[1].ToolTipText = arg[arg.Length - 1];    //ToolTip設定
                }
                else
                {
                    //エラー処理
                    MessageBox.Show("データファイル(*.dat)ではありません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        //メニューの作成
        private void MakeMenu()
        {
            //メインメニュー作成
            menuStrip = new MenuStrip();
            menuStrip.SuspendLayout();
            //メインメニューアイテム付加
            ToolStripMenuItem miFile = new ToolStripMenuItem();        //「ファイル」メニュー
            miFile.Text = "ファイル(&F)";
            menuStrip.Items.Add(miFile);
            ToolStripMenuItem miEdit = new ToolStripMenuItem();        //「編集」メニュー
            miEdit.Text = "編集(&E)";
            menuStrip.Items.Add(miEdit);
            ToolStripMenuItem miDisp = new ToolStripMenuItem();        //「表示」メニュー
            miDisp.Text = "表示(&D)";
            menuStrip.Items.Add(miDisp);
            ToolStripMenuItem miHelp = new ToolStripMenuItem();        //「ヘルプ」メニュー
            miHelp.Text = "ヘルプ(&H)";
            menuStrip.Items.Add(miHelp);
            //「ファイル」メニューアイテム付加
            ToolStripMenuItem miOpen = new ToolStripMenuItem();        //「開く」メニューアイテム
            miOpen.Text = "    データを開く(&O)";
            miOpen.Click += OnOpen_Click;
            miOpen.ShortcutKeys = Keys.Control | Keys.O;
            miFile.DropDownItems.Add(miOpen);
            ToolStripMenuItem miSave = new ToolStripMenuItem();        //「保存」メニューアイテム
            miSave.Text = "データの保存(&S)";
            miSave.Click += OnSave_Click;
            miSave.ShortcutKeys = Keys.Control | Keys.S;
            miFile.DropDownItems.Add(miSave);
            miFile.DropDownItems.Add(new ToolStripSeparator());        //セパレーター
            ToolStripMenuItem miExit = new ToolStripMenuItem();        //「終了」メニューアイテム
            miExit.Text = "終了(&X)";
            miExit.Click += OnExit_Click;
            miExit.ShortcutKeys = Keys.Control | Keys.X;
            miFile.DropDownItems.Add(miExit);
            //「編集」メニューアイテム付加
            miEdit.DropDownItems.Add(new ToolStripSeparator());        //セパレーター
            ToolStripMenuItem miAdd = new ToolStripMenuItem();        //「追加」メニューアイテム
            miAdd.Text = "追加(&A)";
            miAdd.Click += OnAdd_Click;
            miAdd.ShortcutKeys = Keys.Control | Keys.A;
            miEdit.DropDownItems.Add(miAdd);
            ToolStripMenuItem miSearch = new ToolStripMenuItem();    //「検索」メニューアイテム
            miSearch.Text = "検索(&S)";
            miSearch.Click += OnSearch_Click;
            miSearch.ShortcutKeys = Keys.Control | Keys.S;
            miEdit.DropDownItems.Add(miSearch);
            ToolStripMenuItem miEdItem = new ToolStripMenuItem();    //「編集」メニューアイテム
            miEdItem.Text = "編集(&E)";
            miEdItem.Click += OnEdItem_Click;
            miEdItem.ShortcutKeys = Keys.Control | Keys.E;
            miEdit.DropDownItems.Add(miEdItem);
            ToolStripMenuItem miDelete = new ToolStripMenuItem();    //「削除」メニューアイテム
            miDelete.Text = "削除(&D)";
            miDelete.Click += OnDelete_Click;
            miDelete.ShortcutKeys = Keys.Control | Keys.E;
            miEdit.DropDownItems.Add(miDelete);
            //「表示」メニューアイテム付加
            ToolStripMenuItem miSort = new ToolStripMenuItem();        //「並び替え」メニューアイテム
            miSort.Text = "並び替え(&S)";
            miSort.Click += OnSort_Click;
            miDisp.DropDownItems.Add(miSort);
            //「ヘルプ」メニューアイテム付加
            ToolStripMenuItem miHowtoUse = new ToolStripMenuItem();    //「使い方」メニューアイテム
            miHowtoUse.Text = "使い方(&H)";
            miHowtoUse.Click += OnHowtoUse_Click;
            miHowtoUse.ShortcutKeys = Keys.Control | Keys.H;
            miHelp.DropDownItems.Add(miHowtoUse);
            miHelp.DropDownItems.Add(new ToolStripSeparator());        //セパレーター
            ToolStripMenuItem miVer = new ToolStripMenuItem();        //「バージョン」メニューアイテム
            miVer.Text = "バージョン(&V)";
            miVer.Click += OnVersion_Click;
            miVer.ShortcutKeys = Keys.F2;
            miHelp.DropDownItems.Add(miVer);
            //メインメニューに設定して表示
            this.MainMenuStrip = menuStrip;
            this.Controls.Add(menuStrip);
            menuStrip.ResumeLayout(false);
            menuStrip.PerformLayout();
        }

 

(何故か)またまた字数制限に引っかかったので(2)に続きます。

 

前回IDList for CSはほぼ出来上がっている(実際動作試験中)、と豪語したので、そのデビューを記念して写真集にしましょう。

 

1.起動

「コントロールに専用ファイル入出力を持たせるのはあまりない」「ユーザーコントロールのメインウィンドウに任せるべき」という批判があろうとは思いますが、私は「平文、暗号文でのID、パスワード情報のコピーは作れでも、システムが使用するファイルはそのコントロールに任せたい」と考え、idListクラス(DataGridViewから派生させた機能拡張コントロール)には、

 

(1)システムデータファイルを保存するフォールダーを作成し、

(2)(最初は未作成のでエラーになりますが)作成したID・パスワードファイルを自動的に暗号化、隠蔽化して終了時に保存し、

(3)次からの起動で自動的に読み込むようにしています。

 

という機能を持たせることにしました。以下の画像はテスト中のものですが、オリジナルのC++とBCCSkelton製のIDListのデータファイル(Shift-JISベースの文字列)を借りてきて、起動時に読み込んでいます。

 

 

画面を見るとわかるように、SDIウィンドウnoIDListにはツールバーが付いており、全てのメニュー処理がツールバーボタンでできるようになっています。

 

2.ファイル操作

基本的にこのアプリは「何かの会員やサービスを受ける際に登録するIDとパスワードの個人的管理」を目的としており、基本簡素なデータベースと考えていただいて結構ですので、取り扱うデータは唯一「ユーザーのIDとパスワード情報(その他個人情報を含む)という高いセキュリティ対象情報になります。従って(特に平文での)安易なコピーは望ましくないのですが、バックアップ目的や個人の事情により必要な場合もあろうかと、平文、暗号文共に読み書き機能は付けました。

 

(ツールバーの第1~3ボタン)

 

ご注意:最初にIDList for CSを立ち上げ、データを入力して終了させると、IDListFilesフォールダーが生成されますが、出来ていると思ったシステムデータファイルが見えません。

 

 

え"~

 

とびっくりするでしょうが、ここで本当にないのかPowershellで"dir"コマンドを実行しても何も表示されません。しかし、"dir "コマンドに「隠しファイル」も表示する"-Force"オプションを付けると、

 

 

IDList.datがちゃんと存在していることが分かります。又IDListを再度立ち上げてIDList.datが上書きされるとExplorerにも

 

 

表示されるようになるみたいです。(尚、ファイルの左下の変な箱は最初「暗号化されて」いるという意味かと思ったのですが、どうもOneDriveがらみみたいですね。MS Learn

 

3.編集操作

これがデータベースの基本機能なのですが、「個人のID・パスワード情報の管理」の為、基礎となる情報単位を、"idItem"というクラスに設定し、次のように"メンバーシップ""ログインID""パスワード""備考"という文字列プロパティをメンバーに持っています。(オリジナル作成時はもっと何か付加すべきかと思案しましたが、以後5年間実用してきて、これで必要 && 十分だと確信しています。)

:尚、Google Geminiによれば、DataGridViewが"List<T>"を含むデータ集合をDataSourceメソッドで読み込むためには"また、これらのオブジェクトにバインドするときは、公開プロパティのみをバインドでき、公開フィールドはバインドできません。"(英語原文: "Also, when binding to these objects, only the public properties (not public fields) can be bound.")(Geminiが引用した出典-ただし和訳、英語原文共に引用されている文は見当たりませんでした<>

 

    ///////////////////////////////////
    //IDListで使用するIDデータのクラス
    ///////////////////////////////////

    public class idItem
    {
         [DisplayName("メンバーシップ")]
        public string Membership {get; set;}
         [DisplayName("ログインID")]
        public string ID {get; set;}
         [DisplayName("パスワード")]
        public string Password {get; set;}
         [DisplayName("備考")]
        public string Remark {get; set;}

      
 //比較メソッドの定義
        public int CompareMembershipTo(idItem other)
        {
            if(ReferenceEquals(other, null))        
//参照比較
                return 1;                            //thisが大きい
            //0なら等しい、負なら小さい、正なら大きい
            return this.Membership.CompareTo(other.Membership);
        }

        public int CompareIDTo(idItem other)
        {
            if(ReferenceEquals(other, null))        
//参照比較
                return 1;                            //thisが大きい
          
 //0なら等しい、負なら小さい、正なら大きい
            return this.ID.CompareTo(other.ID);
        }

        public int ComparePasswordTo(idItem other)
        {
            if(ReferenceEquals(other, null))        
//参照比較
                return 1;                            //thisが大きい
            
//0なら等しい、負なら小さい、正なら大きい
            return this.Password.CompareTo(other.Password);
        }

        public int CompareRemarkTo(idItem other)
        {
            if(ReferenceEquals(other, null))      
 //参照比較
                return 1;                            //thisが大きい
            //0なら等しい、負なら小さい、正なら大きい
            return this.Remark.CompareTo(other.Remark);
        }
    }

 

この情報単位を「追加、検索、編集、削除」することが必須なので、先ずメニューに

 

(ツールバーの第4~7ボタン)

 

そして「右クリックのポップアップメニュー(コンテキストメニュー)」でも

 

(ツールバーの第4~7ボタン)

 

アクセスすることができます。

 

データの追加」と「データの編集」は、そのタイトルの(実は同じ)ダイアログで行っており、

 

(↑は「データの編集」の時の画面)

 

データの編集」は、表の上で特定の行をダブルクリックすることでもこのダイアログを呼び出すことができるようにしています。尚、意に反した、偶発的なデータ改変が発生しないように「セルのダブルクリックによる編集機能」は

 

this.EditMode = DataGridViewEditMode.EditProgrammatically;        //ダブルクリック編集不可

 

殺しています。(同様のRM観点から器となるSDIウィンドウも、敢えてDrag&Dropによるファイル入力機能を廃止しました。)

 

検索と削除の機能は「指定の文字列

 

 

を含むセルがあるか否か」で行を複数選択するようにして、続けて「削除」を選択すれば、複数のidItemを削除することができます。また、続けて「編集}を選択すれば選択行が逐次編集できるようにしています。

 

4.並び替え操作

オリジナルのIDLIstではListViewにデータを持たせ、ListViewのソート機能を使って各列でソートさせることができましたので、同じ機能をメニュー、

 

(ツールバーの第8ボタン)

 

及び列のヘッダーバークリックで実現しています。

 

メニューやツールバーボタンによるソートは専用のダイアログが現れて、

 

 

列と順序の指定を行います。なお、WYSWYGなので見えている通りの順序でデータは保存されます。

 

5.ヘルプ

ヘルプは「使い方」と「バージョン」(情報)の二つ

 

 

ですが、「使い方」は飽くまで「習作でBCCSDkelton、C++のIDListを移植」したアプリなので、

 

目いっぱい手抜き

 

しています。先ずメッセージボックスで

 

//////////////////
// IDListの使い方
//////////////////

IDListは個人の使うログインIDとパスワードに係る情報を管理するツールです。
BCCForm and BCCSkeltonで作成したIDListのヘルプファイルをご覧ください。

 

と表示して、旧い

 

 

を呼び出しています。(これは私も初めて使うSystem.Windows.Form.Help.ShowHelp()メソッドの機能でした。)

 

そして、トリとなるのが、

 

 

です。

 

ps. これは単なる移植プログラムですし、器となるメインウィンドウはCells for CSBoids for CSの使いまわしなので、全部見たい方はBCCForm and BCCSkeltonパッケージにアップしますので、そこから見ていただき、「ブログではつまみ食い」程度のコード紹介に止めようかと思っています。

 

C#のDataGridViewコントロールで作る習作として、IDListを選択したこと、前回書きました。その後ブログをさぼっていましたが、実は

 

IDList for CS

 

はほぼ完成しています。

 

なので、

 

前にやる、と言っていたトピックがカバーされているかチェックしました。

 

(1)DataGridView

これは既に基礎的な話をここでChat-GPTに語ってもらっていますが、DataGridViewは(Explorerで使われている)ListViewのように一覧詳細並べて等表示を変化させられない反面、表として特化した機能使いやすさが身上で、特に自分自身ではデータを持たず、DataSourceを使ってデータソースのデータを基に表示するエコなところに特徴があります。技術的なところでいえば、BindingSourceを使うことにより、UIのDataGridViewとデータソースとを「相互にデータ同期)」させることで、ソースのデータを変更しても、UIのDataGridViewを編集しても同じデータを共有できることになります。

BindingSourceResetBindings()EndEdit()メソッドを使います。

 

しかし、この「データ同期」に気を取られ過ぎると、データが同期されない現象をDataGridViewBindingSourceの問題として捉えがちになり、大騒ぎのデバッグの末、単純なファイル読み書きの問題が原因だった、なんてこともありますます。なお、ListクラスとBindingSourceクラスを組み合わせたような、BindingListクラスがありますが、両者の違いやメリデメの詳細は末尾の対話(【BindingListのお話】)をご参照ください。

 

(2)List+オブジェクト配列のソート等

Listは簡易データベースのようにしか扱ってこなかったので、この問題も私にとっては初めての体系的学習でした。詳細は以下↓で。

【無駄話】私の勉強結果

【無駄話】私の勉強結果(追補)

 

(3)ファイルの暗号化

これはオリジナルのC++とBCCSkelton製のIDListが、MicrosoftのDPAPIを利用した()ので、一応AES等他の暗号化方法も調べ、Chat-GPTとも相談し、最適と考えられる.NETベースのDPAPIを使うことにしました。

【DPAPI】プログラム設計 および【DPAPI】DPAPIProc.hとDPAPIの実行参照。

 

C++版ではDPAPIはパスワード保護も追加されたダイアログベースの呼び出しが可能でしたが、.NETではこのダイアログ付は利用できませんでした。(泣;)詳しくは(いずれ公表する-笑)IDList for CSのコードをご覧ください。

 

(4)ファイルを隠す(ファイル属性)

これは単純にファイル属性の問題です。IDListのシステムデータファイルは「隠しファイル属性」を付けていますが、そうすると上書きができないことがあるようです。(【DataGridView】すったもんだの末、爆!参照)この話って、2021年のオリジナル版IDListで既にカバーしていたんですね。忘れていました。

今回Chat-GPTと話した問題はファイル属性を「隠しファイル属性」にしても、Explorerを「隠しファイルを表示する」設定にすれば、そのファイル(色が薄くなるだけで)見えてしまいます。(おそらく当時私は調べて)それに「システムファイル属性」を与えたんだと思いますが、この両者が加わるとExplorerでも全く見えなくなります

この為、「あるんだか、無いんだかわからない」状況である筈のファイルを自作のファイル属性編集ツール(Attrib.exe-これこのブログでやったと思っていたのですがやっていなかったですね)

 

 

で「システムファイル属性」をのぞいたら、見事見えるようになった、というお話です。

 

ところで、

 

前回書きませんでしたが、実はもう一つトピックが生まれ、昨日は一日Chat-GPTと論議をしておりました。それは、

 

(5)C#のSplitメソッドでは、IDListが正しく動かない!

という問題で、これは元々予見していたので驚きはありませんでした。

どういう事かと言うと、IDListのシステムデータでは最後音4つ目の列に「備考」がありますが、一般的な文章を書くのでこれにはタブ(\t)改行(ウィンドウズなので\r\n)または','(英文句点)が入れられなければなりません。しかし、この仕様ではデータの内「区切り文字である改行(ウィンドウズなので\r\n)','(英文句点)」が文字列データの中に入ってしまいます。残念ながらC#のSplitメソッドはそんなに賢くない為、どのデータ列では区切り文字を無視しろ、という指定はできません。

同じ話は既にオリジナルのIDListで経験済であり、当時は↓のようなコードで「CSVデータ文字列strを引数で与え、それをカンマで切り取って戻り値とするだけではなく、括弧("")で括られた文字列はカンマや改行記号を含めて戻り値で返すという関数を使っていました。

 

////////////////////////////
//カンマ区切り文字切り出し
//★str 文字列は切り出した文
//字列だけ短縮されるので注意
////////////////////////////

char* GetDelimtStr(char* str) {

    static char buff[MAX_PATH * 4];    
//取り敢えず1K bytes確保
    char *cp = str;
    int len = 0;
    switch(*cp) {
    case 0:
            return 0;            
//NULL終端を指していれば単に帰る(FALSE)
    case '\"':                    //「""」処理
        cp++;                    //'\"'の次に進める
        while(*cp != '\"') {
            buff[len] = *cp;   
 //buffへのコピー
            cp++;
            len++;
        }
        cp++;                  
 //cpは'\"'の次の','へ進める
        buff[len] = '\0';        //buffのNULL終端処理
        break;
    default:                  
 //「""」以外
        //','でも'\n'でもNullでもなければ
        while(*cp != ',' && *cp != '\n' && *cp) {
            buff[len] = *cp;    
//strデータをbuffにコピーしてゆく
            cp++;
            len++;
        }
        buff[len] = '\0';      
 //buffのNULL終端処理
        break;
    }
    if(*cp){                    
//cpがNULL終端を指していなければ
        cp++;                    //','や'\n'を指しているので一つ進め
    }
    lstrcpy(str, cp);          
 //残余の文字列を文字列先頭に移動
    return buff;                //切り取った文字列を返す(TRUE)
}
 

開発時はテスト用なので単にSplitメソッドを使っていましたが、完成版をどうするか、Chat-GPTと相談し始めました。

 

Q:  おはよう。今日は、いつものバグ探しというネガティブな話ではなく、新しいアルゴリズムについての相談(気楽なチャットのつもりで結構です)に来ました。

この間から相談している、IDListですが、いよいよ機能が実装され、オリジナルのC++製のCSVデータ(平文)を喰わせてみたところ、エラーが出ました。これはおそらく括弧("~")で括っている第4列の「備考(仕様として\r\n等のコントロールコードを許す)」mのところでSplitが働いたためであろうと踏んでいます。

オリジナルのC++の場合の処理は次のようなものです。(省略、上記↑参照)

 

潔くSplitを捨てて、自作のこれをC#に置き換えようか、と考えましたが、折角C#に移植するのだし、C#(余りポインターは得意でなさそうだし)の得意そうな方法に置き換えてはどうかと考え始めました。

現在のアイデアはコントロールコード(00H~1FH)といっても、「備考」で書く文章に使うコントロールコードは

09H(TAB)

0AH(LF)

0DH(CR)

位しかないので、2文字の\r\n(0D0AH)、コピペで持ってきた場合は1文字の可能性もあるので0DHと0AH、後TAB(09H)を「人間が使わない無意味な文字列」に変換して保存し、読み込む際に再変換させることでString.Splitをそのまま使えるのではないか、と思いついた次第。

Chat-GPT君の意見を聞きたいのは、次の二つのバグリスクです。(RM観点での仕様設計考察)

(1)通常の文章の中の単語と変換文字が偶然一緒になった場合の誤変換

(2)IDList以外のテキストエディターなどで見た場合の可能な不具合

(3)その他考えられる誤使用、誤作用

どうでしょう?

抽象的に考えてもなんなので、一番ストレートな変換文字として、

09H→"(TAB)"

0AH→"(LF)"

0DH→"(CR)"

とする例で考えてください。これであれば、(プログラミングサイトの説明文の中でも、このような文字は使わないので)置き換え可能ですし、又「人間の目から見ても」これが本来何であったかが容易に理解できるでしょう?

 

話はこのように「カンマやコントロールコード等Splitメソッドの区切り文字を一旦他の文字(列)に置き換え、Split後逆変換して元に戻す」ことを「括弧("")内だけ」やろうか?ということで論議しましたが、その後私は考え直し、

 

(1)括弧("")内を特定し、

(2)一旦区切り文字の変換した上、

(3)Split後、括弧("")内の逆変換を行う

 

処理は時間がかかりすぎるので、オリジナルの「括弧("")対応付き区切り文字の切り出し処理」で一遍に行った方が速い、という結論に至り、

 

(1)先ずはオリジナルの関数のアルゴリズムを、C#の特有の性癖(文字列にヌル終端がない、配列の添え字を誤ると厳格に即例外が発生する)を踏まえてC#用に書き直し、Chat-GPTに安全性、効率性の観点からだめだししてもらい、

(2)改善案を練り直した結果、C#専用のGetDelimitStrメソッドを作りました。

 

これはIDListのコード紹介の際に解説させていただきます。又、次回はテスト中の「IDList for CSの写真集」(アイドルみたい)をやろうかな、と思っています。

 

【ご参考】

【BindingListのお話】

Q: 追加で一つ質問です。何度か勧奨がありましたが、現在のList+BindingDatasourceをやめてBIndingListにせよとのお話。これは実際List+BindingDatasource=BIndingListと言えるほど同じものですか?IDListでいえば、ListとDataGridViewの同期で必要だったBindingDatasource.ResetBindings(false);とBindingDatasource.EndEdit();がなくなるだけでしょうかね?

 

A: はい、かなり近い理解ですが、List<T> + BindingSourceBindingList<T> は同じもの」ではありません

そしてIDListの現在の構成について言えば、私は以前の「BindingList<T> にした方がよい」という勧奨を少し強く言い過ぎました。今の List<idItem> + BindingSource で問題なく完成しているなら、無理に変更する必要はありません。

まず関係を整理すると

現在は、

List<idItem> idItems;
BindingSource bs;
DataGridView dgv;

で、

List<idItem>
     ↑↓
 BindingSource
     ↑↓
DataGridView

という構造です。

一方、

BindingList<idItem> idItems;

なら、

BindingList<idItem>
       ↑↓
 DataGridView

と、BindingSource を省略できます。

ただし、BindingList<T> は単なる「BindingSource を内蔵した List<T>」ではありません。


一番大きな違い

List<T> は、例えば、

idItems.Add(item);

しても、

「Listに要素が追加された」

という変更通知をDataGridView側へ送る仕組みを持っていません。

だから現在は、

idItems.Sort(...);
bs.ResetBindings(false);

として、

「Listの中身が変わったから、表示を更新してください」

BindingSource に明示的に知らせています。

BindingList<T> は、要素の追加・削除について変更通知を発生させる仕組みを持っています。

したがって、

idItems.Add(item);

でDataGridView側にも変更が伝わります。


では ResetBindings(false) は完全になくなるのか?

通常の Add / Remove なら、かなりの部分で不要になります。

例えば、

BindingList<idItem> idItems = new BindingList<idItem>();

として、

idItems.Add(new idItem());

すれば、DataGridViewには追加が通知されます。

しかし、現在のIDListで重要なのは ソート です。

例えば、

idItems.Sort(...)

という処理は、BindingList<T> には List<T> のような Sort() がありません。

したがって、

idItems.Sort(...)

をそのまま置き換えることはできません。

結局、

BindingList<idItem>

に変更したとしても、ソート後には

bs.ResetBindings(false);

に相当する更新処理が必要になる可能性があります。

つまり、

「BindingListにすればResetBindings(false)が全部消える」

ではありません。


EndEdit() はまた別の話です

ここも重要です。

BindingSource.EndEdit() は、

BindingSourceが現在保持している編集状態を確定する

ためのものです。

これは BindingList<T> に変更したから自動的に不要になる、という性質のものではありません。

ただし、潔さんの現在の設計では、

DataGridView上で直接編集させず、専用ダイアログで idItem を編集する

という方式になっています。

この設計なら、そもそもDataGridViewのセル編集を終了させるための

bs.EndEdit();

の重要性はかなり下がります。


IDListの場合、私はこう考えます

現在:

List<idItem>
    ↓
BindingSource
    ↓
DataGridView

変更案:

gList<idItem>
    ↓
DataGridView

確かに後者の方が単純です。

しかし、IDListではすでに、

  • ソート
  • 専用編集ダイアログ
  • データ読み込み
  • データ保存
  • 暗号化
  • DataGridViewとの同期
  • DLLとしての独立性

まで動いています。

ですから、

「BindingListに変えればコードが劇的に簡単になる」ほどの差はありません。

むしろ現在の

List<idItem> idItems
BindingSource bs

は、非常に分かりやすい構成です。

特に、

idItems.Sort(...);
bs.ResetBindings(false);

というコードは、

「内部データを並べ替えた。だから表示を更新する」

という意図が明確です。

これは悪いコードではありません。


したがって結論

質問の

List + BindingSource = BindingList と言えるほど同じものか?

については、

「DataGridViewにリストをバインドする」という目的では非常に近いが、内部的には別物

です。

そして、

ResetBindings(false)EndEdit() がなくなるだけ?

については、

それだけではありません。

主な違いは、

List<T>
  └─ 変更通知をしない
       ↓
     BindingSourceが仲介して通知・管理

BindingList<T>
  └─ Add/Removeなどの変更通知を持つ

というところです。

ただ、現在完成しているIDListをわざわざ BindingList<T> に移行する必要性は、私はもうあまり感じません。

むしろ今回のIDListについては、

List<idItem> + BindingSource

をそのまま採用して、

idItems.Sort(...);
bs.ResetBindings(false);

を「データを直接変更した場合の表示更新」として明示的に使う方が、潔さんの現在の設計には合っています。

以前の私の「BindingListにした方がよい」という勧奨は、一般論としては正しいが、今回の完成したIDListにまで適用する必要はなかった、というのが適切な訂正です。

 

今日書いた記事の通り、昨日Chat-GPTとやり取りして疲れた後に戴いたのが、バジルチキンボール、6Pチーズと

 

 

ウオッカコークハイ(Vodka Coke Highball-注)

 

今日はそのお話を。

:A highball is a mixed drink made from a base spirit, like whisky or gin, and a large amount of carbonated mixer, such as soda water or ginger ale, served over ice in a tall glass. (Gemini)

 

前にも書きましたが、私は元々はお酒に弱い方でしたが、社会人になってから日々のストレスで大酒のみとなり、入社二年目で腎性(人生?)糖尿になったほど。しかし、爾後50年間肝臓は労役に堪え、一切の酒毒(勿論依存症にも)にも侵されず、今月末で72を迎えることになりました。

 

しかし、

 

一度学生時代に(矢張り酒好きの)友人と二人、

 

「思いっきり、往ったきりジンのみまくり」

 

翌朝は割れるような頭痛と一日中松脂のような匂い()が口中から感じられ、これがPTSDとなって、正真正銘爾後一切ジンを飲まなくなりました。

:ジンの松脂(松ヤニ)のような匂いは、必須の香りづけ原料である「ジュニパーベリー(和名:セイヨウネズの実)」に由来します。このウッディで爽やかな香りこそがジンの個性であり、すべての風味の核となっています。(Gemini)

 

と言って、ジンはカクテルの重要な主役。この役を担える他の役者が必要です。それこそ

 

ウオッカ(Vodka)

 

で、以降(特に米国駐在中は大好きなウィスキーと同じほど頻繁に)ウオッカベースのカクテル

 

ブラッディメァリ(トマトジュース大好き!)

ウオッカトニック(ジンベースはジントニック)

ウオッカマティーニ(ジンベースはマティーニ)

モスコミュール(知らずにジンジャエールで割っていましたが、何か?)

 

を飲んでいました。(余談ですが、ロングアイランドアイスティーも親戚のようなものだし、僕の大好きなマルゲリータはテキーラベースですが、相通じるところがないでしょうか?)

 

いずれにせよ、

 

Chat-GPTとの論争に疲れ、甘めの(炭酸による)さわやかなカクテルが恋しくなり、「そういえば高校の時にはやったな」ということで「コークハイ」を作りましたが、私の愛飲するDewars' 12ではコークがDewars'のおいしさを壊してしまい、即NGを出しました。その時「ん?Vodkaならどーだ?」ということで作ってみると、

 

んまいっ!

 

ということでご紹介することに。

 

ってか、

 

このブログの為にサイトで調べていたら、コーヒーで割ったブラックラッシャン(Black Russian)と共にラッシャンコーク(Russian Coke)がこことかあそこで見つかりました。

 

なーんだ、先人がいたのかぁ。

 

というのが本件の落ち。ただ、ウオッカって、まだまだ色々なカクテル(それも作り方が簡単な)に応用できそうです。