CTreeCtrl 'TVN_ITEMCHANGED ' not getting fired when item checkbox is clicked in windows xp? - mfc

I am having a tree control and the Tree control consists of a Root node
and that root node consists of one child node inturn this child node consists of 3 nodes.
This treecontrol is on a propertypage and in this after I click on any of the checkbox I am
enabling the wizard buttons.And in the event handler to TVN_ITEMCHANGED I enabled the sheet
buttons.
When I had run my application and after checking a node of treecontrol I am not able to enable the
sheet buttons.I had seen the code is absolutely fine where I am checking the check state of the node
and enabling the sheet buttons.I started debugging my code and first it was NM_CLICK is getting fired
and then I was expecting the TVN_ITEMCHANGED event to get fired but it is not at all getting fired.
I have no idea why this event is not getting fired,and the environment I am using is WindowsXP.
I had run my application on windows7 ,windows 8,windows vista.There it is working as expected,but in XP only
I figured out this behaviour.
Can anyone please help me to get this problem resolved.

According to MSKB you don't get a specific notification when the checkbox is clicked.
On a TreeView control with the TVS_CHECKBOXES style, there is no
notification that the checked state of the item has been changed.
There is also no notification that indicates that the state of the
item has changed. However, you can determine that the user has clicked
the state icon of the item and act upon that.
Quoting from that article:
When the user clicks the check box of a TreeView item, an NM_CLICK
notification is sent to the parent window. When this occurs, the
TVM_HITTEST message returns TVHT_ONITEMSTATEICON. The TreeView control
uses this same condition to toggle the state of the check box.
Unfortunately, the TreeView control toggles the state after the
NM_CLICK notification is sent.
You can post a user-defined message to the same window that is
processing the NM_CLICK notification, and treat this user-defined
message as a notification that the checked state has changed.
Following is sample code that illustrates how this can be
accomplished:
With the associated example code:
#define UM_CHECKSTATECHANGE (WM_USER + 100)
case WM_NOTIFY:
{
LPNMHDR lpnmh = (LPNMHDR) lParam;
TVHITTESTINFO ht = {0};
if(lpnmh->code == NM_CLICK) && (lpnmh->idFrom == IDC_MYTREE))
{
DWORD dwpos = GetMessagePos();
// include <windowsx.h> and <windows.h> header files
ht.pt.x = GET_X_LPARAM(dwpos);
ht.pt.y = GET_Y_LPARAM(dwpos);
MapWindowPoints(HWND_DESKTOP, lpnmh->hwndFrom, &ht.pt, 1);
TreeView_HitTest(lpnmh->hwndFrom, &ht);
if(TVHT_ONITEMSTATEICON & ht.flags)
{
PostMessage(hWnd, UM_CHECKSTATECHANGE, 0, (LPARAM)ht.hItem);
}
}
}
break;
case UM_CHECKSTATECHANGE:
{
HTREEITEM hItemChanged = (HTREEITEM)lParam;
/*
Retrieve the new checked state of the item and handle the notification.
*/
}
break;

Related

MFC: Using ContextMenuManager to TrackPopupMenu causes CTreeView item to retain TVGN_DROPHILITE status from time to time

