Mesoscopic Programming

タコさんプログラミング専門

ファイルビュワー作成日誌 #002:メニューなどの追加

本日のメニュー

  1. メニュー追加
  2. キーボードアクセラレータ対応
  3. ツールバー追加
  4. ステータスバー追加
  5. プロファイル対応


実行画面と実行ファイルとソースファイル

  1. FileView.zip
  2. main.h
  3. main.cpp
  4. App.h
  5. App.cpp
  6. Window.h
  7. Window.cpp


メニューの作成

HMENU hTopMenu;
HMENU hFileMenu;
HMENU hEditMenu;
HMENU hViewMenu;
HMENU hHelpMenu;

BOOL App :: InitMenu()
{
    hFileMenu = CreateMenu();
    AppendMenu( hFileMenu, MF_STRING, idFileOpen,      _T( "開く(&O)...\tCtrl+O" ) );
    AppendMenu( hFileMenu, MF_STRING, idFileClose,     _T( "閉じる(&C)...\tCtrl+W" ) );
    AppendMenu( hFileMenu, MF_SEPARATOR, 0, NULL );
    AppendMenu( hFileMenu, MF_STRING, idFileExit,      _T( "終了(&X)\tAlt+F4" ) );

    hEditMenu = CreateMenu();
    AppendMenu( hEditMenu, MF_STRING, idEditCopy,      _T( "コピー(&C)\tCtrl+C" ) );
    AppendMenu( hEditMenu, MF_STRING, idEditSelectAll, _T( "すべて選択(&A)\tCtrl+A" ) );
    AppendMenu( hEditMenu, MF_STRING, idEditFind,      _T( "検索(&F)\tCtrl+F" ) );

    hViewMenu = CreateMenu();
    AppendMenu( hViewMenu, MF_STRING, idViewToolBar,   _T( "ツールバー(&T)" ) );
    AppendMenu( hViewMenu, MF_STRING, idViewStatBar,   _T( "ステータスバー(&S)" ) );

    hHelpMenu = CreateMenu();
    AppendMenu( hHelpMenu, MF_STRING, idHelpAbout,     _T( "アバウト(&A)...\tF1" ) );

    hTopMenu = CreateMenu();
    AppendMenu( hTopMenu, MF_POPUP, ( UINT_PTR ) hFileMenu, _T( "ファイル(&F)" ) );
    AppendMenu( hTopMenu, MF_POPUP, ( UINT_PTR ) hEditMenu, _T( "編集(&E)" ) );
    AppendMenu( hTopMenu, MF_POPUP, ( UINT_PTR ) hViewMenu, _T( "表示(&V)" ) );
    AppendMenu( hTopMenu, MF_POPUP, ( UINT_PTR ) hHelpMenu, _T( "ヘルプ(&H)" ) );
}
解説

ここではメニュー項目の作成を行うのみで表示状態に関する設定は行いません。
メニュー項目の表示状態は、状況に応じて動的に変化するものなので別の関数で行います。


キーボードアクセラレータへの対応

int App :: MessageLoop()
{
    static ACCEL accelTable[] =
    {
        { FVIRTKEY | FCONTROL, 'O',   idFileOpen },
        { FVIRTKEY | FCONTROL, 'W',   idFileClose },
        { FVIRTKEY,            VK_F1, idHelpAbout },
    };

    HACCEL haccel = CreateAcceleratorTable( accelTable, sizeof accelTable / sizeof ( ACCEL ) );
    MSG    msg;

    while ( GetMessage( & msg, NULL, 0, 0 ) )
    {
        if ( TranslateAccelerator( hwnd, haccel, & msg ) == 0 )
        {
            TranslateMessage( & msg );
            DispatchMessage( & msg );
        }
    }

    DestroyAcceleratorTable( haccel );

    return ( int ) msg.wParam;
}
解説

