窓の工作室 〜 Windows アプリを作ろう!〜

〜 第6回 ボタンをつけてみる(3) 〜

ボタンの位置がどうもイマイチ…。

前回までの作業で、ウインドウの上についたボタンをクリックするとウインドウが終了する Windows アプリケーションが作れたと思います。でも、ボタンがいつもウインドウの左上にあって、あまりいい格好とは言えませんね。
そこで今回はボタンが常にウインドウの真ん中にあるように調整してみようと思います。もちろん、ウインドウのサイズを変更しても真ん中にいるようにします。
さて、どんなことをすればいいのでしょうか?

まずはプログラムの整理から…「定数」と「変数」について

今回の作業を始める前に少しだけプログラムの修正を行ないます。
ただ、この修正で動作が変わることはありません。今回の作業をするのに必要なので修正するんです。
修正箇所は第4回で追加したボタンを作成する部分です。

ひとまず、以下のようにプログラムの変更を行なってください(青色で表示されている部分です)。

LRESULT CALLBACK WindowProcedure(...)
{
    static HWND hwndButton = 0;
    static int  b_width, b_height;

    switch (message)
    {
           case WM_CREATE:
           b_width = 200;
           b_height = 50;
           hwndButton = CreateWindow("button", "Click Here", 
                        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                        0, 0, b_width, b_height, hwnd, (HMENU)1,
                        ((LPCREATESTRUCT)lParam)->hInstance, NULL);
           return 0;
           break;

           case WM_COMMAND:
           if (LOWORD(wParam) == 1 &&
           :
    }
    return 0;
}

変更できたらコンパイル( Execute -> Compile )をして、タイプミスなどによるエラーがなければ実行( >> Execute << ボタンをクリック)してみてください。
今までと動作が変わってなければ変更は成功です。

今回の変更で何をしたのかと言うと、ボタンの大きさを決めていたものを定数から変数に切り替えたのです。
定数と言うのはその名の通り「値の変わらない数」で、これまで 200 とか 50 と指定されていたものです。
一方、変数と言うのもその名の通り「値の変わる数」でして、今回 b_width と b_height がそれに該当します。
CreateWindow() が呼ばれる前にそれぞれの変数に値 200 と 50 が代入されていますので、結果的にプログラムの動作が変わらないのです。
一番最初の static int で始まる行が、「右の変数をこれから使いますよ」という宣言を行なっている部分です。

ボタンの位置決め(ウインドウサイズが変更された時のイベント)

さて、いよいよボタンの位置変更処理を行なうことにしますが、最初の位置を決めるだけならそれほど難しいことはありません。
先ほど修正していただいた部分の b_width の前にある 0, 0, という部分を適当な値に変更していただくだけでボタンの位置は変わります。
問題なのはウインドウサイズが変更された場合ですので、今回はそちらの処理を中心に見ていこうと思います。
とりあえず以下のような処理を追加してください(最初の方の1行と case WM_SIZE から break; までの青色の部分です)。

LRESULT CALLBACK WindowProcedure(...)
{
    static HWND hwndButton = 0;
    static int  b_width, b_height;
    RECT rc;

    switch (message)
    {
           case WM_CREATE:
           b_width = 200;
           b_height = 50;
           hwndButton = CreateWindow("button", "Click Here", 
                        WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
                        0, 0, b_width, b_height, hwnd, (HMENU)1,
                        ((LPCREATESTRUCT)lParam)->hInstance, NULL);
           return 0;
           break;

           case WM_SIZE:
           if (hwndButton && 
               (wParam == SIZEFULLSCREEN || 
                wParam == SIZENORMAL)
              ) {
               rc.left = (LOWORD(lParam) - b_width) / 2;
               rc.top = (HIWORD(lParam) - b_height) / 2;
               MoveWindow(hwndButton, rc.left, rc.top, 
                          b_width, b_height, TRUE);
           }
           break;

           case WM_COMMAND:
           if (LOWORD(wParam) == 1 &&
           :
    }
    return 0;
}

変更できたらコンパイル( Execute -> Compile )をして、タイプミスなどによるエラーがなければ実行( >> Execute << ボタンをクリック)してみてください。
今までと違ってボタンがウインドウの真ん中に表示されていませんか?さらにウインドウの縁をつかんでサイズを変更してみてください。サイズの変更に合わせてボタンの位置が変わると思います。

Button on Window

【起動直後、ボタンが真ん中にあります。】

Button on Window

【ウインドウサイズを変更しても、ボタンは真ん中にいます。】

と言うことで、期待通りの動きをするようになりました(嬉)。
さて、一体何がどうなっているのかを見ていくことにしましょうか。

まず、ウインドウサイズが変更された場合に発生するイベントです。これはもう見ていただければ分かると思いますが、WM_SIZE というものが発生しています。
よって、WM_SIZE が発生した時に必要な処理を行なえばいいわけです。

重要なのは rc.left から始まる3行(画面上では3行目が折り返しているために4行となってますが)です。
まず最初の2行でボタンの位置をウインドウの真ん中になるように決定しています。
ボタンの位置というのはボタンの左上の場所を指定することで決定されます。

例えば、幅が 100 のウインドウの真ん中に幅が 60 のボタンを置きたい場合、ボタンの左上の位置はどこになるでしょう?
全体の幅が 100 あって、そのうちボタンで使用されるのが 60 ですから、余白の部分は 100 - 60 で 40 となりますね。ボタンを真ん中に置くためには、この余白を半分に分けた場所、すなわち 40 / 2 で 20 の場所にすれば良いということになります。

Position Definition

もちろん、高さの場合でも考え方は同じですね。全体の長さから自分が使う分を引いて、残った長さを半分にすれば真ん中に配置できるというわけです。
LOWORD(lParam) がウインドウの幅、b_width がボタンの幅、HIWORD(lParam) がウインドウの高さ、b_height がボタンの高さを示しますので、追加していただいたような計算式となります。
この辺りは第5回でも軽く説明しましたが、実のところ私もまだ完全に理解できてなかったりします(汗)。

そして表示すべきボタンの座標が決まったところで、ボタンを移動させます。
移動させるために使用するのが MoveWindow() という関数で、第1引数が移動させたいウインドウ、第2、3引数が座標(X座標、Y座標)、第3、4引数がサイズ(幅、高さ)となっています。最後は現状では TRUE にするものだと思ってください。

こうして無事にボタンが真ん中に表示されるようになりました。
でも1つだけ気になることがあります。まだウインドウサイズを変更していない起動直後でもボタンがちゃんと真ん中にいますね( CreateWindow() の引数は 0, 0, のままなのに)。これはなぜでしょう?
理由は簡単で、ウインドウが作成された瞬間に設定された大きさにウインドウが自動的にサイズ変更されているのです。
ですので、起動直後にも WM_SIZE が発行されて、ボタンの位置が調整されるというわけです。

と言うことで、3回に分けてウインドウの上にボタンをつけるという処理について見てきました。ウインドウ操作の本当に基本的な部分は押さえられたかなと思っています。
しかしながら、お世辞にも使えるアプリケーションとは思えないのも事実です(笑)。

ですので、次回からはもう少しは役に立つ(?)アプリケーションを考えてみたいと思っています(しかも、できるだけ難しいことをせずに済むもので)。

しばらくネタを考えますので、おそらく来月はお休みすることになると思いま〜す。ごめんなさいね。

prev next


戻る