MFC Draw Stuff Outside OnPaint in a Dialog-based App - c++

I'm currently trying to draw something outside OnPaint function. I know there are many duplicate questions on the internet, however, I failed to get any of them to work. This is entirely because of my lack of understanding of MFC.
What works inside OnPaint:
CDC* pDC = GetDC();
HDC hDC = pDC->GetSafeHdc();
CRect lDisplayRect;
GetDlgItem(IDC_DISPLAYPOS)->GetClientRect(&lDisplayRect);
GetDlgItem(IDC_DISPLAYPOS)->ClientToScreen(&lDisplayRect);
ScreenToClient(&lDisplayRect);
pDC->FillSolidRect(&lDisplayRect, GetSysColor(COLOR_3DDKSHADOW));
pDC->TextOutW(300, 300, TEXT("test"));
It fills the area of the button control I have with a defined colour.
And it prints out the "test" string without any issue.
But it won't work outside OnPaint.
I've seen numerous suggestions such as using CmemDC,CPaintDC, etc
But none of them worked outside OnPaint.
For example,
CClientDC dc(this);
dc.rectangle( ....);
does not work.
Please note that this is a temporary test code and what I am eventually trying to do is draw incoming frames from a frame grabber within my display thread (a separate thread from the main UI thread) on the DisplayPos area and my dlg object(the dialog) owns an instance of the DisplayThread class. And I'm passing HWND and displayrect upon creating the member mDisplayThread so I can draw stuff within the display thread and that's the reason why I need to be able to draw to DC outside OnPaint (DisplayThread class does not have OnPaint or inherit any class that has it).
I'm in dire need of help...Please help!
Added: I have overridden PreTranslateMessage(MSG* pMsg) and made it return without calling the default function just in case WM_ERASE msg is erasing everything, but this approach didn't work either.

For example:
void CMFCApplicationDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
HDC hdc = ::GetDC(m_hWnd);
Ellipse(hdc, point.x - 10, point.y - 10, point.x + 10, point.y + 10);
::ReleaseDC(m_hWnd, hdc);
CDialogEx::OnLButtonDown(nFlags, point);
}

Two important suggestions:
Do not draw outside of OnPaint. The content may be erased or painted over at the direction of OS.
Do not draw on the dialog surface. First, it is not designed for that. It may be affected by other controls or by theming, skinning, etc. Just create a window with your own window procedure, so you are in control.
I would store the information needed for drawing in you frame grabber, and cause an immediate window painting by calling RedrawWindow

Related

Erase previously drawn window in mfc

am trying to move a custom button whose base class is CWnd, am using MoveWindow() method but each time i move the window the previously drawn window is still visible. i tryed to call InvalidateWindow, InvalidateRect together with UpdateWindow on the parent window but all didn't work. i also tried RedrawWindow nothing happend. how can i properly update the parent window with the new position of the button?
here is my sample code
void CCalendarCtrl::Shift()
{
RECT rc;
m_Up.GetWindowRect(&rc);
rc.top -= 20;
rc.bottom -=20;
m_Up.MoveWindow(&rc,TRUE);
RedrawWindow();
}
Call Invalidate() after movewindow().
https://social.msdn.microsoft.com/Forums/en-US/d6da8041-747c-4b31-b493-343e4516b452/are-the-coordinates-returned-by-cwndgetwindowrect-mfc-and-cwndmovewindow-mfc?forum=vcmfcatl
GetWindowRect returns screen coordinates. MoveWindow works using Client coordinates. So you need to do ScreenToClient(&rc) between the two calls.

CPaintDC(this) in CStatic derived class paints outside of control

