How to get left click notification on an edit control? - c++

I want to track event of single left-click on edit control.
I override PretranslateMessage function as below:
BOOL CMyClass::PreTranslateMessage(Msg* pMsg)
{
switch(pMsg->message)
case WM_LBUTTONDOWN:
{
CWnd* pWnd = GetFocus();
if (pWnd->GetDlgCtrlID == MY_EDIT_CTRL_ID)
{
//Do some thing
}
break;
}
}
The problem is that when I click on the edit control, all other control become disabled (for example buttons don't respond to clicks etc.)
How can I fix this problem? Or how can I track click notificationN on edit box?

You need this:
BOOL CMyClass::PreTranslateMessage(MSG* pMsg)
{
switch(pMsg->message)
{
case WM_LBUTTONDOWN:
{
CWnd* pWnd = GetFocus();
if (pWnd->GetDlgCtrlID() == MY_EDIT_CTRL_ID) // << typo corrected here
{
//Do some thing
}
break;
}
}
return __super::PreTranslateMessage(pMsg); //<< added
}
BTW its a bit awkword to use a switch statement here. Following code is cleaner IMO, unless you want to add morecases than only WM_LBUTTONDOWN:
BOOL CMyClass::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_LBUTTONDOWN)
{
CWnd* pWnd = GetFocus();
if (pWnd->GetDlgCtrlID() == MY_EDIT_CTRL_ID)
{
//Do some thing
}
}
return __super::PreTranslateMessage(pMsg); //<< added
}

Related

Tracking tooltip in CScrollView?

