Amebaの管理画面に、

 

1年前の今日あなたが書いた記事があります

【BCCSkelton】「ハノイの塔」プログラム(1)-リソース | BCCForm and BCCSkelton のサポートセンター (ameblo.jp)

 

が載っていました。当時はまだBCCForm and BCCSkeltonでプログラミングしてたのですね。懐かしいな。

 

ps. このころからC#を学習し始め、どっぷりつかってしまいます。その結果、楽な人生を歩み始め、C++のコードも書かなくなりました。最近はコンソールでC++の処理系プログラムを書きたいな、と感じます。

 

前々回WPFで作るウィンドウには一つのコンテンツしか入れられないので、コントロールが一つしか付けられないことが分かりました。また前回は(WinFormと対比しながら)ウィンドウの"Content"に入れるものの動作や特徴についても書きました。今回は先ずこれを深堀してみようと思います。

 

WPFはウィンドウをwebのページのように扱い、ページに表示するものをUI(User Interface)としてとらえているようです。webで色々とみていると、こんな図を見つけました。

UI要素のクラス階層:FrameworkElementからControl、Decorator、Panel等へ

 

そして、コントロールから派生するContentControlクラス(System.Windows.Controls名前空間)について、この図の出ている記事では「単一のデータに対するデータ・テンプレートの適用」という表現をして、「ボタンなどのように、コンテンツを1つだけ持つコントロールの基底クラスである。アプリケーション本体となることの多いWindowクラスもContentControlクラスから派生(解説:従って、コンテンツは一つしか持てない)している。」とし、「コンテンツを表すContentプロパティはobject型になっていて、何でも格納することができる。格納した型によって、以下のように、異なる表示が行われる。」とした後、

 

・UIElementクラスの場合、そのままUI要素として表示が行われる(OnRenderメソッドで描画を行う)
 ・そのほかのクラスの場合、データ・テンプレートが設定されていればテンプレートを使った表示が行われる
 ・データ・テンプレートも設定されていない場合、ToStringメソッドを使って文字列化された結果が表示される

 

と書いていますが、前回書いたContentに入れたオブジェクトの動作、即ち

(1)文字列は普通に表示される

(2)画像はそのサイズにより、縦か横一杯に表示される

(3)コントロールはクライアントエリア(ページエリア?)いっぱいに表示される

に対応しているようです。(オブジェクト毎にデータテンプレートが設定されているようです。)

 

更にその後、

ContentControlクラスの派生クラスの中に複数のUI要素を並べたい場合、

 (解説:①)まず<Grid>要素などのパネルを置いて

 (解説:②)その中にUI要素を並べる

という「解法」が述べられていました。

 

では、この「<Grid>要素などのパネル」とは何なのか、を↑の図で見ると、FrameworkElementから派生するPanelクラスに"Grid, StackPanel, WrapPanel, DockPanel, Canvas"というクラスが並んでいます。(

:名前空間はSystem.Windows.Controlsにあります。実はパネルには、この他にも

System.Windows.Controls.Primitives.TabPanel
System.Windows.Controls.Primitives.ToolBarOverflowPanel
System.Windows.Controls.Primitives.UniformGrid
System.Windows.Controls.Ribbon.Primitives.RibbonContextualTabGroupsPanel
System.Windows.Controls.Ribbon.Primitives.RibbonGalleryCategoriesPanel
System.Windows.Controls.Ribbon.Primitives.RibbonGalleryItemsPanel
System.Windows.Controls.Ribbon.Primitives.RibbonGroupItemsPanel
System.Windows.Controls.Ribbon.Primitives.RibbonQuickAccessToolBarOverflowPanel
System.Windows.Controls.Ribbon.Primitives.RibbonTabHeadersPanel
System.Windows.Controls.Ribbon.Primitives.RibbonTabsPanel
System.Windows.Controls.Ribbon.Primitives.RibbonTitlePanel
System.Windows.Controls.VirtualizingPanel

というものがあるそうな。
 

こいつらは皆ベースとなるPanel基底クラスのChildrenというUIElementCollection)のプロパティを持ち、Collectionものに共通のAddメソッドで子UIElementUI要素-コントロールのみならず、画像や文字列等)を複数持てるようにしています。

:要すれば、同じUIElementの配列を子供らとして持つようなもの-C++でいえば配列へのポインターのようなもの。

 

例えば簡単なStackPanelは、縦または横方向()に、コントロールを"<StackjPanel名>.Children.Add(<コントロール名>)”のように追加してゆきます。WrapPanelDockPanelは親ウィンドウのサイズを変化させたときの動作が異なりますが、概ねStackPanelと同様の取り扱いでよいです。

OrientationというプロパティにHorizontalまたはVerticalを設定する。

 

Gridはエクセルのように列と行でできた容れ物で、最初に列と行を作ってやる必要があります。後はコントロール等UIElementをマトリクスのどこに入れるか指定し()、同じく"<Grid名>.Children.Add(<コントロール名>)”で子分にして終わりです。

SetColumn/SetRow(<UIElement名>, <0ベースの順番>)というメソッドを使います。

 

CanvasはWin32APIやWinFormの相対座標に慣れた方にやさしい容れ物で、ウィンドウのContentにCanvasを指定するとクライアントエリアいっぱいに広がるので、後は入れるUIElementをの位置を指定()し、"<Canvas名.Children.Add(<UIElement名>)”で子分にしてやります。

Canvas.SetTop(<UIElement名>, 上からのオフセット) Canvas.SetLeft(<UIElement名>, 左からのオフセット)というメソッドを使います。

 

どうでしょう?

 

イメージがわきましたか?

 

要すれば、インターネットとの親和性を高めるために、コントロールや図形、イメージ、文字列等コンテンツの柔軟なレイアウト、区割り、配置を行えるようにし、その為にGrid, StackPanel, WrapPanel, DockPanel, Canvas等が採用され、ウィンドウをwebページのようにデザインするプロセスにしたんですね。その必然の結果としてUI(View)についてはXaml表記が導入され、その裏方処理("Code-Behind"()によるModel)はC#表記という「分離コード」へ発展したということのようです。

