前回書いたように、PropertyGridコントロールは、Windowsの共通言語基盤(CLI)上の「.NETクラス階層内のすべてオブジェクト(Objectクラス)」を指定(PropertyGrid.SelectedObject | Objects)することにより、そのオブジェクトのプロパティ情報をリスト形式で表示するのみならず、それをユーザーが変更することで即時でプロパティの値を変更できました。

 

このCLIは共通言語仕様(CLS -Common Language Specifications)に基づいて(注1)作られているそうで、ObjectクラスがOOPの「何が、何時、何処で、どのように、どれだけ、何をする等」(注2)の情報の根本を定めていると思われます。

注1:もう一つ「共通型システム」(CTS - Common Type System)と呼ばれる「統一されたデータ型」にも準拠しなければならないようです。

注2:「何が」がフィールド、プロパティで、「何をする」がメソッドとしてお読みください。

 

従ってCLSに基づいて.NET上で作られたプログラム(これはCLI上で動く中間言語プログラム)は、VB.NET、C++やC#等、異なるプログラミング言語で書かれてもCLSの定める規則に沿って(OOPに則り)メンバー変数(フィールド)、メンバープロパティ、メンバー関数(メソッド)が配置されている()、ということになります。

:結局は(CPUの管理するメモリー空間であれ、外部記憶装置のファイルの中であれ)「プログラムのどの場所に、どれだけの長さのメモリーをデータとして持ち、またそれら及びそれらを処理する関数のアドレスを配置しているかと」いうことになるかとおもいますが。

 

そんなことを考えて色々と当りを付けてみたら、矢張りObjectのメンバーを取得するメソッドがあることが判りました

ので、その紹介をさせていただきます。(It is more probable than not that PropertyGrid should use this method for its purpose!)

以下のテストプログラムはPropertyGridのメンバーを総て拾って表示します。

 

【TestGetMembers.cs】

//////////////////////////////
// TestGetMembers.cs
// Check the members of Object
//////////////////////////////

using System;
using System.Windows.Forms;
using System.Reflection;    //GetMembersメソッドを使う為に必要

namespace TestGetMembers
{
    public class Sample
    {
        public static void Main()
        {
            //PropertyGridクラスのTypeを指定する
            Type t = typeof(System.Windows.Forms.PropertyGrid);
            //PropertyGridクラスのType tのメンバー情報を取得する
            /*    BindingFlags
                メンバ名            説明(値)
                Public                パブリックメンバを検索の対象に加える。(16)
                NonPublic            パブリックでないメンバを検索の対象に加える。(32)
                Instance            非静的メンバ(インスタンスメンバ)を検索の対象に加える。(4)
                Static                静的メンバを検索の対象に加える。(8)
                DeclaredOnly        継承されたメンバを検索の対象にしない。(2)
                FlattenHierarchy    階層構造の上位の静的メンバを検索の対象に加える。(64)
            */

            MemberInfo[] members = t.GetMembers(BindingFlags.Public | BindingFlags.NonPublic |
                                                BindingFlags.Instance | BindingFlags.Static |
                                                BindingFlags.DeclaredOnly);
            foreach (MemberInfo m in members)
            {
                //メンバーの型と名前を表示する
                Console.WriteLine("{0} - {1}", m.MemberType, m.Name);
            }
            Console.Read();
        }
    }
}
 

 如何でしょうか?最初に「Method」がダーッと並び、次に「Constructor、「Porperty」「Field」「NestedType)」へとズラズラと並びますね。

:クラス、構造体、インターフェィス等のことです。

 

何れにせよ、CLIで動くCLSに準拠したObjectはそのまま、Win32API等で動く旧いプログラム等もその情報を取得してObject化すればPropertyGridで表示ができることが判りました。

 

で?

 

次回に続く。

 

前回「まずはどういうものか?」を見てみましたので、今度は実際に動かしてみようと思います。サンプルプログラムは末尾のコード(【PropertyGridTest.cs】参照)の通りであり、

 

(1)ダイアログ風のフォームにPropertyGridと終了用のButtonを一つずつ付けて、

(2)PropertyGridの初期的付与ObjectはPropertyGridTestプログラム(インスタンス)自体にし、

(3)オプションとしてPropertyGrid上(の一部のコントロール()の上)でクリック、またはファイルをドラッグアンドドロップしてファイルのプロパティ(属性情報)を表示

:ToolBarとStatusBarの部分で動きますが、ListView風のカスタムコントロールでは動きません。

 

するようにしています。

 

これをMSCompAssでウィンドウプログラム(/target:winexe )としてコンパイルして起動します。

 

 