In a standard C++/MFC MDI doc/view project, I want to implement a tracking tooltip in the view (the tabbed view windows which generally occupy most of the main frame window). So, in class MyAppView, I have a member CToolTipCtrl tooltip. Function MyAppView::OnInitialUpdate() contains the initialization
BOOL ok0 = tooltip.Create(this, TTS_ALWAYSTIP);
CRect clientRect; GetClientRect(&clientRect);
BOOL ok2 = tooltip.AddTool(this, LPSTR_TEXTCALLBACK, &clientRect, 1234/*tool ID*/);
tooltip.Activate(TRUE);
to make the entire client area of the view be the "tool". The message map contains an entry
ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
and the function OnNeedToolTipText is defined as
BOOL MyAppView::OnNeedToolTipText(UINT id, NMHDR *pNMHDR, LRESULT *pResult)
{
UNREFERENCED_PARAMETER(id);
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(nID == 1234)
{
// Come here when text is needed for tracking tooltip
}
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %d"), nID);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
}
What happens is that only placing the mouse on the menu items (File, Edit, View, Window, Help) causes the code to enter OnNeedToolTipText, with an ID of 0-5. Moving the mouse into the client area (the view) does nothing.
How can I get the tooltip to appear in the client area of the view only?
Visual Studio 2017; C++; 64-bit Windows 7
In order to solve the problem you need to do the following:
BOOL CMyAppView::PreTranslateMessage(MSG* pMsg)
{
switch (pMsg->message)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONUP:
case WM_RBUTTONUP:
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
if (m_pToolTip->GetSafeHwnd () != NULL)
{
m_pToolTip->RelayEvent(pMsg);
}
break;
}
return CScrollView::PreTranslateMessage(pMsg);
}
If you want a tracking tooltip in a view, these are the steps to follow:
Create tooltip and add the tool.
void CToolTipDemoView::OnInitialUpdate()
{
// ...
m_toolTip.Create(this, TTS_ALWAYSTIP | TTS_NOANIMATE);
m_toolTip.AddTool(this, _T("Doesn't matter"));
}
Handle WM_MOUSEMOVE message. First, call _TrackMouseEvent in order to further receive WM_MOUSELEAVE and activate the tooltip. Second, update the tooltip text, and show it at mouse pointer coordinates.
void CToolTipDemoView::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bTrackingMouseLeave)
{
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd;
::_TrackMouseEvent(&tme);
m_toolTip.Activate(TRUE);
m_bTrackingMouseLeave = TRUE;
}
if (m_pointLastMousePos != point)
{
CString strText;
strText.Format(_T("x = %d y = %d"), point.x, point.y);
m_toolTip.UpdateTipText(strText, this);
m_toolTip.Popup();
m_pointLastMousePos = point;
}
CScrollView::OnMouseMove(nFlags, point);
}
Handle WM_MOUSELEAVE and deactivate the tooltip.
void CCToolTipDemoView::OnMouseLeave()
{
m_bTrackingMouseLeave = FALSE;
// mouse pointer leaves the window so deactivate the tooltip
m_toolTip.Activate(FALSE);
CScrollView::OnMouseLeave();
}
Notes:
there is no more necessary to handle TTN_NEEDTEXT.
also, there is no more necessary to override PreTranslateMessage
So I went back to see what I could be missing. I wrote this stuff over 10 years ago. I had also overridden a CWnd member
virtual INT_PTR OnToolHitTest( CPoint point, TOOLINFO* pTI ) const;
With:
INT_PTR HERichView::OnToolHitTest( CPoint point, TOOLINFO* pTI ) const
{
pTI->hwnd = m_hWnd;
pTI->uId = point.x + ( point.y << 16 );
CRect rect;
GetClientRect( rect );
pTI->rect= rect;
pTI->lpszText= LPSTR_TEXTCALLBACK;
return pTI->uId;
}
And I checked, it won't work without this. So your:
ON_NOTIFY_EX( TTN_NEEDTEXT, 0, OnToolTip )
Should get called if you add the above. And only EnableToolTips( ); Should be needed.
I have not succeeded in getting the tracking tooltip to work within MFC. The closest I have come is
In message map: ON_NOTIFY_EX(TTN_NEEDTEXT, 0, OnNeedToolTipText)
In OnInitialUpdate: BOOL ok1 = EnableTrackingToolTips(TRUE);
In override of virtual function OnToolHitTest:
pTI->hwnd = m_hWnd;
pTI->uId = (UINT_PTR)m_hWnd;
pTI->uFlags = TTF_IDISHWND | TTF_ALWAYSTIP | TTF_TRACK | TTF_NOTBUTTON | TTF_ABSOLUTE | TTF_SUBCLASS;
pTI->lpszText = LPSTR_TEXTCALLBACK;
return pTI->uId;
In OnNeedToolTipText:
NMTTDISPINFO *pTTT = (NMTTDISPINFO *)pNMHDR;
UINT_PTR nID = pNMHDR->idFrom;
BOOL bRet = FALSE;
if(pTTT->uFlags & TTF_IDISHWND)
{
// idFrom is actually the HWND of the tool
nID = ::GetDlgCtrlID((HWND)nID);
if(nID)
{
CURSORINFO ci; ci.cbSize = sizeof(CURSORINFO); // get something interesting to display
GetCursorInfo(&ci);
_stprintf_s(pTTT->szText, sizeof(pTTT->szText) / sizeof(TCHAR),
_T("Control ID = %lld at (%d, %d)"), nID, ci.ptScreenPos.x, ci.ptScreenPos.y);
pTTT->hinst = AfxGetResourceHandle();
bRet = TRUE;
}
}
*pResult = 0;
return bRet;
This produces the following peculiar behavior. When I start the app and move the mouse cursor into the client area of the CScrollView, a tooltip appears right next to the cursor.
If I move the mouse carefully (smoothly) the tooltip tracks properly. After a while, though, it disappears, and no further mouse motions, including leaving the CScrollView window and returning, make it re-appear.
I think what is happening is that when the mouse cursor moves over the tooltip window, the tooltip is turned off, permanently. This disappearance does not seem to be time-related (e g, due to auto-pop); if the mouse is left untouched, the tooltip remains indefinitely.

CMFCListCtrl force selected item to have red color

