SDK Index Previous page Next page

タスクバーみたいなウインドウを作る その1


はじめに

タスクバーやofficeバーのように、デスクトップの一部分を占めるようなウインドウの作り方を説明します。ただし、今回のプログラムはできるだけ短くするため最低限の機能しかありません。それ以外の機能はその2以降で説明します。

サンプルプログラムはここです。

1 今回の仕様

バーの位置は画面上部固定で、サイズ変更だけできます。一部必要なところまでカットしているので少々おかしくなります。

2 原理

今回作成するようなウインドウはApplication Desktop Toolbar(以下AppBar)といいます。AppBarを作る手順は、

1.普通にウインドウを作る
2.AppBarとして、システムに登録する
3.大きさをシステムに登録する
4.要らなくなったら、システムから登録解除する

のようになります。

そして、これらは全てSHAppBarMessageという関数を使います。

3 ウインドウを作る

まずは、ウインドウを作ります。ウインドウスタイルはどんなものでもかまいませんが、WS_EX_TOOLWINDOWは付けておきましょう。後、サイズ変更をするのならWS_THICKFRAMEも必要です。

HWND InitWindow(void)
{
    WNDCLASS    wndclass;
    HWND        hWnd;

    wndclass.style          = 0;
    wndclass.lpfnWndProc    = (WNDPROC) WndProc;
    wndclass.cbClsExtra     = 0;
    wndclass.cbWndExtra     = 0;
    wndclass.hInstance      = Instance;
#ifdef IDI_ICON1
    wndclass.hIcon          = LoadIcon(Instance, MAKEINTRESOURCE(IDI_ICON1));
#else
    wndclass.hIcon          = NULL;
#endif
    wndclass.hCursor        = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground  = (HBRUSH)(COLOR_BTNFACE+1);
    wndclass.lpszMenuName   = NULL;
    wndclass.lpszClassName  = szClassName;

    if (!RegisterClass (&wndclass))     return NULL;


    hWnd = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
                          szClassName,
                          szAppName,
                          WS_POPUP | WS_THICKFRAME | WS_CLIPCHILDREN,
                          CW_USEDEFAULT,
                          CW_USEDEFAULT,
                          400,
                          50,
                          NULL,
                          NULL,
                          Instance,
                          NULL);
    return  hWnd;

}
大したことはしていないので、見なくてもいいと思います。ウインドウのサイズは高さだけが必要です。

4 ウインドウプロシージャ

次に、ウインドウプロシージャの説明をします。AppBarの登録等はここから呼び出します。
int CALLBACK WndProc(HWND hWnd, unsigned wMessage,WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    switch (wMessage)
    {
        case WM_CREATE:
            //AppBarの登録
            AppBar_Register(hWnd);
            //AppBarのサイズ設定
            AppBar_PosChanged(hWnd);
            return  0;

        case WM_DESTROY:
            //AppBarの登録解除
            AppBar_UnRegister(hWnd);
            PostQuitMessage(0);
            return  0;

        case WM_ACTIVATE:
            AppBar_Activate(hWnd);
            return  0;

        case WM_WINDOWPOSCHANGED:
            AppBar_WindowPosChanged(hWnd);
            break;

        case WM_EXITSIZEMOVE:
            AppBar_PosChanged(hWnd);
            break;

        case WM_NCHITTEST:
            return  AppBar_NcHittest(hWnd,wParam,lParam);

        case WM_GETMINMAXINFO:
            AppBar_GetMinMaxInfo(hWnd,wParam,lParam);
            return  0;

        case WM_PAINT:
            hdc = BeginPaint (hWnd, &ps);
            EndPaint (hWnd, &ps);
            return  0;

        case APPBAR_CALLBACK:
            AppBar_Callback(hWnd,wParam,lParam);
            return  0;
    }
    return (DefWindowProc(hWnd, wMessage, wParam, lParam));
}
いろいろなメッセージに応答していますので、一つ一つ説明していきます。

WM_CREATE

ウィンドウができたときに、AppBarの登録をします。登録には、AppBar_Registerという関数を作ったのでそれを使います。登録したら、次にそのウインドウを画面上部に配置します。AppBar_PosChangedという関数内で配置しています。

WM_DESTROY

ウインドウがなくなる前に、登録を解除します。AppBar_UnRegister関数でそれをします。

WM_ACTIVATE,WM_WINDOWPOSCHANGED

この2つのメッセージがきたときには、SHAppBarMessageを呼ばないといけないらしいので、呼びます。

WM_EXITSIZEMOVE