ASP.NETというMicrosoftのウェブプログラム開発フレームワークで使われる概念です。("Code-behind is a concept commonly used in ASP.NET web development.")webデザインのHTMLとは別の言語による処理コードを意味します。("Code-behind refers to code for your ASP.NET page and  allows a clean separation of your HTML from your presentation logic.")

 

まぁ、そんなこたぁどうでもよくって、

 

問題はどうサンプルを書いたらいいのか、ということになりますね。そいつは次回に回しましょう。

 

【ご参考】

Microsoft Learnーコントロール

 

無駄話を始めると止まらなくなるのが、爺いの嫌われるところなんですが、矢張りもう一つ話したくなりました。

 

PCって、暴走しなくなりましたねぇ。

 

というのが今日のお題です。(昔懐かしい暴走族の話でも、Patrol Car<警察ではこう呼びます>の話でもありません。)

 

PC以前の「パソコン(8bitや16bit時代のPC)」、OSが「ROM Basic」だったり、MS-DOSやWindows 3.xだった頃では、PCが良く暴走し、アプリだけではなく、OS(即ちPC自体)が良く「落ちた」ものでした。

 

8bit PC(私の場合はSONYのMSXやSharpのMZ-2500)では、Basicを使っている限り落ちることはないのですが、どうしても「速いプログラムを書きたい」というので、Z80というCPUの機械語プログラムを組み()、走らせるのですが、バグなどがあるとPCがOS諸共落ちて、(通常は自動的にアドレス0から実行されるようになり)再起動することがしょっちゅうでした。

:当時、機械語でプログラムを書くには、「ニモニック(mnemonic)という人間が読める機械語」を「CPUが読める16進数の命令やデータ」に変換してメモリーに16進数を書き込んでいく「アセンブル」という作業を、手作業で行う(「ハンドアセンブル」といいます)か、アセンブラー(アセンブルを行うアプリで、通常自作)で行うか、しかありませんでした。(コンパイラーは結構なお値段だったので、高嶺の花でした。)例えば次のようなものです。

 

 16進数機械語  ニーモニック    解説

  01  AB  12       ;LD BC,12AB        BとCのペアレジスターに16進数12ABH(LE)を代入

  0A                  ;LD A,(BC)     BCの指すアドレス(12ABH)の値をAレジスターに代入

  CD  00 90        ;CALL 9000H       アドレス9000Hをコール(次のアドレスをスタックに入れてジャンプ)する

  C9                  ;RET                     リターンする(スタックからアドレスを出してジャンプする)

  C3  34  12       ;JP 1234H             アドレス1234Hへジャンプする

 

これはWindows 3.xでもあまり変わりなく、致命的なエラーが起きるとDOS画面に戻ってエラー表示がなされ、再起動を促されました。これはWin32 APIになったWindows 95でも16bitモードで作動している間は同じで、行儀の悪いコードでアプリが暴走すると「PC画面が青く」なり、

復帰することもあるでしょうが、内部がどうなっているのか分からないので再起動する方が無難でした。

 

しかし、Windows 10や11になってから、このブルースクリーンやOSが落ちるような機会は激減している感じがします。

 

となると()、「落としてみたくなるのは人の性(サガ、てか、俺だけ?)」

:実際、webで「OSが落ちる場合」の記事を見ても、熱暴走や機器異常等のハードウェアエラーばかりで、ソフトウェアエラーは「悪意のあるプログラム」位しかないようです。

 