I have my own CMFCListCtrl derived class where I implemented the
virtual COLORREF OnGetCellTextColor(int nRow, int nColum)
{
CMyClass* pMyClass = (CMyClass*)GetItemData(nRow);
if (pMyClass && pMyClass->m_bDeleted)
return RGB(255, 0, 0);
return __super::OnGetCellTextColor(nRow, nColum);
}
function for marking red the deleted entries. This works except when a item is selected.
I went to the void CMFCListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult) function and placed a breakpoint with the condition iRow==selected item on the line
lplvcd->clrText = OnGetCellTextColor(iRow, iColumn);
executed it, then I created a new Data Breakpoint for &lplvcd->clrText.
The Data Breakpoint got hit on function
comctl32.dll!SHThemeComputeTextColors()
, as the callstack image shows:
, which is clearly overriding the variable value.
As I search for SHThemeComputeTextColors on Internet and nothing appears, can somebody help me on forcing the selected items text to be red?
Changing the colors and fonts of highlighted items is trickier than it seems, because highlighting selected items is a system-wide issue. In fact, it was something I had delayed for a long time, and just today I've finally tackled...
There are at least two ways to change the looks of a list control: owner draw (you have to do all the drawing yourself) and custom draw (the system tells you when it is about to do some steps of the drawing and lets you change the color, the font, etc.). This answer is about custom draw. This article covers the basics of using Custom Draw with CListCtrl.
How to change the highlight color in a CListCtrl (not a CMFCListCtrl, we'll get to that soon) is explained in this other article. These are the steps you have to do:
Intercept the listview draw routine just before it is about to draw a highlighted row (item).
Turn off the row highlight.
Set the row colors to whatever you want.
Let the listview draw the row.
Intercept the listview draw routine after it has drawn the row (post-draw item).
Turn this row's highlighting back on.
So, when the listview is about to draw a highlighted row, you have to telll the system the row is not highlighted so it doesn't use the system colors to paint it, tell what color to use, and set the item as highlighted again, so the selection works as usual.
Here is the code from that article:
COLORREF g_MyClrFgHi; // My foreground hilite color
COLORREF g_MyClrBgHi; // My background hilite color
HWND g_hListView; // Window handle of listview control
void EnableHighlighting(HWND hWnd, int row, bool bHighlight)
{
ListView_SetItemState(hWnd, row, bHighlight? 0xff: 0, LVIS_SELECTED);
}
bool IsRowSelected(HWND hWnd, int row)
{
return ListView_GetItemState(hWnd, row, LVIS_SELECTED) != 0;
}
bool IsRowHighlighted(HWND hWnd, int row)
{
// We check if row is selected.
// We also check if window has focus. This was because the original listview
// control I created did not have style LVS_SHOWSELALWAYS. So if the listview
// does not have focus, then there is no highlighting.
return IsRowSelected(hWnd, row) && (::GetFocus(hWnd) == hWnd);
}
BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
static bool bIsHighlighted = false;
*pResult = 0;
NMHDR *p = (NMHDR *)lParam;
switch (p->code)
{
...
case NM_CUSTOMDRAW:
NMLVCUSTOMDRAW *lvcd = (NMLVCUSTOMDRAW *)p;
NMCUSTOMDRAW &nmcd = lvcd->nmcd;
switch (nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
// We want item prepaint notifications, so...
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int iRow = (int)nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(g_hListView, iRow);
if (bHighlighted)
{
lvcd->clrText = g_MyClrFgHi; // Use my foreground hilite color
lvcd->clrTextBk = g_MyClrBgHi; // Use my background hilite color
// Turn off listview highlight otherwise it uses the system colors!
EnableHighlighting(g_hListView, iRow, false);
}
// We want item post-paint notifications, so...
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
break;
}
case CDDS_ITEMPOSTPAINT:
{
if (bHighlighted)
{
int iRow = (int)nmcd.dwItemSpec;
// Turn listview control's highlighting back on now that we have
// drawn the row in the colors we want.
EnableHighlighting(g_hListView, iRow, true);
}
*pResult = CDRF_DODEFAULT;
break;
}
default:
*pResult = CDRF_DODEFAULT;
break;
}
break;
...
}
}
This works fine with a CListCtrl, but you are asking about a CMFCListCtrl. The problem is that CMFCListCtrl is already asking to be notified about NM_CUSTOMDRAW notifications (that's when it calls the OnGetCellTextColor function, as you've seen). If you create a handler for that in your own CMFCListCtrl-derived class, those notifications won't get to CMFCListCtrl and you lose that functionality (it may fine, depending on your needs).
So here is what I've done: I've created a NM_CUSTOMDRAW handler in my list control and, if I'm dealing with a highlighted row, I change the colors, otherwise, I call CMFCListCtrl::OnNMCustomDraw(). As you'll see, I just use the normal highlight colors; that's because I just wanted to see the selected items even when the control doesn't have the focus:
void CMyListCtrl::OnNMCustomdraw(NMHDR* pNMHDR, LRESULT* pResult)
{
bool callParent = true;
static bool bHighlighted = false;
LPNMLVCUSTOMDRAW lpLVCustomDraw = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
NMCUSTOMDRAW nmcd = lpLVCustomDraw->nmcd;
*pResult = CDRF_DODEFAULT;
switch (lpLVCustomDraw->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW;
break;
case CDDS_ITEMPREPAINT:
{
int row = nmcd.dwItemSpec;
bHighlighted = IsRowHighlighted(row);
if (bHighlighted)
{
lpLVCustomDraw->clrText = GetSysColor(COLOR_HIGHLIGHTTEXT);
lpLVCustomDraw->clrTextBk = GetSysColor(COLOR_HIGHLIGHT);
EnableHighlighting(row, false);
*pResult = CDRF_DODEFAULT | CDRF_NOTIFYPOSTPAINT;
callParent = false;
}
}
break;
case CDDS_ITEMPOSTPAINT:
if (bHighlighted)
{
int row = nmcd.dwItemSpec;
EnableHighlighting(row, true);
callParent = false;
}
*pResult = CDRF_DODEFAULT;
break;
default:
break;
}
if (callParent)
{
__super ::OnCustomDraw(pNMHDR, pResult);
}
}
bool CMyListCtrl::IsRowHighlighted(int row)
{
bool selected = GetItemState(row, LVIS_SELECTED) != 0;
return selected;
}
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
}
I haven't tested it thoroughly, but it seems to work.
UPDATE:
There is a little problem. When you call EnableHigilighting(), the dialog will get LVN_ITEMCHANGED notifications, which can trigger a redraw, which will trigger a LVN_ITEMCHANGED notification... So if you are listening to LVN_ITEMCHANGED notifications, you might need to do something like this:
void CMyListCtrl::EnableHighlighting(int row, bool enable)
{
m_internalStateChange = true;
SetItemState(row, enable ? 0xff : 0, LVIS_SELECTED);
m_internalStateChange = false;
}
void CWhateverDialog::OnLvnItemchangedListaEjesPane(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
if (!c_List.InternalStateChange() && /* other conditions */)
{
// Respond to state changes
}
}

