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));
Related
I was hoping someone out there would help me with my predicament I ran into. Essentially I have a Checkbox and a RichEditControl next to each other. I want to be able to know when a user has clicked on my RichEditControl so i can send a message to my checkbox to flag it on and off.
At first i tried to overlay my checkbox with empty text to act as a "blank" background for my RichEditControl so i wouldn't have to worry about sending messages left and right. No matter what i tried the "blank" background would overlap the RichEditControl text and leave it completely blank.
I searched on here for some help and i found this which is exactly what I ran into. I understand what he is saying but don't have the knowledge to implement what they said.
Right now I'm playing around with EN_LINK to attempt to capture a message so i can tell my checkbox to flag itself.
BEGIN_MESSAGE_MAP(TempInit, CDialog)
ON_NOTIFY(EN_LINK, IDC_TempInitMsg, &TempInit::OnEnLinkTempinitmsg)
END_MESSAGE_MAP()
void TempInit::OnEnLinkTempinitmsg(NMHDR *pNMHDR, LRESULT *pResult)
{
ENLINK *pEnLink = reinterpret_cast<ENLINK *>(pNMHDR);
// TODO: Add your control notification handler code here
// TODO: Add your control notification handler code here
radioClicked = !radioClicked;
if (radioClicked == true)
{
GetParent()->SendMessage(WM_MYRADIOCLICKED, CHECKENABLED, 0);
}
else
{
GetParent()->SendMessage(WM_MYRADIOCLICKED, CHECKDISABLED, 0);
}
}
*pResult = 0;
}
I'm sorry in advance if this is totally the wrong way to go about this. I've been googling for a few hours and have come empty handed. If anyone has any other method please help me if possible. I can post more code if what i have above isn't enough.
Steven,
One way to go about this would be to handle the EN_MSGFILTER notification from the rich edit control. I can't provide you any code to show you how to do this off hand but here's the documentation for the Notification messages from that the Rich edit control generates. Simply handle it the same way your doing with your radio button.
This will check the check box when the Rich Edit Ctrl has the focus and untick
it when it losses the focus.
BEGIN_MESSAGE_MAP(TempInit, CDialogEx)
ON_EN_SETFOCUS(IDC_RICHEDIT21, &TempInit::OnEnSetfocusRichedit21)
ON_EN_KILLFOCUS(IDC_RICHEDIT21, &TempInit::OnEnKillfocusRichedit21)
END_MESSAGE_MAP()
void CMFCApplication1Dlg::OnEnSetfocusRichedit21()
{
CButton* pCheckBox = (CButton*)GetDlgItem(IDC_CHECK1);
pCheckBox->SetCheck(1);
}
void CMFCApplication1Dlg::OnEnKillfocusRichedit21()
{
CButton* pCheckBox = (CButton*)GetDlgItem(IDC_CHECK1);
pCheckBox->SetCheck(0);
}
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;
The main form opens a child form that has a handful of button CONTROLs on it. I need to trap keyboard events so I subclassed one of the controls. All is good until the control loses focus of course.
Ideally, as long as this child form is open I would like to assign the focus to this control and thus trap all the keystrokes, no matter where the user clicks.
I suspect superclassing might be a better way to go but I am not as familiar with it.
Perhaps what I should do is use accelerators on the main form?
ADDED:
I should mention that the main form has a large listview control that is subclassed to recover up/down arrows and mousewheel etc.
The traditional way is to install a keyboard hook (SetWindowsHookEx), but you need to inject it into every application, and it doesn't work across 32/64 bit boundaries.
What you can do however, and quite easily at that, is to poll the keyboard with GetKeyboardState on a timer and check whether your f1-f12 keys are activated. The timer can be as slow ticking as 100ms and it will catch almost everything while using virtually no resources.
Assuming that this is within Windows and the Win32 API, one option is to look for messages in your main GetMessage, TranslateMessage, DispatchMessage loop. You can special-case any message within this loop, irrespective of which window it's aimed at.
You should probably use IsChild to check that the message is intended for a control on your main window (as opposed to some dialog box or message box that might be displayed separately). Getting the logic right can be fiddly, too. It would be best to only intercept messages when you know your control has lost the focus, and only intercept the exact messages you need to.
Years ago, I wrote a library message loop with a lot of this built in. I had a simple manager class that held pointers to instances of my own little window class. The loop knew the difference between dialogs and normal windows, gave each window class a chance to spy on its childrens messages, and so on. You won't be able to run this directly and the conventions are a bit strange, but you might find this useful...
int c_Window_List::Message_Loop (void)
{
MSG msg;
bool l_Handled;
while (GetMessage (&msg, NULL, 0, 0))
{
l_Handled = false;
c_Windows::c_Cursor l_Cursor;
bool ok;
for (ok = l_Cursor.Find_First (g_Windows); ok; ok = l_Cursor.Step_Next ())
{
if (IsChild (l_Cursor.Key (), msg.hwnd))
{
if (l_Cursor.Data ().f_Accelerators != NULL)
{
l_Handled = TranslateAccelerator (l_Cursor.Key (), l_Cursor.Data ().f_Accelerators, &msg);
if (l_Handled) break;
}
if (l_Cursor.Data ().f_Manager != 0)
{
l_Handled = l_Cursor.Data ().f_Manager->Spy_Msg (l_Cursor.Key (), msg);
}
if (l_Handled) break;
if (l_Cursor.Data ().f_Is_Dialog)
{
l_Handled = IsDialogMessage (l_Cursor.Key (), &msg);
if (l_Handled) break;
}
}
}
if (!l_Handled)
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
if (g_Windows.Size () == 0)
{
// When all windows have closed, exit
PostQuitMessage (0);
}
}
return msg.wParam;
}
The f_ prefixes mean field - I picked up the m_ convention later, but this code hasn't been revisited in a very long time. f_Manager in particular points to an instance of my c_Window_Base class. The c_Cursor class is a kind of iterator, used to step through all the windows stored in the g_Windows variable (actually a static class member rather than a global).
I'm coding an MFC application in which i have a dialog box with multiple CListCtrls in report view. I want one of them to be sortable.
So i handled the HDM_ITEMCLICK event, and everything works just fine .. Except that if i click on the headers of another CListCtrl, it does sort the OTHER CListCtrl, which does look kind of dumb.
This is apparently due to the fact that headers have an ID of 0, which make the entry in the message map look like this :
ON_NOTIFY(HDN_ITEMCLICK, 0, &Ccreationprogramme::OnHdnItemclickList5)
But since all the headers have an id of zero, apparently every header of my dialog sends the message.
Is there an easy way around this problem ?
EDIT: Maybe i wasn't clear, but i did check the values inside the NMHDR structure. The HwndFrom pointer is different depending on which header is clicked, which doesn't help me a lot since it's value is obviously different at each runtime. The idFrom value is 0, for the very reasons i explained above, because that's the id of every header. Thanks
EDIT2: The hwnd pointer values do also not correspond to the CListCtrl, probably because it's coming from a different object entirely.
Check the values of the NMHDR structure.
http://msdn.microsoft.com/en-us/library/bb775514%28VS.85%29.aspx
Ok i found a solution, though i find it a bit dirty but it works, so i'll post it for future reference.
You can get the Header through the GetHeaderCtrl member function of CListCtrl. You can then get it's handler thru m_hWnd. So all you got to do is to test if that handler is the same as the one in the NMHDR structure, so the code looks like this :
void Ccreationprogramme::OnHdnItemclickList5(NMHDR *pNMHDR, LRESULT *pResult)
{
if (pNMHDR->hwndFrom == LC_gen_schedules.GetHeaderCtrl()->mhWnd)
{
// Code goes here
}
*pResult = 0;
}
Thanks all for the help
The LPARAM passed to your message handler is actually a pointer to an NMHEADER structure, which contains an NMHDR structure, which in turn contains the HWND and control ID of the control which sent the message. You may be able to compare that to your list controls' HWNDs to determine which window's header control was clicked.
Alternatively, you could derive a class from CListCtrl and reflect the HDN_ITEMCLICK messages back to the list control. That way, each list control object handles its own header's notifications.
Imagine I have a CDialog which creates controls dynamically when the user clicks a button. It could be like this:
// We don't know which is the first id for the new buttons until runtime (!)
MyDialog::MyDialog(/*whatever parameters needed*/, first_id)
: next_id_(first_id)
{ /*...*/ }
BOOL MyDialog::OnSomeButtonClicked()
{
CButton* new_button = new CButton;
new_button->Create("Caption", WS_CHILD | WS_VISIBLE, this->new_button_rect_,
this, this->next_id_++);
}
Then my question would be: How could I handle messages from this button? Is it possible to use the MFC message map facility?
The solution should work in both vs6 and vs2005.
Thank you!
These are the solutions I've found so far in order of relevance:
Use ON_COMMAND_RANGE if you can define the range of the control IDs you want to handle.
Overload CWnd::PreTranslateMessage() and do whatever stuff you want with the messages received. NOTE: When dealing with buttons, take into account that the BN_CLICKED event is NOT sent to PreTranslateMessage but directly sent to the window procedure.
Overload CWnd::WindowProc() and do whatever stuff you want with the messages received. NOTE that when dealing with buttons this is the ONLY WAY I've found to handle the BN_CLICKED event.
Interesting links:
Please help with PreTranslateMessage and user defined messages handling.
TN006: Message Maps
I hope this helps... thank you all for your contributions.
Eventhough you dont know the exact values of the id, if you know the possible range of IDs then the following macro can be used.
BEGIN_MESSAGE_MAP(MyDialog, CDialog)
...
...
ON_COMMAND_RANGE(1000, 5000, OnButtonDynamic)
END_MESSAGE_MAP()
void MyDialog::OnButtonDynamic(UINT nID)
{
}
This will work for ids in the range 1000 - 5000.
I'm a few years late to this party, but the solution to this is to assign the same control id to each button (no need to 'reserve' id's in resource.h, and no artificial restrictions on the amount of controls that can be created), to save the window handle and to use GetCurrentMessage() in the handler for that button:
// resource.h
#define IDC_DYNAMIC_BUTTON 123
// In message map
ON_BN_CLICKED(IDC_DYNAMIC_BUTTON, OnDynamicButtonClicked)
// Store the window handles when creating them in a member:
std::map<HWND, SomeStruct> m_Buttons;
... fill this map when creating the buttons, presumably in OnInitDialog()
// Actual handler
void MyDialog::OnDynamicButtonClicked()
{
const MSG* message = GetCurrentMessage();
if (m_Buttons.find((HWND)message->lParam) != m_Buttons.end()) {
// Do something with m_Buttons[(HWND)message->lParam]
}
}
I believe this article explains it pretty well and has source code. I have not tried this so I can't guarantee it works, but in the time I have thought it might help.
Article
You can find details (+ a lot more) on modeless dialogs there.
insert the entry of ID of the handler in Resouce.h
Then insert the entry in the message map of the handler like ON_BN_CLICKED(IDC_BTNCREATE, OnBnClickedrunCreated)
or you can directly use the integer ID like ON_BN_CLICKED(1200, OnBnClickedrunCreated). If you use 2nd version then there is
no need to insert entry in resource.h. Give defination and declaration of the handler in .h and .cpp file. you will get your answer.
Use this way: ON_CONTROL_RANGE(wNotifyCode, id1, id2, memberFxn ).
for example:
ON_CONTROL_RANGE(EN_UPDATE, IDC_EDIT_START, IDC_EDIT_END, OnEnUpdateEditParams)