How to paint title bar window only? - winapi

As mention from microsft doc. WM_NCPAINT is use to paint non-client area. it means like title bar. https://learn.microsoft.com/en-us/windows/win32/gdi/wm-ncpaint. But i get unexpected result. it paint client area too. and the weird one. title bar is gone. when launch. after ALT+TAB. the title bar appears with windows 7 style in windows 10.
class CMainFrame::CFrameWnd
{
public:
CMainFrame()
{
Create(
NULL,
"Hello World!",
WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
CRect(CPoint(100, 100), CSize(640, 360));
}
protected:
afx_msg void OnNcPaint();
DECLARE_MESSAGE_MAP()
}
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
ON_WM_NCPAINT()
END_MESSAGE_MAP()
void CMainFrame::OnNcPaint()
{
PAINTSTRUCT ps;
CBrush brush;
brush.CreateSolidBrush(RGB(0, 0, 255));
CDC *pDC = BeginPaint(&ps);
pDC->FillRect(&ps.rcPaint, &brush);
EndPaint(&ps);
}
class CApplication : public CWinApp {
BOOL InitInstance() {
CMainFrame* mainWnd = new CMainFrame();
m_pMainWnd = mainWnd;
mainWnd->ShowWindow(SW_NORMAL);
mainWnd->UpdateWindow();
return TRUE;
}
};

In InitInstance, you must call the base class CWinApp::InitInstance() in the first line.
Create your main frame window using
CreateEx(0, AfxRegisterWndClass(0), "Hello World!",
WS_VISIBLE | WS_OVERLAPPEDWINDOW, 100, 100, 640, 360, NULL, 0);
Add PreCreateWindow to control the edges and other properties
int CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
auto res = CFrameWnd::PreCreateWindow(cs);
cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
return res;
}
Use the appropriate paint classes, CPaintDC dc(this), CWindowDC, for overriding OnPaint or OnNcPaint (avoid overriding OnNcPaint), use CClientDC dc(this) for other client area paints. Avoid calling WinAPI functions directly, for example avoid calling BeginPaint. You can also use Visual Studio to create a sample dialog based application, it will be easier to get started with that.

Related

AnimateWindow() incorrectly draws background on high dpi (Win10)

I am creating a simple Win32/MFC application with a main window, and a child-window that uses AnimateWindow() to show (slide up) and hide (slide down) the window. When running the application on 100% dpi scaling, everything behaves normally.
I have overridden WM_ERASEBKGND to render a random red color, to demonstrate the effect. As the window slides-down (hide) on every "step" of the animation the "update rectangle" of the background is repainted, exactly where the child-window disappeared, and the background is to become visible again.
However, when changing the dpi-scaling via Windows Settings (in this case to 125%), an incorrect area is redrawn. It appears as if the area passed into OnEraseBkgnd() is still using the 100% dpi-scaling size, but also the position is off: it appears as if the x/y position of the upper-left corner is passed in screen-space instead of client-space. So the redrawn area looks different, depending on where the on the screen the window is positioned.
The white area is where the child window was actually positioned, and where the redraw of the background should actually have happened.
I have confirmed this effect on Win10 (1803 and 1809) and on Win8.1
Is this a bug in the OS, or is there something I can do to avoid the problem - other than not using AnimateWindow()? ShowWindow() (with SW_SHOW or SW_HIDE) works just fine, btw.
Update: added the full source-code to reproduce the issue.
The problem occurs when using no dpi-aware manifest at all, but it also occurs when using <gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
class CDialogTestApp : public CWinApp
{
virtual BOOL InitInstance();
};
CDialogTestApp theApp;
class CAboutDlg : public CDialogEx
{
public:
CAboutDlg() : CDialogEx(IDD_ABOUTBOX) {}
};
class CDialogTestDlg : public CDialogEx
{
public:
CDialogTestDlg(CWnd* pParent = nullptr) : CDialogEx(IDD_DIALOGTEST_DIALOG, pParent) {}
protected:
virtual BOOL OnInitDialog();
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
DECLARE_MESSAGE_MAP()
private:
CAboutDlg mDialog;
};
BEGIN_MESSAGE_MAP(CDialogTestDlg, CDialogEx)
ON_WM_ERASEBKGND()
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
BOOL CDialogTestApp::InitInstance()
{
CWinApp::InitInstance();
CDialogTestDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
return FALSE;
}
BOOL CDialogTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
mDialog.Create(IDD_ABOUTBOX, this);
return TRUE;
}
BOOL CDialogTestDlg::OnEraseBkgnd(CDC* pDC)
{
COLORREF color = RGB(rand() & 255, 20, 40);
CRect rect;
GetClipBox(pDC->m_hDC, &rect); // retrieve the update-rectangle
CBrush brush(color);
FillRect(pDC->m_hDC, &rect, (HBRUSH)brush.m_hObject);
return TRUE;
}
void CDialogTestDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (mDialog.IsWindowVisible())
{
mDialog.AnimateWindow(200, AW_HIDE | AW_SLIDE | AW_VER_POSITIVE);
}
else
{
mDialog.SetWindowPos(&CWnd::wndTop, 0, 50, 0, 0, SWP_NOSIZE);
mDialog.AnimateWindow(200, AW_ACTIVATE | AW_SLIDE | AW_VER_NEGATIVE);
}
CDialogEx::OnLButtonUp(nFlags, point);
}