ウインドウのサイズがユーザーによって変更されたら、それをシステムに教えないといけないので、ここでAppBar_PosChanged関数を呼んでいます。WM_SIZEでもかまいませんが、こちらの方がサイズの移動が終わるまで更新されないのでレスポンスがよくなります。

WM_NCHITTEST

画面上部に配置されているときには、下方向以外にはサイズ変更できないようにしたいですね。ここでは、マウスの位置を調べて、ウインドウの下以外ではサイズ変更できないようにしています。

WM_GETMINMAXINFO

あまりウインドウが小さくなりすぎると困る(サイズ変更ができなくなる)ので、最小サイズを調整します。

WM_PAINT

好きにしてやってください。

APPBAR_CALLBACK

AppBarのサイズ変更要請等、システムから何か言ってくるときにはこのメッセージがきます。このメッセージは、自分で勝手に定義したもので、AppBar登録の時に設定します。

5 関数の説明

SHAppBarMessage

AppBarに関する全てのことをやってくれるAPIです。この関数は2つの引数をとります。1つ目の引数には、どのようなことをするかというコマンドを指定します。2つ目の引数には、AppBarに関する情報が入ったAPPBARDATA構造体を指定します。

AppBar_Register

AppBarを登録するための関数です。引数にウインドウハンドルを渡すとそのウインドウをAppBarとして登録してくれます。
APPBARDATA構造体に、構造体サイズ、ウインドウハンドル、何かあったときに送られてくるメッセージをセットして、SHAppBarMessageを呼び出します。このとき、コマンドにはABM_NEW(登録)を指定します。

//App Bar登録
BOOL AppBar_Register(HWND hWnd)
{
    APPBARDATA abd;
    
    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = hWnd;
    abd.uCallbackMessage = APPBAR_CALLBACK;

    return SHAppBarMessage(ABM_NEW, &abd);
}

AppBar_UnRegister

AppBarを登録解除するための関数です。引数にウインドウハンドルを渡すとそのウインドウをAppBarから登録解除してくれます。
APPBARDATA構造体に、構造体サイズ、ウインドウハンドルをセットして、SHAppBarMessageを呼び出します。このとき、コマンドにはABM_REMOVE(登録解除)を指定します。

//App Bar登録解除
BOOL AppBar_UnRegister(HWND hWnd)
{
    APPBARDATA abd;
    
    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = hWnd;

    return  SHAppBarMessage(ABM_REMOVE, &abd);
}       

AppBar_PosChanged

AppBarを画面の上部にきちんと配置する関数です。この関数では、APPBARDATA構造体、大きさ、配置(今回は上端のみ)を設定して、AppBar_QuerySetPosという関数を呼んでいます。この関数から戻ってきたときには、構造体の中にAppBarの大きさがセットされています。

//App Barの位置とサイズを設定(AppBar_QuerySetPosを使いやすくしたもの)
void AppBar_PosChanged(HWND hWnd)
{
    RECT        rc;
    SIZE        size;
    APPBARDATA  abd;

    abd.cbSize = sizeof(abd);
    abd.hWnd = hWnd;

    GetWindowRect(hWnd,&rc);

    size.cx=rc.right-rc.left;
    size.cy=rc.bottom-rc.top;

    AppBar_QuerySetPos(ABE_TOP, &size, &abd);

}

AppBar_QuerySetPos

AppBar_PosChangedから呼ばれる関数です。2つに分ける意味がないような気がしますが気にしないでください。この関数では、指定された配置(上とか右とか)と大きさから、とりあえずウインドウの場所を決めます。しかし、その位置にほかのAppBarがあった場合などには、大きさを調節しないといけないので。コマンドABM_QUERYPOSを指定してSHAppBarMessage関数を呼びます。そうすると、システムが大きさを調節してくれます。
ただ、この調節は位置を動かさずに大きさを調整するので、大きさを戻してやります。ただし、このとき画面の内側に大きくなるように戻します。たとえば、上端に配置する場合は下方向に大きくします。

こうして大きさが確定したら、今度はコマンドABM_SETPOSを指定してSHAppBarMessageを呼びます。これで新しい位置と大きさがシステムに設定されます。この関数では、ウインドウの大きさは変えてくれないのでMoveWindowで位置と大きさを設定します。

