Im building a MFC c++ application in which i let the user read an image, draw lines on it and then save it.
so i have a "CImage" object which is called "Image" in which the user loads the image to.
and i have a device context object and i was able to draw lines on it
the device context object that is in run-time using "OnLButtonDown" and "OnLButtonUp" event handlers.
i then let the user save the image using "CImage.save" .. the image is saved but the device context drawn lines aren't there which is to be expected ..
but I DO want them to appear in the saved image..
the question is how can i get the device context Object to affect my CImage Object?
this is the event handler for mouse button down
void CProFilterDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
curser =point;
if (draw && Boundry.PtInRect(point) )
{
CDialogEx::OnLButtonDown(nFlags, point);
}
}
and this one when the mouse button is up
void CProFilterDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (draw && Boundry.PtInRect(curser) && Boundry.PtInRect(point))
{
CClientDC dc(this);
dc.MoveTo(curser);
dc.LineTo(point);
CDialogEx::OnLButtonUp(nFlags, point);
}
}
this is where i load my Cimage object
void CProFilterDlg::OnBnClickedBtnBrowse()
{
CFileDialog Browse(true);
if(Browse.DoModal() == IDOK)
{
ImagePath = Browse.GetPathName();
}
image.Load(ImagePath);
}
and this is where i save my CImage
void CProFilterDlg::OnBnClickedSave()
{
CFileDialog Save(true);
if(Save.DoModal() == IDOK)
{
ImagePath = Save.GetPathName();
}
image.Save(ImagePath,Gdiplus::ImageFormatBMP);
}
From what you've shown, it appears you are using the wrong DC. You seem to be using the DC for the dialog (ie. CCLientDC) and not the actual CImage. You should be constructing the DC from
CImage::GetDC ().
That DC will have the currently selected bitmap.
Are you looking for CImage:BitBlt? It is used to copy a bitmap from the source device context to current device context.
Related
(Update, see original question below)
After doing a bit of digging, I'm basically trying to understand the following; In the context of an MDI application, if a menu (which is associated with a specific CChildWnd) has an MF_OWNERDRAW, why are the ON_WM_MEASUREITEM and ON_WM_DRAWITEM events send to the CMainWnd instead of the CChildWnd?
In my InitInstance, the document template is registered and the associated menu is modified to add the MF_OWNERDRAW:
BOOL CMyApp::InitInstance()
{
// ...
CMultiDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(
IDR_CHILDFRAME,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CFooWnd),
RUNTIME_CLASS(CFooView)
);
if (pDocTemplate->m_hMenuShared != NULL) {
CMenu* pMenu = CMenu::FromHandle(pDocTemplate->m_hMenuShared);
// Add MF_ONWERDRAW to the items that need it.
pMenu->ModifyMenu([item_id], MF_BYCOMMAND | MF_OWNERDRAW, [item_id]);
}
AddDocTemplate(pDocTemplate);
// ...
}
So, once the document template is registered, the menu associated with the document/frame is modified to add the MF_ONWERDRAW flag to each of the required items (the color selection items in my case).
However, why are the OnMeasureItem and OnDrawItem events send to the CMainWnd and not the CFooWnd? And how can I direct the events to the CFooWnd instead?
The reason I'am asking, if I have 5 different types of documents in my MDI application, each needing custom menus, then the CMainWnd basically becomes a mess of message handling. The logical place for the custom menu logic is in the CChildWnd, not the CMainWnd.
Original question:
I'm doing some work on a very old application (MFC 4.2) and I'm running into a problem with drawing in a menu item.
The original application has a menu to select a color and it actually draws the colors in the menu when opened so it easier for the user to select the color.
The behavior for this implemented in CMainWnd using the OnMeasureItem and the OnDrawItem.
class CMainWnd : public CMDIFrameWnd
{
DECLARE_DYNCREATE(CMainWnd)
protected:
afx_msg void OnMeasureItem(int, LPMEASUREITEMSTRUCT);
afx_msg void OnDrawItem(int, LPDRAWITEMSTRUCT);
DECLARE_MESSAGE_MAP()
};
Then, in the implementation (omitted bits and pieces for brevity):
BEGIN_MESSAGE_MAP(CMainWnd, CMDIFrameWnd)
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
lpmis->itemWidth = ::GetSystemMetrics(SM_CYMENU) * 4;
lpmis->itemHeight = ::GetSystemMetrics(SM_CYMENU) * 1;
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CDC dc;
dc.Attach(lpdis->hDC);
CBrush* pBrush;
// draw the hover/selection rectangle
pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT :
COLOR_MENU));
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
// load a checkbox icon into a bitmap
BITMAP bm;
CBitmap bitmap;
bitmap.LoadOEMBitmap(OBM_CHECK);
bitmap.GetObject(sizeof(bm), &bm);
// if color/item selected then draw the checkbox
if (lpdis->itemState & ODS_CHECKED) {
CDC dcMem;
dcMem.CreateCompatibleDC(&dc);
CBitmap* pOldBitmap = dcMem.SelectObject(&bitmap);
dc.BitBlt(
lpdis->rcItem.left + 4,
lpdis->rcItem.top + (((lpdis->rcItem.bottom - lpdis->rcItem.top) - bm.bmHeight) / bm.bmWidth,
bm.bmHeight,
&dcMem,
0,
0,
SRCCOPY
);
dcMem.SelectObject(pOldBitmap);
}
// draw the actual color bar
pBrush = new CBrush(CPaintDoc::m_crColors[lpdis->itemID - ID_COLOR_BLACK]);
CRect rect = lpdis->rcItem;
rect.DeflateRect(6, 4);
rect.left += bm.bmWidth;
dc.FillRect(rect, pBrush);
delete pBrush;
dc.Detach();
}
What the OnDrawItem does is; it draws a horizontal color bar with a color, prefixed by a check icon if that color is selected and the menu item being hovered over is highlighted by a box being drawn around it.
However, since I'm turning this application into a Multidoc application and I don't really feel that this logic should be in the CMainWnd (since none of the other documents will have this type of menu), but that it should be part of the CChildWnd (which inherits from CMDIChildWnd).
But when I move this logic to that class, when I run the application, I get following message in the console logger:
Warning: unknown WM_MEASUREITEM for menu item 0x0082.
And none of the custom menu behavior seems to work.
so, the question is; How can move the custom behavior of a menu into the frame class of an MDI document rather than having it located in the application main frame?
I figured out a work around. Not ideal but I can understand that this is a quirk in the framework, i.e. the menu seems to be part of the MainWnd so from a technical point of view, that is where the ON_WM_MEASUREITEM and ON_WM_DRAWITEM would be handled.
Anyhow, my work around. Basically capture the events in the MainWnd and then delegate the behaviour to the ChildWnd. The trick here (I guess) is to figure out what ChildWnd to delegate to since in an MDI application there can be any number of different ChildWnd's (each with their own Document and View types).
The work around:
void CMainWnd::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoMeasureItem(nIDCtl, lpmis);
}
}
}
void CMainWnd::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis)
{
CMDIChildWnd* pActiveWnd = MDIGetActive();
if(pActiveWnd && pActiveWnd->IsWindowVisible())
{
if(pActiveWnd->IsKindOf(RUNTIME_CLASS(CMyChildWnd))) {
CMyChildWnd* pMyChildWnd = (CMyChildWnd*)pActiveWnd;
CMyChildWnd->DoDrawItem(nIDCtl, lpdis);
}
}
}
Pretty straight forward, in the context of the MainWnd, get a pointer to the active MDI ChildWnd, check if it is active, then check the type by using IsKindOf and RUNTIME_CLASS and if so, voila, delegate the behavior to the ChildWnd. To DoMeasureItem and the DoDrawItem are just public methods implemented on the ChildWnd (see question for details).
I have an MFC dialog application where the user can upload a photo and it will be displayed within the dialog. After they have uploaded an image they will be able select a region of this image and for this I am using the CRectTracker class. My problem however is that the tracker is currently being displayed behind the image.
What I have tried so far is:
Googling how to set the z-order of the CRectTracker object, but cannot seem to find any documentation on it.
Set the z-order of the image to the bottom using SetWindowPos(&wndBottom,...) but the tracker still appears behind the image.
I am using a Picture Control (type Bitmap) to display the image.
EDIT:
I draw the tracker in the OnPaint() method of my dialog class like this:
CPaintDC dc(this);
m_tracker.Draw(&dc);
which is being called by this->Invalidate(); in OnLButtonDown and OnMouseMove whenever I need to refresh the tracker:
void CTAB3::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_tackerFlag)
{
int nHitTest;
nHitTest = m_tracker.HitTest(point);
if (nHitTest < 0) // if the mouse down point is outside of the rect tracker
{
m_start = point; // Record the start drawing point
m_bDraw = TRUE; // set m_bDraw (in handle funtion WM_MOUSEMOVE will test this to decide whether to draw)
}
else // if the mouse down point is inside of the rect tracker
{
m_tracker.Track(this, point, FALSE); // start drag the rect tracker
this->Invalidate(); // make the window paint to refresh the track
}
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CTAB3::OnMouseMove(UINT nFlags, CPoint point)
{
// m_bDraw is set to true in funtion OnLButtonDown and set to false in funtion OnLButtonDown
// m_bDraw is use for testing if the mouse is moved along with the left button is pressed.
if (m_bDraw)
{
m_tracker.m_rect.SetRect(m_start, point); // set the rect of rect tracker
this->Invalidate(); // make the window paint to refresh the rect
}
CDialogEx::OnMouseMove(nFlags, point);
}
EDIT2:
Based on #ConstantineGeorgiou comment I decided to keep trying to implement the tracker with the resize handles using Track() as this would be the preferred solution for my project. I finally managed to draw the tracker on top of the image, but I am not particularly happy with the solution I ended up with. Basically I moved the Draw() function to the mouse move handler, so that the tracker is drawn after the OnPaint() has drawn everything else on the dialog. If anyone has any suggestion on better ways to do this, I would be very grateful for any feedback.
This is how I implemented it with the Draw() in the OnMouseMove() handler:
//In the header file:
BOOL m_bDraw = FALSE
void CTAB3::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_tackerFlag)
{
m_tracker.Track(this, point, FALSE); // start drag the rect tracker
this->Invalidate();
m_bDraw = TRUE;
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CTAB3::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDraw)
{
//Draw tracker
CDC* dc = GetDC();
m_tracker.Draw(dc);
}
CDialogEx::OnMouseMove(nFlags, point);
}
BOOL CTAB3::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (m_tackerFlag)
{
if (pWnd == this && m_tracker.SetCursor(this, nHitTest))
{
return TRUE;
}
}
return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}
void CTAB3::OnPaint()
{
CPaintDC dc(this); // device context for painting
//m_tracker.Draw(&dc);
}
I developed a small program in MFC that can display shapes and move them around.
I would like to be able to select PNG images that can be used as the background of the the main MFC window.
Currently, the background is set to black in the following code:
...
CBrush brush;
brush.CreateSolidBrush(RGB(0,0,0));
myCDC->FillRect(r,&brush);
...
I've found classes that might allow me to upload Bitmaps - for example [CStatic][1] - but have had no luck finding something for PNGs.
Does anyone know how to do this, or have you ever done something similar?
You can try to use the class Image of Gdiplus
http://msdn.microsoft.com/en-us/library/windows/desktop/ms534462(v=vs.85).aspx
because the png file can have the alpha property, but the bitmap does not have,
so I always use the Image to load the png file.
wchar_t szFile[256] = {0}; // png image file path
Image* m_pImage;
m_pImage = new Image(szFile, FALSE); // Load png file
// drawing png image
CPaintDC dc(this); // 用于绘制的设备上下文
Graphics graphics(dc.m_hDC);
if(m_pImage != NULL && (m_pImage->GetLastStatus() == Gdiplus::Status::Ok))
{
graphics.DrawImage(m_pImage, 0, 0, m_pImage->GetWidth(), m_pImage->GetHeight());
}
// need delete it, if you donot need it
if (m_pImage != NULL)
{
delete m_pImage;
m_pImage = NULL;
}
I think that GDI+ is the best and simple way for display images. It supports jpg, gif, tiff as well as png.
You can just display a image as below code,
void OnPaint(HDC hdc)
{
Graphics G(hdc);
Image I(L"snowman.jpg");
if (I.GetLastStatus() != Ok) {
MessageBox(hWndMain,TEXT("cannot read the image file"), TEXT("error"),MB_OK);
return;
}
G.DrawImage(&I,0,0);
}
Also, you can change image during execution time using FromFile static method.
Image *pI;
pI=Image::FromFile(L"snowman.jpg");
G.DrawImage(pI,0,0);
delete pI;
I have a picture control box (a CStatic) in a dialog. When the user presses a button in the dialog I need the onPaint() to draw an image in it. The problem is, The image is drawn at the loading of the dialog. How do I prevent this and call it only at button press.
My onPaint code;
void CStaticGraph::OnPaint()
{
POINT xy[1000];
CPaintDC dc(this); // device context for painting
CRect Recto;
char LocDim[80];
GetWindowRect(&Recto);
CPoint pBottom,pTop,pLeft;
CPoint p[50];
pBottom.SetPoint(0,0);
pTop.SetPoint(0,Recto.Height());
pLeft.SetPoint(Recto.Width(),Recto.Height());
dc.MoveTo(pBottom);
dc.LineTo(pTop);
dc.LineTo(pLeft);
int y[] ={80,120,180,200};
int x=0;
for(int i=0; i<sizeof(y);i++){
p[i].SetPoint(Recto.Height()-x,y[i]);
if(i>0){
dc.MoveTo(p[i-1]);
dc.LineTo(p[i]);
}
x+=50;
}
}
As you can see I'm plotting a graph, I also need to pass data (the y[] values) at button press. I haven't done that yet.
Thanks.
Add a variable, such as a BOOL, to your CStaticGraph class to act as a flag to tell OnPaint() what to do. Initialize the variable in the constructor and change it when the button is clicked. For example:
In the header file for CStaticGraph add:
BOOL m_fButtonPressed;
In the CStaticGraph constructor add:
m_fButtonPressed = FALSE;
In your button click handler do something like:
void CStaticGraph::OnButtonClick()
{
m_fButtonPressed = TRUE;
Invalidate();
}
Then in your OnPaint only draw the graph when flag is set:
void CStaticGraph::OnPaint()
(
CPaintDC dc(this);
if ( m_fButtonPressed )
{
// Clear the window
return;
}
// Draw the graph
. . .
}
In addition to the Invalidate() in the other answer, you need to also change
for(int i=0; i<sizeof(y);i++)
to
for(int i=0; i<sizeof(y)/sizeof(int);i++)
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