SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ATL/WTLを利用したVisual C++のWindowsプログラミング

ATL/WTLプログラミング 4:ダイアログリサイズ

ATL/WTLを利用したVisual C++のWindowsプログラミング


  • X ポスト
  • このエントリーをはてなブックマークに追加

スケルトン

 ATL/WTL AppWizardによって生成されたスケルトンには、次のような特徴があります。

ATL7.0未満の場合は「atlimpl.cpp」がインクルードされる

 以下に示すのは、ATL/WTL AppWizardによって生成された「stdafx.cpp」ファイルです。

stdafx.cpp
#include "stdafx.h"

#if (_ATL_VER < 0x0700)
#include <atlimpl.cpp>
#endif //(_ATL_VER < 0x0700)

 ATLのバージョンが7.0未満の場合は「atlimpl.cpp」をインクルードするようになっています。

 「atlimpl.cpp」では、_ATL_MIN_CRTが定義されている場合に独自のWinMainCRTStartup()などを呼び出します。これにより、ATLは可能な限りCランタイムライブラリ(CRT)を使用しなくなり、プログラムのファイルサイズが小さくなります。

_ATL_MIN_CRTについて
 ATL/WTL AppWizardで作成したプロジェクトのリリースビルドでは、デフォルトで_ATL_MIN_CRTが定義されています。これは、できる限りCランタイムライブラリを使用しないということを意味します。
 ただし、_ATL_MIN_CRTが定義されていると、一部のCランタイムライブラリ関数が使用できないことやC++例外処理ができないなどの制限があるため、本稿では_ATL_MIN_CRTを使用しません。このため、プロジェクトの設定で_ATL_MIN_CRTを削除します。
_ATL_MIN_CRTを削除
_ATL_MIN_CRTを削除

_tWinMain()にCOM初期化やMSLU用のコードが追加される

 ATL/WTL AppWizardによって生成された_tWinMain()では、COM用に::CoInitialize()/::CoUninitialize()の呼び出しやMSLU(Microsoft Layer for Unicode)用に::DefWindowProc()の呼び出しが追加されています。

 ところで、ATL/WTL AppWizard によって生成されたリソースはデフォルトで英語版です。これを日本語にするためには、リソースファイル(拡張子.rc)をテキストモードで開き、以下の部分を修正します。

リソースファイル修正前
/////////////////////////////////////////////////////////////////////
// English (U.S.) resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
リソースファイル修正後
/////////////////////////////////////////////////////////////////////
// 日本語 resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_JPN)
#ifdef _WIN32
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
#pragma code_page(932)
#endif //_WIN32

ソースコード

 以下に、このサンプルプログラムのソースコードを示します。

stdafx.h
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>