I have a weird issue with a CTreeView context menu. I was just calling pPopup->TrackPopupMenu() as is active in the code below. No problems, but doesn't automatically update status text and icons. So searching the Internet I found there is a ContextMenuManager for this in the MFC Feature Pack (I'm now using the BCGControlBar Pro which is what the feature pack was based on).
I tried using the ContextMenuManager in the code below (change the #if 1 to 0) and while it works, I find that sometimes (many times) afterwards the selected tree item will not show the highlight, it just flashes and goes back to the item that was right clicked on like TVGN_DROPHIILITE is still on. (I confirmed TVGN_DROPHILITE is what the right click uses to select the tree item via the debug print items on the OnNMRClick() function). Also if I enabled the treeCtrl.SelectDropTarget(NULL) it fixes the issue but I shouldn't have to do that?
I'd really like to use the ContextMenuManager but this issue is a show stopper. Does anyone know what is going on?
void CMyTreeView::OnNMRClick(NMHDR *pNMHDR, LRESULT *pResult)
{
CDebugPrint::DebugPrint(_T("NMRClick: In DropHighlightItem %p\n"), GetTreeCtrl().GetDropHilightItem());
// Send WM_CONTEXTMENU to self
SendMessage(WM_CONTEXTMENU, (WPARAM)m_hWnd, GetMessagePos());
CDebugPrint::DebugPrint(_T("NMRClick: Out DropHighlightItem %p\n"), GetTreeCtrl().GetDropHilightItem());
*pResult = 0;
}
void CMyTreeView::OnContextMenu(CWnd* pWnd, CPoint ptMousePos)
{
HTREEITEM htItem;
CTreeCtrl &treeCtrl=GetTreeCtrl();
//
// ...
//
// the popup is stored in a resource
CMenu menu;
menu.LoadMenu(IDR_TREE_CONTEXT_MENU);
CMenu* pPopup = menu.GetSubMenu(0);
#if 1
UINT id=pPopup->TrackPopupMenu(TPM_LEFTALIGN|TPM_RETURNCMD, ptMousePos.x, ptMousePos.y, this);
#else
CBCGPContextMenuManager *manager = theApp.GetContextMenuManager();
UINT id;
if (manager) {
id=manager->TrackPopupMenu(pPopup->GetSafeHmenu(), ptMousePos.x, ptMousePos.y, this);
// treeCtrl.SelectDropTarget(NULL); // fixes issue
}
else id=0;
#endif
//
// ...
//
}
The TVGN_DROPHILITE is a temporary selection, only valid for the duration of drag-and-drop operation. Why are you messing with that?
You should use TVGN_CARET, if anything.
However, the problem is that right-click doesn't select the clicked item. If you like that behavior (I do), select it yourself.
It is also strange that you // Send WM_CONTEXTMENU to self - the WM_CONTEXTMENU should be sent to you by the system, in response to right click.

MFC - Changing dialog item focus programmatically

I have a Modeless dialog which shows a bunch of buttons; some of these are customized to draw stuff with GDI.
Now, when the user clicks on a customized one under certain conditions, a message box appears to alert user of the error and this is fine.
The problem is that after accepting the Message Box (showed as MB_ICON_ERROR), everywhere I click in the dialog, I always get the error message as if the whole dialog send the message to the customized button and the only way to get rid this is to press tab and give the focus to another control.
This is a strange behaviour and knowing why happens wouldn't be bad, but a simple workaround for now should do the job.
Since the moment that is probably a matter of focus, I've tried to set it on another control (in the owner dialog) by doing:GetDlgItem( IDC_BTN_ANOTHER_BUTTON )->SetFocus();
and then, inside the customized control by adding:KillFocus( NULL );but had no results.
How should I use these functions?
Thanks in advance.
PS: if I comment the AfxMessageBox, the control does not show this bizarre behaviour.
EDITI'll show some code as requested.
// This is where Message Box is popping out. It is effectively inside the dialog code.
void CProfiloSuolaDlg::ProcessLBtnDownGraphProfilo(PNT_2D &p2dPunto)
{
// m_lboxProfiles is a customized CListBox
if(m_lboxProfiles.GetCurSel() == 0)
{
// This profile cannot be modified.
/*
CString strMessage;
strMessage.Format( _T("Default Profile cannot be edited.") );
AfxMessageBox( strMessaggio, MB_ICONERROR );
*/
return;
}
// Selecting a node from sole perimeter.
SelectNodo(p2dPoint);
}
Actually, the message is commented to keep the dialog working.
// This is inside the customization of CButton
void CMyGraphicButton::OnLButtonDown(UINT nFlags, CPoint point)
{
PNT_2D p2dPunto;
CProfiloSuolaDlg* pDlg = (CProfiloSuolaDlg*)GetParent();
m_pVD->MapToViewport(point,p2dPunto);
switch(m_uType)
{
case GRF_SEZIONE:
pDlg->ProcessLBtnDownGraphProfilo(p2dPunto);
break;
case GRF_PERIMETRO:
pDlg->ProcessLBtnDownGraphPerimetro(p2dPunto);
break;
}
CButton::OnLButtonDown(nFlags, point);
}
Since you are handling the button down event in the button handler for the custom control, you don't need to call the base class. Just comment out CButton::OnLButtonDown(nFlags, point).

(WinAPI) Simulating item selection in ComboBox

I am currently writing a wrapper for an existing application that has its own GUI. I don't have access to original application's source code (unfortunately). The program that I am writing is in C++ and I am making use of WinAPI. I am manipulating target application by simulating button-clocks, ticking checkboxes etc.
The problem I am facing at the moment is following:
I need to make a selection in droplist implemented as WinAPI ComboBox. I am doing it by using macro ComboBox_SetCurSel. The selection in the droplist changes correctly. However in the original application there is a read-only textbox that changes the value depending on the selection in combobox. And this one does not change when I execute ComboBox_SetCurSel.
The assumption I made is that CBN_SELENDOK and/or CBN_SELCHANGE are sent when selecting an entry in ComboBox manually and this is the bit I am not doing when setting the selection with ComboBox_SetCurSel macro.
However due to lack of experience I cannot figure out how to resolve the problem. Who is normally listening for CBN_SELENDOK and CBN_SELCHANGE. Is it main application window, parent element of the combobox or main application thread? How do I find out.
Is there a macro that would do the whole thing? Like changing the selected item in ComboBox and sending all necessary notifications? Is there some smart workaround?
Any help on the subject, or any additional questions that would help to make situation more clear are welcome.
UPDATE: thanks for comment by Jonathan Potter. I am now attempting to send messages explicitly. Here is the part of the code where I am doing it:
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
PostMessage(GetParent(this->handle),WM_COMMAND, MAKEWPARAM(0,CBN_SELENDOK),0);
PostMessage(GetParent(this->handle),WM_COMMAND, MAKEWPARAM(0,CBN_SELCHANGE),0);
Note this->handle is just a handle to ComboBox itself as I have packed it into the structure for convenience. GetParent(this->handle) Should get immediate parent of ComboBox
Still no result. Does the order of messages matter? Also how do I obtain the identifier that needs to go into LOWORD of WPARAM sent along with WM_COMMAND?
ANSWER:
Thanks to AlwaysLearningNewStuff I have found and an answer. I have been sending messages with 0 as LPARAM. Apparently a handle to ComboBox itself neets to be sent as LPARAM in order for solution to work. This would take me ages to figure it out.
#AlwaysLearningNewStuff, you should have posted this as an answer, not a comment.
Also the bit about using GetDlgCtrlID() to get ControlID of the ComboBox is very useful. This makes code more reliable.
Thank you, everyone who participated.
Here is my final code:
if (this->handle == NULL)
{
return false;
}
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
PostMessage(GetParent(this->handle), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID( this->handle ),CBN_SELENDOK),
(LPARAM)(this->handle));
PostMessage(GetParent(this->handle), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID( this->handle ),CBN_SELCHANGE),
(LPARAM)(this->handle));
return true;
You are correct that CBN_SELCHANGE is not sent when using ComboBox_SetCurSel(), and the documentation says as much:
The CBN_SELCHANGE notification code is not sent when the current selection is set using the CB_SETCURSEL message.
So you have to send the notifications manually. However, you are missing key elements in your messages - the ComboBox's Control ID and HWND. The parent window uses those to identify which child control is sending messages to it so it can then act accordingly.
Try this instead:
int res = ComboBox_SetCurSel(this->handle, index);
if (res == CB_ERR)
{
return false;
}
HWND hParent = GetParent(this->handle);
int iCtrlId = GetDlgCtrlID(this->handle);
if (GetWindowLong(this->handle, GWL_STYLE) & CBS_SIMPLE)
PostMessage(hParent, WM_COMMAND, MAKEWPARAM(iCtrlId,CBN_SELENDOK), LPARAM(this->handle));
PostMessage(hParent, WM_COMMAND, MAKEWPARAM(iCtrlId,CBN_SELCHANGE), LPARAM(this->handle));

