Click here to Skip to main content
15,885,985 members
Articles / Desktop Programming / MFC
Article

The CResizeableFileDialog class

Rate me:
Please Sign up or sign in to vote.
3.86/5 (4 votes)
29 Dec 2003 224.5K   1.2K   31   35
How to make the Windows Common Dialogs resizeable

Introduction

Reading about the Linux user interface I read "system open-file dialogs are resizable". That was my main impulse to try to see whether it is possible to get the same under Windows, because my (older) Windows (95, NT4) versions do not have this feature. Being realistic the goal was "in my program's open-file dialogs" only.

How to make the Windows Common Dialogs resizeable

At codeproject.com and codeguru.com there are many examples on how to create your own resizable dialog. For a long time I use a slightly bit modified version of one of them (I do not remember which one was my base) so I thought there can't be any problem with modifying the "open-file" common dialog.

But my first attempt to apply it to CFileDialog failed: I created a new dialog-based application. Through the class wizard I created a new CFileDialog based class CRezizeableFileDialog. To call it I mapped IDOK button's BN_CLICKED message: (you can use own solution if you wish)

void CMyMainDlg::OnOK()
{
    CResizeableFileDialog fd(FALSE); //or TRUE, for our example not <br>                                     // interesting
    fd.DoModal();
}

(You must #include "ResizeableFileDialog.h"). The next step was mapping the WM_SIZE message for the common trick to enable resizing:

void CResizeableFileDialog::OnSize(UINT nType, int cx, int cy)
{
     ModifyStyle(0, WS_THICKFRAME);

     CCommonDialog::OnSize(nType, cx, cy);
}

After this code ran I still had a fixed file-dialog size.

As a next try I moved ModifyStyle to the WM_INITDIALOG handler and found at the dialog's left side there appeared a small horizontal resizable rectangle. Looking into the help and MFC sources I found the rectangle/dialog is normally a hidden interface, and for the user visible dialog is this parent. This happens when you open a new style file dialog (post w3.1) by OFN_EXPLORER using (MFC CFileDialog's default). (You can see dlgfile.cpp MFC source.)

This brought the first change:

BOOL CResizeableFileDialog::OnInitDialog()
{
     GetParent()->ModifyStyle(0, WS_THICKFRAME);

     CFileDialog::OnInitDialog();

     return TRUE;  // return TRUE unless you set the focus to a control
                   // EXCEPTION: OCX Property Pages should return FALSE
}

Now moving the mouse cursor to the dialog's edge made the cursor change to the resize type but no resizing happened.

To enable it was necessary insert the missing SC_SIZE item into the dialog's system menu. (Thanks codeguru discussion repliers.)

BOOL CResizeableFileDialog::OnInitDialog()
{
     GetParent()->ModifyStyle(0, WS_THICKFRAME);

     CMenu* pMenu = GetParent()->GetSystemMenu(FALSE);
     if (pMenu)
     {
           pMenu->InsertMenu(0, MF_BYPOSITION | MF_STRING, SC_SIZE, _T"&Size");
     }

     CFileDialog::OnInitDialog();

     return TRUE;  // return TRUE unless you set the focus to a control
                   // EXCEPTION: OCX Property Pages should return FALSE
}

The bad point for this solution is the fixed &Size text. This text normally depends on Windows national version but I did not find way to get it from the system except to read it from another window's system menu that had it.

If you want modify this example to have the possibility to maximize the dialog add the WS_MAXIMIZEBOX style and SC_MAXIMIZE and SC_RESTORE system menu items.

For newer Windows version we will eliminate our own resizing:

BOOL CResizeableFileDialog::OnInitDialog()
{
    // cancel our own resize logic for by-system resizeable cases
    if((GetParent()->GetStyle() & WS_THICKFRAME) == 0)
    {
        GetParent()->ModifyStyle(0, /* WS_MAXIMIZEBOX |*/ WS_THICKFRAME);

        CMenu* pMenu = GetParent()->GetSystemMenu(FALSE);
        if (pMenu)
        {
            pMenu->InsertMenu(0, MF_BYPOSITION | MF_STRING, SC_SIZE, "&Size");
        }
    }

     CFileDialog::OnInitDialog();

     return TRUE;  // return TRUE unless you set the focus to a control
                   // EXCEPTION: OCX Property Pages should return FALSE
}

Now the dialog is resizable but without its items resizing and moving and without an acceptable minimal size. To enable this we must get into the CResizeableFileDialog parent's message loop.

At first I created a CCommonDialog-based class CResizeableFileDialogParentWnd. Because we do not its resource handle we uncomment the following from the header created by the wizard:

