如何在MFC中创建可调整大小的CDialog?

29

我需要创建基于对话框的应用程序,而不是旧的CFormView类型的设计。但是,CDialog会生成固定大小的对话框。我该如何创建具有可调整大小对话框的基于对话框的应用程序?


这个问题与C++无关,因此我已经移除了那个标签。 - Ashwin Nanjappa
也许可以在这个上面加一个Win32标签,RC文件的内容是基本的Win32而不是特定于MFC的。 - Aardvark
@Aardvark:RC的东西可能不是仅限于MFC,但标题指定了MFC,CDialog(在问题中指定)是MFC类(请参阅MSVS帮助),而一些好的答案需要与MFC方法进行相当大的交互 - 因此让它保持不变是正确的。至于C++,MSVS为MFC生成C++代码,但它是否为任何其他语言生成C++代码呢?如果没有,[C++]就不是完全无关紧要-但是多余的,所以公平的做法。 - PJTraill
@PJTraill,保留MFC的标签是正确的吗?我建议(这么多年过去了,你现在为什么要插话?)添加一个win32/winapi的标签,而不是删除MFC的标签。 - Aardvark
@Aardvark:是的,让标签保持不变是正确的。我回复是因为我认为“甚至不是MFC特定的”是指整个问题。我不认为评论的年龄有关系。我的评论不仅是针对你,也是为了任何考虑问题+评论+标签的人,希望你没有被冒犯到。 - PJTraill
7个回答

27

如果在RC资源文件中,对话框的样式类似于这种,则其大小将是固定的:

IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU

如果对话框具有这种样式,则可以改变其大小:

IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME

有了这些可调整大小的框架选项,对话框将是可调整大小的,但您仍需要处理许多工作来处理 WM_SIZE 消息,以管理对话框内控件的大小和位置。


21
我建议您不要直接编辑.rc文件 - 尽管格式似乎足够简单,但解析器非常挑剔。此外,由于您正在使用 MFC,可以安全地假设您也在使用 Visual Studio。在这种情况下,您应该在Visual Studio中打开对话框资源,在对话框属性下选择"Resizing"作为"Border"属性即可。 - Nik Bougalis

25

除了将样式设置为WS_THICKFRAME之外,您还可能希望在对话框调整大小时有一个移动和调整大小控件的系统。为了我的个人使用,我创建了一个基类来替换CDialog,并具有此功能。从这个类派生,并在您的InitDialog函数中为每个子控件调用AutoMove函数,以定义它应该相对于父对话框移动和调整大小的量。资源文件中对话框的大小用作最小大小。

BaseDialog.h:

#if !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
#define AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <vector>

class CBaseDialog : public CDialog
{
// Construction
public:
    CBaseDialog(UINT nIDTemplate, CWnd* pParent = NULL);   // standard constructor

    void AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct);

// Overrides
    // ClassWizard generated virtual function overrides
    //{{AFX_VIRTUAL(CBaseDialog)
protected:
    //}}AFX_VIRTUAL

protected:
    //{{AFX_MSG(CBaseDialog)
    virtual BOOL OnInitDialog();
    afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()

public:
    bool            m_bShowGripper;         // ignored if not WS_THICKFRAME

private:
    struct SMovingChild
    {
        HWND        m_hWnd;
        double      m_dXMoveFrac;
        double      m_dYMoveFrac;
        double      m_dXSizeFrac;
        double      m_dYSizeFrac;
        CRect       m_rcInitial;
    };
    typedef std::vector<SMovingChild>   MovingChildren;

    MovingChildren  m_MovingChildren;
    CSize           m_szInitial;
    CSize           m_szMinimum;
    HWND            m_hGripper;
};

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)

BaseDialog.cpp:

#include "stdafx.h"
#include "BaseDialog.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CBaseDialog::CBaseDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
    : CDialog(nIDTemplate, pParent),
      m_bShowGripper(true),
      m_szMinimum(0, 0),
      m_hGripper(NULL)
{
}


BEGIN_MESSAGE_MAP(CBaseDialog, CDialog)
    //{{AFX_MSG_MAP(CBaseDialog)
    ON_WM_GETMINMAXINFO()
    ON_WM_SIZE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CBaseDialog::AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct)
{
    ASSERT((dXMovePct + dXSizePct) <= 100.0);   // can't use more than 100% of the resize for the child
    ASSERT((dYMovePct + dYSizePct) <= 100.0);   // can't use more than 100% of the resize for the child
    SMovingChild s;
    GetDlgItem(iID, &s.m_hWnd);
    ASSERT(s.m_hWnd != NULL);
    s.m_dXMoveFrac = dXMovePct / 100.0;
    s.m_dYMoveFrac = dYMovePct / 100.0;
    s.m_dXSizeFrac = dXSizePct / 100.0;
    s.m_dYSizeFrac = dYSizePct / 100.0;
    ::GetWindowRect(s.m_hWnd, &s.m_rcInitial);
    ScreenToClient(s.m_rcInitial);
    m_MovingChildren.push_back(s);
}