Add custom controller to DialogBox: Faillure on Create

I have to make a custom controller "MyCEdit" inherited from CEdit. I just want to push my custom controller in a dialogbox but the window crash on create. I saw the documention MSDN and made some adjustement :
On InitDialog I have:
if (message == WM_INITDIALOG)
{
CWnd* pWnd = CWnd::FromHandle(hwndDlg);
MyCEdit* pEdit = new MyCEdit();
pEdit->Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER,
CRect(10, 10, 100, 100), pWnd, IDC_CUSTOM_EDIT1); /* Failure here */
return TRUE;
}
I Have check the pointer pWnd, he's not NULL and point to the good window. And pEdit is not NULL.
My custom controller looks like (only override the PreTranslateMessage method):
class MyCEdit : public CEdit
{
public:
BOOL PreTranslateMessage(MSG* pMsg) override
{
if (pMsg->message == WM_KEYDOWN && ::GetKeyState(VK_CONTROL) != 0)
{
switch (pMsg->wParam)
{
case 'X': Cut(); return TRUE;
case 'C': Copy(); return TRUE;
case 'V': Paste(); return TRUE;
case 'Z': Undo(); return TRUE;
case 'A': SetSel(0, -1); return TRUE;
}
}
return CEdit::PreTranslateMessage(pMsg);
}
};
There is a step I miss or I make a wrong operation ?