//removed:     enum { IDD = _UNKNOWN_RESOURCE_ID_ };

Create a CRezizeableFileDialog class member

CResizeableFileDialogParentWnd ParentWnd;

and modify the CResizeableFileDialogParentWnd's contructor:

CResizeableFileDialogParentWnd::CResizeableFileDialogParentWnd(CWnd* pParent)
     : CCommonDialog(/* removed: ParentWnd::IDD,*/ pParent)
{

Now is the time to add

CWnd *pParent = GetParent();
BOOL boo = ParentWnd.SubclassWindow(pParent->GetSafeHwnd());

into our BOOL CResizeableFileDialog::OnInitDialog() resize section.

From this moment we have control over the dialog's messages to add the necessary functionality.

The problem is we get the dialog already created, so adding any initialisation source to

CResizeableFileDialogParentWnd::OnInitDialog() 
makes no sense. Because of this I modified the previously mentioned resizable-dialog code into different handles.

The resize code uses these CResizeableFileDialogParentWnd class members:

int m_minWidth, m_minHeight;
bool m_bMinInfo;  //flag that m_min... not filled yet
bool m_CanResize; //can apply resize algorhytm

//previous window size
int m_OldX;
int m_OldY;

void ResizeChild(CWnd *pWnd, int dx, int dy, int Anchore);

and some defines

#define ANCHORE_LEFT     0x0000
#define ANCHORE_TOP      0x0000
#define ANCHORE_RIGHT    0x0001
#define ANCHORE_BOTTOM   0x0002
#define RESIZE_HOR       0x0004
#define RESIZE_VER       0x0008
#define RESIZE_BOTH     (RESIZE_HOR | RESIZE_VER)

Variable initialisation is in the constructor

CResizeableFileDialogParentWnd::CResizeableFileDialogParentWnd(CWnd* pParent)
     : CCommonDialog(/* removed: ParentWnd::IDD,*/ pParent)
{
     //{{AFX_DATA_INIT(CResizeableFileDialogParentWnd)
          // NOTE: the ClassWizard will add member initialization here
     //}}AFX_DATA_INIT

     m_minWidth = m_minHeight = 0;
     m_bMinInfo = false;
     m_CanResize = false;
}

To define the dialog's items resize logic through the class wizard create a WM_SIZE handler. There we will find the dialog's list control and that will be resized. Items under it will be kept with the dialog's bottom and possible items at the list control's right side at the dialog's right border.

You can find the system dialog item IDs in the .dlg files in your DevStudio installation (thanks for original article comments). Most examples use comparison of the items's GetClassName() to "SysListView32". I believe the list control in FileDialog will stay but there will come time when they will change the sources to #ifdef WIN64/_T"Sys64ListView" or similar.

void CResizeableFileDialogParentWnd::OnSize(UINT nType, int cx, int cy)
{
     CCommonDialog::OnSize(nType, cx, cy);

     if(m_CanResize)
     {
        int dx = cx - m_OldX;
        int dy = cy - m_OldY;

        CWnd *pWnd;

        //Find list control's rect
        CRect Rect;
        CRect LCRect;
        for(pWnd = GetTopWindow(); pWnd;)
        {
            pWnd->GetWindowRect(&Rect);

            if((pWnd == GetTopWindow())
                || (LCRect.Width() * LCRect.Height()<br>                                            < Rect.Width() * Rect.Height()))
            {
                LCRect = Rect;
            }

            pWnd = pWnd->GetNextWindow();
        }

          // Move and Size the controls
        for(pWnd = GetTopWindow(); pWnd;)
        {
            pWnd->GetWindowRect(&Rect);

            int Anchore = ANCHORE_LEFT | ANCHORE_TOP;

            if(Rect == LCRect)
            {
                //list ctrl
                Anchore = RESIZE_BOTH;
            }
            else
            {
                if(Rect.bottom > LCRect.top)
                {
                    //item under
                    Anchore = ANCHORE_BOTTOM;
                }

                if(Rect.left > LCRect.right)
                {
                    //item at right size
                    Anchore = ANCHORE_RIGHT;
                }
            }

            ResizeChild(pWnd, dx, dy, Anchore);

            pWnd = pWnd->GetNextWindow();
        }

        RedrawWindow(NULL, NULL,
          /* RDW_UPDATENOW | RDW_ERASE |*/ RDW_INVALIDATE | RDW_ALLCHILDREN);
    }

     m_OldX = cx;
     m_OldY = cy;
}

Of course it is possible to create a quicker or alternate solution. (If you will want to retrieve by ID you can get IDOK and IDCANCEL items.)

void CResizeableFileDialogParentWnd::ResizeChild(CWnd *pWnd, int dx, int dy, <br>                                                 int Anchore)
{
     CRect WndRect;

     pWnd->GetWindowRect(&WndRect);  ScreenToClient(&WndRect);

     //InvalidateRect(WndRect);

     if(Anchore & RESIZE_HOR)
          WndRect.right += dx;
     else if(Anchore & ANCHORE_RIGHT)
          WndRect.OffsetRect(dx,0);

     if(Anchore & RESIZE_VER)
          WndRect.bottom += dy;
     else if(Anchore & ANCHORE_BOTTOM)
          WndRect.OffsetRect(0,dy);

     pWnd->MoveWindow(&WndRect);
}

As we already know ResizeChild is not my own code.

You can see CResizeableFileDialogParentWnd::OnSize() contains

if(m_CanResize)

to eliminate some system WM_SIZE calls. The moment when a resize command comes from a user is after the WM_SIZING call. Because the class wizard does not handle this message we must add a handler manually:

class CResizeableFileDialogParentWnd : public CCommonDialog
     ...
     // Generated message map functions
     //{{AFX_MSG(CResizeableFileDialogParentWnd)
     afx_msg void OnSize(UINT nType, int cx, int cy);
     afx_msg void OnSizing( UINT nSide, LPRECT lpRect);
     //}}AFX_MSG
BEGIN_MESSAGE_MAP(CResizeableFileDialogParentWnd, CCommonDialog)
     //{{AFX_MSG_MAP(CResizeableFileDialogParentWnd)
     ON_WM_SIZE()
     ON_WM_SIZING()
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CResizeableFileDialogParentWnd::OnSizing(UINT fwSide, LPRECT pRect)
{
    m_CanResize = true;

    CCommonDialog::OnSizing(fwSide, pRect);
}

The last case is to define a minimal dialog size. WM_GETMINMAXINFO requires a manual solution too.

class CResizeableFileDialogParentWnd : public CCommonDialog
...
     // Generated message map functions
     //{{AFX_MSG(CResizeableFileDialogParentWnd)
     afx_msg void OnSize(UINT nType, int cx, int cy);
        afx_msg void OnSizing( UINT nSide, LPRECT lpRect);
     afx_msg void OnGetMinMaxInfo(MINMAXINFO* pMMI);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CResizeableFileDialogParentWnd, CCommonDialog)
     //{{AFX_MSG_MAP(CResizeableFileDialogParentWnd)
     ON_WM_SIZE()
     ON_WM_SIZING()
     ON_WM_GETMINMAXINFO()
     //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CResizeableFileDialogParentWnd::OnSizing(UINT fwSide, LPRECT pRect)
{
    m_CanResize = true;

    if (m_bMinInfo == false)
    {
        CRect rect;
        GetWindowRect(rect);
        m_minWidth = rect.Width();
        m_minHeight = rect.Height();

        m_bMinInfo = true;
    }

    CCommonDialog::OnSizing(fwSide, pRect);
}