BOOL CBaseDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    // use the initial dialog size as the default minimum
    if ((m_szMinimum.cx == 0) && (m_szMinimum.cy == 0))
    {
        CRect rcWindow;
        GetWindowRect(rcWindow);
        m_szMinimum = rcWindow.Size();
    }

    // keep the initial size of the client area as a baseline for moving/sizing controls
    CRect rcClient;
    GetClientRect(rcClient);
    m_szInitial = rcClient.Size();

    // create a gripper in the bottom-right corner
    if (m_bShowGripper && ((GetStyle() & WS_THICKFRAME) != 0))
    {
        SMovingChild s;
        s.m_rcInitial.SetRect(-GetSystemMetrics(SM_CXVSCROLL), -GetSystemMetrics(SM_CYHSCROLL), 0, 0);
        s.m_rcInitial.OffsetRect(rcClient.BottomRight());
        m_hGripper = CreateWindow(_T("Scrollbar"), _T("size"), WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP,
                                  s.m_rcInitial.left, s.m_rcInitial.top, s.m_rcInitial.Width(), s.m_rcInitial.Height(),
                                  m_hWnd, NULL, AfxGetInstanceHandle(), NULL);
        ASSERT(m_hGripper != NULL);
        if (m_hGripper != NULL)
        {
            s.m_hWnd = m_hGripper;
            s.m_dXMoveFrac = 1.0;
            s.m_dYMoveFrac = 1.0;
            s.m_dXSizeFrac = 0.0;
            s.m_dYSizeFrac = 0.0;
            m_MovingChildren.push_back(s);

            // put the gripper first in the z-order so it paints first and doesn't obscure other controls
            ::SetWindowPos(m_hGripper, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
        }
    }

    return TRUE;  // return TRUE  unless you set the focus to a control
}

void CBaseDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI) 
{
    CDialog::OnGetMinMaxInfo(lpMMI);

    if (lpMMI->ptMinTrackSize.x < m_szMinimum.cx)
        lpMMI->ptMinTrackSize.x = m_szMinimum.cx;
    if (lpMMI->ptMinTrackSize.y < m_szMinimum.cy)
        lpMMI->ptMinTrackSize.y = m_szMinimum.cy;
}

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

    int iXDelta = cx - m_szInitial.cx;
    int iYDelta = cy - m_szInitial.cy;
    HDWP hDefer = NULL;
    for (MovingChildren::iterator p = m_MovingChildren.begin();  p != m_MovingChildren.end();  ++p)
    {
        if (p->m_hWnd != NULL)
        {
            CRect rcNew(p->m_rcInitial);
            rcNew.OffsetRect(int(iXDelta * p->m_dXMoveFrac), int(iYDelta * p->m_dYMoveFrac));
            rcNew.right += int(iXDelta * p->m_dXSizeFrac);
            rcNew.bottom += int(iYDelta * p->m_dYSizeFrac);
            if (hDefer == NULL)
                hDefer = BeginDeferWindowPos(m_MovingChildren.size());
            UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
            if ((p->m_dXSizeFrac != 0.0) || (p->m_dYSizeFrac != 0.0))
                uFlags |= SWP_NOCOPYBITS;
            DeferWindowPos(hDefer, p->m_hWnd, NULL, rcNew.left, rcNew.top, rcNew.Width(), rcNew.Height(), uFlags);
        }
    }
    if (hDefer != NULL)
        EndDeferWindowPos(hDefer);

    if (m_hGripper != NULL)
        ::ShowWindow(m_hGripper, (nType == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
}

巧妙地使用DeferWindowPos以避免重绘。 - AJG85
易于使用又简便。我已经在我的博客里写了一篇文章:http://www.technical-recipes.com/2011/how-to-create-resizable-dialogs-in-mfc/ - AndyUK
1
似乎您正在泄漏夹持器窗口(m_hGripper)。 - Cody Gray
1
@CodyGray 你说得对,我在这段代码中确实没有遵循3/5/0规则。但在这种情况下,我认为没有任何有害影响。夹持器窗口本身将在父窗口被销毁时被销毁,可能会在C++析构函数被调用之前。而句柄本身只是一个简单的整数或指针,不会真正泄漏任何东西。 - Mark Ransom

6
自从Visual Studio 2015以来,您可以使用MFC动态对话框布局,但似乎没有办法将对话框大小限制为最小大小(仍然只能通过处理WM_GETMINMAXINFO的旧方法)。
可以通过以下方式进行动态布局:
  • 在资源编辑器中,在设计时选择控件并设置移动类型调整大小类型属性(这会将新的AFX_DIALOG_LAYOUT部分发出到.rc文件);
  • 或通过使用CMFCDynamicLayoutclass进行编程。
文档:动态布局

4

如果您使用的是对话框模板,则在资源编辑器中打开对话框模板,并将样式属性设置为弹出式,将边框属性设置为可调整大小。我相信这将与jussij所说的设置WS_POPUP和WS_THICKFRAME样式的效果相同。要动态设置这些样式,请覆盖PreCreateWindow函数并添加以下内容:

cs.style |= WS_POPUP | WS_THICKFRAME;

http://www.flounder.com/getminmaxinfo.htm 这个网页展示了如何使用对话框的属性来改变边框,其中还有一个视觉化的例子(在页面开头)。 - JHowzer

3

1

我有一些博客说明,教您如何在MFC中创建一个非常简约且可调整大小的对话框。

基本上,这是CodeProject上Paulo Messina发布的实现,尽可能地去除了多余的东西,以便更好地阐明如何做得更好。

一旦您练习了一段时间,它就相当容易实现:重要的是:

i. 确保您已将他的CodeProject库等引入项目,并且所有内容都编译正确。

ii. 在OnInitDialog方法中进行所需的额外初始化:使夹子可见,设置最大对话框大小,在您希望“伸展”的对话框控件项上添加锚点等。

iii. 在适当的点上用CResizableDialog替换CDialog的使用:在对话框类定义、构造函数、DoDataExchange、BEGIN_MESSAGE_MAP、OnInitDialog等处。


0
我尝试了许多MFC布局库,发现这个是最好的:http://www.codeproject.com/KB/dialog/layoutmgr.aspx。请查看那里的评论以获取一些错误修复和改进(免责声明:其中一些由我提供 ;))。当您使用此库时,窗口上设置正确的调整大小标志将由库自动处理。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接