#include <atlctrls.h>
#include <atlctrlx.h>  // CSortListViewCtrlを使用するため
#include <atlframe.h>  // CDialogResizeを使用するため
maindlg.h
class CMainDlg : public CDialogImpl<CMainDlg>,
                 public CDialogResize<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CEdit m_edit_width;
    CEdit m_edit_height;
    CSortListViewCtrl m_list_window;  // ソートリストビュー

    // ダイアログリサイズマップ
    BEGIN_DLGRESIZE_MAP(CMainDlg)
        DLGRESIZE_CONTROL(IDC_STATIC_WIDTH, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_EDIT_WIDTH, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_STATIC_HEIGHT, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_EDIT_HEIGHT, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_BUTTON_APPLY, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_BUTTON_UPDATE, DLSZ_MOVE_X)
        DLGRESIZE_CONTROL(IDC_LIST_WINDOW, DLSZ_SIZE_X | DLSZ_SIZE_Y)
    END_DLGRESIZE_MAP()

    // メッセージマップ
    BEGIN_MSG_MAP_EX(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_APPLY, OnButtonApply)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_UPDATE, OnButtonUpdate)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
        // CDialogResizeクラスへのチェーン
        CHAIN_MSG_MAP(CDialogResize<CMainDlg>)
    END_MSG_MAP()

    LRESULT OnInitDialog(HWND hWnd, LPARAM lParam){
        // スクリーンの中央に配置
        CenterWindow();

        // 大きいアイコン設定
        HICON hIcon = AtlLoadIconImage(
            IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXICON),
            ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        
        // 小さいアイコン設定
        HICON hIconSmall = AtlLoadIconImage(
            IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXSMICON),
            ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // コントロール設定
        m_edit_width = GetDlgItem(IDC_EDIT_WIDTH);
        m_edit_height = GetDlgItem(IDC_EDIT_HEIGHT);
        m_list_window.SubclassWindow(GetDlgItem(IDC_LIST_WINDOW));

        m_list_window.SetExtendedListViewStyle(
          LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP);

        // ソートリストビューにカラム設定
        CRect rcList;
        m_list_window.GetWindowRect(rcList);
        int nScrollWidth = GetSystemMetrics(SM_CXVSCROLL);
        int n3DEdge = GetSystemMetrics(SM_CXEDGE);
        m_list_window.InsertColumn(
            0, _T("タイトル"), LVCFMT_LEFT,
            rcList.Width() - 100 - nScrollWidth - n3DEdge * 2, -1);
        m_list_window.InsertColumn(
            1, _T("幅"), LVCFMT_RIGHT, 50, -1);
        m_list_window.InsertColumn(
            2, _T("高さ"), LVCFMT_RIGHT, 50, -1);

        // ソートタイプ設定
        m_list_window.SetColumnSortType(0, LVCOLSORT_TEXT);
        m_list_window.SetColumnSortType(1, LVCOLSORT_LONG);
        m_list_window.SetColumnSortType(2, LVCOLSORT_LONG);

        ::EnumWindows((WNDENUMPROC)SetWindowListFunc,
            (LPARAM)&m_list_window);

        // ダイアログリサイズ初期化
        DlgResize_Init(true, true,
            WS_THICKFRAME | WS_MAXIMIZEBOX | WS_CLIPCHILDREN);

        return TRUE;
    }

    static BOOL CALLBACK SetWindowListFunc(HWND hWnd, LPARAM lParam){
        // ウィンドウクラス名取得
        TCHAR szClassName[256];
        ::GetClassName(hWnd,
            szClassName, sizeof(szClassName)/sizeof(TCHAR));

        if(lstrcmp(szClassName, _T("IEFrame")) == 0){
            CSortListViewCtrl* pList = (CSortListViewCtrl*)lParam;

            int index = pList->GetItemCount();

            // ウィンドウのタイトル名をリストに設定
            TCHAR szTitle[256];
            ::GetWindowText(hWnd, szTitle,
                sizeof(szTitle)/sizeof(TCHAR));
            pList->InsertItem(index, szTitle);

            // ウィンドウの幅と高さをリストに設定
            CRect rcWindow;
            ::GetWindowRect(hWnd, rcWindow);
            CString tmp;
            tmp.Format(_T("%d"), rcWindow.Width());
            pList->SetItemText(index, 1, tmp);
            tmp.Format(_T("%d"), rcWindow.Height());
            pList->SetItemText(index, 2, tmp);

            // ウィンドウハンドルをリストアイテムに設定
            pList->SetItemData(index, (DWORD)hWnd);
        }
        return TRUE;
    }

    void OnButtonApply(UINT uNotifyCode, int nID, HWND hWndCtl){
        int width = GetDlgItemInt(IDC_EDIT_WIDTH, NULL, FALSE);
        int height = GetDlgItemInt(IDC_EDIT_HEIGHT, NULL, FALSE);
        for(int i=0; i<m_list_window.GetItemCount(); i++){
            if(m_list_window.GetCheckState(i)){
                HWND hWnd = (HWND)m_list_window.GetItemData(i);
                ::SetWindowPos(hWnd, NULL, 0, 0, width, height,
                    SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
            }
        }
    }

    void OnButtonUpdate(UINT uNotifyCode, int nID, HWND hWndCtl){
        m_list_window.DeleteAllItems();
        ::EnumWindows((WNDENUMPROC)SetWindowListFunc,
                      (LPARAM)&m_list_window);
    }

    void OnOK(UINT uNotifyCode, int nID, HWND hWndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl){
        EndDialog(nID);
    }
};
proj04.cpp
#include "stdafx.h"

#include "resource.h"