//App Barの位置とサイズを設定
void AppBar_QuerySetPos(UINT uEdge, LPSIZE lpsize, PAPPBARDATA pabd) 
{ 
    int iHeight = 0; 
    int iWidth = 0; 
 
    pabd->uEdge = uEdge; 
 
    pabd->rc.top = 0; 
    pabd->rc.bottom = GetSystemMetrics(SM_CYSCREEN); 
    pabd->rc.left = 0; 
    pabd->rc.right = GetSystemMetrics(SM_CXSCREEN); 

    switch(uEdge){
        case ABE_LEFT:
            pabd->rc.right=pabd->rc.left+lpsize->cx;
            break;
        case ABE_RIGHT:
            pabd->rc.left=pabd->rc.right-lpsize->cx;
            break;
        case ABE_TOP:
            pabd->rc.bottom=pabd->rc.top+lpsize->cy;
            break;
        case ABE_BOTTOM:
            pabd->rc.top=pabd->rc.bottom-lpsize->cy;
            break;
    }

    //システムにサイズを調整してもらう
    SHAppBarMessage(ABM_QUERYPOS, pabd); 

    switch (uEdge) { 
        case ABE_LEFT: 
            pabd->rc.right = pabd->rc.left + lpsize->cx; 
            break; 
 
        case ABE_RIGHT: 
            pabd->rc.left = pabd->rc.right - lpsize->cx; 
            break; 
 
        case ABE_TOP: 
            pabd->rc.bottom = pabd->rc.top + lpsize->cy; 
            break; 
 
        case ABE_BOTTOM: 
            pabd->rc.top = pabd->rc.bottom - lpsize->cy; 
            break; 
    } 

    //最終的なサイズをシステムに伝える
    SHAppBarMessage(ABM_SETPOS, pabd); 

    //ウインドウの大きさを変える
    MoveWindow(pabd->hWnd, pabd->rc.left, pabd->rc.top, 
        pabd->rc.right - pabd->rc.left, 
        pabd->rc.bottom - pabd->rc.top, TRUE); 
}

AppBar_Activate,AppBar_WindowPosChanged

WM_ACTIVATEとWM_WINDOWPOSCHANGEDメッセージがきたら、それぞれABM_ACTIVATEコマンドとABM_WINDOWPOSCHANGEDコマンドを送らないといけないので送ります。一つにまとめてもいいような関数です。

//WM_ACTIVATEの時の処理
void AppBar_Activate(HWND hWnd)
{
    APPBARDATA abd;
    
    // Always send the activate message to the system
    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = hWnd;
    abd.lParam = 0;
    SHAppBarMessage(ABM_ACTIVATE, &abd);
}

//WM_WINDOWPOSCHANGEDの時の処理
void AppBar_WindowPosChanged(HWND hWnd)
{
    APPBARDATA abd;

    abd.cbSize = sizeof(APPBARDATA);
    abd.hWnd = hWnd;
    abd.lParam = 0;

    SHAppBarMessage(ABM_WINDOWPOSCHANGED, &abd);
}

AppBar_NcHittest

ウインドウの端をドラッグするとサイズが変更できますが、AppBarの場合特定の方向だけ変更できればいいので、それ以外の方向でのサイズ変更をできないようにします。今回は、上端固定なので短くなっていますが、本当ならちゃんと場合分けをしないといけません。

//WM_WINDOWPOSCHANGEDの時の処理
//変な側をサイズ変更できないようにする
int AppBar_NcHittest(HWND hWnd,WPARAM wParam,LPARAM lParam)
{
    LRESULT lHitTest;

    lHitTest=DefWindowProc(hWnd, WM_NCHITTEST, wParam, lParam);

    if (lHitTest == HTBOTTOM) return HTBOTTOM;

    return HTCLIENT;                            
}

AppBar_GetMinMaxInfo

ウインドウの大きさが小さくなりすぎると、サイズ変更ができなくなってしまうのである程度の大きさ以下にはならないようにします。MINMAXINFOの最小高さを40にしているだけです。

//小さくなりすぎると困るので、最小値の設定
void AppBar_GetMinMaxInfo(HWND hWnd,WPARAM wParam,LPARAM lParam)
{
    ((LPMINMAXINFO) lParam)->ptMinTrackSize.y=40;
}

AppBar_Callback

AppBarの位置や設定を変えなければいけないときには、システムからAppBar登録の時に設定したメッセージが送られてきます。ここでは、そのメッセージに対する処理をします。
今回は、ABN_POSCHANGED(サイズ変更要請)しか処理していませんが、ほかにも処理しないといけないメッセージがあります。それらについては、次回に説明します。

//App Barからのメッセージの処理
void AppBar_Callback(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    switch(wParam){
        case ABN_POSCHANGED:
            AppBar_PosChanged(hWnd);
            break;
    }
}

おわりに

かなり短くしたと思ったのですが、思ったよりも説明が長くなってしまいました。次回からは、配置の変更等の機能を少しずつ付けていこうと思います。


to sdk prev next