how to handle this problem everytime i resize my drawing seems to not draw right.
i think i need to call Invalidate() everytime i resize the window but doesnt WM_PAINT automatically called every time i move or resize the window?
CPaintDC dc(this); // device context for painting
CRect rect;
GetClientRect(rect);
if (IsIconic())
{
//CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
for(int ndx(0); ndx < rect.Width() - 150; ndx += 10)
{
dc.MoveTo( rect.left + 50, rect.bottom / 2 );
dc.LineTo( rect.left + 50 + ndx, rect.bottom / 2 );
}
CBrush mybrush(RGB(30,30,30));
dc.FillRect( CRect(rect.left + 10, rect.top + 10, rect.Width() / 4, rect.Height() / 4),&mybrush );
CDialogEx::OnPaint();
}
Before Resizing:
After Resizing:
When you start painting and use CPaintDC it doesn't make sense to call the base class that tries this again and may again erase the background...
What happens.
Your CPaintDC ist created
BeginPaint is called and WM_ERASEBKGND is sent.
You paint your stuff.
You call the base class anbd a new CPaintDC calles BeginPaint.
Because EndPaint isn't called the paint area isn't validated. So BeginPaint is executed and the WM_ERASEBKGND is called again.
Finally the CPaintDC's destructors are called and the client area is validated.
Never call the OnPaint baseclass function if you start using CPaintDC/BeginPaint!
I do not know if this is the optimal solution.
In your paint code, before painting, fill the entire client area with the background color:
dc.FillSolidRect(&rect, ...);
Remove CDialogEx::OnPaint().
Add an OnEraseBkgnd handler:
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
//...
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
//...
BOOL OnEraseBkgnd(CDC * pDC)
{
RedrawWindow();
return TRUE;
}
If the dialog is flickering, use MFC's CMemDC (undocumented).
this is fixes my problem
step 1: BeginPaint()
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CTestDrawDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rect;
GetClientRect(rect);
// draw background manually
EraseBkgnd(&dc);
if (IsIconic())
{
//CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
for(int ndx(0); ndx < rect.Width() - 150; ndx += 10)
{
dc.MoveTo( rect.left + 50, rect.bottom / 2 );
dc.LineTo( rect.left + 50 + ndx, rect.bottom / 2 );
}
CBrush mybrush(RGB(30,30,30));
dc.FillRect( CRect(rect.left + 10, rect.top + 10, rect.Width() / 4, rect.Height() / 4),&mybrush );
}
}
STEP: 2 RedrawWindow() on WM_ERASEBACKGND
BOOL CTestDrawDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
RedrawWindow();
return TRUE;
}
and Manually draw the background
void CTestDrawDlg::EraseBkgnd( CDC * pDC )
{
CRect rect;
GetClientRect(rect);
pDC->FillSolidRect(rect, RGB(255,255,255));
// paint whatever you want in the background
}
STEP 3: EndPaint()
I have a Dialog in MFC C++ that has a CButton attached.
I want to modify OnSize() so that the button will anchor to bottom-left.
if (btn.m_hWnd) {
CRect winRect;
GetWindowRect(&winRect);
int width = winRect.right - winRect.left;
int height = winRect.bottom - winRect.top;
int x = width - wndWidth;
int y = height - wndHeight;
CRect rect;
btn.GetWindowRect(&rect);
rect.left += x;
rect.top += y;
btn.MoveWindow(&rect);
ScreenToClient(&rect);
btn.ShowWindow(SW_SHOW);
}
x and y are the difference of how much the window has changed and will be added to the button's start coordinates.
I am not sure about the last 2 commands (I might as well delete them) but then I run the program the button disappears.
I need to know a way to move the button by x and y.
The original code was using the wrong coordinate system for both the parent dialog, and the button.
A correct way to dock to the bottom left would be like this:
if (btn.m_hWnd) {
CRect winRect;
GetClientRect(&winRect);
CRect rect;
btn.GetWindowRect(&rect);
ScreenToClient(&rect);
int btnWidth = rect.Width();
int btnHeight = rect.Width();
rect.left = winRect.right-btnWidth;
rect.top = winRect.bottom-btnHeight;
rect.right = winRect.right;
rect.bottom = winRect.bottom;
btn.MoveWindow(&rect);
}
OR
if (btn.m_hWnd) {
CRect winRect;
GetClientRect(&winRect);
CRect rect;
btn.GetWindowRect(&rect);
ScreenToClient(&rect);
int btnWidth = rect.Width();
int btnHeight = rect.Width();
btn.SetWindowPos(NULL,winRect.right-btnWidth,winRect.bottom-btnHeight,0,0,SWP_NOSIZE|SWP_NOZORDER);
}
Basically, the answer should be to do ScreenToClient before MoveWindow!
Some more detail: You need to familiarize yourself with what function returns or uses client coordinates (and relative to what these client coordinates are) and which screen coordinates; this is one of the parts of MFC which can be really confusing. As to your question:
CWnd::GetWindowRect returns screen coordinates; CWnd::MoveWindow expects coordinates relative to the parent CWnd (or to the screen if it's a top level window). So before calling MoveWindow, you have to convert the rect returned by GetWindowRect to client coordinates of your parent window; suppose pParent is the CWnd * to the parent window of btn, then your moving code should look like this:
CRect rect;
btn.GetWindowRect(&rect);
pParent->ScreenToClient(&rect); // this line and it's position is important
rect.left += x;
rect.top += y;
btn.MoveWindow(&rect);
If you're in a method of the parent window (as I think you are, since you mention OnSize of the dialog), then just leave out the pParent->. The way you do ScreenToClient at the moment, it has no effect on MoveWindow, since it's executed after it!
I am trying to display an image in a dialog dynamically, it works no problem if I put the code in the on paint method and use the dc from there, I can't do this though I need to display after the window is shown, the code I am using is as follows, I am getting the dc of the client window creating the bitmap from a resource and "trying" to display it in the window but nothing displays, Any suggestions what might be wrong?
void CProcessSteps::OnShowWindow(BOOL bShow, UINT nStatus)
{
CDialog::OnShowWindow(bShow, nStatus);
SetupInstructions();<-----------------Call To Method
}
void CProcessSteps::OnPaint()
{
CPaintDC dc(this);
}
void CProcessSteps::SetupInstructions()
{
CDC *pDC = new CDC();<------------------------------Problem starts here
CFontUtil cfu;
cfu.SetFont(&LineFont,30);
CDC memDC;
memDC.CreateCompatibleDC(pDC);
int stepTop = 10;
int stepEnd = 230;
int imageLeft = 30;
STEP_STRUCT* step;
CBitmap iconImage;
iconImage.LoadBitmap ( IDB_TID_CHECK );
memDC.SelectObject(&iconImage);
CRect iconRect;
BITMAP bmInfo;
iconImage.GetObject ( sizeof ( bmInfo ), &bmInfo );
iconRect.SetRect ( imageLeft, stepTop, imageLeft+bmInfo.bmWidth, stepTop+bmInfo.bmHeight );
pDC = this->GetDC();
pDC->BitBlt(imageLeft, stepTop, imageLeft+bmInfo.bmWidth, stepTop+bmInfo.bmHeight, &memDC, 0, 0, SRCCOPY);
//RedrawWindow();<-------- tried this here no luck
int stepCount = m_pageStructure->PageSteps.GetCount();<----------------------------Bellow this works correctly
POSITION pos = m_pageStructure->PageSteps.GetHeadPosition();
while (pos)
{
step = m_pageStructure->PageSteps.GetNext(pos);
CStatic *label = new CStatic;
label->Create(_T( step->StepInstruction ),WS_CHILD | WS_VISIBLE, CRect(80, stepTop, 480, stepEnd), this);
label->SetFont(&LineFont, true);
label->GetWindowRect(rect);
ScreenToClient(rect);
pDC = label->GetDC();
pDC->SelectObject(&LineFont);
pDC->DrawText(step->StepInstruction, &rect, DT_CALCRECT|DT_WORDBREAK);
label->ReleaseDC(pDC);
label->MoveWindow(rect);
stepTop += rect.Height();
stepTop += 30;
stepEnd += rect.Height();
}
}
Reasons why you can't use OnPaint() are not clear.
The usual strategy when one needs to redraw all or part of a window upon some event is to call InvalidateRect().
Windows will in turn send WM_PAINT (handled by your OnPaint() method) to your app, specifying which part of the window should be redrawn.
I think there's more in the BeginPaint-function than just giving you the CDC. And BeginPaint can only be called from the OnPaint-method.
To solve your problem, use the Invalidate-functions to force a repaint from your "SetupInstructions" method. Then do the drawing inside the OnPaint function.
I suppose CProcessSteps derives from CWnd, perhaps a CDialog?
If you want to draw in the client area of a CWnd derived class you have to get the DC using the CWnd GetDC method. I don't understand why you create your own CDC, you should get the CWnd DC at the beginning of SetupInstructions and use this DC everywhere, also to create your memDC.
Also you should be careful when you allocate memory (new CStatic) if you don't call delete for this variables you will have memory leaks. If you really need to create this CStatics dynamically you will have to keep a pointer to all of them in order to delete them before closing the dialog/view.
As people suggested, I don't think you are following the right way by drawing using OnShowWindow. You should use OnPaint to make your drawing stuff, if you don't want to draw the image until the window is fully initialized you should use a member variable of the window (for instance a bool) initialized to false in the constructor and set it to true when you are ready to draw the image. Then calling Invalidate will draw the image. Something like:
In the .h:
class CProcessSteps : CDialog
{
...
private:
bool m_bReadyToDraw;
};
In the .cpp:
CProcessSteps::CProcessSteps() : CDialog()
{
m_bReadyToDraw = false;
}
BOOL CProcessSteps::OnInitDialog()
{
CDialog:OnInitDialog();
m_bReadyToDraw = true;
return TRUE;
}
void CProcessSteps::OnPaint()
{
CPaintDC dc(this);
if(m_bReadyToDraw)
{
CFontUtil cfu;
cfu.SetFont(&LineFont,30);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
...
}
}
Hope it helps.
Javier
I have a fairly standard MFC application that consists of a main window, and occasionally brings up modal dialogs. As we all know nothing can be done outside a modal dialog until it is closed.
Therefore, a nice UI feature is to "dim" the rest of the main window behind the dialog, to visually indicate you can't use it until you're done with the modal dialog. Some web apps and java/mac apps do this, but I've never seen it done in a traditional C++/MFC application. I'd like to give it a try, even if it's unusual for the platform.
How can this be done? I have several modal dialogs in the application, used in this pattern:
// pMainFrame is available as a pointer to the CWnd of the main window
CMyDialog dialog;
dialog.DoModal(); // invoke modal dialog; returns after dialog closed
Is there an easy way to have the window dimmed before any DoModal() and restored afterwards? I'm using Visual Studio 2010 in case the updated MFC has any features that might help.
Edit: I've posted a solution based on oystein's answer, but I'm starting a bounty in case anyone can improve on it - especially with a smooth fade in/fade out.
You can create another window, completely black, on top of the window you want to dim, and set the black window's opacity with SetLayeredWindowAttributes. It doesn't have to be black, of course, but I guess that's the best dimming color.
EDIT: I hacked together an example - but note that I am not an MFC developer, I usually use the Windows API directly. It seems to work okay, though.
Here is a pastebin. Feel free to add fade-ins etc. yourself. Also note that this dims the entire screen, you'll have to resize my dimming-window if you don't want this behaviour. See code comments.
/**********************************************************************************************
MFC screen dim test
:: oystein :: November 2010
Creates a simple window - click it to toggle whether a translucent black "dimmer" window
is shown. The dimmer-window covers the entire screen, but the taskbar ("superbar" in
Windows 7) will jump on top of it if clicked - it seems. Simple suggestions to fix that
are welcome.
Should work on Windows 2000 and later.
Disclaimer: This is my first MFC program ever, so if anything seems wrong, it probably is.
I have previously only coded with pure Win32 API, and hacked this together using online
tutorials. Code provided "as-is" with no guarantees - I can not be held responsible for
anything bad that happens if you run this program.
***********************************************************************************************/
#include "stdafx.h"
#undef WINVER
#define WINVER 0x500 // Windows 2000 & above, because of layered windows
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// Black window used to dim everything else
//
class CDimWnd : public CFrameWnd
{
public:
CDimWnd()
{
// Get screen res into rect
RECT rc;
GetDesktopWindow()->GetWindowRect(&rc);
CreateEx(WS_EX_LAYERED | // Layered window for translucency
WS_EX_TRANSPARENT | // Click through
WS_EX_TOPMOST | // Always on top
WS_EX_TOOLWINDOW, // Do not appear in taskbar & similar
NULL, TEXT(""),
WS_POPUP, // No frame/borders - though there is
// still some border left - we'll remove
// it with regions
0, 0, rc.right + 10, rc.bottom + 10, // Make the window 10px larger
// than screen resolution in both
// directions - it is still positioned
// at 0,0
NULL, NULL);
// Grab a part of the window the size of the desktop - but 5px into it
// Because the window is larger than the desktop res, the borders are removed
CRgn rgn;
rgn.CreateRectRgn(rc.left + 5, rc.top + 5, rc.right + 5, rc.bottom + 5);
SetWindowRgn((HRGN)rgn, FALSE);
rgn.Detach();
// We have to reposition window - (0,0) of window has not changed
SetWindowPos(NULL, -5, -5, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
// This is where we set the opacity of the window: 0-255
SetLayeredWindowAttributes(RGB(0,0,0), 150, LWA_ALPHA);
}
void Close()
{
CFrameWnd::OnClose();
}
BOOL CDimWnd::OnEraseBkgnd(CDC* pDC); // Set BKG color
DECLARE_MESSAGE_MAP()
};
BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
{
// Set brush to desired background color
CBrush backBrush(RGB(0, 0, 0));
// Save old brush
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect); // Erase the area needed
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Global variable - is screen dimmed?
bool g_bIsDimmed = false;
// The main window
class CMainWnd : public CFrameWnd
{
// Contains a CDimWnd - I'm not sure if this is the "MFC way" of doing things
CDimWnd dimmer;
public:
CMainWnd()
{
Create(NULL, TEXT("Screen dimmer - Press left mouse button on window to toggle"),
WS_OVERLAPPEDWINDOW, CRect(50, 50, 400, 250));
}
// Left mouse button toggles dimming
afx_msg void OnLButtonDown(UINT Flags, CPoint Point)
{
if(!g_bIsDimmed)
{
dimmer.ShowWindow(SW_SHOW);
dimmer.BringWindowToTop();
g_bIsDimmed = true;
}
else
{
dimmer.ShowWindow(SW_HIDE);
g_bIsDimmed = false;
}
}
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMainWnd, CFrameWnd)
ON_WM_LBUTTONDOWN()
END_MESSAGE_MAP()
// The app
class CApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
BOOL CApp::InitInstance()
{
m_pMainWnd = new CMainWnd();
m_pMainWnd->ShowWindow(m_nCmdShow);
m_pMainWnd->UpdateWindow();
return TRUE;
}
CApp HelloApp;
UPDATE:
I hacked together some more code for you, to handle the fading. I'm still no MFC dev, and I left the code in a "rough" state (little error handling, not very robust) to give you something to do too. :) Anyway, here's one way to do it, that I think is fairly clean:
To use it, make your main window contain a dimmer window
class CMainFrm : public CFrameWnd
{
CDimWnd* dimmer;
public:
CMainFrm()
{
// constructor code here ...
dimmer = new CDimWnd();
}
// rest of class ...
};
It can then be used e.g. like this:
dimmer->Show();
MessageBox(TEXT("Hello world"));
dimmer->Hide();
Alternatively I guess you could put this code (Show()/Hide() calls) in the constructor and destructor of the modal dialog, if you want to keep the code there. If you want a "scope"-dim, like in the example you posted, this code would have to go in the constructor & destructor of the CDimWnd class, and you would need something like a static member variable to ensure that only one dimmer is running at a time (unless you want to use a global variable).
For the dimmer window - I did this:
CDimWnd.h
#define TARGET_OPACITY 70 // Target opacity 0-255 for dimmed window
#define FADE_TIME 20 // Time between each fade step in milliseconds
#define FADE_STEP 5 // How much to add to/remove from opacity each fade step
#define ID_FADE_TIMER 1
// Call Show() and Hide() to fade in/fade out dimmer.
// Creates the dimmer window in constructor.
class CDimWnd : public CFrameWnd
{
bool m_isDimming;
public:
CDimWnd();
void Show();
void Hide();
protected:
BOOL OnEraseBkgnd(CDC* pDC);
void OnTimer(UINT_PTR nIDEvent);
DECLARE_MESSAGE_MAP()
};
CDimWnd.cpp
#include "stdafx.h"
#include "CDimWnd.h"
#include "MainFrm.h"
BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
CDimWnd::CDimWnd()
{
// Get the main frame of the application which we want to dim.
CMainFrame* pParent = theApp.pMainFrame;
// Don't do anything if the main frame doesn't appear to be there
if (pParent != NULL)
{
// Get the client area of the window to dim.
CRect rc;
pParent->GetClientRect(&rc);
pParent->ClientToScreen(&rc); // convert to screen coordinates
// Do some fudging to fit the client area exactly.
// Other applications may not need this if the above client area fits already.
rc.top += GetSystemMetrics(SM_CYFRAME);
rc.top += GetSystemMetrics(SM_CYCAPTION); // MFC feature pack seems to include caption in client area
rc.left -= GetSystemMetrics(SM_CXBORDER);
rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
// Create a layered window for transparency, with no caption/border.
CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""),
WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
pParent->GetSafeHwnd(), NULL);
}
}
void CDimWnd::Show()
{
// If we are not already dimming, go for it
if(!m_isDimming)
{
// Bring in front of main window.
BringWindowToTop();
// Set opacity to 0
SetLayeredWindowAttributes(RGB(0,0,0), 0, LWA_ALPHA);
// Show the dimmer window
ShowWindow(SW_SHOW);
// Create timer - the rest is handled in OnTimer() function
SetTimer(ID_FADE_TIMER, FADE_TIME, NULL);
}
}
void CDimWnd::Hide()
{
// If we are dimming, go for it
if(m_isDimming)
{
// Create timer - the rest is handled in OnTimer() function
SetTimer(ID_FADE_TIMER, FADE_TIME, NULL);
}
}
void CDimWnd::OnTimer(UINT_PTR nIDEvent)
{
static int fade = 0;
if(nIDEvent == ID_FADE_TIMER)
{
// We are dimming => we want to fade out
if(m_isDimming)
{
if(fade < 0)
{
// Fading finished - hide window completely, update status & destroy timer
fade = 0;
ShowWindow(SW_HIDE);
KillTimer(nIDEvent);
m_isDimming = false;
}
else
{
// Set window opacity & update fade counter
SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
fade -= FADE_STEP;
}
}
else
// fade in
{
if(fade > TARGET_OPACITY)
{
// Fading finished - destroy timer & update status
fade = TARGET_OPACITY; // but first, let's be accurate.
SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
KillTimer(nIDEvent);
m_isDimming = true;
}
else
{
// Set window opacity & update fade counter
SetLayeredWindowAttributes(RGB(0,0,0), fade, LWA_ALPHA);
fade += FADE_STEP;
}
}
}
}
BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
{
// Fill with black
CBrush backBrush(RGB(0, 0, 0));
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect); // Erase the area needed
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
Okay. As I said, this was thrown together fairly quickly and is in a rough state, but it should give you some code to work from, and a general idea of how (I think) timers are used in MFC. I am definitely not the right person to think anything about that, though :)
I've accepted oystein's answer, since it led me to the solution, but I thought I'd post my modifications. I had to modify it a bit to make it work for me, so it might come in useful to someone else.
For the record, the dimming works well, but it doesn't look as natural as I hoped. In an application which frequently brings up dialogs, the dimming becomes distracting in its regularity of seemingly switching the main window on and off. To compromise, I've made the dimming fairly subtle (about 25% opacity) which gently highlights the active dialog; the instant dimming is still a little distracting, but I'm not sure how to have it fade in or fade out smoothly, especially when scoped.
Also, I'm not a UI expert, but the dimming gave me a sort of impression that the dialog was less related to the window content behind it. This made it feel a bit detached from what I was working on in the application, even though the dialogs are directly manipulating that content. This might be another distraction.
Here it is anyway:
CDimWnd.h
// Dim the application main window over a scope. Creates dimmer window in constructor.
class CDimWnd : public CFrameWnd
{
public:
CDimWnd();
BOOL OnEraseBkgnd(CDC* pDC);
~CDimWnd();
protected:
DECLARE_MESSAGE_MAP()
};
CDimWnd.cpp
#include "stdafx.h"
#include "CDimWnd.h"
#include "MainFrm.h"
BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
// For preventing two dimmer windows ever appearing
bool is_dimmer_active = false;
CDimWnd::CDimWnd()
{
// Get the main frame of the application which we want to dim.
CMainFrame* pParent = theApp.pMainFrame;
// Don't do anything if the main frame doesn't appear to be there,
// or if there is already dimming happening.
if (pParent != NULL && !is_dimmer_active)
{
// Get the client area of the window to dim.
CRect rc;
pParent->GetClientRect(&rc);
pParent->ClientToScreen(&rc); // convert to screen coordinates
// Do some fudging to fit the client area exactly.
// Other applications may not need this if the above client area fits already.
rc.top += GetSystemMetrics(SM_CYFRAME);
rc.top += GetSystemMetrics(SM_CYCAPTION); // MFC feature pack seems to include caption in client area
rc.left -= GetSystemMetrics(SM_CXBORDER);
rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
// Create a layered window for transparency, with no caption/border.
CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""),
WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
pParent->GetSafeHwnd(), NULL);
// Bring in front of main window.
BringWindowToTop();
// Apply 25% opacity
SetLayeredWindowAttributes(RGB(0,0,0), 64, LWA_ALPHA);
// Show the dimmer window
ShowWindow(SW_SHOW);
is_dimmer_active = true;
}
}
CDimWnd::~CDimWnd()
{
is_dimmer_active = false;
}
BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
{
// Fill with black
CBrush backBrush(RGB(0, 0, 0));
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect); // Erase the area needed
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
Usage is dead simple: because CDimWnd creates itself in its constructor, all you need to do is add CDimWnd dimmer as a member of the dialog class, and it automatically dims the main window, no matter where you call the dialog from.
You can also use it within a scope to dim system modal dialogs:
{
CDimWnd dimmer;
MessageBox(...);
}
I couldn't resist doing it.
It's your code with added timers and implemented a fade in / fade out. Also I changed to use mid grey rather than black for the obscuring block.
You can tweak the constants that control the fade to make it smoother by increasing the duration or the increasing the rate. Experiment shows me that a rate of 10hz is smooth for me, but YMMV
// DimWnd.h : header file
#pragma once
class CDimWnd : public CFrameWnd
{
public:
CDimWnd(class CWnd * pParent);
virtual ~CDimWnd();
BOOL OnEraseBkgnd(CDC* pDC);
int opacity, opacity_increment;
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnTimer(UINT_PTR nIDEvent);
void fadeOut();
};
// DimWnd.cpp : implementation file
//
#include "stdafx.h"
#include "dimmer.h"
#include "DimWnd.h"
#include "MainFrm.h"
#include <math.h>
const int TIMER_ID = 111;
// For preventing two dimmer windows ever appearing
bool is_dimmer_active = false;
// constants to control the fade.
int ticks_per_second = 1000; // ms
int start_opacity = 44; // 20%
int max_opacity = 220; // 0->255
double fade_in_duration = 4; // seconds to fade in (appear)
double fade_out_duration = 0.2; // seconds to fade out (disappear)
int rate = 100; // Timer rate (ms
CDimWnd::CDimWnd(CWnd * pParent)
{
// Don't do anything if the main frame doesn't appear to be there,
// or if there is already dimming happening.
if (pParent != NULL && !is_dimmer_active)
{
// Get the client area of the window to dim.
CRect rc;
pParent->GetClientRect(&rc);
pParent->ClientToScreen(&rc); // convert to screen coordinates
// Do some fudging to fit the client area exactly.
// Other applications may not need this if the above client area fits already.
rc.top += GetSystemMetrics(SM_CYFRAME);
rc.top += GetSystemMetrics(SM_CYCAPTION); // MFC feature pack seems to include caption in client area
rc.left -= GetSystemMetrics(SM_CXBORDER);
rc.right += GetSystemMetrics(SM_CXBORDER) + 1;
rc.bottom += GetSystemMetrics(SM_CYBORDER) + 1;
// Create a layered window for transparency, with no caption/border.
CreateEx(WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, NULL, TEXT(""),
WS_POPUP, rc.left, rc.top, rc.Width(), rc.Height(),
pParent->GetSafeHwnd(), NULL);
// Bring in front of main window.
BringWindowToTop();
// Show the dimmer window
ShowWindow(SW_SHOW);
double increment_per_second = ((max_opacity - start_opacity) / fade_in_duration);
opacity_increment = ceil( increment_per_second / (ticks_per_second / rate) ) ;
is_dimmer_active = true;
opacity = start_opacity;
SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
SetTimer(TIMER_ID, rate,NULL);
}
}
CDimWnd::~CDimWnd()
{
fadeOut(); // fade the window out rather than just disappearing.
is_dimmer_active = false;
}
void CDimWnd::fadeOut()
{
// can't use timers as may be in the process of being destroyed so make it quick..
double increment_per_second = ((opacity - start_opacity) / fade_out_duration);
opacity_increment = ceil( increment_per_second / (ticks_per_second / rate) ) ;
while(opacity > start_opacity)
{
opacity -= opacity_increment;
SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
Sleep(100);
}
}
BOOL CDimWnd::OnEraseBkgnd(CDC* pDC)
{
// Fill with midgray
CBrush backBrush(RGB(128,128,128));
CBrush* pOldBrush = pDC->SelectObject(&backBrush);
CRect rect;
pDC->GetClipBox(&rect); // Erase the area needed
pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOldBrush);
return TRUE;
}
BEGIN_MESSAGE_MAP(CDimWnd, CFrameWnd)
ON_WM_ERASEBKGND()
ON_WM_TIMER()
END_MESSAGE_MAP()
void CDimWnd::OnTimer(UINT_PTR nIDEvent)
{
if (opacity >= max_opacity)
{
// stop the timer when fade in finished.
KillTimer(TIMER_ID);
return;
}
opacity += opacity_increment;
SetLayeredWindowAttributes(RGB(0,0,0), opacity, LWA_ALPHA);
CFrameWnd::OnTimer(nIDEvent);
}
I have an MFC app which I have been working on for a few weeks now, I want to manually set the dimensions of the main frame when it is loaded, can someone give me a hand with this, specifically where to put the code as well?
Thanks!
You can also set the size (with SetWindowPos()) from within CMainFrame::OnCreate(), or in the CWinApp-derived class' InitInstance. Look for the line that says pMainFrame->ShowWindow(), and call pMainFrame->SetWindowPos() before that line. That's where I always do it.
Find your screen size with ..
CRect rect;
SystemParametersInfo(SPI_GETWORKAREA,0,&rect,0);
screen_x_size=rect.Width();
screen_y_size=rect.Height();
use these values to calculate the X and Y size of your window then ..
::SetWindowPos(m_hWnd,HWND_TOPMOST,0,0,main_x_size,main_y_size,SWP_NOZORDER);
Where main_x_size and main_y_size are your sizes.
I think you're looking for PreCreateWindow and that your app isn't dialog based.
It's a virtual member function of CWnd class and it's called by framework just before a window is created. So it's a right place to place your changes.
You should write something like this:
BOOL CMyWindow::PreCreateWindow(CREATESTRUCT& cs)
{
cs.cy = 640; // width
cs.cx = 480; // height
cs.y = 0; // top position
cs.x = 0; // left position
// don't forget to call base class version, suppose you derived you window from CWnd
return CWnd::PreCreateWindow(cs);
}
you can use this:
CRect rect;
SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0);
left = -3, right = 3;
rect.top = 100;
rect.bottom = 500;
rect.left = 100;
rect.right = 800;
//or use
CRect cr;
cr.SetRect(POINT{ 100,100 }, POINT{ 500,800 });
MoveWindow(rect);
BOOL YourProjectApp::InitInstance()
{
:
:
m_pMainWnd->MoveWindow(0, 0, 1900, 1000); // add this line for fixing the default size of mainWindow
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}