#include "maindlg.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
    HRESULT hRes = ::CoInitialize(NULL);

    ATLASSERT(SUCCEEDED(hRes));

    ::DefWindowProc(NULL, 0, 0, 0L);

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES);

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    int nRet = 0;
    {
        CMainDlg dlgMain;
        nRet = dlgMain.DoModal();
    }

    _Module.Term();
    ::CoUninitialize();

    return nRet;
}

 まず、ダイアログリソースに以下のコントロールを配置し、IDを次のように設定します。なお、2つのエディットコントロールはプロパティで[番号]にチェックを入れ、リストビューコントロールはプロパティで表示を[レポート]に設定します。

リソース
コントロール名リソースID説明
リストビューIDC_LIST_WINDOWIEタイトル名リストビュー
スタティックIDC_STATIC_WIDTH[幅]ラベル
スタティックIDC_STATIC_HEIGHT[高さ]ラベル
エディットIDC_EDIT_WIDTH[幅]エディット
エディットIDC_EDIT_HEIGHT[高さ]エディット
ボタンIDC_BUTTON_APPLY[適用]ボタン
ボタンIDC_BUTTON_UPDATE[更新]ボタン

 次に、「stdafx.h」で「atlctrlx.h」と「atlframe.h」をインクルードします。これらはソートリストビューとダイアログリサイズを使用するために必要です。

 CMainDlgクラスでは、まず基底クラスにCDialogResizeを追加し、メッセージマップにCDialogResizeへのチェーンを追加します。CDialogResizeは内部にWM_SIZEメッセージハンドラを用意しており、ダイアログウィンドウサイズが変更されてWM_SIZEメッセージが送られてきたときに、ダイアログ上のコントロールの位置やサイズを調整します。

 コントロールをどのように調整するかは、ダイアログリサイズマップで指定します。

ダイアログリサイズマップ
BEGIN_DLGRESIZE_MAP(CMainDlg)
    DLGRESIZE_CONTROL(IDC_STATIC_WIDTH, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_EDIT_WIDTH, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_STATIC_HEIGHT, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_EDIT_HEIGHT, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_BUTTON_APPLY, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_BUTTON_UPDATE, DLSZ_MOVE_X)
    DLGRESIZE_CONTROL(IDC_LIST_WINDOW, DLSZ_SIZE_X | DLSZ_SIZE_Y)
END_DLGRESIZE_MAP()

 このマップにDLGRESIZE_CONTROLマクロでエントリを追加することで、特定のコントロールのサイズまたは位置、または両方を変更させることができます。

 DLGRESIZE_CONTROLマクロの第1引数にはコントロールIDを指定し、第2引数にはフラグを指定します。フラグには次のような種類があります。

DLGRESIZE_CONTROLマクロのフラグ
DLSZ_SIZE_Xダイアログの幅が変更された時にコントロールの幅を変更
DLSZ_SIZE_Yダイアログの高さが変更された時にコントロールの高さを変更
DLSZ_MOVE_Xダイアログの幅が変更された時にコントロールの位置を水平方向に移動
DLSZ_MOVE_Yダイアログの高さが変更された時にコントロールの位置を垂直方向に移動
DLSZ_REPAINTコントロールのサイズや位置が変更された時にコントロールを再描画

 ダイアログリサイズ用マクロにはコントロールをグループ化するマクロが用意されています。例えばダイアログに次のようにボタンが配置されていたとします。

BEGIN_DLGRESIZE_GROUP
BEGIN_DLGRESIZE_GROUP

 それぞれのボタンにはIDC_BUTTON1IDC_BUTTON4というIDを設定します。次に、ダイアログリサイズマクロに次のようなエントリを追加します。

BEGIN_DLGRESIZE_GROUPマクロ
BEGIN_DLGRESIZE_MAP(CMainDlg)
    // 4つのボタンをグループ化
    BEGIN_DLGRESIZE_GROUP()
        DLGRESIZE_CONTROL(IDC_BUTTON1, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_BUTTON2, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_BUTTON3, DLSZ_SIZE_X)
        DLGRESIZE_CONTROL(IDC_BUTTON4, DLSZ_SIZE_X)
    END_DLGRESIZE_GROUP()