How to handle closing MessageBox

my environment is C++, MFC, compact-framework for WM 6.0+ devices.
In many places, I am showing pop-up messages using 'MessageBox()' to give a simple warning or get Yes/No repsonse from user. What I want to do is that whenever any message is closed, call some common function before I perform specific codes.
I tried WM_SHOWWINDOW in parent window but it doesn't seem to occur.
Any suggestion will be appreciated.
[Added] my screen has many buttons and I have to make sure only one button is focused all the time. When I show message box, button seems to loose its focus so I want to focus it back when message is closed. Of course, I can do it in every place where message is used but looking for a better way to handle this situation.
The MessageBox function returns specific return codes when it is closed, you can wrap the MessageBox function and check the return values and run some code based on that.
Here are the return codes from MSDN :
IDABORT 3 The Abort button was selected.
IDCANCEL 2 The Cancel button was selected.
IDCONTINUE 11 The Continue button was selected.
IDIGNORE 5 The Ignore button was selected.
IDNO 7 The No button was selected.
IDOK 1 The OK button was selected.
IDRETRY 4 The Retry button was selected.
IDTRYAGAIN 10 The Try Again button was selected.
IDYES 6 The Yes button was selected.
So the following code can be used to run different functions based on the return code.
void MyMessageBox(wstring title,wstring message)
{
int msgboxID = MessageBox(
NULL,
(LPCWSTR)message.c_str(),
(LPCWSTR)title.c_str(),
MB_ICONWARNING | MB_CANCELTRYCONTINUE | MB_DEFBUTTON2
);
switch (msgboxID)
{
case IDCANCEL:
// TODO: add code
break;
case IDTRYAGAIN:
// TODO: add code
break;
case IDCONTINUE:
// TODO: add code
break;
//so on
}
}
More info here :
http://msdn.microsoft.com/en-us/library/windows/desktop/ms645505(v=vs.85).aspx
You might try intercepting the WM_ACTIVATE message in the parent window.