As the title says.
Even with CPaintDC in the derived class the GDI drawing is not cut off.
Thanks in advance.
void CGraph::OnPaint ()
{
CPaintDC dc(this);
dc.SetViewportOrg (0, 400);
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(1000, 800);
dc.SetViewportExt(1000, -800);
// MessageBox(L"OnPaint");
ProcessData ();
DrawCoordinateSystem (&dc);
DrawGrid (&dc);
DrawGraph (&dc);
}
So, your CGraph is derived from CStatic, and the drawing code you show draws outside of the CStatic control, onto the dialog it is on? That's impossible, a control can only draw on itself. Are you sure the control isn't bigger than you think it is, and what you think is off-control actually isn't? Use spy++ to select your cstatic, it'll show you the border of the window.
Maybe what you are seeing is improper invalidation. Try dragging another window over your control, see what that does.
Otherwise, the methods to restrict the drawing area are
You manually track where to draw. Tedious.
Use SetClipRgn() to set the area to which to restrict drawing.
Not quite the same, but symptoms sometimes look similar: check the WS_CLIPSIBLINGS and WS_CLIPCHILDREN flags of your control and the dialog it's on.
Here is the way I solved the problem
CDC* pDC = GetDC();
CRect rClient(0,0,1000,800);
//GetClientRect(rClient);
CRgn ClipRgn;
if (ClipRgn.CreateRectRgnIndirect(&rClient))
{
pDC->SelectClipRgn(&ClipRgn);
}
pDC->SelectObject (PenBlack);
pDC->MoveTo (-leftMargin*zoomWidth, setPointsCorrected);
pDC->LineTo (1000*zoomWidth, setPointsCorrected);
pDC->SelectClipRgn(NULL);
ReleaseDC(pDC);

Transparent child control

I'm writing a control that may have some of its parts transparent or semitransparent. Basically this control shows a png image (with alpha channel). The control's parent window has some graphics on it. Therefore, to make the png control render correctly it needs to get image of what the parent window would draw beneath it. Parent dialog might have WS_CLIPCHILDREN flag set, that means that the the parent window won't draw anything under the the png control and in this case png control won't work properly.
This control has to work on Windows Mobile also, so it can't have WS_EX_TRANSPARENT
Funny you should ask. I just wrote code to do this yesterday. It's in the code base for Project Resistance. Look at how ResistorView (which has a PNG with transparency) interacts with MainForm (which has the background image).
I'm not doing c#, so I don't think that could be helpful. I tried to look into your code and I don't see anything that can solve my problem even though you accomplish the same task as me.
To give more details, my control also supports gif animation and I also used the same IImage load from stream as you do in your project. From my experience, that IImage load from stream is unbelievable junk code, it's extremely slow. I have no idea how that possibly could be so slow. Loading 32x32 gif with 31 frames takes like 1.5secods using that junk IImage stuff. I wrote my own loader (using some opensource gif library) and without any optimization entire decoding/loading of the gif frames takes less than 100ms. I'm using TouchPro2... I can't imagine how badly this code would perform on a low end device.
As a quick alternative here's a possible solution to my question:
in the WM_PAINT handler of the child control that draws images (gif or png) I do the following:
first, I call SetRedraw(false) on self and on parent window.
then I hide the child control, and send WM_PAINT to parent window with optional HDC (as wParam). The parent window renders everything to the offscreen bitmap (passed via hdc to WM_PAINT) and after WM_PAINT successfully returns I take relevant part of the offscreen bitmap.
Then I enable show the child window and call SetRedraw(true) on both child and the parent window. That trick works but has some disadvantages obviously (I need to createa huge offscreen bitmap to capture entire screen area even though I need 32x32 pixel from the middle of parent window).
the code is below:
bool pic_control::get_parent_bg(MyBitmap & bg)
{
CWindow parent = GetParent();
CClientDC dc(parent);
bool is_visible = IsWindowVisible() && parent.IsWindowVisible();
if(!is_visible){
return false;
}
parent.SetRedraw(false);
SetRedraw(false);
CRect rect;
parent.GetClientRect(rect);
MyBitmap bmp;
bmp.create(rect.Width(), rect.Height());
ShowWindow(SW_HIDE);
parent.SendMessage(WM_PAINT, (WPARAM)(HDC)bmp.dc());
ShowWindow(SW_SHOW);
GetWindowRect(rect);
parent.ScreenToClient(rect);
bg.create(rect.Width(), rect.Height());
bg.dc().BitBlt(0, 0, rect.Width(), rect.Height(), bmp.dc(), rect.left, rect.top, SRCCOPY);
IF_DEBUG SAL::saveHBITMAPToJpeg(bg.GetBitmap(), "frames/BG.jpg", 100);
SetRedraw(true);
parent.SetRedraw(true);
return true;
}
WS_CLIPCHILDREN is forced on in WinCe, you cannot toggle it. I dont know why, maybe it is done for performance reasons.
From my experience what i did in this situation.
1) If the parent window bacgkround is dynamic (for instance window that contains map, which can be moved), then it is painted to memory canvas first, then to the screen, and memory canvas is saved and used for painting transparent childs. Memory canvas would not contain holes in place of child windows, so it can be used for futher pixel merge. The disadvantage here is memory consumption to hold canvas in memory.
2) If parent window background is static (dialog box, menu, etc) then you can make non-window childs.
class CImageButton
{
public:
bool IsPointInside(POINT pt);
void OnPaint(HDC canvas);
void OnClick();
void SetRect(RECT& rc);
private:
RECT m_rc;
};
Your parent window will contain an array of such objects and redirect WM_PAINT and
mouse clicks to them.
The disavantage is additional code needed to be added to parent window, but you can make a base class for all your parent windows, which would handle issues with non-windowed controls.

