Comment créer un CDialog redimensionnable dans MFC?
Question
Je dois créer une application basée sur une boîte de dialogue au lieu de l'ancien type de conception CFormView. Mais CDialog produit des boîtes de dialogue de taille fixe. Comment créer des applications avec des boîtes de dialogue redimensionnables?
La solution
Dans le fichier de ressources RC, si la boîte de dialogue a ce style, sa taille sera fixe:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
Si le dialogue a ce style, il sera important:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
Avec ces options de cadre dimensionnables, la boîte de dialogue sera ré-dimensionnable, mais vous devrez néanmoins effectuer beaucoup de travail pour gérer le message WM_SIZE afin de gérer le dimensionnement et le positionnement des contrôles dans la boîte de dialogue.
Autres conseils
En plus de définir le style sur WS_THICKFRAME
, vous souhaiterez probablement également disposer d'un système pour déplacer et redimensionner les contrôles d'une boîte de dialogue au fur et à mesure de son redimensionnement. Pour mon usage personnel, j'ai créé une classe de base pour remplacer CDialog qui offre cette possibilité. Dérivez de cette classe et dans votre fonction InitDialog
, appelez la fonction AutoMove
pour chaque contrôle enfant afin de définir le déplacement et le redimensionnement par rapport au dialogue parent. La taille de la boîte de dialogue dans le fichier de ressources est utilisée comme taille minimale.
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);
}
Si vous utilisez un modèle de boîte de dialogue, ouvrez-le dans l'éditeur de ressources et définissez la propriété Style sur Popup et la propriété Bordure . Redimensionnement . Je suis presque sûr que cela fera la même chose que ce que jussij a dit et définit les styles WS_POPUP et WS_THICKFRAME. Pour les définir dynamiquement, remplacez la fonction PreCreateWindow et ajoutez les éléments suivants:
cs.style |= WS_POPUP | WS_THICKFRAME;
Il n’existe pas de moyen facile de le faire. Fondamentalement, vous devrez disposer de manière dynamique les contrôles lorsque la taille de la fenêtre est modifiée.
Voir http://www.codeproject.com/KB/dialog/resizabledialog. aspx pour un exemple
Depuis Visual Studio 2015 , vous pouvez utiliser Disposition du dialogue dynamique MFC , mais il semble qu’il n’existe aucun moyen de limiter la taille du dialogue à une taille minimale (toujours à l’ancien par traitement WM_GETMINMAXINFO ).
La mise en page dynamique peut être réalisée:
- au moment de la conception dans l’éditeur de ressources en sélectionnant le contrôle et en définissant les propriétés Type de déplacement et Type de dimensionnement (cette fonction génère une nouvelle section AFX_DIALOG_LAYOUT dans fichier .rc);
- ou par programme à l'aide de CMFCDynamicLayout , class .
Documentation: Disposition dynamique
J'ai essayé de nombreuses bibliothèques de structures MFC et trouvé celle-ci comme étant la meilleure: http: //www.codeproject.com/KB/dialog/layoutmgr.aspx . Consultez les commentaires pour des corrections de bugs et des améliorations (disclaimer: certains d’entre eux par moi;)). Lorsque vous utilisez cette bibliothèque, la définition des indicateurs de redimensionnement corrects sur votre fenêtre sera gérée pour vous.
J'ai quelques instructions de blog sur la façon de créer un dialogue redimensionnable très minimaliste dans MFC.
Il s'agit essentiellement d'une implémentation de de la publication de Paulo Messina sur CodeProject . mais avec autant de choses étrangères que possible supprimées, histoire de clarifier comment le faire mieux.
Il est assez simple à mettre en œuvre une fois que vous avez eu un peu de pratique: les éléments importants sont les suivants:
i. Assurez-vous que ses bibliothèques CodeProject, etc., sont bien insérées dans votre projet et que tout est compilé correctement.
ii. effectuez l'initialisation supplémentaire requise dans la méthode OnInitDialog: affichez le préhenseur, définissez la taille maximale du dilog, ajoutez des points d'ancrage aux éléments de contrôle de la boîte de dialogue que vous souhaitez "étirer", etc.
iii. Remplacez l'utilisation de CDialog par CResizableDialog aux points appropriés: dans la définition de classe de dialogue, le constructeur, DoDataExchange, BEGIN_MESSAGE_MAP, OnInitDialog, etc.