見出し画像

自分だけのGUI画面を作成(C#篇)

Viausl Studio 2019でWindows Fromアプリケーションを開発する際、ウィドウズ・フォーム(GUI画面)はデフォルトでは四角のものしか作れません。

今回は普段と違った、自分だけのGUI画面を作成する方法について解説していきます。

下記の「ファイル・ツー・ビットマップ」というシェアウェア(無料)を公開していますが、このソフトのGUIは本文章の紹介する方法で作成しています。

画像1

では下記の順で解説していきます。

GUI画面の設計

「あらゆることは人の考え」から始まります。プログラミングスキールも単なる技術に過ぎません。これを使って「何をつくったらいいのか?」が重要になります。

筆者も何を作ったらヒットするのか。。よく分かりませんが、とりあえず今回はちょっと個性のあるGUI画面を作ってみることにします。

先ずは「どのようなレイアウトを作成したいのか?」をイメージし、GUIの画面をデザインします。何を使ってもいいですが、筆者はPhotoshopしか勉強してないので、一応photoshopでGUIをデザインしました。

画像2

↑図のようにデザインした後、赤枠の部分を切り取って保存します。

特に、②、④、⑤、⑦の部分はwindows form画面のサイズによって繰り返しながら塗りつぶす部分なので、境界線が無いように作成します。

上記の要領で分解し、保存した画像ファイルは↓の通りです。

画像3

top-leftは、左上の部分に塗りつぶす画像;
top-middleは、画面上部のtop-left、top-right画像の間に塗りつぶす画像;
top-rightは、右上の部分に塗りつぶす画像。。。のような感じです。

これで画像の準備は完了です。

C#プロジェクトの作成

Visual Studioを開き、新しいプロジェクトを作成します。

今回は「C# Windows Forms App (.NET Framework)」型のテンプレートを選びます。

画像4

次の設定画面でプロジェクト名と.NET Frameworkのバージョンを指定し、「作成」ボタンを押します。

画像5

プロジェクト名は好きな名前を指定してください。
.NET Frameworkのバージョンは何でもいいですが、筆者はあんまり古すぎず新しすぎずの4.5を選びました。

プロジェクトが作成されると、何もしないですぐビルド、実行してみましょう。

画像6

↑図のように、普通に四角い地味な画面が作成されます。

プロジェクト名(JDRForm)を右クリック > 「プロパーティズ」をクリックします。すると↓図のような画面が表示されるので、「Resources」をクリックしてから、画像を矢印のように引っ張ります(drag&drop)。

画像7

すると、↓図のように画像がリソースとしてプロジェクトに取り込まれます。

画像8

画像を描画する、ベースとなるフォームを新規作成

プロジェクト名を右クリック>「新規」>「Form(windows Forms)... 」をクリックします。

画像9

次の画面で、フォームファイル名を指定します。後でこのフォームを継承することで画面レイアウトを継承することにしたいので、筆者はフォーム名を「JDRBaseForm」にしました。

画像10

↓図のようなフォームが新規作成されます。これからはこのフォームに画像を塗りつぶし、自分だけのGUIを作成します。

画像11

先ずは「プロパーティ」の「FormBorderStyle」を「None」に変更します。すると、↓図のようにwindows画面の枠がなくなります。

画像12

これで、フォームに画像を塗りつぶす準備ができました。

画面に画像を塗りつぶす

JDRBaseForm.csクラスJDRBaseFormに対し、次のようにプログラミングしていきます。

■ソース抜粋1  ==========

先ずはリソースから画像を取り出し、JDRBaseFormクラスのメンバー変数に保持します。

    public partial class JDRBaseForm : Form
   {
       //リソースから画像取得
       private Bitmap bmpTL = Properties.Resources._101_top_left;
       private Bitmap bmpTM = Properties.Resources._102_top_middle;
       private Bitmap bmpTR = Properties.Resources._103_top_right;
       private Bitmap bmpML = Properties.Resources._104_m_left;
       private Bitmap bmpMR = Properties.Resources._105_m_right;
       private Bitmap bmpBL = Properties.Resources._106_bottom_left;
       private Bitmap bmpBM = Properties.Resources._107_bottom_middle;
       private Bitmap bmpBR = Properties.Resources._108_bottom_right;

       public JDRBaseForm()
       {
           InitializeComponent();
       }

    。。。

    }
リソースのメンバーはコーディング中に直接使ってもいいが、ソースを理解しやすくするために、JDRBaseFormのメンバー変数に保存しておきます。
private Bitmap bmpTL = ...   :  Top Left(左上)の画像 。。。のように変数名を指定しておけば、後で使いやすいです。

windowsフォームは、画面を描画する必要が出てくる度に、WM_PAINTというwindows messageを送ってきます。このメッセージがどうやって処理されるかは、windowsというOSのアーキテクチャになるので必要なければ詳しく分からなくてもいいですが、C#では、xxxx_Paint(object sender, PaintEventArgs e)というメソッドが呼び出されます

何故かというと、↓図のように、「Paint」というメッセージに「JDRBaseForm_Paint」をいうメソッドを登録しておいたからです。

画像13

画像を描画したり、塗りつぶしたりする画像処理関連のアプリを開発すると、必ずこのwindows メッセージを処理することになります。

よく分からなくても、「フォーム上に画像を塗りつぶす場合は必ずWM_PAINTメッセージの処理メソッド(xxxx_Paint())内で処理するんだなあ」と覚えておけばOKです。ここでxxxxはフォームクラス名になります。

これから、JDRBaseForm_Pain()メソッド内で画像の塗りつぶし処理を行います。

■ソース抜粋2  ==========

       private void JDRBaseForm_Paint(object sender, PaintEventArgs e)
       {
           Graphics g = e.Graphics;
       。。。
        }

先ずは引数のeから「Graphics」型のインスタンスを取得します。これが「フォームの描画領域を操作するオブジェクトになります。

■ソース抜粋3  ==========

           //1. top left描画
           Rectangle rect = new Rectangle(0,
                                          0,
                                          bmpTL.Width,
                                          bmpTL.Height);
           TextureBrush br = new TextureBrush(bmpTL);

           g.FillRectangle(br, rect);

左上の画像をフォームの左上に塗りつぶします。
これだけ書いて実行すると、↓図のようになります。

画像14

画像26

同じ感じで、Top Left, Top Middle, Top Rightの3つを塗りつぶします。

■ソース抜粋4  ==========

       private void JDRBaseForm_Paint(object sender, PaintEventArgs e)
       {
           Graphics g = e.Graphics;

           //1. top left描画
           Rectangle rect = new Rectangle(0,
                                          0,
                                          bmpTL.Width,
                                          bmpTL.Height);
           TextureBrush br = new TextureBrush(bmpTL);

           g.FillRectangle(br, rect);

           //2. top middle描画
           rect = new Rectangle(bmpTL.Width,
                                4,
                                this.Width - bmpTL.Width - bmpTR.Width,
                                bmpTM.Height);
           br = new TextureBrush(bmpTM);
           br.TranslateTransform(0, 4);
           

           g.FillRectangle(br, rect);

           //3. top right描画
           rect = new Rectangle(this.Width - bmpTR.Width,
                                0,
                                bmpTR.Width,
                                bmpTR.Height);
           br = new TextureBrush(bmpTR);
           br.TranslateTransform(rect.Left, 0);

           g.FillRectangle(br, rect);

        }

ここで実行すると↓図のようになります。

画像15

残りの部分も全て塗りつぶします。

■ソース抜粋5  ==========

       private void JDRBaseForm_Paint(object sender, PaintEventArgs e)
       {
           Graphics g = e.Graphics;

           //1. top left描画
           Rectangle rect = new Rectangle(0,
                                          0,
                                          bmpTL.Width,
                                          bmpTL.Height);
           TextureBrush br = new TextureBrush(bmpTL);

           g.FillRectangle(br, rect);

           //2. top middle描画
           rect = new Rectangle(bmpTL.Width,
                                4,
                                this.Width - bmpTL.Width - bmpTR.Width,
                                bmpTM.Height);
           br = new TextureBrush(bmpTM);
           br.TranslateTransform(0, 4);
           

           g.FillRectangle(br, rect);

           //3. top right描画
           rect = new Rectangle(this.Width - bmpTR.Width,
                                0,
                                bmpTR.Width,
                                bmpTR.Height);
           br = new TextureBrush(bmpTR);
           br.TranslateTransform(rect.Left, 0);

           g.FillRectangle(br, rect);

           //4. middle right描画
           rect = new Rectangle(this.Width - bmpMR.Width - 3,
                                bmpTR.Height,
                                bmpMR.Width,
                                this.Height - bmpTR.Height - bmpBR.Height);
           br = new TextureBrush(bmpMR);
           br.TranslateTransform(this.Width - bmpMR.Width - 3, bmpTR.Height);

           g.FillRectangle(br, rect);

           //5. bottom right描画
           rect = new Rectangle(this.Width - bmpBR.Width - 3,
                                this.Height - bmpBR.Height,
                                bmpBR.Width,
                                bmpBR.Height);
           br = new TextureBrush(bmpBR);
           br.TranslateTransform(this.Width - bmpBR.Width - 3, this.Height - bmpBR.Height);

           g.FillRectangle(br, rect);

           //6. bottom middle描画
           rect = new Rectangle(bmpBL.Width - 1,
                                this.Height - bmpBM.Height,
                                this.Width - bmpBL.Width - bmpBR.Width,
                                bmpBM.Height);

           br = new TextureBrush(bmpBM);
           br.TranslateTransform(bmpBL.Width - 1, this.Height - bmpBM.Height);

           g.FillRectangle(br, rect);

           //7. bottom left描画
           rect = new Rectangle(0 - 1,
                                this.Height - bmpBL.Height,
                                bmpBL.Width,
                                bmpBL.Height);
           br = new TextureBrush(bmpBL);
           br.TranslateTransform(0 - 1, this.Height - bmpBL.Height);

           g.FillRectangle(br, rect);

           //8. middle left描画
           rect = new Rectangle(3,
                                bmpTL.Height,
                                bmpML.Width,
                                this.Height - bmpTL.Height - bmpBL.Height);
           br = new TextureBrush(bmpML);
           br.TranslateTransform(3, bmpTL.Height);

           g.FillRectangle(br, rect);
       }

実行すると↓図のように回りが全て描画されたことが分かりますね。

画像16

以上で、画像を塗りつぶす処理は完了です。

ところが、お気づきになった通り、実行してみるとフォームの位置が固定のまま、移動できないですね。実際の開発ではこれじゃまずいです。

ある領域を指定し、「マウスがその領域内にある時だけフォームを移動させる」のようなこともできますが、今回は便宜上フォーム上のどこにいても、位置移動できるようにプログラミングします。

これを実現するためには、フォームの「ウィドウズプロシージャ(WndProc)」メソッドをオーバライドする必要があります。

画像17

これも又windowsというOSのアーキテクチャになります。windows Form型アプリケーションといのは、windowsというOS上で動くアプリのことなので、もちろん「windowsプログラミング」に関するスキールが必要になります。

よく分からない、とゆ方はこれもそのまま覚えておきましょう。

■ソース抜粋6  ==========

       protected override void WndProc(ref Message m)
       {
           base.WndProc(ref m);

           const int WM_NCHITTEST = 0x84;
           const int HTCLIENT = 1;
           const int HTCAPTION = 2;

           //windowのcaption領域をクリックした振る舞い
           if((m.Msg == WM_NCHITTEST) && (m.Result.ToInt32() == HTCLIENT))
           {
               m.Result = (IntPtr)HTCAPTION;
           }//end if
       }

これで、フォームのどの位置を押しても、画面を移動できるようになりました。

画像18

画面の右部分がおかしくなってますが、これはnoteにアップロードする時の問題で、実際のアプリには問題ありません。

一応、これで自作は完了です。

後はこのフォーム(JDRBaseForm)を継承すれば、「画面上は似てるが、サイズは色々」のフォームが作成できます。

次に、JDRBaseForm.csのソース全体を貼り付けます。

■JDRBaseForm.csソース全体  ==========

using System;
using System.Drawing;
using System.Windows.Forms;

namespace JDRForm
{
   /// <summary>
   /// 自作フォームのベースクラス
   /// ※本クラスを継承すると、同じレイアウトのwindows formを作成することができる。
   /// </summary>
   public partial class JDRBaseForm : Form
   {
       //リソースから画像取得
       private Bitmap bmpTL = Properties.Resources._101_top_left;
       private Bitmap bmpTM = Properties.Resources._102_top_middle;
       private Bitmap bmpTR = Properties.Resources._103_top_right;
       private Bitmap bmpML = Properties.Resources._104_m_left;
       private Bitmap bmpMR = Properties.Resources._105_m_right;
       private Bitmap bmpBL = Properties.Resources._106_bottom_left;
       private Bitmap bmpBM = Properties.Resources._107_bottom_middle;
       private Bitmap bmpBR = Properties.Resources._108_bottom_right;

       public JDRBaseForm()
       {
           InitializeComponent();
       }

       private void JDRBaseForm_Load(object sender, EventArgs e)
       {

       }

       /// <summary>
       /// 画面描画イベント
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       private void JDRBaseForm_Paint(object sender, PaintEventArgs e)
       {
           Graphics g = e.Graphics;

           //1. top left描画
           Rectangle rect = new Rectangle(0,
                                          0,
                                          bmpTL.Width,
                                          bmpTL.Height);
           TextureBrush br = new TextureBrush(bmpTL);

           g.FillRectangle(br, rect);

           //2. top middle描画
           rect = new Rectangle(bmpTL.Width,
                                4,
                                this.Width - bmpTL.Width - bmpTR.Width,
                                bmpTM.Height);
           br = new TextureBrush(bmpTM);
           br.TranslateTransform(0, 4);
           

           g.FillRectangle(br, rect);

           //3. top right描画
           rect = new Rectangle(this.Width - bmpTR.Width,
                                0,
                                bmpTR.Width,
                                bmpTR.Height);
           br = new TextureBrush(bmpTR);
           br.TranslateTransform(rect.Left, 0);

           g.FillRectangle(br, rect);

           //4. middle right描画
           rect = new Rectangle(this.Width - bmpMR.Width - 3,
                                bmpTR.Height,
                                bmpMR.Width,
                                this.Height - bmpTR.Height - bmpBR.Height);
           br = new TextureBrush(bmpMR);
           br.TranslateTransform(this.Width - bmpMR.Width - 3, bmpTR.Height);

           g.FillRectangle(br, rect);

           //5. bottom right描画
           rect = new Rectangle(this.Width - bmpBR.Width - 3,
                                this.Height - bmpBR.Height,
                                bmpBR.Width,
                                bmpBR.Height);
           br = new TextureBrush(bmpBR);
           br.TranslateTransform(this.Width - bmpBR.Width - 3, this.Height - bmpBR.Height);

           g.FillRectangle(br, rect);

           //6. bottom middle描画
           rect = new Rectangle(bmpBL.Width - 1,
                                this.Height - bmpBM.Height,
                                this.Width - bmpBL.Width - bmpBR.Width,
                                bmpBM.Height);

           br = new TextureBrush(bmpBM);
           br.TranslateTransform(bmpBL.Width - 1, this.Height - bmpBM.Height);

           g.FillRectangle(br, rect);

           //7. bottom left描画
           rect = new Rectangle(0 - 1,
                                this.Height - bmpBL.Height,
                                bmpBL.Width,
                                bmpBL.Height);
           br = new TextureBrush(bmpBL);
           br.TranslateTransform(0 - 1, this.Height - bmpBL.Height);

           g.FillRectangle(br, rect);

           //8. middle left描画
           rect = new Rectangle(3,
                                bmpTL.Height,
                                bmpML.Width,
                                this.Height - bmpTL.Height - bmpBL.Height);
           br = new TextureBrush(bmpML);
           br.TranslateTransform(3, bmpTL.Height);

           g.FillRectangle(br, rect);
       }

       protected override void WndProc(ref Message m)
       {
           base.WndProc(ref m);

           const int WM_NCHITTEST = 0x84;
           const int HTCLIENT = 1;
           const int HTCAPTION = 2;

           //windowのcaption領域をクリックした振る舞い
           if((m.Msg == WM_NCHITTEST) && (m.Result.ToInt32() == HTCLIENT))
           {
               m.Result = (IntPtr)HTCAPTION;
           }//end if
       }
   }//end class
}//end namespace

JDRBaseFormを継承する方法

これかれは応用編になります。

上記のように、自分の好きなフォームを作成したら、その後はアプリの全ての画面に採用していきます。

アプリ内の画面の見た目はすべて統一した方がよいでしょう。そうじゃないと、同じアプリなのに画面がバラバラで使いにくいソフトになりかねません。
そして、上記JDRBaseFormをDLLに作成しておけば、後でそのDLLを取り込むだけで簡単に全てのアプリで使えることができるようになるのでお勧めです。(※筆者の前の記事にも紹介しています)

下記のように操作し、新しいフォームを新規作成します。

画像19

新しいフォーム名を指定します。

画像20

これで作成された新しいフォームクラス「MessageForm」のソースについては継承先クラスを「Form」から「JDRBaseForm」に変えるだけで、他はなにもしません。

画像21

こうすると、MessageFormもJDRBaseFormの画面GUIをそのまま継承することになります。

後は↓図のように、フォームサイズを決め、普通のフォーム上にコントロールを置くのと同じように開発していけばOKです。

※下図は新しく作成したMessageFormのサイズ調整する場合の効果です。

画像22

上記もnoteにアップロードしたgif画像の問題で、実際のアプリには問題ありません。

画面のサイズなどを決めたら後は普通に画面開発していけばOKです。

例:

画像23

まとめ

こんな感じで、自分が好きに画面をデザインし、実装する方法について解説しました。

実は、これもただ画像を画面に塗りつぶしただけで、画面はやはり四角のままですね。

実はGraphicsPathクラスを使って任意の形のRegionオブジェクトを作成し、それをフォームの領域に指定すれば、「任意の形の画面」を作成することができます。

画像24

上記の要領を使って、「JDRBaseForm」の画面を丸角に変更してみてください。(本記事では割愛します)

画像25

上図をみると分かるように、今回塗りつぶした画像は「丸角」の画像なので、実際は4つの角に「白い部分」(画像によって塗りつぶされなかった、フォーム本来の角)が出来てしまいます。これは青色の枠のように、「丸い角」の四角をフォームに指定することで解消できます。

では、バイバイ! Have a nice day!

この記事が気に入ったらサポートをしてみませんか?