前回の「【Visual Studio探検記】「Hello World!巡り」-ウィンドウズ編(C#)総則」でC#のウィンドウズプログラミングには色々とプラットフォームが分かれているので、一応それらの概観を確認しました。
今回は旧い(Win95~のGDIとWinXP~のGDI+)グラフィックを使う、Win32 APIに近いWinFormsで「HelloWorld4」を作ってみましょう。
【全体イメージ】
これは既に作ったHelloWorld4の画面ですが、ソリューションエクスプローラーを見ると大分当事者(ファイル等)が多いので、ズームしてみます。
「依存関係」はこのアプリケーションがよって立つプラットフォームのシステムやDLLなどが表示されます。(これをユーザーがいじることはまずありません。)尚、「依存関係」-「フレームワーク」を見ると、このプログラムは.NETCore上で走るようです。(「アナライザー」がコード自動生成やファイル管理、「フレームワーク」がプラットフォームなのかな、という感じを受けますね。)
「Form1.cs」というのがこのプロジェクトの中核なのですが、「Form1」(Form1()はコンストラクター、OnPaint()はイベント処理メソッドですね)と「Form1.Designers.cs」(「してその実体は」UI設定処理で、リソーススクリプトForm1.resxもその手下のようです)からなります。
「Program.cs」はエントリーポイントのMain()メソッドを持ち、コンソールプログラムではユーザープログラムの中核だったのですが、どうなってしまったのでしょうか?
では、エディターで開かれるファイルを見てみましょう。
(1) Form1.cs(ユーザーが処理を書き込む「プログラム本体」部分)
今回私が書き込んだ部分は緑色のコメント部分です。内容はC++のときと同じく、WM_PAINTメッセージの際のシステムの処理(OnPaintメソッド処理-"base.OnPaint(e)")の後に、Form1ウィンドウのクライアントエリア(ウィンドウの内枠部分)を求め、StringFormatクラスインスタンス sf を(画面の中央に表示をするよう)初期化し、文字列とフォントを設定してGraphicsクラスのDrawStringメソッドで"Hello World!"と描画します。
(2) Form1.cs [デザイン](これはForm1というクラスのUIをWYSWYGで設計し、XAMLで記述する部分)
コントロールやメニュー等は何も使わないのでこのままです。以下(↓)のXAMLファイルでも設定はすべてシステムの既定値です。
(3) Form1.Designers.cs(これはForm1クラスの一部で、Form1.resxというリソースの取り込みを含めてUIを規定する部分)
最初にSystem.ComponentModel.IContainerのインスタンスcomponents(実際はポインター)を初期化し、Form1のDisposeメソッドをオーバーライド(注)して(リソースがある場合<components != null>の)components.Dispose()メソッドを追加してから、componentsを生成(new)、Form1の初期設定を行っています。
注:C++からの用語で、(異なる引数や戻り値の)同じ名前の関数やメソッドを作れるfeature(特長?)です。
<Form1.resx - ご参考(これがXAMLというマークアップ言語です)>
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
(4) Program.cs(これがForm1のエントリーポイント<Main()>で、初期化と実行を行う部分)
お馴染みの"[STAThread] static void Main()"(注)というエントリーポイントメソッドがあり、アプリケーションの初期化と実行を行います。
注: シングルスレッド アパートメント (Single-Threaded Apartment)
いかがだったでしょうか?私的にはまだまだC++で書いたWIndowsプログラム(HellowWorld3)との相関が理解し得る範囲ではありますが、Visual Studio側で多くの処理を裏方の黒子がやっているので、「ユーザーは自分がやることが少なくなっている反面、(自動生成コードと自動処理が多く)プログラム全体で何がどうなっているのかがわからない」というジレンマが生じる、とも言えそうです。
いずれにしても、
これをビルドして実行すると、
こんな風になります。尚、「OnPaintイベント処理時点のクライアントエリアの中央に文字列を書き込むので、ウィンドウサイズを変更すると真ん中にならない」プログラムとなってしまいました。これを常に真ん中に持ってくるには「クライアント領域サイズをクラスメンバーとし、OnPaintメソッドにある描画位置処理を、ReSizeイベントやOnSizeChanged(EventArgs e)メソッドをオーバーライドしたメソッドへ移動」してください。(面倒くさくてやりませんでした。テヘッ)
.
.
.
.
わかったよ、分かったから。
【変更版ーVisual Studioにやらせると厄介なので、変更は手作業で行いました。】
namespace HelloWorld4
{
public partial class Form1 : Form
{
//解説:クラスメンバー変数(フィールド)
int width = 0;
int height = 0;
//解説:コンストラクター
public Form1()
{
InitializeComponent();
width = this.ClientSize.Width;
height = this.ClientSize.Height;
}
//解説:Paintイベントで描画します。
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//解説:クライアントエリアの幅と高さを取得
RectangleF rec = new RectangleF(0, 0, width, height);
//解説:StringFormatインスタンスの設定
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center, // 水平方向中央
LineAlignment = StringAlignment.Center // 垂直方向中央
};
//解説:表示文字列
string str = "Hello World!";
//解説:描画用フォントとブラシの作成(usingを使い、Dispose()メソッドを省略)
using (Font font = new Font("Meiryo", 20, FontStyle.Bold))
using (Brush brush = new SolidBrush(Color.DarkBlue))
{
//解説:文字列を描画する
e.Graphics.DrawString(str, font, brush, rec, sf);
}
}
//解説:SizeChangedイベントで描画します。
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
//解説:クライアントエリアの幅と高さを取得
width = this.ClientSize.Width;
height = this.ClientSize.Height;
this.Refresh(); //解説:強制的にクライアント領域を無効化、再描画します。(Win32API のinvalidaterect(hWnd, NULL, false);相当)
}
}
}







