ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail

オートリサイズ

Last modified: Sat Apr 05 04:42:11 2003 PDT

一つ上へ

オートリサイズクラス

ダイアログの大きさをユーザが変更出来るようにしたとき、いちいちコントロールの配置を手で変えるのは面倒です。 というわけで、いくつかの自作アプリで使っている簡易オートリサイズクラスを公開。

面倒なので、インライン関数で全部ヘッダに書いています。 インクルードするだけで済むし。

使い方

準備

ダイアログを、CMyDialog クラスとします。 ヘッダで autoresize.h をインクルードして、CMyDialog クラスに CAutoResize クラスのインスタンスを作成します。

#include "autoresize.h"
class CMyDialog : public CDialog {
    ...
protected:
    hirofumi::CAutoResize m_autoResize;
};

また、MFC のコンテナテンプレートを使うので、stdafx.h に次のように記述しておくとコンパイルが速くなります。
#include <afxtempl.h>

リサイズしたいチャイルドウィンドウの指定

ダイアログの場合、WM_INITDIALOG のハンドラ OnInitDialog() で、自動リサイズしたいコントロールを CAutoResize に教えてやります。

BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    ...
 // オートサイズの登録
 VERIFY(
  m_autoResize.AddSnapToBottom(&m_proposal) &&
  m_autoResize.AddSnapToRightBottom(&m_showbox) &&
  m_autoResize.AddSnapToBottom(GetDlgItem(IDC_CAPTION_TRANSLUCENCY), hirofumi::NO_VERT_RESIZE) &&
  m_autoResize.AddSnapToBottom(&m_translucency, hirofumi::NO_VERT_RESIZE)
 );
    ...
}

ビュー上のチャイルドウィンドウをリサイズしたい場合は、OnInitialUpdate() で行うのが適当でしょう。

リサイズの実行

ペアレントウィンドウの OnSize() で、CAutoResize::Resize() を呼んでやります。

void CMyDialog::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);

    if (m_hWnd == NULL)
        return;

    if (nType != SIZE_RESTORED)
        return;

    m_autoResize.Resize(this);
}

ウィンドウの登録オプション

ウィンドウのどの縁を基点とするか、リサイズを許すか、というオプションを選べます。

  bool AddSnap(CWnd* wnd, CRect rc, UINT resizeSpec)
resizeSpecに渡せるのはまず、LEFT, RIGHT, TOP, BOTTOM の OR です。どの辺を基準にするか指定します。 LEFT | RIGHT のような指定も出来ます。 左右の辺からの距離が固定になり、チャイルドウィンドウの幅が変化します。
さらに、NO_HOR_RESIZE と NO_VERT_RESIZE を OR で渡します。 NO_xxx_RESIZE が指定されている場合にのみ、チャイルドウィンドウの幅・高さは変化しません。

フラグの組み合わせによってはエラーになります。 例えば、LEFT | RIGHT | NO_HOR_RESIZE の組み合わせでは、リサイズしようがありません。

RIGHT のみが指定されている場合、その動作は LEFT | RIGHT と同じになります。

コンビニエンス関数

いくつかコンビニエンス関数を用意しています。 例えば、ウィンドウの右辺を基点とし、リサイズありで登録する場合は、AddSnapToRight(CWnd* wnd) を呼び出します。 この場合、チャイルドウィンドウのTOP/LEFTのオフセットは変化せず、RIGHT がペアレント ウィンドウに従って変化します。 チャイルドウィンドウの右辺とペアレントウィンドウの右辺の距離は、AddSnapToRight() を呼び出したときの距離になります。

制限

現在のインプリメントでは、基準となるウィンドウを渡しておらず、チャイルドウィンドウの直接の親を暗黙の基点として利用することになっています。

//
// AutoResize.h
//
// (extracted from 03test/ecalc/atresize.[h,cpp]
//
// Copyright (C) 1998-2001 by Hirofumi Yamamoto
// All rights reserved
//
//
 
#ifndef AUTORESIZE_H
#define AUTORESIZE_H
 
#ifndef __AFXTEMPL_H__
#include <afxtempl.h>
#endif
 
namespace hirofumi {
    enum ResizeSpecifier {
        LEFT = 0x01, RIGHT = 0x02, TOP = 0x04, BOTTOM = 0x08,
        NO_HOR_RESIZE = 0x10, NO_VERT_RESIZE = 0x20,
    };
 
    struct ResizeSpec {
        HWND m_hwnd;
        CRect m_rc;
        UINT m_resizeSpec;
    };
 
