We are using below code to populate the messages in a dialog box, when the message appears the message is highlighted in blue - it's like we selected the message using the mouse. i want it the messages not to select when it appears. Anybody can help me on this issue.
CDialog::OnInitDialog();
CFont *m_pFont = new CFont();
LOGFONT lf;
memset(&lf, 0,sizeof(LOGFONT));
lf.lfHeight = 16;
lf.lfWeight = FW_BOLD;
strncpy_s(lf.lfFaceName,"Arial",6);
m_pFont->CreateFontIndirectA(&lf);
GetDlgItem(IDC_EDIT1)->SetFont(m_pFont,TRUE);
m_message.SetWindowTextA((LPCTSTR)Message);
return TRUE;
The selection will be set to all the text whenever the edit box becomes the selected item. If the edit box is the first in the tab order, or if you tab to it or click on it, all the characters are selected. You can override this behavior by capturing the EN_SETFOCUS event and resetting the selection yourself:
void CMyDlg::OnEnSetfocusEdit1()
{
m_edit1.SetSel(0, 0); // or (-1, -1) to set the selection at the end
}
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 CMFCRibbonBar control. I need to create my custom tooltip. My tooltip derives from CMFCToolTipCtrl and works quite well. But...
When I hover a ribbon button, tooltip shows up. That's great. But when I move the mouse out of the button, tooltip is closed. That is not what I want. I just need to be able to move the mouse on the tooltip and click the link that is on the tooltip. Imagine this is some kind of interactive tooltip. What can I do to achieve that?
OK, I've done something that is useful, but the outcome is not satisfying 100%.
So, first of all, create your own tooltip, inheriting from CMfcToolTipCtrl.
The idea is that:
- user may want to interact with your tooltip, or not. So we have to create some smart way from closing and showing the tooltip.
- We can assume, that when user hovers the tooltip with mouse, then he wants to interact.
Unfortunately whenever user moves the mouse from the ribbon button, the tooltip dissapears. But sometimes we can catch MouseMove inside it. But it's rather rare. So, we have to get the moment, when tooltip is closed by a system.
There is such a message that we can add to message map:
ON_NOTIFY_REFLECT(TTN_POP, &CAsInteractiveToolTip::OnPop)
Now, our OnPop will look like that (I am using pImpl idiom):
void CAsInteractiveToolTip::OnPop(NMHDR* pNMHDR, LRESULT* pResult)
{
if (m_pImpl->m_forceClose)
{
CMFCToolTipCtrl::OnPop(pNMHDR, pResult);
m_pImpl->m_forceOpened = false;
m_pImpl->m_forceClose = false;
m_pImpl->StopForceOpenTimer();
}
else
{
m_pImpl->StartForceOpenTimer();
}
*pResult = 0;
}
Now, what's happening here is:
- when tooltip is being closed, check if it's force closed by our code. If not, it means that it's closed by system. In such case, we have to give the user a chance to hover the mouse over our tooltip. So, we have to show the tooltip again (force it to show). This is done in timer method. StartForceOpenTimer is simple method that starts the timer:
void StartForceOpenTimer()
{
if (!m_forceOpenTimerActive)
{
m_self.SetTimer(IDT_FORCE_OPEN_TIMER, 100, (TIMERPROC)NULL);
m_forceOpenTimerActive = true;
}
}
Now, the magic starts in timer method:
void CAsInteractiveToolTip::OnForceTimer()
{
static DWORD waitForUserStartTime = 0;
static bool waitingForUserReaction = false;
if (!waitingForUserReaction)
{
//open and give the user chance to mouse move over it within 0.5 seconds
SetWindowPos(&wndTopMost, -1, -1, -1, -1, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
waitForUserStartTime = GetTickCount();
waitingForUserReaction = true;
return;
}
if (GetTickCount() - waitForUserStartTime > 500)
{
m_pImpl->StopForceOpenTimer();
m_pImpl->m_forceClose = true;
waitingForUserReaction = false;
m_pImpl->PopToolTip();
return;
}
if (m_pImpl->m_doForceOpen)
{
m_pImpl->StopForceOpenTimer();
waitingForUserReaction = false;
m_pImpl->m_forceOpened = true;
}
}
Overall idea is:
- force to show the tooltip
- wait about 0.5 second for a user to hover the mouse
- if user hovers the mouse over tooltip window, we can assume that he wants to interact. So we can leave the window opened.
- if user doens't interact with the window within 0.5 second, just close the tooltip again.
Now, PopToolTip method just starts another timer with interval of 100 ms.
And here is the other part of the magic:
void CAsInteractiveToolTip::OnPopTimer()
{
m_pImpl->StopForceOpenTimer();
KillTimer(IDT_POP_TIMER);
//Pop();
m_pImpl->m_forceClose = true;
m_pImpl->m_hdr.idFrom = 2;
m_pImpl->m_hdr.hwndFrom = GetSafeHwnd();
m_pImpl->m_hdr.code = (int)TTN_POP; //4294966774
GetParent()->SendMessage(WM_NOTIFY, 1, (LPARAM)&m_pImpl->m_hdr);
//GetParent()->SendMessage(WM_NOTIFY, 2, (LPARAM)&m_pImpl->m_hdr);
ShowWindow(SW_HIDE);
}
Now, this method should just pop (hide) the tooltip. But for some reason in my case calling Pop() method does nothing. So I would have to send WM_NOTIFY message with appropriate parameters (they are taken from my debug observations).
Now, OnPop will start again, but this time m_forceClose is set to true, so the tooltip will not show again (the first timer will not run).
Now the third part of the magic - Mouse Move. Just add it to your message map:
ON_WM_MOUSEMOVE()
And the method:
void CAsInteractiveToolTip::OnMouseMove(UINT nFlags, CPoint point)
{
m_pImpl->m_doForceOpen = true; //let the first timer know, that user wants to interact
CMFCToolTipCtrl::OnMouseMove(nFlags, point);
}
And you can just hide the tooltip when user clicks on it. Just:
void CAsInteractiveToolTip::OnLButtonDown(UINT nFlags, CPoint point)
{
m_pImpl->m_forceClose = true;
m_pImpl->PopToolTip();
}
This is not the ideal solution, but it somehow works. If anyone has any suggestions, I will be happy to hear them :)
I’ve created my own CXTabCtrl that extends CTabCtrl and override the DrawItem Function.
During the phase of rewriting the DrawItem Function, I wasn’t able to differentiate between this two states of CTabCtrl Item:
CTabCtrl item is selected and have focus.
CTabctrl item is selected but doesn’t have focus.
By focus I mean the Focus rectangle is not drawing. Here are two images that will help identify the two states:
Here’s the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
Here’s a part of the DrawItem current code, in which I can detect the selected states, but still Unable to detect the focus states.
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
BOOL bFontSuccess = FALSE;
CFont* def_font = NULL;
CFont font_italic;
TC_ITEM tci;
CRect rect(lpDrawItemStruct->rcItem);
wchar_t szTabText[256];
wmemset(szTabText,_T('\0'),256);
RECT rectComplet;
GetClientRect(&rectComplet);
CBrush brtmp(ColorCategoryBackgroundTop);
int nbItem = GetItemCount();
tci.mask = TCIF_TEXT;
tci.pszText = szTabText;
tci.cchTextMax = sizeof(szTabText) -1;
GetItem(lpDrawItemStruct->itemID, &tci);
BOOL bSelect = (lpDrawItemStruct->itemState & ODS_SELECTED) &&
(lpDrawItemStruct->itemAction & (ODA_SELECT | ODA_DRAWENTIRE));
BOOL bfocus = (lpDrawItemStruct->itemState & ODS_FOCUS) &&
(lpDrawItemStruct->itemAction & (ODA_FOCUS | ODA_DRAWENTIRE));
if (bSelect)//Draw In a Specific Way
if (bFocus) //Draw In a Specific Way
}
So, I would be grateful if someone can describe the proper way to detect the two states of a CTabCtrl Item “Selected & Focused”, “Selected & But not focused”
For a standard tab control, the UI will not always draw the focus rectangle. To see the focus rectangle, the tab control must have WS_TABSTOP flag.
The focus rectangle will then be visible when user clicks the Tab key to go through the dialog's controls, or when Alt key is pressed and tab control has focus.
The focus rectangle should be drawn automatically for owner draw tab control when applicable. Make sure WS_TABSTOP is set for tab control (in dialog editor, go to tab control's properties and set "Tabstop = true")
BOOL focused = selected && (GetFocus() == this); will always be TRUE when user clicks on the tab control. ODS_NOFOCUSRECT will indicate if focus rectangle is not requested by the UI. See the example below.
Side note, sizeof(szTabText) returns the wrong value for wchar_t. Use _countof(szTabText) or sizeof(szTabText)/sizeof(*szTabText)
void CXtabCtrl::DrawItem(LPDRAWITEMSTRUCT di)
{
CDC* pDC = CDC::FromHandle(di->hDC);
TC_ITEM tci;
wchar_t text[256] = { 0 };
tci.mask = TCIF_TEXT;
tci.pszText = text;
tci.cchTextMax = _countof(text);
GetItem(di->itemID, &tci);
BOOL selected = di->itemState & ODS_SELECTED;
BOOL focused = selected && (GetFocus() == this);
//The UI may not be drawing focus rectangle, even if we click on the tab
if(di->itemState & ODS_NOFOCUSRECT)
focused = FALSE;
CString str;
if(selected) str += L"SEL ";//indicate selected
if(focused) str += L"FOC ";//indicate focused
CRect rect(di->rcItem);
pDC->TextOut(rect.left, rect.top, str);
}
I have a list view where I want to disable the horizontal scroll bar.
Basically, I know none of my data will exceed the width of the single column, but if enough entries get added to the list view, the vertical scroll bar pops up, reducing the width available, making the horizontal scroll bar pop up.
I was thinking about some how catching a message right before the vertical scroll bar gets added, and then re-sizing the column to make enough room, but I don't know what message I would need to catch to do this.
EDIT:
Does anyone know if there is a message sent after an item in a list view is deleted? LVN_ITEMCHANGED appeared to only be sent after an item is added. And LVN_DELETEITEM only before an item is deleted.
You could send the message: LVM_SETCOLUMNWIDTH to the listview with cx param set to LVSCW_AUTOSIZE_USEHEADER
Well I worked out one solution.
There is a bug though, if I only remove one item it doesn't resize the column.
case LVN_DELETEITEM:
{
LPNMLISTVIEW listView = (LPNMLISTVIEW) lParam;
// After an item is deleted,
// if there is not a vertical scroll bar and GWL_USERDATA is TRUE,
// resize the column back to normal.
if (!(GetWindowLong(listView->hdr.hwndFrom, GWL_STYLE) & WS_VSCROLL) &&
GetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA) == TRUE)
{
const int ColWidth = ListView_GetColumnWidth(listView->hdr.hwndFrom, 0);
ListView_SetColumnWidth(listView->hdr.hwndFrom, 0, ColWidth + GetSystemMetrics(SM_CXVSCROLL));
SetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA, FALSE);
}
break;
}
case LVN_ITEMCHANGED:
{
LPNMLISTVIEW listView = (LPNMLISTVIEW) lParam;
// After an item is added, if there is a horizontal scrollbar,
// resize the column and set GWL_USERDATA to TRUE.
if (GetWindowLong(listView->hdr.hwndFrom, GWL_STYLE) & WS_HSCROLL)
{
const int ColWidth = ListView_GetColumnWidth(listView->hdr.hwndFrom, 0);
ListView_SetColumnWidth(listView->hdr.hwndFrom, 0, ColWidth - GetSystemMetrics(SM_CXVSCROLL));
SetWindowLong(listView->hdr.hwndFrom, GWL_USERDATA, TRUE);
}
break;
}
I'd still love to see a better solution, but this works for now.
If you create a new MFC application (with MFC Feature Pack), and using all the defaults, click Finish. It creates an MDI application with the new "Tabbed Documents" style.
I think these are great except it really annoys me that I can't close a Tabbed Document window by middle-clicking on the tab.
This is possible in Firefox, IE, Chrome and more importantly VS2008. But clicking the middle-button on a tab doesn't do anything.
I cannot figure out how to override the tab bar to allow me to handle the ON_WM_MBUTTONDOWN message. Any ideas?
Edit: Guessing I need to subclass the CMFCTabCtrl returned from CMDIFrameWndEx::GetMDITabs...
No subclassing needed (phew). Managed to get it working by hijacking the PreTranslateMessage of the mainframe. If the current message is a middle-mouse-button message, I check the location of the click. If it was on a tab then I close that tab.
BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
case WM_MBUTTONDBLCLK:
case WM_MBUTTONDOWN:
{
//clicked middle button somewhere in the mainframe.
//was it on a tab group of the MDI tab area?
CWnd* pWnd = FromHandle(pMsg->hwnd);
CMFCTabCtrl* tabGroup = dynamic_cast<CMFCTabCtrl*>(pWnd);
if (tabGroup)
{
//clicked middle button on a tab group.
//was it on a tab?
CPoint clickLocation = pMsg->pt;
tabGroup->ScreenToClient(&clickLocation);
int tabIndex = tabGroup->GetTabFromPoint(clickLocation);
if (tabIndex != -1)
{
//clicked middle button on a tab.
//send a WM_CLOSE message to it
CWnd* pTab = tabGroup->GetTabWnd(tabIndex);
if (pTab)
{
pTab->SendMessage(WM_CLOSE, 0, 0);
}
}
}
break;
}
default:
{
break;
}
}
return CMDIFrameWndEx::PreTranslateMessage(pMsg);
}