void CResizeableFileDialogParentWnd::OnGetMinMaxInfo(MINMAXINFO* lpMMI)
{
    CCommonDialog::OnGetMinMaxInfo(lpMMI);

    if (m_bMinInfo)
    {
        lpMMI->ptMinTrackSize.x = m_minWidth;
        lpMMI->ptMinTrackSize.y = m_minHeight;
    }
}

There is no problem using, for example, a CFindReplaceDialog derived class and adding resize functionality, except that the class wizard knows nothing about CFindReplaceDialog, so you must create a class derived from CDialog, manually change its parent (all CDialog mentions into generated .h and .cpp file), uncomment _UNKNOWN_RESOURCE_ID_ and change the default constructor's parameters. It can be useful to set lpMMI->ptMaxTrackSize.y equal to lpMMI->ptMinTrackSize.y.

If you want to use a resizable dialog class instance for more DoModal() or Create() calls do not forget to reset the variables in the WM_DESTROY handler:

m_bMinInfo = false;
m_CanResize = false;

I believe there is no problem in extending the shown functionality for remembering the last dialog's size and position (virtual CFileDialog::OnInitDone() calls GetParent()->CenterWindow()). Look at the mentioned websites for source examples.

A General note on resizable dialogs

If you want to change the default minimal dialog size (now the dialog's initial one - see the logic in OnSizing source) you can find your hard-coded value is not a good solution.

If you will define for your dialog something like

m_minWidth = 100;

you can have trouble with large fonts in the system setting (see display properties in the control panel).

(There is a

HKEY_CURRENT_USER\Control 
Panel\Desktop\WindowMetrics\AppliedDPI
registry entry but this seems not to be present for all windows versions.)

Try to recalculate it by something like this:

m_minWidth = MulDiv(100, LOWORD(GetDialogBaseUnits()), 4); <br>//units originally computed with 4/normal font size

Maybe you will get a more realistic solution. (Please look into GetDialogBaseUnits() help for more details about how to work with horizontal and vertical values.)

Similar you can get calling GetDeviceCaps() with LOGPIXELSX or LOGPIXELSY.

But you will have problems using this to calculate dialog's sizes. For this must use MapDialogRect(). Unfortunately input must be handler to dialog. I found no similar function for property sheet but you can get font-size depending offsets by combining MapDialogRect() to GetActivePage() (it is NULL when calling too soon!) with recalculating of bottom buttons (height) by GetDialogBaseUnits().

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Slovakia Slovakia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralThanks Pin
Arkadi Kagan25-Jun-13 23:05
Arkadi Kagan25-Jun-13 23:05 
GeneralOnSize is called but there is a bug,controls disapear ! Pin
angel42011-Oct-06 9:21
angel42011-Oct-06 9:21 
GeneralRe: OnSize is called but there is a bug,controls disapear ! Pin
Tibor Blazko12-Oct-06 1:11
Tibor Blazko12-Oct-06 1:11 
GeneralNot calling the OnSizing Pin
Member 291569523-Mar-05 0:46
Member 291569523-Mar-05 0:46 
GeneralRe: Not calling the OnSizing Pin
auralius manurung10-Jan-09 19:12
auralius manurung10-Jan-09 19:12 
GeneralRe: Not calling the OnSizing Pin
Tibor Blazko10-Jan-09 23:46
Tibor Blazko10-Jan-09 23:46 
GeneralRe: Not calling the OnSizing Pin
auralius manurung11-Jan-09 3:37
auralius manurung11-Jan-09 3:37 
QuestionHow to autoscaling a Dialog Box for different system font sizes Pin
ayviD29-Apr-03 13:34
ayviD29-Apr-03 13:34 
GeneralResize.h Pin
Stone Free7-Apr-03 23:38
Stone Free7-Apr-03 23:38 
GeneralRe: Resize.h Pin
Tibor Blazko7-Apr-03 23:44
Tibor Blazko7-Apr-03 23:44 
GeneralRe: Resize.h Pin
Stone Free7-Apr-03 23:47
Stone Free7-Apr-03 23:47 
GeneralRe: Resize.h - article updated Pin
Tibor Blazko7-Apr-03 23:59
Tibor Blazko7-Apr-03 23:59 
GeneralDownload source trouble Pin
Piccinano29-Mar-03 21:16
Piccinano29-Mar-03 21:16 
GeneralRe: Download source trouble Pin
Tibor Blazko30-Mar-03 18:16
Tibor Blazko30-Mar-03 18:16 
GeneralI am a chinese Pin
lovexinqing29-Mar-03 20:07
lovexinqing29-Mar-03 20:07 
GeneralRe: I am a chinese Pin
TomPeakz31-Mar-03 20:26
TomPeakz31-Mar-03 20:26 
GeneralRe: I am a chinese Pin
benben9-Apr-03 2:20
benben9-Apr-03 2:20 
GeneralRe: I am a chinese Pin
SGarratt31-Dec-03 6:45
SGarratt31-Dec-03 6:45 
JokeRe: I am a chinese Pin
auralius manurung10-Jan-09 19:17
auralius manurung10-Jan-09 19:17 
GeneralFix Pin
Tibor Blazko25-Feb-03 1:31
Tibor Blazko25-Feb-03 1:31 
QuestionHow to get standard menu tems Pin
Paolo Messina25-Jul-01 13:17
professionalPaolo Messina25-Jul-01 13:17 
AnswerRe: How to get standard menu tems Pin
Tibor Blazko25-Jul-01 20:18
Tibor Blazko25-Jul-01 20:18 
GeneralAnother way Pin
Paolo Messina25-Jul-01 23:48
professionalPaolo Messina25-Jul-01 23:48 
GeneralRe: Another way Pin
Tibor Blazko26-Jul-01 0:07
Tibor Blazko26-Jul-01 0:07 
GeneralRe: Another way Pin
Paolo Messina26-Jul-01 0:09
professionalPaolo Messina26-Jul-01 0:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.