    class CAutoResize
    {
    public:
        CAutoResize()
        {
            TRACE0("CAutoResize::ctor\n");
            m_hwndParent = NULL;
        }
        CAutoResize(CWnd* wndParent)
        {
            ASSERT(wndParent);
            ASSERT(IsWindow(wndParent->GetSafeHwnd()));
            m_hwndParent = wndParent->GetSafeHwnd();
        }
        virtual ~CAutoResize()
        {
            if (m_list.GetSize()) {
                m_list.RemoveAll();
            }
        }
 
        bool AddSnap(CWnd* wnd, CRect rc, UINT resizeSpec)
        {
            HWND hwnd = wnd->GetSafeHwnd();
            if (!::IsWindow(hwnd)) {
                TRACE1("CAutoResize::AddSnap: hwnd=%x is not a window!\n", hwnd);
                return false;
            }
 
            for (int i = 0; i < m_list.GetSize(); ++i) {
                if (m_list[i].m_hwnd == hwnd) {
                    break;
                }
            }
            if (resizeSpec == 0) {
                if (i < m_list.GetSize()) {
                    TRACE1("CAutoResize::AddSnap: removing hwnd=%x\n", hwnd);
                    m_list.RemoveAt(i);
                    return true;
                }
                TRACE1("CAutoResize::AddSnap: failed to remove hwnd=%x, not registered.\n", hwnd);
                return false;
            }
 
            if ((resizeSpec & (LEFT | RIGHT)) == (LEFT | RIGHT)  && (resizeSpec & NO_HOR_RESIZE)) {
                TRACE1("CAutoResize::AddSnap: both LEFT and RIGHT are specified, but NO_HOR_RESIZE is also specified. %x\n",
                    resizeSpec);
                return false;
            }
            if ((resizeSpec & (TOP | BOTTOM)) == (TOP | BOTTOM) && (resizeSpec & NO_VERT_RESIZE)) {
                TRACE1("CAutoResize::AddSnap: both TOP and BOTTOM are specified, but NO_VERT_RESIZE is also specified. %x\n",
                    resizeSpec);
                return false;
            }
 
            if (rc == CRect(INT_MIN, INT_MIN, INT_MIN, INT_MIN)) {
                CWnd* wndParent = wnd->GetParent();
                if (wndParent == NULL) {
                    TRACE0("AddSnapTo: no parent window!\n");
                    return false;
                }
                CRect rcParent;
                wndParent->GetClientRect(&rcParent);
                wnd->GetWindowRect(&rc);
                wndParent->ScreenToClient(&rc);
                // no need to calc left/top, 'cause rcParent is always (0,0)-(?,?).
                rc.BottomRight() = rcParent.BottomRight() - rc.BottomRight();
            }
 
            if (i >= m_list.GetSize()) {
                hirofumi::ResizeSpec spec;
                spec.m_hwnd = NULL;
                m_list.Add(spec);
            }
 
            m_list[i].m_hwnd = hwnd;
            m_list[i].m_rc = rc;
            m_list[i].m_resizeSpec = resizeSpec;
 
            return true;
        }
 
        HWND SetParentWindow(CWnd* wnd)
        {
            ASSERT(wnd);
            ASSERT(IsWindow(wnd->GetSafeHwnd()));
            HWND hwndOld = m_hwndParent;
            m_hwndParent = wnd->GetSafeHwnd();
            return hwndOld;
        }
 
        CWnd* WindowFromId(UINT id)
        {
            ASSERT(m_hwndParent);
            CWnd* wnd = CWnd::FromHandle(m_hwndParent);
            if (wnd == NULL) {
                return NULL;
            }
            wnd = wnd->GetDlgItem(id);
            return wnd;
        }
 
        bool AddSnapToRight(CWnd* wnd, UINT opt = 0, int cx = INT_MIN)
        {
            return AddSnap(wnd, CRect(INT_MIN, INT_MIN, cx, INT_MIN), RIGHT | opt);
        }
        bool AddSnapToRight(UINT id, UINT opt = 0, int cx = INT_MIN)
        {
            CWnd* wnd = WindowFromId(id);
            if (wnd == NULL) {
                return false;
            }
            return AddSnapToRight(wnd, opt, cx);
        }
 
        bool AddSnapToBottom(CWnd* wnd, UINT opt = 0, int cy = INT_MIN)
        {
            return AddSnap(wnd, CRect(INT_MIN, INT_MIN, INT_MIN, cy), BOTTOM | opt);
        }
        bool AddSnapToBottom(UINT id, UINT opt = 0, int cy = INT_MIN)
        {
            CWnd* wnd = WindowFromId(id);
            if (wnd == NULL) {
                return false;
            }
            return AddSnapToBottom(wnd, opt, cy);
        }
 
