I'm using C++ on Visual Studio 2012 update 4, and I have a Dialog where I want to display a button showing a bitmap (.bmp file), without borders
I have extended CButton to add my tooltip, and so on.
Using the Resource View to open the Dialog .rc file, I set the button Property Bitmap to true. Then, from the Dialog OnInitDialog function, I used this code to set the bitmap, identified as IDB_HELP
myButton.SetBitmap((HBITMAP)LoadImage(AfxGetApp()->m_hInstance,
MAKEINTRESOURCE(IDB_HELP), IMAGE_BITMAP, 16, 16, LR_COLOR));
But it displays this and I don't want that half-border.
I tried making it Flat and Transparent in the Resource View, but it only gets uglier.
Then I tried to only draw the image by setting Owner Draw to true and then redefining DrawItem in my button class, but I can't quite figure that out either.
Any easy way to make an icon-only button?
You have to use owner draw button or custom draw. Below is a simple example, it uses icon instead of bitmap (it's easier to assign transparent background for it)
class CMyButton:public CButton
{
void OnPaint()
{
CPaintDC dc(this);
CRect rc = dc.m_ps.rcPaint;
dc.FillSolidRect(&rc, GetSysColor(COLOR_3DFACE));
BOOL offset = (BST_PUSHED & GetState()) ? 1 : 0;
int w = 24;
int h = 24;
HICON hicon = (HICON)LoadImage(AfxGetApp()->m_hInstance, MAKEINTRESOURCE(IDC_ICON),
IMAGE_ICON, w, h, LR_DEFAULTCOLOR);
DrawIconEx(dc, offset, offset, hicon, w, h, 0, 0, DI_NORMAL);
DestroyIcon(hicon);
}
DECLARE_MESSAGE_MAP()
};
BEGIN_MESSAGE_MAP(CMyButton, CButton)
ON_WM_PAINT()
END_MESSAGE_MAP()
Usage:
BOOL CMyDialog::OnInitDialog()
{
BOOL res = CDialogEx::OnInitDialog();
static CMyButton bn;
bn.SubclassDlgItem(IDC_BUTTON1, this);
return res;
}
You do NOT need to do your own icon painting algorithm if you use a CMFCButton and you are a comfortable using an ICO file instead of a BMP. Although you can directly say in your resources file a button is of this type, I do not recommend it, because it adds an unmaintainable hexadecimal piece of text on the rc file. And if you use several rc files, one for each language, it's really devilish!
So lets go. In your form class, declare a member
CMFCButton m_button1;
The DoDataExchange should look like:
void MyDialog::DoDataExchange(CDataExchange* pDX)
{
__super::DoDataExchange(pDX);
DDX_Control(pDX, IDC_BUTTON1, m_button1);
// ...
}
Then the OnInitDialog should be something like:
BOOL CMyDialog::OnInitDialog()
{
if(!__super::OnInitDialog())
return FALSE;
m_button1.m_nFlatStyle= CMFCButton::BUTTONSTYLE_NOBORDERS;
m_button1.SetIcon(IDI_HELP);
return TRUE;
}
Use CMFCbutton and set the border style to BUTTONSTYLE_NOBORDERS;
Use a .ico instead of png for pictures.
Also points to note:
Load the library or exe which has the icon.
Pass the dll/exe loaded handle to loadicon.
Use MFC button handle to set the icon with property set as Noborder.
Example code:
m_HResdll = LoadLibrary("C:\\Repos\\iFIX\\SCADABin\\en\\UAAClientConfigurationRes.dll");
//m_hTrustIcon = LoadIcon(m_HResdll, MAKEINTRESOURCE(IDI_ICON1));
m_hTrustIcon = (HICON)LoadImage(m_HResdll, MAKEINTRESOURCE(IDI_ICON2),1,18,22, LR_DEFAULTCOLOR);
unsigned int err = GetLastError();
m_btnTrustIcon.SetIcon(m_hTrustIcon);
this->m_btnTrustIcon.EnableWindow(true);
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'm writing an mfc application.
I've a simple CWnd with OnEraseBkgnd and OnPaint. I'm experiencing some problems when another window covers partially my window.
So When the covering window is being moved out my CWnd gets WM_ERASEBKGND. I'm cleaning up dirty area and I return TRUE. What I can see here is that CDC I get has clipping box set and I use it so only a covered part is being erased. That's good.
But then WM_PAINT comes. CDC I get with GetDC does not have any clipping box so the whole window area is being repainted. This is a problem because in my paint event I use CDC::DrawText with a transparent background (CDC::SetBkMode(TRANSPARENT)) and painting the same text in the same not-erased place causes that text becomes 'bold'. Simply painting text over and over in the same place without wiping out background makes it look ugly.
Is it a normal behavior? Is my approach ok?
EDIT:
Here I attach more inforamtion about issue.
SSCCE:
class Foo : public CFrameWnd
{
public:
BOOL OnEraseBkgnd(CDC* pDC)
{
CRect rect;
pDC->GetClipBox(rect);
HBRUSH brush = ::GetSysColorBrush(COLOR_WINDOW);
HGDIOBJ pOld = pDC->SelectObject(brush);
const BOOL result = pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOld);
return result;
}
void OnPaint()
{
CWnd::OnPaint();
CDC *dc = GetDC();
CRect clipBox;
dc->GetClipBox(clipBox);
CRect rect;
GetClientRect(rect);
CFont *font = &globalFont; // in my app here is the font I use but it doesn't matter
HFONT hFont = static_cast<HFONT>(font->GetSafeHandle());
auto oldFont = dc->SelectObject(hFont);
const int bkMode = dc->SetBkMode(TRANSPARENT);
dc->DrawText("AAAAAAAAA", -1, rect, 0);
dc->SetBkMode(bkMode);
dc->SelectObject(oldFont);
}
DECLARE_MESSAGE_MAP()
};
Creation:
Foo* f = new Foo;
f->Create( 0, "test", WS_VISIBLE| WS_OVERLAPPEDWINDOW);
Below how does the window look normally:
And below after moving the window so half of the text was out of monitor and then moved back:
So the part of window which was invisible was erased and then text was placed again. For the visible part of the window text was not erased and in OnPaint was redrawn again causing 'bold'.
You should be using CPaintDC, not just because it controls resources, as Barmak pointed out, but also because it retrieves clipping data. GetDC does not do that. (Barmak also mentioned PAINTSTRUCT, but it may not be clear that is the key to the clipping issue.)
This is unrelated issue, but GetDC is causing GDI resource leak in above code. ReleaseDC must be called before exiting the function:
void OnPaint()
{
CWnd::OnPaint();
CDC *dc = GetDC();
dc.DrawText(...);
...
ReleaseDC(dc);
}
Better yet, MFC has automatic cleanup with CClientDC
void myWnd::foo()
{
CClientDC dc(this);
dc.DrawText(...);
}
OnPaint can use special CPaintDC class which corresponds to PAINTSTRUCT:
void myWnd::OnPaint()
{
CPaintDC dc(this); //don't call CWnd::OnPaint
dc.DrawText(...);
}
Back to the problem:
It looks like part of background is not repainted, but part of the text is repainted. This makes it look ugly specially with clear type fonts (it looks like bold but it isn't).
You can fix the problem with this:
dc.SetBkMode(OPAQUE);
dc.SetBkColor(GetSysColor(COLOR_WINDOW));
dc.DrawText(L"AAAAAAAAA", -1, rect, 0);
Another option: override OnEraseBkgnd and force it to do nothing:
BOOL OnEraseBkgnd(CDC*)
{
return TRUE;
}
Do all of the painting in OnPaint()
void myWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
dc.FillSolidRect(rect, ::GetSysColor(COLOR_WINDOW) );
CFont *font = &globalFont;
auto oldFont = dc.SelectObject(font->GetSafeHandle());
dc.SetBkMode(TRANSPARENT);
dc.DrawText(L"AAAAAAAAA", -1, rect, 0);
dc.SelectObject(oldFont);
}
I'm trying to create a Modeless Messagebox class in MFC 6.0. The end result should look pretty similar to this:
I'm trying to add an icon with the following, but it doesn't seem to work.
HICON hIcon = LoadIcon(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ERROR));
SetIcon(hIcon, FALSE);
Also, is there a way to set the background color of the top part of a dialog to a different color from the rest of the dialog?
There is some design issues here, for this type of interface the program should save automatically. If user is deleting the file then confirm it with modeless dialog. This way data is not lost if user is impatient. Anyway, you can just override OnPaint etc. and draw the icon
void CMyDlg::OnPaint()
{
CDialog::OnPaint();
//draw white rectangle on top-half of dialog
//draw lt-gray rectangle at bottom-half, GetSysColor(COLOR_BTNFACE)
HICON hIcon = LoadIcon(NULL, IDI_ERROR);
dc.DrawIcon(x, y, hIcon);
DestroyIcon(hIcon);
}
BOOL CMyDlg::OnEraseBkgnd(CDC* pDC)
{
return 1;
}
You might also need to override OnCtlColor
I create two CStatic controls. One property is set to transparent mode; another one is normal.
After I change font size, one is OK, it is changed, but the set transparent mode one is not changed in size.
Does anybody know why?
//////////////////////////////////////////////////
/* Resource File */
LTEXT "This Is Normal Text.",IDC_FONT2,7,119,303,21,WS_BORDER
LTEXT "This Include Transparent.",IDC_FONT,7,7,306,21,WS_BORDER | NOT WS_GROUP | WS_TABSTOP,WS_EX_TRANSPARENT
/* FontTest.CPP */
class CFontSizeDlg : public CDialogEx
{
public:
CStatic m_myFont;
CStatic m_myFont2;
}
/* FontTest.CPP */
void CFontSizeDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_FONT, m_myFont);
DDX_Control(pDX, IDC_FONT2, m_myFont2);
}
void CFontSizeDlg::OnBnClickedButton2()
{
CFont hNewFont;
LOGFONT lf; // Used to create the CFont.
CFont *currentFont = GetFont();
currentFont->GetLogFont(&lf);
lf.lfHeight = 25;
lf.lfWidth = 10;
hNewFont.DeleteObject();
hNewFont.CreateFontIndirect(&lf); // Create the font.
// Use the font to paint a control.
m_myFont2.SetFont(&hNewFont);
m_myFont.SetFont(&hNewFont);
// hNewFont.Detach(); // will create GDI leak
hNewFont.DeleteObject();
}
You need to make sure that the scope of the 'new' font is the same as the static control(s). In your example, the font is destroyed when the button event handler is done. Try making hNewFont a member variable and setting it once.
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