アクセラレータテーブルはメッセージループ内でしか使わないので関数ローカルにしました。
そもそもウィンドウズにはホットキー機能もあるので、キーボードアクセラレータで処理する必要なんてあるのかな?
キーボードアクセラレータだと状況に応じて動的に変化させるのがめんどくさいじゃないですか。
その点ホットキーなら1個1個登録と削除ができるので動的処理に向いている気がするんですよね。
なので動的に変化しない予定のショートカットキーだけをアクセラレータで処理するようにしました。


ツールバーとステータスバーの作成

    DWORD dwStyle;

    bToolBar = GetProfInt( GetSectionName(), pszKeyToolBar, TRUE );
    dwStyle  = bToolBar ? WS_VISIBLE : 0;
    dwStyle |= WS_CHILD | WS_CLIPSIBLINGS | TBSTYLE_TOOLTIPS | TBSTYLE_LIST;
    hToolBar = CreateWindow( TOOLBARCLASSNAME, NULL, dwStyle, 0, 0, 0, 0, hwnd, ( HMENU ) ( UINT_PTR ) idToolBar, GetModuleHandle( NULL ), 0 );

    bStatBar = GetProfInt( GetSectionName(), pszKeyStatBar, TRUE );
    dwStyle  = bStatBar ? WS_VISIBLE : 0;
    dwStyle |= WS_CHILD | WS_CLIPSIBLINGS | CCS_BOTTOM;
    hStatBar = CreateStatusWindow( dwStyle, NULL, hwnd, idStatBar );

    SendMessage( hToolBar, TB_SETEXTENDEDSTYLE, 0, ( LPARAM ) TBSTYLE_EX_MIXEDBUTTONS );
    SendMessage( hToolBar, TB_BUTTONSTRUCTSIZE, sizeof ( TBBUTTON ), 0 );
    SendMessage( hToolBar, TB_SETBUTTONSIZE, 0, 0 );
    SendMessage( hToolBar, TB_SETBITMAPSIZE, 0, 0 );
    SendMessage( hToolBar, TB_AUTOSIZE, 0, 0 );
解説

ステータスバーは特に説明する必要もないと思います。
ツールバーは空の状態で作成しています。
なぜならばメニュー同様、動的に変化するからです。
メニューの更新処理でツールバーボタンを登録と削除を行います。


プロファイル対応

レジストリとか良く分かんないしめんどくさそうなので、昔ながらのイニファイル使ってます。
何か問題でも?

INT Window :: GetProfInt( LPCTSTR section, LPCTSTR key, INT value )
{
    return GetPrivateProfileInt( section, key, value, pszIniFile );
}

VOID Window :: SetProfInt( LPCTSTR section, LPCTSTR key, INT value )
{
    TCHAR text[ 256 ];

    sprintf( text, _T( "%d" ), value );

    WritePrivateProfileString( section, key, text, pszIniFile );
}

BOOL Window :: ResumeWindowPos()
{
    INT left   = GetProfInt( GetSectionName(), pszKeyLeft,   0 );
    INT top    = GetProfInt( GetSectionName(), pszKeyTop,    0 );
    INT width  = GetProfInt( GetSectionName(), pszKeyWidth,  -1 );
    INT height = GetProfInt( GetSectionName(), pszKeyHeight, -1 );

    if ( width >= 0 && height >= 0 )
    {
        SetWindowPos( hwnd, 0, left, top, width, height, SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING );

        return TRUE;
    }

    return FALSE;
}

VOID Window :: SaveWindowPos()
{
    WINDOWPLACEMENT wp;

    GetWindowPlacement( hwnd, & wp );
    SetProfInt( GetSectionName(), pszKeyLeft,   wp.rcNormalPosition.left );
    SetProfInt( GetSectionName(), pszKeyTop,    wp.rcNormalPosition.top );
    SetProfInt( GetSectionName(), pszKeyWidth,  wp.rcNormalPosition.right  - wp.rcNormalPosition.left );
    SetProfInt( GetSectionName(), pszKeyHeight, wp.rcNormalPosition.bottom - wp.rcNormalPosition.top );
}
解説