        bool AddSnapToRightBottom(CWnd* wnd, UINT opt = 0, int cx = INT_MIN, int cy = INT_MIN)
        {
            return AddSnap(wnd, CRect(INT_MIN, INT_MIN, cx, cy), RIGHT | BOTTOM | opt);
        }
        bool AddSnapToRightBottom(UINT id, UINT opt = 0, int cx = INT_MIN, int cy = INT_MIN)
        {
            CWnd* wnd = WindowFromId(id);
            if (wnd == NULL) {
                return false;
            }
            return AddSnapToRightBottom(wnd, opt, cx, cy);
        }
 
 
        bool Resize(CWnd* parent = NULL)
        {
            //
            // This version uses DeferWindowPos
            //
            HDWP hdwp = BeginDeferWindowPos(m_list.GetSize());
            if (hdwp == NULL) {
                return false;
            }
 
            if (parent == NULL) {
                ASSERT(m_hwndParent);
                parent = CWnd::FromHandle(m_hwndParent);
                ASSERT(parent);
                if (parent == NULL) {
                    return false;
                }
            }
 
 
            CRect rc;
            parent->GetClientRect(&rc);
            for (int i = 0; i < m_list.GetSize(); ++i) {
                hirofumi::ResizeSpec& spec = m_list[i];
                ASSERT(spec.m_resizeSpec != 0);
 
                CWnd* wnd = CWnd::FromHandle(spec.m_hwnd);
                CRect rcNew;
                wnd->GetWindowRect(&rcNew);
                parent->ScreenToClient(&rcNew);
 
                int width = rcNew.Width();
                int height = rcNew.Height();
 
 
                if (spec.m_resizeSpec & LEFT) {
                    rcNew.left = rc.left + spec.m_rc. left;
                    if (spec.m_resizeSpec & NO_HOR_RESIZE) {
                        rcNew.right = rcNew.left + width;
                    }
                }
                if (spec.m_resizeSpec & RIGHT) {
                    rcNew.right = rc.right - spec.m_rc. right;
                    if (spec.m_resizeSpec & NO_HOR_RESIZE) {
                        rcNew.left = rcNew.right - width;
                    }
                }
                if (spec.m_resizeSpec & TOP) {
                    rcNew.top = rc.top + spec.m_rc.right;
                    if (spec.m_resizeSpec & NO_VERT_RESIZE) {
                        rcNew.bottom = rcNew.top + height;
                    }
                }
                if (spec.m_resizeSpec & BOTTOM) {
                    rcNew.bottom = rc.bottom - spec.m_rc. bottom;
                    if (spec.m_resizeSpec & NO_VERT_RESIZE) {
                        rcNew.top = rcNew.bottom - height;
                    }
                }
                hdwp = DeferWindowPos(hdwp, wnd->GetSafeHwnd(), NULL, rcNew.left, rcNew.top, rcNew. Width(), rcNew.Height(), SWP_NOZORDER);
                if (hdwp == NULL) {
                    return false;
                }
            }
 
            EndDeferWindowPos(hdwp);
 
            return true;
        }
 
    protected:
        CArray<hirofumi::ResizeSpec, hirofumi::ResizeSpec&> m_list;
        HWND m_hwndParent;
    };
 
}   // namespace hirofumi
 
#endif  // AUTORESIZE_H

他にやっておきたいこと

ダイアログのリサイズでは、デザインした大きさよりも小さくさせないのが無難です。 というわけで、次のようなコードを追加します。

class CMyDialog : public CDialog {
    ...
protected:
    CSize m_szMin;
};


BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();
    ...
    // 最小サイズを記録しておく
    CRect rc;
    GetWindowRect(&rc);
    m_szMin = rc.Size();
    ...
}

void CMyDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
    if (m_hWnd) {
        lpMMI->ptMinTrackSize.x = m_szMin.cx;
        lpMMI->ptMinTrackSize.y = m_szMin.cy;
    }

    CDialog::OnGetMinMaxInfo(lpMMI);
}

あぁそっか、この辺も含めてクラスにまとめてしまえばよかったんだ。 また今度。

あ、最後に蛇足ですが、リソースエディタでダイアログをリサイズ可にしておかないと意味ありません。 …なんて言うまでもないですね。ほんとに蛇足でした。

Since 1996

一つ上へ

ホーム  ざれごと  ワシントン州  ツール  NT豆知識  Win32プログラミングノート  私的用語  ジョーク  いろいろ  ゲーム雑記  Favorites  掲示板   Mail