What parameters are valid to be together to create an HWND?

I'm trying to create a window using CWnd::CreateEx(). Unfortunately, I can't seem to get the parameters correct to get it to generate a HWND (function always returns FALSE).
class COverlay : public CWnd
{
public:
COverlay();
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(COverlay, CWnd)
END_MESSAGE_MAP()
COverlay::COverlay()
{
}
class CMyView : CView
{
public:
CMyView() {}
int OnCreate(LPCREATESTRUCT lpCreateStruct);
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyView, CView)
ON_WM_CREATE()
END_MESSAGE_MAP()
int CMyView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
m_overlay.CreateEx(WS_EX_COMPOSITED | WS_EX_LAYERED | WS_EX_TRANSPARENT
, _T("Static"), _T(""), WS_VISIBLE | WS_CHILD, CRect(), this, -1);
return 0;
}
I'm trying to create a click through window that overlays the CMyView. In this way I can paint over the view via the overlay and not get flickering as the OS should handle the double buffering and I will only have to deal with the view implementation in a minimal fashion. I will also have an ON_WM_SIZE hander to deal with resizing the overlay to the extents of the view.
What am I doing wrong?
Is there an easier way to do what I am trying to do?
WS_POPUP flag is required to create window with transparency level:
static CString className = AfxRegisterWndClass(0);
DWORD style = WS_CAPTION | WS_SYSMENU | WS_POPUP;
DWORD styleEx = WS_EX_LAYERED;// | WS_EX_TRANSPARENT;
if (!m_overlay.CreateEx(styleEx, className, 0, style, CRect(), this, 0))
{
//error...
return;
}
m_overlay.SetLayeredWindowAttributes(0, 255 * 50 / 100, LWA_ALPHA);
m_overlay.SetWindowPos(0, 0, 0, 400, 300, SWP_SHOWWINDOW | SWP_NOACTIVATE);
Or to change the transparency color:
COLORREF transparentColor = RGB(255, 0, 0);
...
m_overlay.SetLayeredWindowAttributes(transparentColor, 0, LWA_COLORKEY);
void COverlay::OnPaint()
{
CPaintDC dc(this);
CRect rc;
GetClientRect(&rc);
dc.FillSolidRect(rc, transparentColor);
//opaque drawings...
dc.TextOut(0, 0, L"Test...");
}
Note, WS_EX_TRANSPARENT is commented out because that only affects mouse clicks, otherwise it doesn't change the appearance. You can add that flag back in.
This window is popup so it doesn't move when you move the frame window or child window. You would have to override frame and child frame window's OnWindowPosChanged to move it manually to sync with View window.

Creating separate MFC GUI thread, can't move/resize/maximize CWnd