ウィンドウの位置とサイズとか、表示状態フラグとかを保存するために使います。
特に難しいところはないと思います。


メニューバーとツールバーの更新

まだドキュメント処理未対応なので何も動的に変化する要素がありませんが、
メニューとツールバーは動的変化に対応させる必要があるので最初から準備しときます。

VOID App :: UpdateMenu()
{
    int         count = ( int ) SendMessage( hToolBar, TB_BUTTONCOUNT, 0, 0 );
    TBADDBITMAP tbbmp;
    int         idxstd;

    while ( count > 0 )
    {
        for ( int i = 0; i < count; i++ )
        {
            SendMessage( hToolBar, TB_DELETEBUTTON, i, 0 );
        }

        count = ( int ) SendMessage( hToolBar, TB_BUTTONCOUNT, 0, 0 );
    }

    tbbmp.hInst = HINST_COMMCTRL;
    tbbmp.nID   = IDB_STD_SMALL_COLOR;
    idxstd      = ( int ) SendMessage( hToolBar, TB_ADDBITMAP, 0, ( LPARAM ) & tbbmp );

    TBBUTTON tbbtns[] =
    {
        { idxstd + STD_FILEOPEN, idFileOpen,  0, BTNS_BUTTON },
        { idxstd + STD_COPY,     idEditCopy,  0, BTNS_BUTTON },
        { idxstd + STD_FIND,     idEditFind,  0, BTNS_BUTTON },
        { idxstd + STD_HELP,     idHelpAbout, 0, BTNS_BUTTON },
    };

    SendMessage( hToolBar, TB_ADDBUTTONS, ARRAYSIZE( tbbtns ), ( LPARAM ) tbbtns );

    GrayedMenu( hTopMenu );

    EnableMenuItem( hTopMenu,  menuFile,      MF_BYPOSITION | MF_ENABLED );
    EnableMenuItem( hTopMenu,  menuEdit,      MF_BYPOSITION | MF_ENABLED );
    EnableMenuItem( hTopMenu,  menuView,      MF_BYPOSITION | MF_ENABLED );
    EnableMenuItem( hTopMenu,  menuHelp,      MF_BYPOSITION | MF_ENABLED );
    EnableMenuItem( hFileMenu, idFileExit,    MF_BYCOMMAND  | MF_ENABLED );
    EnableMenuItem( hViewMenu, idViewToolBar, MF_BYCOMMAND  | MF_ENABLED );
    EnableMenuItem( hViewMenu, idViewStatBar, MF_BYCOMMAND  | MF_ENABLED );
    EnableMenuItem( hHelpMenu, idHelpAbout,   MF_BYCOMMAND  | MF_ENABLED );

    SendMessage( hToolBar, TB_SETSTATE, idHelpAbout, MAKELONG( TBSTATE_ENABLED, 0 ) );

    if ( bToolBar )
    {
        CheckMenuItem( hViewMenu, idViewToolBar, MF_BYCOMMAND | MF_CHECKED );
    }

    if ( bStatBar )
    {
        CheckMenuItem( hViewMenu, idViewStatBar, MF_BYCOMMAND | MF_CHECKED );
    }

    DrawMenuBar( hwnd );
}
解説

ツールバーはメニューと違って登録したボタンの数だけ表示領域を必要とするので、
状況に応じてボタンを登録したりしなかったりする必要があります。
なので最初に現在登録中のボタンを全部削除しています。
でもなぜか一発で削除しきれない不具合があるので、本当に登録数がゼロになったか確認しつつ何度も繰り返し削除しています。
メニュー項目もいったんすべて無効状態にしてチェックも外します。
そんで現在のフラグなどに応じて有効とチェックを行います。


以上です。