というわけで、(高級言語のC#なんかは、そういう危ない遊びはやらせてくれないので)「現代のアセンブラー」と言われる(ポインターが使える)CやC++で「落としのテク()」を磨こうと、サンプルを作ってみました。(一応、try~catchを使っています。落とすためにはcatchと__finallyのブロックを採ってください。要すればcatchに行った場合、落ちることになります。)

:まずはアドレス0へのジャンプ(コンパイル不能)保護領域へのアクセス0による除算を試してみました。

 

【DivisionbyZero.cpp】ーCっぽい、C++のコンソールプログラムです

/*
    C/C++ Exception Sample
*/

#include <stdio.h>
#include <string.h>
#include <conio.h>        //getch()を使う為
#include <windows.h>    //Exceptionを使う為

class Exception        //Exceptionクラス-
{
public:
    Exception(char* s = "不明なエラー") {what = strdup(s);}   //未定義エラーのコンストラクター
    Exception(const Exception& e)        {what = strdup(e.what); }   //定義済エラーのコンストラクター
    ~Exception()                        {free(what);}   //デストラクター(文字列を開放)
    char* msg() const                    {return what;}    //エラーメッセージを返す関数
private:
    char* what;   //エラーメッセージポインター
};

int main(int argc, char** argv) {    //解説:典型的なC言語nomain関数ですね。

    bool Div_by_Zero;    //解説:このフラグは保護領域のアクセスと0による除算を使い分けるために使います。
    try {
        try {
            try {
                //関数ポインタを宣言し、アドレス0番地を代入→エラー(例外)発生失敗
                //void (*functionPtr)() = reinterpret_cast<void(*)()>(0);    //アドレスゼロの偽関数を作る
                //アドレス0番地から関数を呼び出す
                //functionPtr();    //アドレスゼロの偽関数を作ってコールさせる
                //解説:
bcc32c.exeが次を含むエラーメッセージを出してコンパイル不可能
                //"bcc32c.exe: error: clang frontend command failed due to signal (use -Xdriver -v to see invocation)"

 

/* このコメント化で以下はコメントになってしまいますが、可読性の為にコメントにしていません。
                //アドレス0番地のデータを読む→エラー(例外)発生成功
                //int valueのアドレスと値を表示する
                Div_by_Zero = false;    //解説:保護領域のアクセスなので「偽」。
                int* ptr = nullptr; // ポインタをNULLに初期化
                int value = 42;
                ptr = &value; //変数valueのアドレスをポインタに代入
                printf("変数valueの値: %d\r\n", value);
                printf("変数valueのアドレス: %0X\r\n", &value);
                printf("ポインタptrの値: %0X\r\n", ptr);
                printf("ポインタptrが指す値: %d\r\n", *ptr);
                //アドレス0番地を直接呼び出す
                int* nullPtr = reinterpret_cast<int*>(0);
                printf("アドレス0番地の値: %0X\r\n", *nullPtr);
*/
                //ゼロによる除算→エラー(例外)発生成功
                Div_by_Zero = true;   //解説:ゼロによる除算なので「偽」。
                float e, f = 1.0, g = 0.0;
                puts("浮動小数点変数でゼロによる除算を実行する。");
                e = f / g;
            }

            //解説:以下では色々と試したためにコメント付きの文が多くなっています。
            //__except(EXCEPTION_CONTINUE_SEARCH)        //OSにエラーハンドラーを探させる(プログラムが落ちる)
            __except(EXCEPTION_EXECUTE_HANDLER)            //エラーハンドラーに処理を移行
            //catch(const Exception& e)                    //C++ベースのエラーハンドラー(ここでcatchされなくなる)
            {
                puts("C++ベースで例外を検出");
                //printf("Cベースの例外を検出: %s\r\n", e.msg());      //解説:catchの場合
                if(Div_by_Zero)    //ゼロによる除算フラグがtrueの場合
                    throw(Exception("ハードエラー: 0による除算\r\n"));
                else                    //falseの場合(アクセス違反)
                    throw(Exception("ハードエラー: アクセス違反\r\n"));
            }
        }
        catch(const Exception& e)
        {
            printf("C++の例外を検出: %s\r\n", e.msg());
        }
    }
    __finally
    {
        puts("C++では\"__finally\"も使える。何かキーを押してください。");
        getch();    //エラーが出てここに来ると、__finally{}以降は実行されないから。
    }
    getch();
    return 0;
}

 

結果的に、アドレス0へのジャンプ(コンパイル不能)保護領域へのアクセス(例外発生)0による除算(例外発生)という結果ですが、OSが落ちるというには程遠い安定性でした。

残念っ!

 

ps. 因みにCLIのC#なんかでゼロ除算なんかをやると、ダイアログが出て例外を処理してくれますね。ありがたい限りです。

 

(本来Embarcadero C++を使った、BCCFormとBCCSkeltonの解説ブログの筈なんですが)完全にC#と.NET Frameworkというか、Microsoft帝国のプラットフォーム探検ブログになっているので、脱線ついでにMSBuild(注)についても書かせてもらいます。

注:Windows 10/11であれば、"C:\Windows\Microsoft.NET\Framework(64)\v4.0.30319\MSBuild.exe"に見つかります。

 

1.「コンパイル」と「ビルド」(ご存じ方は2へスキップしてください)

先ず「コンパイル」と「ビルド」についてザックリと説明します。

人間が作る、人間が理解できる処理の指示文を「ソースコード」といい、その文法はプログラミング言語により異なります。(私が時々載せる奴がC++またはC#のソースコードです。)

PCはこのソースコードを理解せず、「数字だけのコード(バイナリーコードとか機械語と言います)」しか分からないので、「ソースコード」を「バイナリコード」へ変換する作業が必要です。これを「コンパイル」と言います。従ってC++言語で書かれたソースコードをバイナリーコードに変換するものを「C++コンパイラー」、C#言語で書かれたものを変換するものを「C#コンパイラー」などと言います。

簡単なプログラムなら、一つのソースコード(それがファイルとして記録されたものを「ソースファイル」と言います)から直接実行可能なバイナリーコード(同じくそれがファイルになったものがバイナリファイルです)にして、即実行(「実行ファイル」などと言います)することが出来ますが、リソース(同様に「リソースファイル」)が別にあったり、最終の実行ファイルにはいくつかのバイナリーファイルをまとめる必要があったりすると、それらを関連付ける(Link-リンクする、リンカーなどと言います)必要があります。

ビルド」とは、このような最後の実行ファイルを作る為に、種々のソースファイルをコンパイルしたり、それによってできたバイナリーファイルをリンクしたりする手順をまとめて行い、コンパイラーやその他のツールプログラムに指示を与えるプログラムを言います。

BCCForm and BCCSkeltonの話でいえば、bcc32c.exeというコンパイラーでコンパイルする為に、"*.mak"というビルドファイルを使って"make.exe"がビルドし、Microsoft .NET FrameworkのC#であれば、csc.exeというコンパイラーでコンパイルする為に"*.csproj"という、(またしても!)Xamlで記述されたビルドファイルを使って”MSBuild.exe”がビルドします。(

csc.exeと同じく、Windows10や11の"C:\Windows\Microsoft.NET\Framework\v4.0.30319"フォールダーにあります。なお、"*.sln"というソリューションファイルがありますが、これは(最終的に実行ファイルとなる)複数のプロジェクトファイル(例:*.csproj、*.vbproj)が一つのソリューションを構成している場合の統括ファイルと考えてよいです。なお、Main()メソッドだけで何もしないプログラムのソースとそれをビルドする際のプロジェクトファイル、ソリューションファイルの例がGitHubに載っているで参考になるかもしれません。

 

2.MSBuildとは

C#のみならず、Microsoftの他の言語も含めて(注)、Visual Studioで開発する際に利用されるMicrosoftのビルダーです。

Microsoftも解説はしていますが、やや「寿限無感」が強いですね。代わりに、大分古いですがMSBuild創成期のこの記事でMSBuildの何たるかが分かると思います。

注:Copilotによれば「MSBuildは、Microsoftが提供するビルドツールであり、.NET FrameworkやVisual Studioのプロジェクトをビルドするために使用されます。MSBuildは、C#、VB.NET、C++/CLI、F#などのプログラム言語で書かれたプロジェクトをビルドできます。ただし、MSBuildでビルドできるプログラム言語には制限がありません。」だそうです。

 

3.MSBuildの使い方

頭がまともな方は、(特にWPFプロジェクトの場合)統合開発環境(IDE-Intedrated Develpment Environmentの略)であるVisual Studioを使ってください。自動的に*.sln、*.csprojファイルを作成して、(私はやったことがありませんが)「ビルド」ボタンを押せば自動的にMSBuildを呼び出してビルド(コンパイル、リンク etcの一連処理)をやってくれるはずです。

 

頭がおかしい方は、これを読んでXamlを学習し、*.csprojファイルを作成して、次のようなバッチファイルを作ってビルドしてください。

 

【Build.bat】-なお、このバッチファイルはANSIでセーブしてください。UTF-8でも文字化けします。

ECHO OFF
ECHO ----------------------------------
ECHO  MSBuildを起動します。
ECHO  C#の場合、*.csprojファイルのある
ECHO  フォールダーで実行してください。
ECHO ----------------------------------
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe"
pause

 

因みにこの記事のサンプルがあったので、実際にやってみました。見事ビルドされました!

(ビルドしたフォールダー)

(実行ファイル)

 

が、

 

↑の記事にあるように(注)、MSBuildを手書きでプロジェクトファイルを書いて利用するのはお勧めしません。素直にVisual Studio Community Editionを使われた方が良いです。(私は「まだ」使いませんが...)

注:記事から引用→「『こんなものを手で書けるのか?』と感じてもらうためである。」「MSBuildを動かすためにはプロジェクトファイルが必要だ。ただし、プロジェクトファイルの書き方は難しいので、ゼロから手書きするのはあまり現実的とは言い難い。

 

さて、前回「次回からはこれ(注)をもっと詳しく見て(get more details)行きましょう。」と書きましたが、思い付きで他の話題を取り上げてしまうのが、このブログの作法です。(笑)

注:複数コントロールの使い方、です。

 

前回WPFでウィンドウ(Windowクラス)のContentに文字列、画像、コントロールを入れてみましたが、

 

(1)文字列は普通に表示される

(2)画像はそのサイズにより、縦か横一杯に表示される

(3)コントロールはクライアントエリア(ページエリア?)いっぱいに表示される

 

ことに気が付きました。

 

では、コントロールにサイズを指定するとどうなるのか?また実験です。

 

【Window04.cs(注)

注:このプログラムはWPFと格闘を始めて比較的初期に書いたSimple_Window.csを書き換えたものです。イベント処理なども確認しているので、私自身の備忘の為に色々と書き込み(コメント)がありますが、皆様の参考にもなると思います。

///////////////
// Window04.cs
///////////////

using System;
using System.Windows;
using System.Windows.Controls;

/* WPFの場合に参照すべきDLL
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\WindowsBase.dll
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationCore.dll
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF\PresentationFramework.dll
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\Xaml.dll    //Xaml使用のみならず↑のdllで参照される
*/


namespace Window04
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
/*            Windows.Formでは問題が無かった次の書式、
            "Application.Run(new MainWindow());"
            が"CS0120: 静的でないフィールド、メソッド、またはプロパティでオブジェクト参照が必要です"
            というエラーになるので注意。
    解説:にやった話です。WPFを調べ始めた当時は「へぇ~」という感じでした。*/


            MainWindow mwnd = new MainWindow();
            Application ap = new Application();    //明示的にインスタンスを作らなければならない
            ap.Run(mwnd);
        }
    }

    public partial class MainWindow : Window    //派生元が"Form"から"Window"となる
    {
        public MainWindow()
        {
            //this.Loaded += new RoutedEventHandler(Window_Loaded);
            //this.ContentRendered += new EventHandler(Window_ContentRendered);

            //解説:RoutedEventとEventの違いはこれが参考になります。まだ私も完全に理解していませんが...(笑)

            this.Loaded += Window_Loaded;
            this.ContentRendered += Window_ContentRendered;
            //this.Activated += new EventHandler(Window_Activated);        //これを有効にするとMopuseDownとButton Clickイベントがsupersedeされる
            this.MouseDown += Window_MouseDown;
            this.Closing += Window_Closing;
            InitControl();
        }

        public void InitControl()
        {
            //Windowのプロパティ:https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.window?view=windowsdesktop-8.0
            //this.Text = "WPF サンプル";            //TextからTitleになった
            this.Title = "WPF サンプル";
            //this.Size = new Size(640, 480);        //Sizeは無くなった
            this.Width = 640;
            this.Height = 480;
            //this.StartPosition = FormStartPosition.CenterScreen;    //StartPositionは廃止された
            //this.Location = new Point(100, 100);    //Locationも廃止された

            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;    //Manual, CenterOwnerもある(Manualの場合、LeftとTopプロパティの値)
            //this.BackColor = SystemColors.Window;    //Color BackColorからBrush Backgroundになった
            //WPFのSystemColors-https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.systemcolors?view=windowsdesktop-8.0

            this.Background = SystemColors.WindowBrush;
            /* 解説:以下ボタンコントロールにを一つ作り、サイズを絶対値で指定し、

                         クライアントエリアの中央に余白(Thickness)50で配置します。

            */
            Button btn = new Button();
            btn.Width = 240;          //解説:これはWinFormにありますが、Sizeは無くなりました。
            btn.Height = 80;
            btn.HorizontalAlignment = HorizontalAlignment.Center;    //解説:これはWinFormにはないものです。
            btn.VerticalAlignment = VerticalAlignment.Center;    //解説:これはWinFormにはないものです。
            //解説:WinFormでは次のようになります。

            //btn.Location = new Point((ClientSize.Width - btn.Width) / 2, (ClientSize.Height - btn.Height) / 2);

            btn.Margin = new Thickness(50, 50, 50, 50);    //構造体 Thickness(左, 上, 右, 下)
            btn.Content = "I am a button.";    //解説:Textではなく、Contentとして文字列を受け付けます。
            btn.Click += Button_Click;    //解説:この記述方法は基本的に変わりませんね。
            this.Content = btn;    //解説:WinFormでは"this.Controls.Add(btn);"でしたね。
        }

        private void Window_Loaded(object sender, EventArgs e)
        {
            MessageBox.Show("プログラムがロードされました。", "Loadedイベント", MessageBoxButton.OK, MessageBoxImage.Information);    //解説:細かいですが、WinFormではMessageBoxButtons、MessageBoxIconでした。以下同じ。
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            MessageBox.Show("ウィンドウが完成しました。", "ContentRenderedイベント", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            MessageBoxResult result = MessageBox.Show("ウィンドウがアクティブになりました。\r\n終了しますか?", "Activatedイベント", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if(result == MessageBoxResult.Yes)
                Close();
        }

        void Window_MouseDown(object sender, EventArgs e)
        {
            MessageBox.Show("ウィンドウがクリックされました。", "Clickイベント", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            MessageBoxResult result =
            MessageBox.Show("ウィンドウを閉じようとしています。\r\nよろしいですか?", "Closingイベント", MessageBoxButton.YesNo, MessageBoxImage.Question);
            if(result == MessageBoxResult.No)
                e.Cancel = true;
        }

        void Button_Click(object sender, RoutedEventArgs e)    //解説:引数形式が変わりました。Routingについては↑参照
        {
            MessageBox.Show("ボタンがクリックされました。", "Button Clickイベント", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }
}
/* 参考-Visual Studioが作成するInitializeComponent()を含む自動作成コード

    解説:自動生成コードについて調べた人がいますね

             「どういうことだろうか。」とありますが、記事の中でラベルのタイ
             トル"Label"が変わらないのは、Xamlコードの方が"Label"になっていて、
             ビルドすると自動生成コードが更新されてまた"Label"になってしまうか
             らです。自動生成コードでコンテンツを文字列"ABC"にするのではなく、
             Xamlコードを"ABC"としておけば "ABC"と表示されるはずです。

*/

// SampleWpfApplication
using System.Windows;
using System.Windows.Markup;
 
namespace SampleWpfApplication
{
    public partial class MainWindow : Window, IComponentConnector
    {
        private bool _contentLoaded;   
//解説:コンテントがロードされているか否かのフラグですね。

        public void InitializeComponent()
        {
            if (_contentLoaded) return;
            _contentLoaded = true;
            System.Uri resourceLocater = new System.Uri(

                //解説:ウィンドウやコンテンツを定義したXamlファイルへのパスですね。
                "/SampleWpfApplication;component/mainwindow.xaml",
                 System.UriKind.Relative);
            System.Windows.Application.LoadComponent(

                this, resourceLocater);    //解説:Xamlファイルをバイナリーに変換して実装するのでしょう。
        }

        void IComponentConnector.Connect(int connectionId, object target)
        {

            //解説:この後、MSBuildによってswitch分を使ったコントロールのコードが追加されます。

            【追加されるコード例】

            switch (connectionId)
            {
              case 1:
                 this.text1 = ((System.Windows.Controls.TextBlock)(target));
                 return;
              case 2:
                 this.button1 = ((System.Windows.Controls.Button)(target));
                 this.button1.Click +=
                 new System.Windows.RoutedEventHandler(this.Button_Click);
                 return;
            }

            this._contentLoaded = true;
        }
    }
}

*/

 

【対比用Form04.cs】-↑のファイルと(同等とまではいきませんが)似たような動作をするWinFormのコードです。見た目が大分違いますね。

///////////////
// Form04.cs
///////////////

using System;
using System.Windows.Forms;
using System.Drawing;        //SystemColorsを使う為

namespace Window04
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            Application.Run(new MainWindow());
        }
    }

    public partial class MainWindow : Form
    {
        Button btn;

        public MainWindow()
        {
            InitControl();
            this.Load += Window_Loaded;
            this.Shown += Window_ContentRendered;
            //this.Activated += new EventHandler(Window_Activated);        //これを有効にするとWindow_MouseDownとButton Clickイベントがsupersedeされる
            this.SizeChanged += On_SizeChanged;
            this.MouseDown += Window_MouseDown;
            this.Closing += Window_Closing;
        }

        public void InitControl()
        {
            this.Text = "Form サンプル";
            this.Size = new Size(640, 480);
            this.StartPosition = FormStartPosition.CenterScreen;
            this.BackColor = SystemColors.Window;

            btn = new Button();
            btn.Size = new Size(240, 80);
            btn.Location = new Point((ClientSize.Width - btn.Width) / 2, (ClientSize.Height - btn.Height) / 2);
            btn.Text = "I am a button.";
            btn.Click += Button_Click;
            this.Controls.Add(btn);
        }

        private void On_SizeChanged(object sender, EventArgs e)
        {
            btn.Location = new Point((ClientSize.Width - btn.Width) / 2, (ClientSize.Height - btn.Height) / 2);
        }

        private void Window_Loaded(object sender, EventArgs e)
        {
            MessageBox.Show("プログラムがロードされました。", "Loadedイベント", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            MessageBox.Show("ウィンドウが完成しました。", "ContentRenderedイベント", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            DialogResult result =
            MessageBox.Show("ウィンドウを閉じようとしています。\r\nよろしいですか?", "Closingイベント", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(result == DialogResult.No)
                e.Cancel = true;
        }

        private void Window_Activated(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("ウィンドウがアクティブになりました。\r\n終了しますか?", "Activatedイベント", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(result == DialogResult.Yes)
                Close();
        }

        void Window_MouseDown(object sender, EventArgs e)
        {
            MessageBox.Show("ウィンドウがクリックされました。", "Clickイベント", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

        void Button_Click(object sender, EventArgs e)
        {
            MessageBox.Show("ボタンがクリックされました。", "Button Clickイベント", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }
}
 

2023年に作成したチンチロリン博打シミュレーター、Chinchirorinですが、まだ乱数による出目と打ち方によりどのような結果の変化が出るのかを検証中です。

 

現在の勝率トップは私(60.00%)で二位坊や哲(57.14%)、三位出目徳(53.57%)をしのいでいます。又、母数の勝負数も一番多い(注)ので安定しているともいえましょう。勝ち金も少しづつですが確実に増やしています。

注:70勝負。坊や哲21勝負、出目徳28勝負。

 

「勝率」だけでいえば、「負けそうなときにコマを張らない」ということはできないので、「勝つ秘訣」はないです。唯一出来そうな対処法は「負けそうなときにコマを小さくし、潮目が変わったらコマを増やす」ことぐらいでしょう。しかし、50%を超えて勝率を維持することが仮に可能だとすると、「負け始めたら、その場はやめる」ことに尽きるのではないかと思います。どんな博打でもそうですが、「負けてアツくなってエスカレートしてゆく」のが破滅パターンだからです。

 

又、勝負数を増やしてその成績を報告させていただきます。

 

前回までの検証で、WPFのウィンドウ(Windowクラス)は、ウィンドウコントロールを、文字列や画像と同様にUI用情報表示用のContent(コンテンツ)で表示することが分かりました。

 

では、

 

複数のコントロールを表示するとどうなるのでしょうか?見る前に飛べ、ということで、実際にやってみましょう。

 

【Window03.cs】

///////////////
// Window03.cs
///////////////

using System;
using System.Windows;
using System.Windows.Controls;            //コントロールを利用する為
using System.Windows.Media.Imaging;        //BitmapFrameを使用する為

namespace Window03
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            MainWindow mwnd = new MainWindow();
            Application ap = new Application();
            ap.Run(mwnd);
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            /* ウィンドウスタイル、クライアントエリア色、アイコンとZオーダーの指定 */
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;    //None、SingleBorderWindow(規定値)、ThreeDBorderWindowまたはToolWindow
            this.Background = SystemColors.WindowBrush;            //本例ではウインドウのクライアントエリア色にする
            this.Icon = BitmapFrame.Create(new Uri("Icon.ico", UriKind.Relative));    //同じフォールダーのIcon.icoを読んで使用する
            this.Topmost = false;                                //最上位ウィンドウとするか否か
            /* 起動時の位置-上、左座標で指定または、起動時の位置で指定 */
            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            /* ウィンドウサイズ-幅、高さで指定または、コンテンツサイズに合わせる */
            this.Width = 640;
            this.Height = 480;
            //this.Content = "このウィンドウはWPFベースで、プロパティチェックの為に作られました。";
            //this.SizeToContent = SizeToContent.WidthAndHeight;    //Contentサイズに合わせる(Height、Manual、WidthまたはWidthAndHeight)

            this.ResizeMode = ResizeMode.CanResizeWithGrip;        //CanMinimize、CanResize、CanResizeWithGrip、NoResizeから選択
            this.ShowActivated = true;                            //アクティブ状態で初期表示するかどうか(規定値 true)
            this.ShowInTaskbar = true;                            //タスクバーにボタン表示するか否か
            this.Title = "Window03";
            InitControls();
        }

        public void InitControls()
        {
            /* ボタンコントロールをつけてみる */
            Button btn1 = new Button();
            btn1.Content = "I'm Button1. Click me!";
            this.Content = btn1;
            Button btn2 = new Button();
            btn2.Content = "I'm Button2. Click me!";
            this.Content = btn2;
            Button btn3 = new Button();
            btn3.Content = "I'm Button3. Click me!";
            this.Content = btn3;
        }
    }
}
 

さて、どうなるでしょう?

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

こうなりましたぁ!

(平常時)

(マウスカーソルが領域内の時)

 

どうも"this.Content ="でbtn1とbtn2が上書きされて、最後のbtn3が生き残ったようです。又、何の指定もなければウィンドウのクライアントエリア全体にストレッチされるようです。次回からはこれをもっと詳しく見て(get more details)行きましょう。

 

前回、WinFormでは(Win32の)伝統的な「ウィンドウにコントロール(子ウィンドウ)を付けて表示」するのに対して、WPFではWEB的に「(ページとして)ウィンドウにコンテンツを表示」するパラダイムシフト(変化)が起こっているようだ、と書きました。

 

それを確認する前に、先ずWPFで「(このあいだ作った)ウィンドウに何かを表示する」サンプルを書いてみようと思います。

【ウィンドウにコンテンツを表示する】

///////////////
// Window02.cs
///////////////

using System;
using System.Windows;
using System.Windows.Controls;            //コントロールを利用する為
using System.Windows.Media.Imaging;        //BitmapFrameを使用する為

namespace Window02
{
    ///////////////////////////
    //エントリーポイントクラス
    ///////////////////////////

    class MainApp
    {
        [STAThread]
        public static void Main()
        {
            MainWindow mwnd = new MainWindow();
            Application ap = new Application();    //解説:インスタンスを作らないとエラーになりました。
            ap.Run(mwnd);
        }
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DefWindow();    //ウィンドウのプロパティを設定する
        }

        /* Windowクラスのプロパティを設定する */
        public void DefWindow()
        {
            int swt = 1;    //解説:これを0(文字列)、1(画像)、2(コントロール)で変えてコンパイルしてみてください。
            /* ウィンドウスタイル、クライアントエリア色、アイコンとZオーダーの指定 */
            this.WindowStyle = WindowStyle.ThreeDBorderWindow;    //None、SingleBorderWindow(規定値)、ThreeDBorderWindowまたはToolWindowがある
            //this.AllowsTransparency = true;                    //WindowStyleがNone以外に設定された場合、InvalidOperationExceptionとなる
            this.Background = SystemColors.WindowBrush;            //本例ではウインドウのクライアントエリア色にする
            this.Icon = BitmapFrame.Create(new Uri("Icon.ico", UriKind.Relative));    //同じフォールダーのIcon.icoを読んで使用する(解説:因みにプログラムアイコンとは違うものを表示しています。)
            this.Topmost = false;                                //最上位ウィンドウとするか否か
            /* 起動時の位置-上、左座標で指定または、起動時の位置で指定 */
            this.Top = 100;
            this.Left = 100;
            //this.WindowStartupLocation = WindowStartupLocation.CenterScreen;

            //解説:↑のTopとLeftプロパティによる絶対座標指定をコメントアウトして、このプロパティを有効化すると真ん中に現れます。
            /* ウィンドウサイズ-幅、高さで指定または、コンテンツサイズに合わせる */
            this.Width = 640;
            this.Height = 480;
            //this.SizeToContent = SizeToContent.WidthAndHeight;    //Contentサイズに合わせる(Height、Manual、WidthまたはWidthAndHeight)
            //解説:↑のWidthとHeightプロパティによる指定をコメントアウトして、このプロパティを有効化するとコンテンツの大きさまで縮小されます。
            this.ResizeMode = ResizeMode.CanResizeWithGrip;        //CanMinimize、CanResize、CanResizeWithGrip、NoResizeから選択
            this.ShowActivated = true;                            //アクティブ状態で初期表示するかどうか(規定値 true)
            this.ShowInTaskbar = true;                            //タスクバーにボタン表示するか否か
            this.Title = "Window02";
            //this.IsActive                                        //ウィンドウがアクティブか否か
            //this.LogicalChildren                                //ウィンドウの論理上の子要素に対する列挙子を取得
            //this.OwnedWindows                                    //このウィンドウがオーナーとなるウィンドウ群
            //this.Owner                                        //このウィンドウのオーナーウィンドウ
            //this.RestoreBounds                                //最小化、最大化前のthisのサイズ(Width, Height)、場所(X, Y)をRectで返す
            //this.TaskbarItemInfo                                //Gets or sets the Windows 7 taskbar thumbnail for the Window.
            //this.WindowState                                    //WindowState.Maximized、MinimizedまたはNormal

            switch(swt)
            {
                case 0:    //テキスト
                    this.Content = "このウィンドウはWPFベースで、プロパティチェックの為に作られました。";
                    break;
                case 1:    //画像
                    Image bmp = new Image();
                    bmp.Source = new BitmapImage(new Uri("..\\..\\Samples\\Resources\\resources\\msCat.jpg", UriKind.Relative));
                    this.Content = bmp;
                    break;
                case 2:    //リッチエディットコントロール
                    RichTextBox rtb = new RichTextBox();
                    this.Content = rtb;
                    break;
            }
        }
    }
}

 

WinFormとの対比で出てきた種々のプロパティに値を設定してどうなるかを検証するとともに、(表示ページとしての)ウィンドウにContentとして

テキスト 

画像   

コントロール 

等様々な情報が表示できることが分かります。これはもう「従来の、伝統的なWin32のUIの概念」ではありませんね。

 

前回の疑問から、WindowクラスとFormクラスのプロパティ、メソッド、イベントを並べて比較し、大きな違いがあることが分かりました。

 

しかし、

 

この比較で「Formに(コントロールを追加する時に使うForm.Controls.Add()の親玉である)Controlsがプロパティにない」ことに気が付きました。またWPFのWindowクラスではForm.Controls.Add()は使えないので(エラーが出ます)、ウィンドウにコントロールをどのように入れるのでしょうか?Web等では「コントロールなどを追加する際に"(Window).Content = Object"のようにしてウィンドウにオブジェクトを設定」するようなことを書いていますが、これもプロパティにはありません。

 

なので、

 

自分で調べてみました。

 

【WinForm】

Microsoftは「フォームの Controls コレクションを使用して、コントロール(注)を作成し、実行時にフォームに追加することができます。」(出典)と書いていますが、FormクラスとControlsコレクション(Control.Controls)の関係は直接言及していません。その為、クラス定義由来を見てみますと、

Form→public class Form : System.Windows.Forms.ContainerControl

とあり、ContainerControlから派生し、それを内包していることが分かります。そしてこのContainerControlクラスは、「他のコントロールのコンテナーとして機能するコントロールにフォーカスを管理する機能を提供します。」ということで、謎が解けました。

注:この「コントロール」とはWin32のコモンコントロール等("The majority of the controls in the System.Windows.Forms namespace use the underlying Windows common control as a base to build on.")、これらの伝統的なWindows コントロールを意味します。

 

【WPF】

Windowクラスも同様に、その定義で、

Window→public class Window : System.Windows.Controls.ContentControl

Controls.ContentControl(注)から派生して、それを内包しているので、謎が解けました。しかし、今度は「コンテンツ」という耳慣れない言葉が出てきました。

注:参照先にContentControlは「任意の種類のコンテンツContentクラス)が 1 つあるコントロールを表します。」と書かれています。

 

しかし、このContentControlが管理するContent(注)とは具体的に何なのか、複数のWin32のコントロール等はどう扱えばよいのか、などという疑問が生じます。

注:参照先に

Contentコントロールは、コンテンツ ページのコンテンツとコントロールのコンテナーです。 Contentコントロールは、対応するコントロールを定義するマスター ページでのみ使用されますContentPlaceHolder。 Content実行時にコントロールはコントロール階層に追加されません。 代わりに、コントロール内の Content 内容は、対応する ContentPlaceHolder コントロールに直接マージされます。

と書かれていますが、自動訳文のようで却ってわかりにくいので英文原文を載せます。「コンテンツページ」「マスターページ」「コンテンツプレースホールダー」等の耳慣れない言葉が多くなってきました。

"Content control is a container for the content and controls of a content page. A Content control is used only with a master page that defines a corresponding ContentPlaceHolder control. A Content control is not added to the control hierarchy at runtime. Instead, the contents within the Content control are directly merged into the corresponding ContentPlaceHolder control."

いずれにしてもまだ「寿限無寿限無」ですね。

 

全体像をもう少しわかりやすくしてくれるのは、このページですね。

WPF のコンテンツモデル

結構なんでもコンテンツとして表示できそうです。

 

しかし、

 

もうここら辺から、伝統的な「ウィンドウにコントロールを貼り付けて表示する」というWin32 "Windows Conttols"の世界(従って、そのラッパーとして作られたC# WinFormの世界)から大きく離れて、「インターネット上に掲載されている情報すべて」「Webサイト上に掲載された情報(中身)全てを指す言葉」というようなWEB "Content"の世界に入り込んでゆくようです。

 

前回(「WPFを学習する(2)」)で、「『両者は似たようで非なるものだ』という印象を強くし」たと書きましたが、

 

 vs 

C#の膨大なクラス等すべての対比をするわけにはいかないので、サンプルとして前回作ったウィンドウ(WinFormではFormクラス、WPFではWindowクラス)に関わるプロパティ、メソッド、イベントをWindowクラスとFormクラスで対比してみようと思います。

出典はMS Learningですので、個々の詳細はご自身で確認してください。

 

【WindowクラスとFormクラスの対比】

Window Form
プロパティ AllowsTransparency プロパティ AcceptButton
Dialogresult Activeform
アイコン ActiveMdiChild
IsActive AllowTransparency
Left Autoscale
LogicalChildren AutoScaleBaseSize
OwnedWindows 自動スクロール
所有者 AutoSize
ResizeMode AutoSizeMode
RestoreBounds AutoValidate
ShowActivated BackColor
ShowInTaskbar CancelButton
SizeToContent ClientSize
TaskbarItemInfo ControlBox
タイトル CreateParams
DefaultImeMode
最 上位 DefaultSize
WindowStartupLocation DesktopBounds
WindowState DesktopLocation
[WindowStyle] Dialogresult
メソッド アクティブ化 FormBorderStyle
ArrangeOverride HelpButton
閉じる アイコン
DragMove IsMdiChild
GetWindow IsMdiContainer
非表示 IsRestrictedWindow
MeasureOverride KeyPreview
OnActivated 場所
OnClosed MainMenuStrip
OnClosing 余白
OnContentChanged MaximizeBox
OnContentRendered MaximizedBounds
OnCreateAutomationPeer MaximumSize
OnDeactivated MdiChildren
OnDpiChanged MdiChildrenMinimizedAnchorBottom
OnLocationChanged MdiParent
OnManipulationBoundaryFeedback MinimizeBox
OnSourceInitialized MinimumSize
OnStateChanged モーダル
OnVisualChildrenChanged Opacity
OnVisualParentChanged OwnedForms
表示 所有者
ShowDialog RestoreBounds
イベント アクティブ RightToLeftLayout
Closed ShowIcon
閉じる ShowInTaskbar
ContentRendered ShowWithoutActivation
非アクティブ化 サイズ
DpiChanged SizeGripStyle
LocationChanged StartPosition
SourceInitialized TabIndex
StateChanged TabStop
  Text
TopLevel
最 上位
TransparencyKey
WindowState
メソッド アクティブ化
ActivateMdiChild
AddOwnedForm
AdjustFormScrollbars
ApplyAutoScaling
CenterToParent
CenterToScreen
閉じる
CreateAccessibilityInstance
CreateControlsInstance
CreateHandle
DefWndProc
Dispose
GetAutoScaleSize
GetScaledBounds
Layoutmdi
OnActivated
OnBackgroundImageChanged
OnBackgroundImageLayoutChanged
OnClosed
OnClosing
OnCreateControl
OnDeactivate
OnDpiChanged
OnEnabledChanged
Onenter
OnFontChanged
OnFormClosed
OnFormClosing
OnGetDpiScaledSize
OnGotFocus
OnHandleCreated
OnHandleDestroyed
OnHelpButtonClicked
OnInputLanguageChanged
OnInputLanguageChanging
OnLayout
Onload
OnMaximizedBoundsChanged
OnMaximumSizeChanged
OnMdiChildActivate
OnMenuComplete
OnMenuStart
OnMinimumSizeChanged
OnPaint
Onresize
OnResizeBegin
OnResizeEnd
OnRightToLeftLayoutChanged
OnShown
OnStyleChanged
OnTextChanged
OnVisibleChanged
ProcessCmdKey
ProcessDialogChar
ProcessDialogKey
ProcessKeyPreview
ProcessMnemonic
ProcessTabKey
RemoveOwnedForm
ScaleControl
ScaleCore
ScaleMinMaxSize
Select
SetBoundsCore
SetClientSizeCore
SetDesktopBounds
SetDesktopLocation
SetVisibleCore
表示
ShowDialog
ToString
UpdateDefaultButton
ValidateChildren
Wndproc
イベント アクティブ
AutoSizeChanged
AutoValidateChanged
Closed
閉じる
非アクティブ化
DpiChanged
FormClosed
FormClosing
HelpButtonClicked
InputLanguageChanged
InputLanguageChanging
[読み込み]
MarginChanged
MaximizedBoundsChanged
MaximumSizeChanged
MdiChildActivate
MenuComplete
MenuStart
MinimumSizeChanged
ResizeBegin
ResizeEnd
RightToLeftLayoutChanged
表示
TabIndexChanged
TabStopChanged

 

これだけで、Microsoftが大ナタを振るってクラスを書き換えていることが分かります。でも、過去の遺産にとらわれず、すっぱりと簡素化している点は好感が持てますね。