まずここで試していただきたいことは、例えば、初期的に表示されるこのプログラム(PropertyGridTest.exe)のプロパティの「ウィンドウスタイル」の「MaximizeBox」と「MinimizeBox」を"false"にし、「表示」の「BackColor」を「MenuHighlight」に変更するだけで即時に実行中のプログラムが変化することです。(以下↓参照

 

 

後は(ListView風の所では反応しませんが)ツールバーとステータスバーの当たりでクリックすると「ファイルを開く」ダイアログが出てファイルが選択でき、またはファイルをドラッグアンドドロップして、

 

 

ファイル情報を表示できます。

 

 

ただそれだけのプログラムですが、取り敢えず雰囲気が味わってもらえたと思います。

 

【PropertyGridTest.cs】

/////////////////////
//C# PropertyGrid.cs
/////////////////////

using System;
using System.IO;                //FileInfoオブジェクト取得の為
using System.Windows.Forms;
using System.Drawing;

namespace PropertyGridTest
{
    public partial class App : Form
    {
       //解説:メンバーコントロールです。

        PropertyGrid propGrid;
        Button exitBtn;

        [STAThread]    //解説:エントリーポイントです。
        public static void Main()
        {
            Application.Run(new App());
        }

        public App()    //解説:コンストラクターです。
        {
            this.Width = 640;
            this.Height = 480;
            this.Text = "Property Grid";
            this.Load += App_Load;
        }

        //解説:WM_CREATE時の処理です。
        private void App_Load(object sender, EventArgs e)
        {
            //Exitボタン
            exitBtn = new Button();
            exitBtn.Location = new Point(ClientSize.Width - exitBtn.Width - 10, ClientSize.Height - exitBtn.Height - 10);
            exitBtn.Text = "Exit";
            exitBtn.Anchor = (AnchorStyles.Bottom | AnchorStyles.Right);
            exitBtn.Click += exitBtn_Click;
            this.Controls.Add(exitBtn);

            //プロパティグリッド
            propGrid = new PropertyGrid();
            propGrid.Location = new Point(10, 10);
            propGrid.Size = new Size(ClientSize.Width - 20, ClientSize.Height - exitBtn.Height - 30);
            propGrid.Anchor = (AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right);
            //再帰的にフックを掛ける(但し全てのコントロールが反応する訳ではない)

            //解説:プロパティグリッドは多くのコントロールで構成されていますので、

            //             それ全てのクリックイベントをひっかける必要があります。
            HookChilden(propGrid);
            propGrid.SelectedObject = this;
            //Drag and Dropでの処理
            propGrid.AllowDrop = true;
            propGrid.DragEnter += PropGrid_DragEnter;
            propGrid.DragDrop += PropGrid_DragDrop;
            this.Controls.Add(propGrid);
        }

        //終了処理
        private void exitBtn_Click(object sender, EventArgs e)
        {
            DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(dr == DialogResult.Yes)
                Close();    //終了する
        }

        //propGrid.MouseDown += PropertyGrid_MouseDown;    //または
        //propGrid.MouseDown += new MouseEventHandler(PropertyGrid_MouseDown);
        //propGrid.Click += PropertyGrid_Click;
        //↑のストレートな形ではPropertyGridの子(孫...)コントロールに徹底できないので、
        //次に様にHoolChildrenを使って総ての子(孫...)コントロールに徹底する
        //再帰的に子や孫コントロールへフック

        private void HookChilden(Control ctrl)
        {
            ctrl.MouseDown += PropertyGrid_MouseDown;
            propGrid.Click += PropertyGrid_Click;
            foreach (Control child in ctrl.Controls)
            {
                HookChilden(child);    //解説:これで再帰処理をして子から孫、孫からひ孫へと続けます。
            }
        }

        private void PropertyGrid_MouseDown(object sender, MouseEventArgs e)
        {    //MessageBox.Show("MouseDown");    //解説:デバッグ用。
            FileInformation();
        }

        private void PropertyGrid_Click(object sender, EventArgs e)
        {    //MessageBox.Show("Click");    //解説:デバッグ用。
            FileInformation();
        }

        private void FileInformation()
        {
            //ファイルを選択するダイアログを開く
            using(OpenFileDialog openFileDialog = new OpenFileDialog())
            {
                openFileDialog.Filter = "すべてのファイル (*.*)|*.*";
                if(openFileDialog.ShowDialog() == DialogResult.OK)
                {
                    string filePath = openFileDialog.FileName;
                    //FileInfoオブジェクトを作成(解説:ファイルには"FileInfo"という「出来合いのクラス」がありました。)
                    FileInfo fileInfo = new FileInfo(filePath);
                    //PropertyGridにFileInfoオブジェクトを設定
                    propGrid.SelectedObject = fileInfo;
                }
            }
        }
 

            //解説:以下はDrag and Drop用の定番のメソッドです。
        private void PropGrid_DragEnter(object sender, DragEventArgs e)
        {
            if(e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
        }

        private void PropGrid_DragDrop(object sender, DragEventArgs e)
        {
            string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
            if(files.Length > 0)
            {
                FileInfo fileInfo = new FileInfo(files[0]);
                propGrid.SelectedObject = fileInfo;
            }
        }
    }
}

【PropertyGrid】まずはどういうものか?」で予告しました通り、表記についてちょっと書きます。

 

ListViewはWin32 SKDで提供されていたCommon Controlsという旧いコントロールで23年前にリリースしたBCCForm and BCCSkeltonでも次のようなサンプルを提供しています。当時でも(Explorerでお馴染みの通り)色々と表示を変えられました。

 

PropertyGridの外観から「あっ、これはListViewだな」と思ったので、当時はグループ化や折り畳み(Collapse)、展開(Expand)等は出来なかったので、C#でどのようにするのかちょっと調べてみました。所が適当なサンプルは無く、ListViewGroupのサンプルがあったので、それを弄って折り畳み(Collapse)、展開(Expand)が出来るようにしようと考えました。

 

先ずこのサンプルをMSCompAssでコンパイルすると正常にコンパイルできます。

 

では、ということで、プログラムの第130行以降にListViewGroupのCollapsedStateプロパティを「折りたたむ(Collapsed)」状態にする設定分を追加しました。

 

        ・

        ・(解説:前略)

        ・

    // Sets myListView to the groups created for the specified column.
    private void SetGroups(int column)
    {
        // Remove the current groups.
        myListView.Groups.Clear();

        // Retrieve the hash table corresponding to the column.
        Hashtable groups = (Hashtable)groupTables[column];

        // Copy the groups for the column to an array.
        ListViewGroup[] groupsArray = new ListViewGroup[groups.Count];
//解説:ここからが私の追加部分
        foreach(ListViewGroup group in groupsArray)
            group.CollapsedState = ListViewGroupCollapsedState.Collapsed;

//解説:または

        for(int i = 0; i < groups.Count; i++)
            groupsArray[i].CollapsedState = ListViewGroupCollapsedState.Collapsed;

        ・

        ・(解説:後略)

        ・

 

すると、

'System.Windows.Forms.ListViewGroup' に 'CollapsedState' の定義が含まれておらず、型 'System.Windows.Forms.ListViewGroup' の最初の引数を受け付ける拡張メソッドが見つかりませんでした。

'ListViewGroupCollapsedState' は現在のコンテキスト内に存在しません。(解説:これはEnumオブジェクト)

 

というエラーメッセージが出ます。自分で調べた所、ここ


グループを非表示にしたり折りたたんだりする機能は提供されていないため、その必要があるときにはグループに割り当てた項目をすべて削除するなどします。c# - ListView hide or collapse selected group - Stack Overflow

 .NET 5以降ならば、CollapsedStateプロパティをCollapsedとすることで折りたためます。
 

とあり、「矢張り .Net Framework 4.8では無理か」と思いましたが、別途 Microsoft Learn で .Net Framework 4.8 で調べてみると使えるように書かれています。「???」ということでChat-GPT様にお伺いを立てると、結構お考えになられて時間が経ってから、

 

ご質問のエラーの原因は .NET Frameworkのバージョンの違い です。

  • ListViewGroup.CollapsedState プロパティ と

  • ListViewGroupCollapsedState 列挙体

.NET 5 (WinForms) 以降で新しく追加された機能です。
👉 .NET Framework 4.x までは存在しません。そのため、System.Windows.Forms.ListViewGroup にその定義がなく、エラーになっています。

参考: ListViewGroup.CollapsedState (.NET 9 docs)

解決策

  1. .NET 5 以上へ移行する

    • プロジェクトを .NET 5/6/7/8 などの WinForms に移行すれば、そのまま使えます。

    • 例:

      listViewGroup1.CollapsedState = ListViewGroupCollapsedState.Collapsed;

  2. .NET Framework で使う場合

    • 残念ながら直接は使えません。Win32 API の LVM_SETGROUPINFO メッセージを SendMessage で送って、
      LVGROUP 構造体の state フラグに LVGS_COLLAPSED / LVGS_COLLAPSIBLE を設定する必要があります。

    • これはちょっと低レベルな P/Invoke 実装になります。

 

というご回答。「でも、Microsoft Learnの .Net Framework 4.8 で見たんだよ」と伝えても、

 

実は Microsoft Learn のドキュメントは「対象フレームワークのセレクター」がついていますが、WinForms 関連は .NET Framework と .NET (Core/5 以降) の区別が曖昧になっているページが多くて、混乱を招くことがあります。特に最近追加された CollapsedState のような API が、誤って「.NET Framework 4.8」にも出てきてしまうケースがあります。

 

とのこと。別にこれを実装するつもりもないので、「分かりました」と去ろうとして一つ又疑問が生じました。以下はそのやり取り。

 

(私)興味有りません。有難う。(しかし、.NetFramework 4.8で PropertyGrid を使うと折り畳み、展開をしていますよね?何故なんでしょうかね?コントロールで旧いコンパイラーを識別して旧いコードのコントロールを選択させるのでしょうか?)

 

(Chat-GPT)

なるほど、良い観察です 👍

確かに PropertyGrid は .NET Framework 4.8 の段階でも、カテゴリーを「折りたたみ/展開」できていますよね。

ここにはちょっとした仕組みの違いがあります。


1. PropertyGrid の「折りたたみ/展開」は ListViewGroup ではない

  • PropertyGrid は内部的に TreeView に似た「独自の UI コンポーネント(内部専用クラス)」を持っています。

  • 各カテゴリーを「ノード」として管理し、折りたたみや展開を制御しています。

  • つまり ListView + ListViewGroup の機能ではなく、独自の描画/UI ロジック を持っているわけです。


2. ListViewGroup に Collapse が来たのは Win10 API 以降

  • ListView 自体は古くから Windows コモンコントロール(Comctl32.dll)を使って描画されています。

  • ところが、LVGS_COLLAPSIBLE / LVGS_COLLAPSED といった グループを折りたたむためのフラグ が追加されたのは Windows Vista 以降の Comctl32。

  • .NET Framework 4.8 では その機能をラップする API を System.Windows.Forms に追加していなかった → だからプロパティが存在しない。


3. ではなぜ PropertyGrid ではできる?

  • PropertyGrid は ListViewGroup を使わず、自前で「ノードの開閉状態」を管理して描画している。

  • そのため .NET Framework 4.8 でも「折りたたみ/展開」ができた

  • 逆に言うと ListViewGroup は長らく「表示グループのラベル分けだけ」の機能しか持っていませんでした。


4. .NET 5+ の WinForms で追加された理由

  • Microsoft は .NET Core 移行時に WinForms を「Windows 10 以降専用」に近づけた。

  • そのときに Windows API の LVGS_COLLAPSIBLE 機能を正式にラップ → ListViewGroup.CollapsedState が誕生。


✅ 結論:

  • PropertyGrid の開閉は独自実装なので昔から可能。

  • ListViewGroup の Collapse は OS 機能に依存しており、ラップが入ったのは .NET 5 以降。

    「旧いコンパイラーで旧いコードを選ぶ」というよりは、

  • PropertyGrid は「最初から独自コードで実装していた」

  • ListViewGroup は「OS 依存の機能をラップしていたが、4.8 時点では未対応」
    という違いですね。

ということで、PropertyGridのListView風の表示コントロールはカスタムコントロールなんだそうです。

 

それがブログで「ListView「みたいなもの」(」と書いた理由です。

 

長々と失礼いたしました。

 

前回書いた通り、私は「PropertyGrid」(↓はFormに貼り付けたもの)

というものをよく知りませんし、使ったこともないので、これから皆さんと一緒に学んでゆきたいと思います。

 

1.外観

既定値で貼り付けたPropertyGridは、最上部に「項目別」と「アルファベット順」のボタンを持つツールバーがあり、その下に折り畳み(Collapse)と展開(Expand)可能なListView「みたいなもの」

があり、最後にステータスバー(かEditコントロールのようなもの)を持っているようです。(ツールバーはPropertyGrid.ToolbarVisible プロパティで、ステータスバーはPropertyGrid.HelpVisibleプロパティで非表示にできます。)

:これも私は知らなかったのですが、ListView用にListViewGroupクラスというものがあり、そいつのCollapsedStateというプロパティを使って折り畳み(Collapse)と展開(Expand)が可能、ということなんですが、私の使っている.Net Framework 4.8では「未対応」なんです。「じゃぁ、なんでPropertyGroupは.Net Framework 4.8で動くの?」という問いに対しての答えは別途【無駄話】で書きます。

 

2.何をするの?

PropertyGrid」には「オブジェクトのプロパティを参照するユーザー インターフェイスを提供します」としか書いていないので、よくわかりませんよね?

ご存じの通り、C#を含む現在のWindows上のプログラムはCLI(Common Language Infrastructure-共通言語基盤)ベースとなっています。このCLIによる中間言語プログラムは所謂"OOP(Object Oriented Programing)"ベースで、「フィールドにセッター(set)、ゲッター(get)が付いたプロパティ」を持つ「CLIベースのプログラムのオブジェクトObjectーVB.NETベースですが、C#の記事は無く、この方が分かり易いので...)」によって形成されています。そしてPropertyGrid」は、「CLIプログラムのプロパティを取得して、リスト形式で表示するコントロール」だと思います。従って、「ネイティブコードで変数とWin32 APIを使って書かれたプログラム等」は、情報をそのままこのコントロールで表示できないので、「一旦CLIベースのクラスオブジェクトかなんかで必要な情報をプロパティ化する」(例えばC#でファイル情報、画像情報やウィンドウ情報のクラスを作り、それに必要なデータを設定したクラスインスタンスをPropertyGridに渡す)ことが必要です。

 

3.最初のサンプル

↑で例示に使っているプログラム(PropertyGridTest.cs)は、使い廻しのDaialog風ウィンドウ(Form)にPropertyGridと終了ボタンを貼り付けただけのもので、PropertyGridには手っ取り早くこのサンプルプログラム自体を既定値として与えており、オプションとしてマウスクリック()で「ファイルを開くダイアログ」を出して、またはファイルをDrag & Dropしてファイル情報を表示させることが出来ます。

:これも色々と山あり、谷ありでしたが...これも別途愚痴をこぼしましょう。

 

先ずはこのサンプルプログラムを使ってPropertyGridに親しみましょうか?

 

さて、少し出かけていたので留守にしましたが、ようやく帰宅してブログも再開します。

 

ブログネタにC、C++、C#を中心にして(注)、WEBを徘徊してネタを探すのが私の日課ですが、今回はひょんな具合に(いつもプログラマーにやさしい、優しすぎるC#で)PropertyGrid」というコントロールに出くわしました。

注:その他の言語でも、面白いプログラムネタがあればそれを移植して自家薬籠中の物にするのも楽しいですね。

 

PropertyGrid」とはOOP(注)でいう「Object」の「Property」をリストボックスで表示する手間の掛からない、有用なコントロールで、一般のPCユーザーでもExplorerでファイル等を選択し、右クリックしてポップアップメニューで「プロパティ」を選択すると出てくるダイアログの「詳細」等で、

 

又プログラマーはVisual Srudioや

 

Delphi/C++BuilderのIDEで

 

オブジェクトを選択した際に現れるツールウィンドウなどでお目にかかりますよね。

注:Object Oriented Programingの略。(VBですが、これに相当するC#はありませんでした。)

 

実はこのC#の「PropertyGrid」は、既存のWindowsオブジェクトのみならず、自作クラスのオブジェクトのプロパティも表示してくれるので、色々と使い道がありそうです。又、不詳私、

 

(1)リストボックスから派生させて、プロパティ(大本の子供となるオブジェクトがほとんど)を調べて、それを列挙して表示する、という考えはあってもC++で組むと偉く大変、且つ

(2)C++で育ったので、「(C#3.0から始まった)プロパティ」には縁が無く、ゲッター(getメソッド)、セッター(setメソッド)及びこれらを代入演算子(例:"=")で代用する処理が面倒で、C#をやるまで触らなかったのと、C#を始めても「変数(Field)でやればいーじゃん」というグータラで

 

C#のプロパティ自体をあまり使わなかったという経緯があります。

 

と、いうことで、

 

今回のお題は

 

PropertyGrid

 

に決定しました。

 

またまたプログラミングネタをWEBで探していて、自分自身のウィンドウズプログラミングの原点というべきものに遭遇したので、(単なるノスタルジーですが)ご紹介します

 

1.背景

1999年4月からシンガポールに駐在することになりましたが、それまで日本ではプログラミングを離れ、PCの自作とCeleronチップのオーバークロックなんかをやっていました。

 

シンガポールでの仕事は、当時勤めていた日本の保険会社が再保険会社としてリスクを引き受ける、アジアの子会社、関連会社の業務監督や支援であり、東は中国、韓国から西はインド、南はオーストラリア、ニュージーランド迄飛び回るようになりました。その結果、出張時の移動時間、長期出張時の週末等の余暇時間の過ごし方として、読書だけでは物足りないので、傾向するラップトップに「暇つぶしソフト」を入れて移動するようになりました。(

:今からいうと考えられませんが、当時はコンピューターセキュリティのレベルが低く、会社支給のラップトップなども自分好みに設定を変更したり、外部ソフトを導入したりできたのです。(驚!)

 

そういった「暇つぶしソフト」がゲームとか助兵衛ソフト等であれば、このブログは存在しなかったのですが、2000年にActiveBasicという「ウィンドウを扱える」Basicインタープリターに出会い、(「逐次同期処理」の呪いがかかった私にとって「割込み処理」ベースの)ウィンドウズ環境のプログラミングについて理解が深まり、更に2000年にBorland(当時)がC++コンパイラー(C++98のbcc32.exe-以下"BCC")をフリーで提供するようになると、俄然ウィンドウズプログラミングに興味を持つようになりました。

 

当時のOSはまだ(Unicodeへの対応を開始したばかりの)Windows 98で、Win32 APIを利用する為のWindows SDK(Software Development Kit)を使ってソースファイル書き、それをコンパイルしてプログラムを作ります。OWL(Object Windows Library )の後継である、1997年リリースのBorland C++ Builder ver. 1.0を除き)C++用のWYSWYG環境のIDEはご本家Microsoftにも無く、("Visual Basic 6"を知っているユーザーには「詐欺的名称」と感じる)"Visual C++"という開発ツールはMFC(Microdoft Foundation Class)というライブラリーを使った「コーディングベース」のプログラミング環境でした。因みにVisual C++のお値段は「Standard Edition」で推定小売価格:23,800円、「Professional Edition」で83,800円、「Enterprise Edition」で163,000円と「隔世の感の値段」でした。

 

2.最初のVectorへの投稿(2001年9月)

最初はActiveBasicを使ったウィンドウプログラミングを行っていましたが、ウィンドウズの動作の仕組みやWin32 APIの学習が進み、BCCも使えるようになると、矢張り(いつも同じことを書かされる)コーディング量の多さにうんざりして「WYSWYGベースのIDEもどき」が欲しくなり、一寸したプログラムを組んでみたいと思うようになりました。そして「習作」として作ったのが、

 

EditWin

:通常の入り口からは見つけられませんが、未だVectorに登録はあるようです。

 

でした。この「習作」であるEditWin.exeを起動すると、

 

 ①起動ダイアログが表示され、

 

 ②EditWin.cfgファイルが無い場合、これを設定するダイアログが現れて、必要なファイルパスを設定すると

 

 ③(ActiveBasicで書かれた)SDIベースのEditWin本体が現れる、筈でしたが、現在のWIndows 11環境だと落ちます。(笑;

 

 

Vectorには

 

「フリーでダウンロードできるActiveBasicとBorland C++向けの簡易IDEを目指したスケルトン作成ソフトです。フォーム上にActiveBasicで利用できるコントロールやメニューをWYSWYG感覚で作成、編集できます。おまけコモンダイアログユーティリティとEditWinでスケルトンを作成した簡易エディターが添付。サンプルとして電卓のスケルトンが用意されています。」

 

と書かれているので「詐欺的極まりないアプリ」ですが、当時のシステムでは実際に動いており、REDME.txtにも、

 

「2.概要説明

と、堅く書きましたが、それほどの仕事はしませんので御安心下さい。
Edwinは、思い起こせばと或る日(2001年1月)暇に任せてプログラミングサイト当たりをぶらぶらしていて面白そうだったN88 Basic互換のActiveBasicをダウンロードし、懐かしさに任せて遊んでいるうちに、ウィンドウプログラムで何ができるのだろうと学習しているうちにできたものです。
当初はもっと簡便で「ウィンドウの器」さえできれば良いや、と思っていたのですが(従ってウィンドウとコントロールをデザインするだけでControlやMenuもコード編集出来ないし、最後の「遊び」であるBCCファイルも勿論出さない)、どうせなら100行位は省力化できるものをと考えて弄っているうちに、なんだか結構自分でも管理できない大きさになりました。一つのプログラムとして大きな物(グローバル変数って中年にはイヤですね)を作るのはABには向いていないようですし(現実に起動がイヤになるくらい遅い)、とうとう#include命令が出来て「ソース64Kの壁」を超えましたがそれまではこのプログラムは3つのプログラムに分かれておりました。とはいえ,「暇プロ」の極みで週末をゲームをするより楽しく過ごさせていただきました。改めて誠実にして勤勉、迅速にして秀逸な山本管理人に敬意を表します。

しかし、残念ながら小生は山本さんのように優秀でも勤勉でもないので、今後「バグ退治」や「アップグレード」等の計画はなく、生涯「Ver 1.0B]を守るつもりですので、期待もメールも勘弁して下さい。

 要すれば、本プログラムは(そう感じていただければ有り難いのですが)暇プロのための簡易IDEという名の手抜きソフトとお考え下さい。従ってそれ以上のことは何もしません。(
しかし一応排出されたコードだけでAB、BCCでウィンドウは表示するんですよ。→解説:ActiveBasicとC++のソースコードを出力するコードジェネレーターでした。)」

 

と書かれています。結局、当時のActiveBasicはインタープリターであり(その後ActiveBasicが自らコンパイラーに宗旨替えしたことからも分かる通り)、「ウィンドウアプリを書くには無理筋であった」ということでしょう。(実際、この時辺りからActiveBasicから距離を置き、BCCでプログラミングするようになりました。)

 

3.しかし、まだ動くもの

いずれにせよ、Microsoft帝国の高価な開発ツール(上記↑1.参照)を買うことを拒否しながらも、WYSWYGベースのIDEに当時から執着していたことが判りますが(それが翌2002年にフリーのBCCで"BCC Form and BCCSkelton"を開発する動機にもなりましたが)、このEditWinにも現在のWindows 11の環境で動くものがあります。それらはWindows SDKベースで書き、BCCでコンパイルした、

 

(1)CmnDlg.exe(おまけコモンダイアログユーティリティ

解説:ファイル選択、フォールダー選択、フォント選択、色選択コモンダイアログをチェックボックスで選んで「了解」ボタンを押すと、ファイルパス、フォールダーパス、フォントハンドルやRGBの値がテキストとしてクリップボードに保存されます。当時のActiveBasicがコモンダイアログを使えなかったので、自分なりに工夫した結果でした。

 

(2)EditBox.exe(おまけ簡易エディター

解説:「清く正しいテキストエディター」で、テキストファイルの入出力や元に戻す、切り取り、コピー、貼り付け、検索、置換はもとより、フォント設定も可能ですが、その他ActiveBasicとC++の予約語やループなどの文字列を挿入する機能があります。

 

です。

 

(3)サンプル-(おまけ電卓のスケルトン

サンプルは二種類あり、ActiveBasicで書かれた”SampleCalcAB.exe”とC++で書かれた"SampleCalcBCC.exe"です。ActiveBasicで書かれた奴は矢張りWindows 11では動作が不安定で落ちますが、BCCの奴はしっかりと(スケルトンだけですが)動きます。

 

この電卓は(wincalc.exeがあるので、不要ですが)実際に完成させ、私のBCCアプリ第一号になりました。と、いうことでタイトルに戻るわけです。

 

私のウインドウズプログラミングの原点(赤面の至り)

 

就学前から「なんで?」「どうして?」と母親泣かせだった私。

見ればわかることは覚えなくていい。分からないことを考える事こそが重要だ。」と父親に育てられた幼年時代。

答えよりも、問うことに意味がある。」と嘯いた学生時代。

と、こういうこと。分かった?」「分かりました。」「じゃ、自分の言葉で説明してみて?説明できなけりゃ、分かったとは言えないぜ?と後輩や部下を調練した現役時代。

 

そんな私も疾うに古希を過ぎ、知識(情報)はGoogleに、調べても分からないことはChat-GPT様にお世話になりっぱなしとなり、毎週日曜日は日曜版で「ボケ防止」の数独、迷路、間違い探しや漢字読み書きクイズをする日々。特に「漢字が書けない」「字を忘れた」「固有名詞が思い出せない」が(我ながら)いよいよしんどくなってきました。

 

そんな毎日を過ごしていて、何故か今朝(早朝の2時!)「生中華麺を電子レンジ調理して焼きそばにする方法()」を考え、「現代の科学技術はプロメテウスの火(末尾参照)だよな」等と考えていたら、

:「焼きそば麺」だと芸がないので、汁そば用生中華麺をα化して、表裏面だけdeep fryするために、生中華麺を濡らしてシリコン容器で電子レンジで加熱することを思いつきました ... が、ググったら(焼きそばは無かったですが)「電子レンジラーメン」の作り方等は結構出ているのですね。

 

「ネット検索からAIが人間の手に渡り、『知らないことが何もなくなる』ということは本当にそうなのか?寧ろ最近は字も書けないし、読み間違える等、人間がダメになってきているじゃないか?膨大な情報は手に入るが、『知っているつもり』で本当は『分かって(理解して)いないのじゃないか?

 

という考えに至った時に突然、

 

「新文盲」

:私の時代では「しんもんもう」ですね。「ぶんもう」と書くと「はーい、ひっかかったー。」と先生が喜んだものです。しかし、時代は変わり「新文盲」は「『新文盲』という言葉自体に確定的な読み方はなく、文脈によりますが、多くの場合、**「新しい文盲」**を指す言葉です。そのため、読み方は「しんぶんもう」と読むのが自然です。」とMicrosoft系AIは主張します。私的には「はっ?」ですが。

 

という言葉が頭に浮かびました。(そして悲しい哉、またしてもググってしまい)従来の「文盲(非識字)」が、現代では(新しい科学技術の革新で)「字は知っていても意味が分からない(機能的非識字)」となっていることを知りましたが、私には今後

 

「コンピューター技術の発展は人間能力の減衰」

 

につながってゆくのではないか、という思いが益々強くなりました。

 

しかし、調べてみると教育現場からもこのような話がでており、深刻です。確かに大人の世界でも「本を(全く)読まない」「新聞を(全く)読まない」「情報はwebから」「急いでいるので、画面をスクロールして結論だけ見る読まない)のがタイパ」というのが常態となっています。

 

デジタル世界の情報量が膨大となり、処理しきれなくなるフラストレーションで(中抜きして)結論に飛びつき、結果盲信状態になっているのでは?と懸念しても、「妄信」だとは言えないでしょう。そんな風におもっていたら、こういう方

 

「新・文盲」が増えると独裁化が進む。(1)

「新・文盲」が増えると独裁化が進む。(2)

「新・文盲」が増えると独裁化が進む。(3)

 

と書いておられました。私の懸念と結構合わさっていますよね。

 

私ですか?

 

本は大好きで月に5~10冊は読みますね。この間も10数年愛用していたブックカバーが傷んだので、神様が新しいアジャスタブルな本革使用を新調してくれました。量を読むので大体はBook Offですが(電車に乗ってもほとんどすべての人がスマホを見て、本を読んでいるのは自分だけ、なんて光景が続いているので)、いずれ同店も経営が苦しくなるんでしょうか?(まぁ、僕はその時はもういないでしょうが。)

 

最後にもう一度言います。

 

答えよりも、問うことに意味がある。

 

【ご参考】

機能的非識字(wiki)・・・「(ある程度)字を読め(書け)るけど、意味は理解していない」という伝統的に意味でした。なお、wikiには「新文盲」はありませんでした。

新文盲(百度百科)・・・「機能的非識字」は日本語なのでバイドゥにはありませんが、「新文盲」はありました。なお「機能的非識字」は」「功能性文盲」と訳すようで、これはありました。しかし、この意味はどちらかというとコンピューターリテラシー等、新技術理解についていけない状態を指す伝統的な意味でした。

プロメテウスの火ー「プロメテウスの火」はギリシャ神話の一つで、タイタン族の神プロメテウスがゼウスの命に反して天界の火を盗み出し、人類に与えたことから、ゼウスによって3万年の拷問を受けることになるという話です。一方、人間は暖房、調理、道具作り等の能力を得られましたが、「ゼウスの予言通り、その火を使って武器を作り戦争を始めるに至」り、現在は原子力等「人間の力で制御できないような強大な科学技術」を指す比喩としても使われます。なお、ギリシャ神話は人間の不幸の根源は「パンドーラの箱」にあるとしていますが、そのきっかけが「プロメテウスの火」であったことは「因果」の示唆に富みます。

 

ここから始まった【Cell】シリーズが終了し、少し気抜けしています。

 

ところで、

 

皆さんはデータベースってご存じですよね?「あー、知ってるよ。」と答えられた方も、実際にご自身で使われたり、プログラムを組んだりしたことは余りないかと存じます。仮にあっても、それはお仕事の関係で、データ量もExcel等スプレッドシートでも処理できるようなものではなかったでしょうか?

 

何故なら、

 

個人に関係する私的データで、データベース管理するような膨大な量のものは稀」だからでないでしょうか?(

:あるとしたら、一日の間の行動のログとか、毎日の身体状況データとかになるでしょうが、そんなもの管理しようと思わないでしょう?また、それらが個人情報や機微情報であったりするので厄介です。

 

私の場合、若い時に米国の訴訟管理をやっていたこともあり、日本では簡単なカード型データベースを使っていましたが、米国に赴任し、大手を振って会社のお金でリレーショナルデータベースである(当時はまだMS-DOS時代だったので)dBASEIVを購入し、サンクスギビング、クリスマス、正月(日本資本だったので、現地休暇の他に日本の暦に合わせた休暇もありました、テヘッ)休暇を返上して、自動レポーティングが出来る訴訟管理システムを組むまでになりました。

 

その後、帰国してからはさすがにWindowsの時代になり、国内の委託先管理や、シンガポールに赴任してからは海外子会社の重要事案管理を、またコンサルティング子会社に出向してからも海外訴訟判例等の管理をMS-Accessベースの簡単なアプリを作って行っていました。

 

しかし、

 

会社を完全にリタイアして仕事上のデータが無くなると、データベースを組んで管理するようなものはないじゃないですか!実際、リタイア後から作成したプログラムも、データ管理を行うものはC++で書いた「銀行や証券会社の口座管理ソフト」と「各種ID、パスワード管理ソフト」だけで、C#では全くありません。(第一扱うデータ量が100にも満たないのですから...)

 

従って、C++にせよ、C#にせよ、高度のデータベース機能が使えるようになっても、

 

宝の持ち腐れ

 

でしかありません。

 

そうであれば、データを作っちゃえ!

 

という発想もあり、Cellゲームを書いている時に「ログ出力」を追加しました。例えば、

 

【CellsOf(2025-9-5_8_21)from24.txt】

No. 1 生存状態:存命(余命:15408)、種族:ブルー、愛着性:59、敵対性:31、生命力:2923、攻撃力:30、防御力:33、生殖力:60 
No. 2 生存状態:存命(余命:5772)、種族:ブルー、愛着性:42、敵対性:29、生命力:1924、攻撃力:48、防御力:34、生殖力:16 
No. 3 生存状態:存命(余命:1567)、種族:グリーン、愛着性:37、敵対性:82、生命力:3571、攻撃力:96、防御力:23、生殖力:51 
No. 4 生存状態:存命(余命:14592)、種族:ブルー、愛着性:46、敵対性:68、生命力:338、攻撃力:27、防御力:13、生殖力:55 
No. 5 生存状態:存命(余命:6472)、種族:グリーン、愛着性:39、敵対性:95、生命力:2265、攻撃力:23、防御力:25、生殖力:22 
No. 6 生存状態:存命(余命:11147)、種族:グリーン、愛着性:36、敵対性:99、生命力:1609、攻撃力:87、防御力:13、生殖力:73 
No. 7 生存状態:存命(余命:6823)、種族:グリーン、愛着性:57、敵対性:14、生命力:417、攻撃力:69、防御力:23、生殖力:94 
No. 8 生存状態:存命(余命:11203)、種族:ブルー、愛着性:22、敵対性:38、生命力:2145、攻撃力:46、防御力:10、生殖力:31 
No. 9 生存状態:存命(余命:4020)、種族:グリーン、愛着性:30、敵対性:28、生命力:3484、攻撃力:53、防御力:30、生殖力:14 
No.10 生存状態:存命(余命:11378)、種族:ブルー、愛着性:78、敵対性:38、生命力:1281、攻撃力:73、防御力:37、生殖力:96 
No.11 生存状態:存命(余命:14707)、種族:ブルー、愛着性:24、敵対性:87、生命力:115、攻撃力:23、防御力:13、生殖力:96 
No.12 生存状態:存命(余命:8049)、種族:ブルー、愛着性:44、敵対性:47、生命力:244、攻撃力:74、防御力:22、生殖力:20 
No.13 生存状態:存命(余命:4545)、種族:グリーン、愛着性:51、敵対性:91、生命力:158、攻撃力:61、防御力:43、生殖力:49 
No.14 生存状態:存命(余命:1742)、種族:グリーン、愛着性:31、敵対性:29、生命力:3268、攻撃力:62、防御力:38、生殖力:78 
No.15 生存状態:存命(余命:5421)、種族:ピンク、愛着性:54、敵対性:29、生命力:158、攻撃力:60、防御力:12、生殖力:28 
No.16 生存状態:存命(余命:6017)、種族:ブルー、愛着性:71、敵対性:99、生命力:562、攻撃力:94、防御力:37、生殖力:30 
No.17 生存状態:存命(余命:13831)、種族:グリーン、愛着性:77、敵対性:51、生命力:1968、攻撃力:69、防御力:41、生殖力:24 
No.18 生存状態:存命(余命:5596)、種族:グリーン、愛着性:25、敵対性:24、生命力:1065、攻撃力:85、防御力:41、生殖力:54 
No.19 生存状態:存命(余命:11553)、種族:ブルー、愛着性:55、敵対性:26、生命力:3700、攻撃力:75、防御力:28、生殖力:78 
No.20 生存状態:存命(余命:11728)、種族:ピンク、愛着性:22、敵対性:72、生命力:3091、攻撃力:72、防御力:23、生殖力:79 
No.21 生存状態:存命(余命:15875)、種族:ブルー、愛着性:61、敵対性:90、生命力:1439、攻撃力:43、防御力:48、生殖力:63 
No.22 生存状態:存命(余命:13229)、種族:ピンク、愛着性:69、敵対性:63、生命力:2360、攻撃力:97、防御力:26、生殖力:52 
No.23 生存状態:存命(余命:3669)、種族:ピンク、愛着性:25、敵対性:72、生命力:326、攻撃力:30、防御力:42、生殖力:62 
No.24 生存状態:存命(余命:14882)、種族:グリーン、愛着性:17、敵対性:23、生命力:547、攻撃力:23、防御力:29、生殖力:30 

 

こういうデータが人口爆発ごとに1,6~700程度入手できるので、簡単に万、十万単位のデータがそろいます。後は複数のファイル出力(ファイル名にも日付・時間や開始した際のセルの数が記録されています)を読み込んで、分析用のCSVファイルに纏め、生命力や各種属性、能力に関連させて

 

分析することが出来るようになりました!

 

って、

 

何のために?

 

で、

 

何が得られるの?

 

空しくて、何だか面倒で、(余り得るところもなさそうで)取り敢えずデータベース化はいいかなって、思っています。(歳、ですかね?)

 

前回で【Cell】のウィンドウズ版

 

はすべてカバーしましたが、に「Ccell_world.hファイルは、純粋にC++(C++11)だけで動くように書いており、例えば(x, y)座標のデータもWin32APIのPOINT構造体が使えるのにわざわざlocationという構造体を定義しています。従って表記方法もWindows SDKやBCCSkelton等に一切依存しない形にしています。従ってこのヘッダーファイルを読み込むと、コンソールベースでも、他のシステムのC++環境でも使えることを意味します。」と書いたこと、またCellは元々コンソールプログラムとして書き始めたことが気になって、一応実証する必要があるかな、と考えました。

 

流石にウィンドウズ版程丁寧には書いておらず、単に「動作確認できればいいや!」程度のプログラムであること、ウィンドウズ版の様にm_mapに全セルの変化を書き込んで、それを一挙に更新する形にしているので、「見た目」が遅いのと、チラツキが気になります。そんな訳で、画面更新はユーザー操作に任せる形にしてみました。

 

【Cell.cpp】-コンソール版

////////////////////////////////
//    Cell.cpp for C++
//    Copyright (c) 2025 by Ysama
////////////////////////////////


//ヘッダーファイル
#include    <iostream>
#include    <conio.h>            //getch() を使うため
#include    <cstdlib>            //system() 関数を使用するために必要
#include    "Ccell_world.h"  //解説:同じCellゲーム用のヘッダーを使います。

///////////////////
//ユーザー定義関数
///////////////////
//背景(m_map)を表示する

bool showmap1(Cworld *world) {    //モノクロ版

    std::system("cls");                            //画面クリア
    char line[MAX_WIDTH + 1] = {0};                //1行用char配列
    for(int j = 0; j < MAX_HEIGHT; j++) {        //Y座標
        for(int i = 0; i < MAX_WIDTH; i++) {    //X座標
            //キャラクター表示
            int n = world->check_what(i, j);    //(i, j)の位置に何があるか
            switch(n) {
            case 0:    //野原
                line[i] = ' ';
                break;
            case 1:    //山
                line[i] = '^';
                break;
            case 2:    //河川
                line[i] = '~';
                break;
            case 3:    //食物
                line[i] = 'O';
                break;
            default:
                if(n > 3) {        //Cell(4 + m_id)の場合、同じCellでも「生死」変化があり得る

                                          //種族は'A''B''C'、死んだセルは'D'で表示する
                    Ccell *cell = world->get_cell(i, j);    //(i, j)の位置あるCellのポインターを取得
                    switch(cell->get_tribe()) {
                    case 0:
                        line[i] = 'A';
                        break;
                    case 1:
                        line[i] = 'B';
                        break;
                    case 2:
                        line[i] = 'C';
                        break;
                    case 3:
                        line[i] = 'D';
                        break;
                    }
                }
                else if(n == -1)
                    return false;
                break;
            }
        }
        cout << line << endl;
    }
    return true;
}

bool showmap2(Cworld *world) {    //カラー版

    std::system("cls");                            //画面クリア
    for(int j = 0; j < MAX_HEIGHT; j++) {        //Y座標
        for(int i = 0; i < MAX_WIDTH; i++) {    //X座標
            //キャラクター表示
            int n = world->check_what(i, j);    //(i, j)の位置に何があるか
            switch(n) {
            case 0:    //野原
                cout << ' ';
                break;
            case 1:    //山
                cout << "\e\[38;5;94m";        //茶色
                cout << '^';
                break;
            case 2:    //河川
                cout << "\e\[38;5;51m";        //水色
                cout << '~';
                break;
            case 3:    //食物
                cout << "\e\[93m";        //黄色
                cout << 'O';
                break;
            default:
                if(n > 3) {        //Cell(4 + m_id)の場合、同じCellでも「生死」変化があり得る

                                          //種族は'A''B''C'、死んだセルは'D'で表示する
                    Ccell *cell = world->get_cell(i, j);    //(i, j)の位置あるCellのポインターを取得
                    switch(cell->get_tribe()) {
                    case 0:
                        cout << "\e\[38;5;128m";    //ピンク
                        cout << 'A';
                        break;
                    case 1:
                        cout << "\e\[94m";            //青色
                        cout << 'B';
                        break;
                    case 2:
                        cout << "\e\[92m";            //緑色
                        cout << 'C';
                        break;
                    case 3:
                        cout << "\e\[38;5;103m";    //灰色
                        cout << 'D';
                        break;
                    }
                }
                else if(n == -1)
                    return false;
                break;
            }
            cout << "\e\[39m";    //標準色に戻す
        }
        cout << endl;
    }
    return true;    
}

////////////
//main 関数
////////////

int main(int argc, char **argv) {

    //生成するcellの個数を取得する
    int n = 0;
    while(n < 8 || n > 64) {
        std::system("cls");            //画面クリア
        cout << "生成するcellの個数を入力してください(8~64):";
        cin >> n;
    }
    //異種族間婚を許可するか
    char intmarg = ' ';
    while(intmarg != 'Y' && intmarg != 'y' && intmarg != 'N' && intmarg != 'n') {
        std::system("cls");            //画面クリア
        cout << "異種族間婚を許可しますか(Y/N):";
        cin >> intmarg;
    }
    //Cworldクラスインスタンスの生成
    Cworld world;
    //地形図の読み取り
    if(!world.read_map("world_terrain.trn")) {
        cout << "地形図を読み込めませんでしたので、終了します。" << endl;
        getch();
        return 0;
    }
    //セルの生成
    world.create_cells(n, (intmarg == 'Y'|| intmarg == 'y'));
    //"Cell"ゲームの本体
    while(!world.is_gameover()) {
        world.goes_around();    //「世界」の中でセルが活動する
        //showmap1(&world);        //「世界」を表示する
        showmap2(&world);        //「世界」を表示する
        cout << "「世界」の地形がすべて見えるようにプロンプトウィンドウのサイズを調整してから、キーを押してください。(E/eで終了)" << endl;
        //if(kbhit() && tolower(getch()) == 'e') {    //リアルタイム終了キー入力(Embarcadero C++では_が無い)
        if(tolower(getch()) == 'e') {    //終了キー入力(Embarcadero C++では_が無い)
            //ゲーム終了処理
            cout << "Cellゲームを終了します。" << endl;
            world.gameover();        //Cworldのm_gameoverフラグをtrueにする
        }
    }
    cout << world.get_message() << endl;    //終了メッセージ
    std::system("pause");                    //画面停止
    return 0;
}

 

このプログラムを簡単に説明すると、「#include    <cstdlib>            //system() 関数を使用するために必要」は「DOSコマンド」を使用できるので、(エスケープシーケンスで問題となった、「画面送り」ではなく)完全にプロンプトウィンドウの出力バッファを空にする」ことが出来ます。(換言すると、過去の出力をズラズラと残しません。)その為、(遅くてチラつきますが)きれいに表示できます。(このチラツキをごまかすために、ゲームを開始後、ユーザーにウィンドウサイズを変更、毎回の画面変化をキー入力で行わせています。)

 

 

一回限りのゲームですが、ちゃんと終了メッセージを出して終わります。

 

 

とはいえ、矢張り手間を掛けただけウィンドウズ版(冒頭参照)の方が見た目で完全に勝りますね。何れにせよ、

 

このヘッダーファイルを読み込むと、コンソールベースでも、他のシステムのC++環境でも使える

 

事だけはお伝えしたかったので...

 

ps. (コンソール版を見限って)ウィンドウズ版を作った今から振り返ると、画面表示をこのコンソール版で満足していたら、「それなりに動くけど、余り感心しないね」という結末になっていたかもしれなかったので、この世は皆

 

塞翁が馬

 

といえるかな?