Does overriding OnNcPaint() affect the painting of the client area of a window?

I want to change the appearance of a window's caption bar, so I decided to override the OnNcPaint() method of CMainFrame. But when I did this, I found a problem. If there is another window covering my window, and I drag the window quickly, the content of the client area of my window disappeared, which came to sight only when I stopped the dragging.
My overridden OnNcPaint() is like below:
void CMainFrame::OnNcPaint()
{
CDC* pWinDC = GetWindowDC();
//do some drawing
ReleaseDC(pWinDC);
}
Is there something wrong with my approach?
Thank you!
Unless you use a clipping region set up to exclude the client area, you can paint over it from OnNcPaint(). So... if your drawing logic can't be modified to exclude the client in some other way, set up an appropriate clipping region first:
CRect rect;
GetWindowRect(&rect);
ScreenToClient(&rect);
CRect rectClient;
GetClientRect(&rectClient);
rectClient.OffsetRect(-rect.left, -rect.top);
rect.OffsetRect(-rect.left, -rect.top);
pWinDC->ExcludeClipRect(&rectClient);
// ...
// draw stuff here
// ...
pWinDC->SelectClipRgn(NULL);

How to draw in the nonclient area?

I'd like to be able to do some drawing to the right of the menu bar, in the nonclient area of a window.
Is this possible, using C++ / MFC?
Charlie hit on the answer with WM_NCPAINT. If you're using MFC, the code would look something like this:
// in the message map
ON_WM_NCPAINT()
// ...
void CMainFrame::OnNcPaint()
{
// still want the menu to be drawn, so trigger default handler first
Default();
// get menu bar bounds
MENUBARINFO menuInfo = {sizeof(MENUBARINFO)};
if ( GetMenuBarInfo(OBJID_MENU, 0, &menuInfo) )
{
CRect windowBounds;
GetWindowRect(&windowBounds);
CRect menuBounds(menuInfo.rcBar);
menuBounds.OffsetRect(-windowBounds.TopLeft());
// horrible, horrible icon-drawing code. Don't use this. Seriously.
CWindowDC dc(this);
HICON appIcon = (HICON)::LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_MAINFRAME), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
::DrawIconEx(dc, menuBounds.right-18, menuBounds.top+2, appIcon, 0,0, 0, NULL, DI_NORMAL);
::DestroyIcon(appIcon);
}
}
In order to draw in the non-client area, you need to get the "window" DC (rather than "client" DC), and draw in the "window" DC.
You should try handling WM_NCPAINT. This is similar to a normal WM_PAINT message, but deals with the entire window, rather than just the client area. The MSDN documents on WM_NCPAINT provide the following sample code:
case WM_NCPAINT:
{
HDC hdc;
hdc = GetDCEx(hwnd, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN);
// Paint into this DC
ReleaseDC(hwnd, hdc);
}
This code is intended to be used in the message loop of your applicaton, which is canonically organized using a large 'switch' statement.
As noted in the MFC example from Shog, make sure to call the default version, which in this example would mean a call to DefWindowProc.
If you just want something in the menu bar, maybe it is easier/cleaner to add it as a right-aligned menu item. This way it'll also work with different Windows themes, etc.