Detecting Enter/Return on Keydown event in C++

I am trying to detect in my application, if the Enter/Return buttons are pressed. My problem is that the LVN_KEYDOWN event (Indicates that a key has been pressed) does not detect the Enter/Return key.
I have seen similar questions for other languages, but can not find a solution for C++.
My event to read the key press is:
void ListOption::OnLvnKeydownList1(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMLVKEYDOWN pLVKeyDow = reinterpret_cast<LPNMLVKEYDOWN>(pNMHDR);
// TODO: Add your control notification handler code here
if(pLVKeyDow->wVKey == VK_RETURN)
{
OnItemActivateList1(pNMHDR, pResult);
*pResult = 1;
}
*pResult = 0;
}
This code works for almost any key, execept for the Enter key.
My dialog has only one button, and it's "Default Button" value is FALSE. How is it possible to detect the keypress?
Update: My application uses modal dialogs.. It contains a CImageSheet that contains CImagePages(tabs). Here is an image to explain better (I have placed grey blocks to hide some private data).
When I press Enter, I wish to open a new dialog to change the option. Currently this is done with the LVN_ITEMCTIVATE event (when the user double clicks an item):
You can override PreTranslateMessage in the window which owns the ListView. In this case it seems to be a CPropertyPage.
BOOL CMyPropertyPage::PreTranslateMessage(MSG* pMsg)
{
//optional: you can handle keys only when ListView has focus
if (GetFocus() == &List)
if (pMsg->message == WM_KEYDOWN)
{
if (pMsg->wParam == VK_RETURN)
{
//return 1 to eat the message, or allow for default processing
int sel = List.GetNextItem(-1, LVNI_SELECTED);
if (sel >= 0)
{
MessageBox("VK_RETURN");
TRACE("ListView_GetNextItem %d\n", sel);
return 1;
}
else
TRACE("ListView_GetNextItem not-selected, %d\n", sel);
}
if (pMsg->wParam == VK_ESCAPE)
{
//do nothing!
}
}
return CPropertyPage::PreTranslateMessage(pMsg);
}

hide mouse cursor over specific client area in windows

I have directx embedded into a child window of my application and would like to hide the windows cursor only when it's over that client area. I know how to hide the cursor in general and did manage to find a make-shift example if only showing the cursor while it's not over any client areas, but it wasn't helpful for this.
How can I hide the cursor only while it's over a specific client area (/child window)?
edit:
this is as close as I've gotten but the cursor flickers unpredictably (as the mouse moves) while over the dx area
case WM_SETCURSOR:
{
static bool bCursorVisible = TRUE;
if( hWnd!=hwD3DArea && !bCursorVisible )
{
ShowCursor((bCursorVisible=TRUE));
}
else if( hWnd==hwD3DArea && bCursorVisible )
{
ShowCursor((bCursorVisible=FALSE));
return TRUE;
}
}
break;
edit2:
AHAH!
you have to use wParam instead of hWnd in this message
Here's the working code:
case WM_SETCURSOR:
{
static bool bCursorVisible = TRUE;
if( ((HWND)wParam)!=hwD3DArea && !bCursorVisible )
{
ShowCursor((bCursorVisible=TRUE));
}
else if( ((HWND)wParam)==hwD3DArea && bCursorVisible )
{
ShowCursor((bCursorVisible=FALSE));
return TRUE;
}
}
break;
case WM_SETCURSOR:
{
if (LOWORD(lParam) == HTCLIENT)
{
SetCursor(NULL);
return TRUE;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
I think it would be simpler if you just set the cursor for that specific client window to a null cursor.
the fix:
case WM_SETCURSOR:
{
static bool bCursorVisible = TRUE;
if( ((HWND)wParam)!=hwD3DArea && !bCursorVisible )
{
ShowCursor((bCursorVisible=TRUE));
}
else if( ((HWND)wParam)==hwD3DArea && bCursorVisible )
{
ShowCursor((bCursorVisible=FALSE));
return TRUE;
}
}
break;
I was on the right track but was using hWnd when I should have been using wParam (which holds the REAL handle of the window the cursor is in)