I am creating a CWinThread that will have it's own GUI. When I create a CWnd on that thread, it displays, but I can't move the window. I am sure that the message pump is running, because I can performn MoveWindow from another thread, and the window moves.
UIThread.h
#pragma once
class CUIThread : public CWinThread
{
public:
DECLARE_DYNCREATE(CUIThread)
CUIThread();
// Attributes
public:
HWND hwnd;
// Operations
public:
void KillThread();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CGDIThread)
//}}AFX_VIRTUAL
// Implementation
public:
virtual ~CUIThread();
protected:
virtual BOOL InitInstance();
// Generated message map functions
//{{AFX_MSG(CUIThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
UIThread.cpp
#include "stdafx.h"
#include "UIThread.h"
#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CUIThread
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
BEGIN_MESSAGE_MAP(CUIThread, CWinThread)
//{{AFX_MSG_MAP(CUIThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
CUIThread::CUIThread() : hwnd(NULL)
{
m_bAutoDelete = FALSE;
}
BOOL CUIThread::InitInstance()
{
this->m_pMainWnd = new CWnd;
m_pMainWnd->Create(_T("STATIC"), _T("Hi"), WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CRect(0, 0, 500, 500), CWnd::GetDesktopWindow(), 1234);
this->hwnd = m_pMainWnd->GetSafeHwnd();
return TRUE;
}
CUIThread::~CUIThread()
{
}
void CUIThread::KillThread()
{
// Note: this function is called in the context of
// other threads, not the thread itself.
this->PostThreadMessage(WM_QUIT, 0, 0);
// allow thread to run at higher priority during
// kill process
SetThreadPriority(THREAD_PRIORITY_ABOVE_NORMAL);
WaitForSingleObject(m_hThread, INFINITE);
}
main.cpp
...
CUIThread* pUIThread = static_cast< CUIThread*>(AfxBeginThread(RUNTIME_CLASS(CUIThread)));
getchar();
MoveWindow(pUIThread->hwnd, 100, 100, 500, 500, true); // works
getchar();
CloseWindow(pUIThread->hwnd); // works
getchar();
pUIThread->KillThread(); // works
delete pUIThread;
getchar();
...
I can see the window, I just can't move/maximize/resize it.
I believe you are creating the window the wrong way. You are creating a child window with the desktop as parent window. This should work:
LPCSTR strClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE);
VERIFY(m_pMainWnd->CreateEx(0, strClass, _T("title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CRect(0, 0, 500, 500), NULL, 0));

How would I animate a window with the Win32 Api?

How would you go about painting something onto a window in regular intervals.
I have come up with the this (stripped quite a bit for clarity)
#include <windows.h>
void DrawOntoDC (HDC dc) {
pen = CreatePen(...)
penOld = SelectObject(dc, pen)
..... Here is the actual drawing, that
..... should be regurarly called, since
..... the drawn picture changes as time
..... progresses
SelectObject(dc, pen_old);
DeleteObject(pen);
}
LRESULT CALLBACK WindowProc(....) {
switch(Msg) {
case WM_PAINT: {
PAINTSTRUCT ps;
dc = BeginPaint(hWnd, &ps);
..... A Memory DC is created
..... In order to prevent flickering.
HBITMAP PersistenceBitmap;
PersistenceBitmap = CreateCompatibleBitmap(dc, windowHeight, windowHeight);
HDC dcMemory = CreateCompatibleDC(dc);
HBITMAP oldBmp = (HBITMAP) SelectObject(dcMemory, PersistenceBitmap);
DrawOntoDC(dcMemory);
..... "copying" the memory dc in one go unto dhe window dc:
BitBlt ( dc,
0, 0, windowWidth, windowHeight,
dcMemory,
0, 0,
SRCCOPY
);
..... destroy the allocated bitmap and memory DC
..... I have the feeling that this could be implemented
..... better, i.e. without allocating and destroying the memroy dc
..... and bitmap with each WM_PAINT.
SelectObject(dcMemory, oldBmp);
DeleteDC(dcMemory);
DeleteObject(PersistenceBitmap);
EndPaint (hWnd, &ps);
return 0;
}
default:
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
}
DWORD WINAPI Timer(LPVOID p) {
..... The 'thread' that makes sure that the window
..... is regularly painted.
HWND hWnd = (HWND) *((HWND*) p);
while (1) {
Sleep(1000/framesPerSecond);
InvalidateRect(hWnd, 0, TRUE);
}
}
int APIENTRY WinMain(...) {
WNDCLASSEX windowClass;
windowClass.lpfnWndProc = WindowProc;
windowClass.lpszClassName = className;
....
RegisterClassEx(&windowClass);
HWND hwnd = CreateWindowEx(
....
className,
....);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
DWORD threadId;
HANDLE hTimer = CreateThread(
0, 0,
Timer,
(LPVOID) &hwnd,
0, &threadId );
while( GetMessage(&Msg, NULL, 0, 0) ) {
....
}
return Msg.wParam;
}
I guess there's a lot that could be improved and I'd appreciate any pointer to things I have overlooked.
Doing this kind of thing with a worker thread is not optimal.
Given that the optimal code path for painting is always via a WM_PAINT that leaves two ways to do this:
Simply create a timer on the GUI thread, post WM_TIMER messages to a timerproc, or the window directly, and invoke the OnTick() part of your engine. IF any sprites move, they invalidate their area using InvalidateRect() and windows follows up by automatically posting a WM_PAINT. This has the advantage of having a very low CPU usage if the game is relatively idle.
Most games want stricter timing that can be achieved using a low priority WM_TIMER based timer. In that case, you implement a game loop something like this:
Message Loop:
while(stillRunning)
{
DWORD ret = MsgWaitForMultipleObjects(0,NULL,FALSE,frameIntervalMs,QS_ALLEVENTS);
if(ret == WAIT_OBJECT_0){
while(PeekMessage(&msg,0,0,0,PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(TickGame()) // if TickGame indicates that enough time passed for stuff to change
RedrawWindow(hwndGame,...); // Dispatch a WM_PAINT immediately.
}
The danger with this kind of message loop is, if the ... application goes into any kind of modal state :- the user starts to drag the window / a modal dialog box pops up, then messages are being pumped by the modal loop, so the animation stops. As a result you need to have a fallback timer if you need to mix a high performance message loop with modal operations.
WRT your WM_PAINT implementation - its usually better to (re)create your backbuffer in response to WM_SIZE messages. That way its always the right size, and you don't incurr the rather large cost of recreating a large memory buffer many times per second.
Borrowing bits and pieces from various places, I came up with the following approach for my similar problem. The following is a test application to test the concept.
In this test application I am using an MFC static window which I am updating with a text string by periodically calling the ::SetWindowText() function with the MFC static window's handle. This works fine to display a marching right angle bracket to demonstrate that the animation works.
In the future I intend on using a memory resident bitmap image which is modified in the animation loop which is then posted into a bitmap attached to the static text window. This technique allows for an animated bitmap to be presented with a more elegant indication of something in progress.
Testing this concept was done with an MFC dialog application that contains two static windows for the progress indicators and two additional buttons, Start and Stop, to start and stop the progress indicators. The goal is that when the Start button is pressed a series of greater than signs are written across the static window and then cleared and then once more started. So the animation looks like an LED sign that has arrows marching across from left to right in a horizontal marque.
These two buttons do nothing more than set an indicator in the animation object to be either one (on) or zero (off). The animation object thread which does the actual animation just reads from the m_state variable and does not modify it.
The timer delay quantity is hardcoded for the purposes of this test. It could easily be a parameter.
The dialog is still responsive and even as it is updating I can display the default About Box for the dialog application and move the About Box around. I can also drag the dialog application itself around screen (without the About Box displayed as that is a modal dialog) with the static window still updating.
The Animation class source
The source code for the animation logic is a simple class that starts a thread which then updates the specified dialog control. While the animation loop is a static method of the class, the data used by the loop is specified in the object itself so multiple animations can be done with different objects using the same static loop.
This approach is fairly straightforward and simple. Rather than using a more sophisticated approach with a thread pool and a timer pool, it dedicates a thread and a timer object to a single animation. It appears obvious that this approach will not scale well however for an application with a couple of animations, it works well enough.
class AnimatedImage
{
UINT m_state; // current on/off state of the animation. if off (0) then the window is not updated
UINT m_itemId; // control identifier of the window that we are going to be updating.
HWND m_targetHwnd; // window handle of the parent dialog of the window we are going to be updating
UINT m_i; // position for the next right angle bracket
wchar_t m_buffer[32]; // text buffer containing zero or more angle brackets which we use to update the window
DWORD m_lastError; // result of GetLastError() in case of an error.
HANDLE m_hTimer; // handle for the timer
HANDLE m_hThread; // handle for the thread created.
LARGE_INTEGER m_liDueTime; // time delay between updates
public:
AnimatedImage(UINT itemId = 0, HWND hWnd = NULL) : m_state(0), m_itemId(itemId), m_targetHwnd(hWnd), m_i(0), m_lastError(0), m_hTimer(NULL), m_hThread(NULL) { memset(m_buffer, 0, sizeof(m_buffer)) ; }
~AnimatedImage() { Kill(); CloseHandle(m_hTimer); CloseHandle(m_hThread); } // clean up the timer and thread handle.
static unsigned __stdcall loop(AnimatedImage *p); // animation processing loop
void Run(); // starts the animation thread
void Start(); // starts the animation
void Stop(); // stops the animation
void Kill(); // indicates the thread is to exit.
// Functions used to get the target animation window handle
// and to set the parent window handle and the dialog control identifier.
// This could be simpler by just requiring the target animation window handle
// and letting the user do the GetDlgItem() function themselves.
// That approach would make this class more flexible.
HWND GetImageWindow() { return ::GetDlgItem(m_targetHwnd, m_itemId); }
void SetImageWindow(UINT itemId, HWND hWnd) { m_itemId = itemId; m_targetHwnd = hWnd; }
};
unsigned __stdcall AnimatedImage::loop(AnimatedImage *p)
{
p->m_liDueTime.QuadPart = -10000000LL;
// Create an unnamed waitable timer. We use this approach because
// it makes for a more dependable timing source than if we used WM_TIMER
// or other messages. The timer resolution is much better where as with
// WM_TIMER is may be no better than 50 milliseconds and the reliability
// of getting the messages regularly can vary since WM_TIMER are lower
// in priority than other messages such as mouse messages.
p->m_hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
if (NULL == p->m_hTimer)
{
return 1;
}
for (; ; )
{
// Set a timer to wait for the specified time period.
if (!SetWaitableTimer(p->m_hTimer, &p->m_liDueTime, 0, NULL, NULL, 0))
{
p->m_lastError = GetLastError();
return 2;
}
// Wait for the timer.
if (WaitForSingleObject(p->m_hTimer, INFINITE) != WAIT_OBJECT_0) {
p->m_lastError = GetLastError();
return 3;
}
else {
if (p->m_state < 1) {
p->m_i = 0;
memset(p->m_buffer, 0, sizeof(m_buffer));
::SetWindowText(p->GetImageWindow(), p->m_buffer);
}
else if (p->m_state < 2) {
// if we are updating the window then lets add another angle bracket
// to our text buffer and use SetWindowText() to put it into the
// window area.
p->m_buffer[p->m_i++] = L'>';
::SetWindowText(p->GetImageWindow(), p->m_buffer);
p->m_i %= 6; // for this demo just do a max of 6 brackets before we reset.
if (p->m_i == 0) {
// lets reset our buffer so that the next time through we will start
// over in position zero (first position) with our angle bracket.
memset(p->m_buffer, 0, sizeof(m_buffer));
}
}
else {
// we need to exit our thread so break from the loop and return.
break;
}
}
}
return 0;
}
void AnimatedImage::Run()
{
m_hThread = (HANDLE)_beginthreadex(NULL, 0, (_beginthreadex_proc_type)&AnimatedImage::loop, this, 0, NULL);
}
void AnimatedImage::Start()
{
m_state = 1;
}
void AnimatedImage::Stop()
{
m_state = 0;
}
void AnimatedImage::Kill()
{
m_state = 3;
}
How the class is used
For this simple test dialog application, we just create a couple of global objects for our two animations.
AnimatedImage xxx;
AnimatedImage xx2;
In the OnInitDialog() method of the dialog application the animations are initialized before returning.
// TODO: Add extra initialization here
xxx.SetImageWindow(IDC_IMAGE1, this->m_hWnd);
xxx.Run();
xx2.SetImageWindow(IDC_IMAGE2, this->m_hWnd);
xx2.Run();
return TRUE; // return TRUE unless you set the focus to a control
There are two button click handlers which handle a click on either the Start button or the Stop button.
void CMFCApplication2Dlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler code here
xxx.Start();
xx2.Start();
}
void CMFCApplication2Dlg::OnBnClickedButton2()
{
// TODO: Add your control notification handler code here
xxx.Stop();
xx2.Stop();
}
The dialog application main dialog resource is defined as follows in the resource file.
IDD_MFCAPPLICATION2_DIALOG DIALOGEX 0, 0, 320, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
DEFPUSHBUTTON "OK",IDOK,209,179,50,14
PUSHBUTTON "Cancel",IDCANCEL,263,179,50,14
CTEXT "TODO: Place dialog controls here.",IDC_STATIC,10,96,300,8
LTEXT "Static",IDC_IMAGE1,7,7,110,21
LTEXT "Static",IDC_IMAGE2,64,43,112,27
PUSHBUTTON "Start",IDC_BUTTON1,252,16,50,19
PUSHBUTTON "Stop",IDC_BUTTON2,248,50,57,21
END

How to create a resizable CDialog in MFC?

I have to create a dialog based application, instead of old CFormView type of design. But CDialog produces fixed-size dialogs. How can I create dialog based applications with resizable dialogs?
In the RC resource file if the dialog has this style similar to this it will be fixed size:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
If the dialog has this style it will be sizeable:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
With these sizable frame options the dialog will be re-sizeable but you will still need to do a lot of work handling the WM_SIZE message to manage the sizing an positioning of the controls within the dialog.
In addition to setting the style to WS_THICKFRAME, you'll probably also want to have a system to move and resize the controls in a dialog as the dialog is resized. For my own personal use I've created a base class to replace CDialog that has this capability. Derive from this class and in your InitDialog function call the AutoMove function for each child control to define how much it should move and how much it should resize relative to the parent dialog. The size of the dialog in the resource file is used as a minimum size.
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);
}
Since Visual Studio 2015, you can use MFC Dynamic Dialog Layout, but it seems, there is no way to restrict dialog size to minimal size (still only the old way by handling WM_GETMINMAXINFO).
Dynamic layout can be done:
at design time in resource editor by selecting the control and setting the Moving Type and Sizing Type properties (this emits new AFX_DIALOG_LAYOUT section into .rc file);
or programatically using the CMFCDynamicLayout class.
Documentation: Dynamic Layout
If your using a dialog template then open the dialog template in the resource editor and set the Style property to Popup and the Border property to Resizing. I'm pretty sure this will do the same as what jussij said and set the WS_POPUP and WS_THICKFRAME styles. To set these dynamically then override the PreCreateWindow function and add the following:
cs.style |= WS_POPUP | WS_THICKFRAME;
There is no easy way to do this. Basically, you will need to dynamically layout controls when the window size is changed.
See http://www.codeproject.com/KB/dialog/resizabledialog.aspx for an example
I have some blog instructions on how to create a very minimalist re-sizeable dialog in MFC.
It is basically an implementation of Paulo Messina's posting at CodeProject
but with as much extraneous stuff removed as possible, just to help clarify how to do it better.
It is fairly straightforward to implement once you've had a bit of practice: the important bits are to:
i. ensure you have his CodeProject libraries etc pulled into your project and it all compiles correctly.
ii. do the extra initialization required inside the OnInitDialog method: make the gripper visible, set the maximum dilog size, add anchor points to the dialog control items that you wish to 'stretch' etc.
iii. Replace usage of CDialog with CResizableDialog at the appropriate points: in the dialog class definition, constructor, DoDataExchange, BEGIN_MESSAGE_MAP, OnInitDialog etc.
I've tried many MFC layout libraries and found this one the best: http://www.codeproject.com/KB/dialog/layoutmgr.aspx. Check out the comments there for some bug fixes and improvements (disclaimer: some of them by me ;) ). When you use this library, setting the correct resize flags on your window will be handled for you.

Resources