How to implement the mouse click for URLs at rich edit control

I added a read-only rich edit 2.0 control to my dialog (code is using C windows API, the dialog is created by using function DialogBox)
At the dialog call back, at the WM_INITDIALOG, I add the following code to enable url detection and also enable the event ENM_LINK is sent to the parent dialog instead of the rich edit control itself:
LRESULT mask = SendMessage(hWndText, EM_GETEVENTMASK, 0, 0); //hWndText is rich edit control
SendMessage(hWndText, EM_SETEVENTMASK, 0, mask | ENM_LINK);
::SendMessage(hWndText, EM_AUTOURLDETECT, TRUE, NULL);
I had a little trouble to enable the url detection when dialog is initially launched (which seems a known issue or behavior since rich edit control would only enable url detection of modified text). However I worked around this issue by setting the dialog text again on every WM_PAINT event.
The code is generally working. I also implemented the following code to launch the URL at the browser when mouse is hovering over the url:
case WM_NOTIFY:
plink = (ENLINK *) lParam;
switch(LOWORD(wParam))
{
case IDC_DISPLAY_TEXT_2: //this is ID for my rich edit control
szURL =m_strDisplay.Mid(plink->chrg.cpMin, plink->chrg.cpMax - plink->chrg.cpMin);
LaunchURL(szURL); //function to launch the url with default browser
break;
default:
break;
}
It seems that I would get WM_NOTIFY event every time when I hovered the mouse over the url. However when I clicked on it, I always get same event as the mouse hover over.
Based on the structure of ENLINK, I should get more detailed NM event at the NMHDR structure, however the value plink->nmhdr.code is always 1803 which is not even NM_HOVER (its defined value is (NM_FIRST-13) and NM_FIRST is (0U- 0U), so NM_HOVER value is 4294967283 on my 64 bit machine). I know that I am missing something here. Could someone shed some lights here? How can I get the mouse click event for the rich edit control?
I think you should capture the EN_LINK notification. I implemented the following code. It enables a url link in a richedit control placed into the parent window, not into a dialog. You could adapt it for your dialog, as well.
Consider beginning with the code:
case WM_NOTIFY: {
switch (((LPNMHDR)lParam)->code) { //NMHDR structure contains information about a notification message.
case EN_LINK: {
ENLINK *enLinkInfo = (ENLINK *)lParam; // pointer to a ENLINK structure
then, if you choose to launch url on LBUTTONUP, you have to check the value contained in enLinkInfo->msg (remember to adapt it for your dialog, though)
if (enLinkInfo->msg == WM_LBUTTONUP) {
// select all the text from enLinkInfo->chrg.cpMin to enLinkInfo->chrg.cpMax
// lauch the url
}
Besides, you can intercept WM_MOUSEMOVE:
if(enLinkInfo->msg == WM_MOUSEMOVE) {
; // do nothing
}
Hope it helps.
As the answer by #A_nto2 shows, to intercept a mouse click do:
case WM_NOTIFY: {
//NMHDR structure contains information about a notification message.
switch (((LPNMHDR)lParam)->code) {
case EN_LINK: {
ENLINK *enLinkInfo = (ENLINK *)lParam; // pointer to a ENLINK structure
if (enLinkInfo->msg == WM_LBUTTONUP) {
But the tricky part is to get the link that was clicked on.
One gets a "range" that was clicked on in the enLinkInfo->chrg of the type CHARRANGE.
An answer to Detect click on URL in RichEdit suggests using the EM_EXSETSEL with the enLinkInfo->chrg. And then using the EM_GETSELTEXT to retrieve the text.
That works with auto-detected plain-text URLs (EM_AUTOURLDETECT).
A problem is with friendly name hyperlinks (i.e. those that have an anchor text different than the URL itself):
{\rtf1{\field{\*\fldinst{ HYPERLINK "https://www.example.com"}}{\fldrslt{Example}}}}
(Note that these are supported in Rich Edit 4.1 and newer only)
For these, the CHARRANGE points to the HYPERLINK "https://www.example.com" part, which is hidden and cannot be selected using the EM_EXSETSEL. Actually it can be selected on Windows 10. But it cannot be selected on Windows 7, Vista and XP. Sending the EM_EXSETSEL to these systems results in selecting a zero-length block just after the hidden part.
So either you have to go back in the rich edit buffer and scan for the link; or use another method to retrieve the clicked text.
In my case, as I have small texts only in the rich edit, I've used the WM_GETTEXT. It returns a plain-text version of the rich edit document, but with the friendly name hyperlinks preserved in this form:
HYPERLINK "https://www.example.com" Example
The CHARRANGE points to the URL, strangely including the leading quote: ("https://www.example.com).
But the indexes correspond to a text with a single-character (LF) line separators. While the WM_GETTEXT returns the CRLF separators. So you have to convert the text to the LF before extracting the URL using the CHARRANGE.
According to the documentation of EM_AUTOURLDETECT, you are supposed to get an EN_LINK notification, which should be reflected in the nmhdr.code. According to Google,
#define EN_LINK 0x70B
which is 7 * 256 + 11 = 1750 + 42 + 11 = 1803.
Please note that your code misses a check for nmhdr.code == EN_LINK.
I'm not sure if the control sends NM_HOVER messages at all.