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);
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;
}
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;
}
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()
{
if((GetParent()->GetStyle() & WS_THICKFRAME) == 0)
{
GetParent()->ModifyStyle(0, WS_THICKFRAME);
CMenu* pMenu = GetParent()->GetSystemMenu(FALSE);
if (pMenu)
{
pMenu->InsertMenu(0, MF_BYPOSITION | MF_STRING, SC_SIZE, "&Size");
}
}
CFileDialog::OnInitDialog();
return TRUE;
}
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:
Create a CRezizeableFileDialog
class member
CResizeableFileDialogParentWnd ParentWnd;
and modify the CResizeableFileDialogParentWnd
's contructor:
CResizeableFileDialogParentWnd::CResizeableFileDialogParentWnd(CWnd* pParent)
: CCommonDialog( 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;
bool m_CanResize;
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( pParent)
{
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;
CRect Rect;
CRect LCRect;
for(pWnd = GetTopWindow(); pWnd;)
{
pWnd->GetWindowRect(&Rect);
if((pWnd == GetTopWindow())
|| (LCRect.Width() * LCRect.Height()
< Rect.Width() * Rect.Height()))
{
LCRect = Rect;
}
pWnd = pWnd->GetNextWindow();
}
for(pWnd = GetTopWindow(); pWnd;)
{
pWnd->GetWindowRect(&Rect);
int Anchore = ANCHORE_LEFT | ANCHORE_TOP;
if(Rect == LCRect)
{
Anchore = RESIZE_BOTH;
}
else
{
if(Rect.bottom > LCRect.top)
{
Anchore = ANCHORE_BOTTOM;
}
if(Rect.left > LCRect.right)
{
Anchore = ANCHORE_RIGHT;
}
}
ResizeChild(pWnd, dx, dy, Anchore);
pWnd = pWnd->GetNextWindow();
}
RedrawWindow(NULL, NULL,
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,
int Anchore)
{
CRect WndRect;
pWnd->GetWindowRect(&WndRect); ScreenToClient(&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
...
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnSizing( UINT nSide, LPRECT lpRect);
BEGIN_MESSAGE_MAP(CResizeableFileDialogParentWnd, CCommonDialog)
ON_WM_SIZE()
ON_WM_SIZING()
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
...
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnSizing( UINT nSide, LPRECT lpRect);
afx_msg void OnGetMinMaxInfo(MINMAXINFO* pMMI);
DECLARE_MESSAGE_MAP()
BEGIN_MESSAGE_MAP(CResizeableFileDialogParentWnd, CCommonDialog)
ON_WM_SIZE()
ON_WM_SIZING()
ON_WM_GETMINMAXINFO()
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);
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()
.