END_DLGRESIZE_MAP()

 こうすると、ダイアログのサイズを変更した時に、グループ化された各ボタンのサイズと位置が、自動的に均等に配置されるようになります。

BEGIN_DLGRESIZE_GROUP
BEGIN_DLGRESIZE_GROUP

 次に、WM_INITDIALOGメッセージハンドラでDlgResize_Init()を呼び出します。第1引数はダイアログの右下にサイズ変更用のグリップを表示するかどうかを示すbool値、第2引数はダイアログが変更できる最小サイズをダイアログリソースのサイズに固定するかどうかを示すbool値、第3引数はダイアログに追加するウィンドウスタイルを指定します。リサイズ可能なダイアログを作成する場合はWS_THICKFRAMEスタイルを追加する必要があります。すべての引数は省略可能です。DlgResize_Init()の定義では、各引数のデフォルト引数は次のようになっています。

DlgResize_Init()のデフォルト引数
void DlgResize_Init(bool bAddGripper = true,
    bool bUseMinTrackSize = true,
    DWORD dwForceStyle = WS_CLIPCHILDREN)
{
    ...
    ...

 WM_INITDIALOGメッセージハンドラではソートリストビューも設定しています。

 まず、ダイアログ上のリストビューコントロールのハンドルを取得し、SubclassWindow()を呼び出してサブクラス化します。次に拡張リストビュースタイルを設定し、カラムを追加してから各カラムにソートタイプを設定します。

 ソートリストビューでは以下のソートタイプを設定することができます。

ソートタイプ
ソートタイプ意味オーバーライド可能な比較関数
LVCOLSORT_NONEソートしない-
LVCOLSORT_TEXTテキスト値としてソート(デフォルト)LVCompareText()
LVCOLSORT_TEXTNOCASE大文字と小文字を区別しないテキスト値としてソートLVCompareTextNoCase()
LVCOLSORT_LONGlong値としてソートLVCompareLong()
LVCOLSORT_DOUBLEdouble値としてソートLVCompareDouble()
LVCOLSORT_DECIMAL小数値としてソートLVCompareDecimal()
LVCOLSORT_DATETIME日時としてソートLVCompareDouble()
LVCOLSORT_DATE日付としてソートLVCompareDouble()
LVCOLSORT_TIME時間としてソートLVCompareDouble()
LVCOLSORT_CUSTOM独自のデータとしてソートLVCompareCustom()

 カラムにソートタイプを設定しなければ、デフォルトでLVCOLSORT_TEXTが設定されます。

 ソートリストビューでは、カラムをクリックすると比較関数が呼び出されます。比較関数はオーバーライド可能で、ユーザ独自の比較コードを書くことができます。なお、ソートタイプにLVCOLSORT_CUSTOMを設定した場合は、比較関数であるLVCompareCustom()の内部でCompareItemsCustom()が呼び出されるのでCompareItemsCustom()をオーバーライドします。

 ソートリストビュー設定後はEnumWindows()を呼び出し、リストに現在表示中のIEのタイトル名、幅、高さを追加します。さらにリストアイテムにはIEのウィンドウハンドルを関連付けておきます。

 [適用]ボタンを押したときのハンドラ関数OnButtonApply()では、ソートリストビュー内のチェックされたタイトル名のウィンドウサイズを変更します。

 [更新]ボタンを押したときのハンドラ関数OnButtonUpdate()では、リストアイテムをすべて削除してから再度IEウィンドウのタイトル名を追加します。

まとめ

 このように、CDialogResizeを使用すると簡単にコントロールの自動調整機能が付いたダイアログを作成することができます。なお、CDialogResizeはダイアログウィンドウだけではなく、フォームビューや通常のウィンドウにも利用できます。

最後に

 本稿では、ダイアログリサイズとソートリストビューコントロールを使用しました。次回はスプリッタウィンドウやペインコンテナを扱いたいと思います。

参考資料

  1. Windows Template Library (WTL) 7.1
  2. Windows Template Library (WTL)
  3. WTL support list
  4. The Code Project

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
ATL/WTLを利用したVisual C++のWindowsプログラミング連載記事一覧

もっと読む

この記事の著者

So()

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/262 2006/05/25 10:14

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング