I have a MFC app written in Visual Studio 6 which I am adding some new functionality to. What I want to be able to do is display a context menu when the user right clicks on the header column on the list control within a property page. The CListCtrl class was added view the Class Wizard.
Via the ClassWizard for the property page, I have added a handler for the right click on the listctrl. This does get called and I added the following code to work out if the rclick was over the header section and if so which header item. Like this
POINT Point;
GetCursorPos (&Point);
ScreenToClient(&Point);
HDHITTESTINFO HitTest;
//Offset of right scrolling
HitTest.pt.x = Point.x+ m_ctrlRecordList.GetScrollPos(SB_HORZ); //Offset of right scrolling
HitTest.pt.y = Point.y;
//Send the Hit Test Message
m_ctrlRecordList.GetHeaderCtrl()->SendMessage(HDM_HITTEST,0,(LPARAM)&HitTest);
// Check hit test result.
*pResult = 0;
However, the hit test always returns -1.
I tried just on left click instead by handling the HDN_ItemClick message of the header control in the property page. This is all done in the ClassWizard so I expected to be able to handle this notification here. However, from what I have researched so far, there may be a bug in MFC where the ClassWizard puts this code into your code for you but this notification will never get as far as the parent of your list control. Is this the case?
What would be the best way to do this? I would prefer on right click but left click would do if necessary.
You cannot do it trying to handle message from a list’s header in a dialog, nor can you do it in the CListCtrl derived class.
MFC is using message reflection for certain controls and only for certain messages/notification codes.
Most likely you are passing coordinates of the mouse click on the list control, hence hit test fails.
Try this:
Add class derived from CHeaderCtrl. Declare member variable of the derived class in a dialog.
I assume you have already subclassed (have variable inserted by the wizard) list control.
In OnInitDialog write the following:
// m_List is the dialog’s member of the subclassed list control,
// m_header is a member variable of your new header class:
// insert this code after list control is already
// initialized and all columns are added.
CHeaderCtrl* pHeaeder = m_List.GetHeaderCtrl();
m_Header.SubclassWindow(pHeaeder->m_hWnd);
Insert handler for WM_CONTEXTMENU or WM_LBUTTONUP in the derived class and popup menu. You will receive CPoint type for click position.
I have managed to sort this out and thought I would add the answer in case anyone else stumbles upon this with the same problem. The code I posted originally is fine but it needs to go in the OnNotify handler of a class derived from CListCtrl. The ClassWizard allows you to add a reflect handler to the parent of the list control but the message never gets that far.
Related
Using Visual Studio 2013, I created a dialog resource using the resource editor. It is a child control with no border and is just a collection of radio buttons, push buttons, and static text. I want to turn this into a custom control in order to place this in several different locations. Let's call this a "Panel".
I then created a regular dialog and using the Toolbox "Custom Control", defined an area for the Panel. The Panel registers itself and has a valid window handle.
I used the following example:
https://www.codeproject.com/Articles/521/Creating-Custom-Controls
The parent's DDX gets hit and the _panel is properly instantiated:
MyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX)
DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)
}
I read that I need to override the OnPaint() and OnEraseBkgnd(CDC* pDC) methods so the Panel class has these but they are empty. I do not have any custom painting to do as the Panel contains nothing but regular buttons.
What do I have to include in OnPaint()?
I also noticed that none of the member buttons are instantiated in the Panel like would normally happen in a dialog's DoDataExchange method. Instead, I've had to resort to dynamically creating each of the control's inside the Panel's PreSubclassWindow() method:
void MyPanel:PreSubclassWindow()
{
_groupBox.Create(_T("Options"), WS_CHILD|WS_VISIBLE|BS_GROUPBOX, CRect(11, 11, 112, 231), this, IDC_STATIC_GROUPBOX);
//... do this for every dialog element??? seems like overkill...
CWnd::PreSubclassWindow()
}
Why do I need to do this when I've already defined/designed the Panel and each of its controls in the resource editor?
If I do not do this in the PreSubclassWindow method, nothing will appear on the dialog.
Any help is appreciated. Thanks.
The article says override OnPaint and OnEraseBkgnd if you want to change the functionality. It doesn't say you have to override always.
Just remove ON_WM_PAINT and ON_WM_ERASEBKGND, remove OnPaint and OnEraseBkgnd if you don't need them. Or call the base class implementations if you are not making any changes:
void MyPanel::OnPaint() { CWnd::OnPaint(); }
BOOL MyPanel::OnEraseBkgnd(CDC* pDC) { return CWnd::OnEraseBkgnd(pDC); }
This will show a blank control with nothing in it, unless you add a child window to _panel as you have done in MyPanel:PreSubclassWindow
You are adding _groupBox to _panel. And you are adding _panel to the MyDialog.
MyDialog::DoDataExchange(...){DDX_Control(pDX, IDC_CUSTOM_PANEL, _panel)} is needed to invoke SubclassWindow for _panel. That in turn calls _groupBox.Create.
If MyPanel::OnPaint and MyPanel::PreSubclassWindow are not doing anything MyPanel appears as a blank control.
... do this for every dialog element??? seems like overkill...
You can directly add _groupBox to the main dialog. But if you want to add specific controls within MyPanel then you have to do it manually.
You can also create a child dialog within a main dialog. For example that's how a tab control works.
The scenario I have is as follow:
I have a CDialog class that contains two EditBox controls, lets say it's IDC_EB1 and IDC_EB2.
I want to be able to listen to a Double Click event on IDC_EB1 and IDC_EB2 to open up a FileDialog and display the selected file location on IDC_EB1 and IDC_EB1.
IDC_EB1 and IDC_EB2 is doing through the DDX Control mechanism and stored into their respective member variables.
The member variables type is of CMyEdit (derived from CEdit) in order to handle double click event.
The dialog runs and I was able to open up a FileDialog when I double click on IDC_EB1 or IDC_EB2 to browse. The problem is within CMyEdit, I no longer know which EditBox triggered the double click event in order to display the file path selected from the FileDialog.
What would be a good way to resolve this problem? I'm running Visual Studio 2010 SP1.
PS: There is the MfcEditBrowse Control which would solve the problem above nicely but it doesn't fit my need because the plan is to reuse this CDialog window at various stages in my program and apparently you cannot invoke DoModal more than once on a Dialog that has MfcEditBrowse Control. the MfcEditBrowse Control i nthat Dialog will throw an assertion failure (known problem apparently according to what I found on Microsoft documentation).
I no longer know which EditBox triggered the double click event in order to display the file path selected from the FileDialog.
You can use CWnd::ChildWindowFromPoint(point) which will return the clicked child control within the body of OnLButtonDblClk().
You know the ID inside the double click handler. Just call GetDlgCtrlID from the message handler.
You can easily add some data to CMyEdit to make it easy to identify the control for the action of the double click
You can send a WM_COMMAND message upon the double click from the CMyEdit to the parent and the parent handle the rest. The parent knows the control by its Id and should know what to do.
I want to build a MFC application, with one main dialog, and all the other dialogs are child of this main dialog (and embedded in it).
Now, i was able to embed the first child in the main dialog, but i want to pass to the next dialog (note that the order of opened dialogs is random), so i need to hide the first dialog and show another. To know which dialog is shown at the moment and hide it, i've tried using a CDialog variable to store the current opened dialog, but i get a CObject::operator =' : cannot access private member declared in class 'CObject' error.
Is there another way to do this "hide and show dialogs" game?
EDIT: Could i store some ID of the dialogs and use it to acomplish this task?
So i managed to accomplish this task using classes IDDs.
First, i store the last opened dialog's IDD
m_dlgStartPage.Create(CStartPageDlg::IDD, this);
m_openedWin.nDialogIDD = m_dlgStartPage.IDD;
m_dlgStartPage.ShowWindow(SW_SHOW);
Then, when a new dialog needs to be shown, i send a message to my main dialog (nIDD is the IDD of pending dialog to show):
AfxGetApp()->m_pMainWnd->SendMessage(WM_COMMAND_CHANGE_WINDOW, nIDD, 0);
And last, in my main dialog, i parse all the child dialogs and check if m_openedWin.nDialogIDD matches with each dialog's IDD, so i can hide it. After this, i parse once again all the chid dialogs and use the nIDD from the sent message to show the correct one.
I don't really like this approach, because of all the parsing and sent messages to the main dialog's class, so if anyone has a better idea or method, please post it.
If I click anywhere on the dialog window, like in the "background", or on a Static Text, the function OnLButtonDown() is fired. But if I click a ListBox/EditBox/Combo/Calendar/Checkbox etc is not fired.
I thought that because these have control variables atached to it, and the Static Text don't. But adding a new Listbox to my Dialog and testing, I see that it doesn't fire either, so now I am confused...
I added OnLButtonDown() with the Class Wizard, it appears in:
BEGIN_MESSAGE_MAP(CMFCTesting2Dlg, CDialogEx)
ON_WM_SYSCOMMAND()
// other handlers, etc
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
My function:
void CMFCTesting2Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
AfxMessageBox(CString("BUTTON DOWN"));
CDialogEx::OnLButtonDown(nFlags, point);
}
I tried calling CDialogEx:: ... before AfxMessageBox... but same result.
The CListBox item, has the Notify option set to True, as I seen some advices in other posts.
I suspect that the WM notification message is captured somehow by the ListBox, and not sent "forward" to my CMFCTesting2Dlg, but I understood that this should happen with the Notify option set to True, on the ListBox, no? (well, obviously, not...)
Sorry, I am new to dealing with WM in MFC.
BTW, I use Visual Studio 2010 Ultimate, and this is a Visual C++ - MFC- MFC Application - Dialog Based project.
How can I capture this mouse down event if clicked on a listbox / combo / etc?
On the LONG STORY, I am actually trying to accomplish this issue:
I have two listboxes (lets say), and I want to scroll them synchronously, when user scrolls one of them, others must scroll the same (or update in the next moment). And I thought of using on mouse down to track the position of every Listbox (with the GetTopIndex), and on mouse up, to GetTopIndex again and compare with the previous ones. If a change was made, then a listbox was scrolled and then update all listboxes with SetTopIndex. The unfriendly solution for the user, but simpler for me, would be clicking a button that does this verification / update, but its not elegant at all, and it can only set them at the same level, but can't determine which one was scrolled last time. This automatically scrolling of the listboxes should be made just for displaying, it is not important the selections in the listboxes. I tried using the Message Type in the Add Event Handler for the Listbox, but none that are displayed work for my problem, KillFocus and SetFocus, are not fired if the scroll-bar is dragged, only if an item in the listbox is clicked... and I didn't succeed on the others message type either, from the Add Event Handler.
Once a window handles a message, it stops being sent to the other windows beneath it. A static window doesn't handle a mouse down, so it goes to the dialog. The other controls all handle it in some fashion (setting focus at least) so it never gets to the dialog.
You can override the OnLButtonDown handler in each of the controls to do something else once they've finished their default handling in the base class. You might also get to see the message in the PreTranslateMessage method of the dialog.
As far as getting the List Controls to sync scrolling goes, your best bet would be to intercept WM_VSCROLL by subclassing CListCtrl or perhaps by PreTranslateMessage, and then calling SetScrollInfo on the other list. If the number of items in the lists are the same, it should be fairly simple.
I've created a simple Dialog box with a few controls (edit, text, etc) and saved
it to the resource folder in GME/GME.rc/Dialog
I added an auto-gen'd event handler which created a new class (Class.cpp
for the purposes of this example) Note: the Class::Class(CWnd *pParent) :
CDialogEx(Class::IDD, pParent) constructor is empty, I'm not sure if that's
relevant but I don't think it is..
There's a MESSAGE_MAP COMMAND(menu_item_id, &class::member_function())
invocation within the Class.cpp was auto-generated. It's wrapped in the
standard BEGIN_MESSAGE_MAP macro stuff.
However: when the menu item is clicked, the item remains gray. The
properties for "enabled=true" and "gray=false" are both properly
configured. So, I assume this is error is due to the message handler isnt
registered.
Any help would be appreciated.
Without code, it's pretty darn hard to help. Are you sure you put the message handler for the menu id in either a CView, CFrame, CDocument, or CWinApp derived class? If you put the menu handler in your dialog class, it's not going to do you much good.
Dialogs do not contain the code to run through the list of ON_UPDATE_COMMAND_UI handlers that MFC uses to enable menu items. Ordinarily this is handled by CFrameWnd for example. Try calling UpdateDialogControls in your OnInitDialog function.
I see that your code is also missing the ON_UPDATE_COMMAND_UI macro for the menu item, even though the handler it would reference was created for you.
You add menu item handlers to the window that has the menu, which is usually the CMainFrame. Copy the message map and handler to the CMainFrame and see if that helps. I'm not sure what you're trying to do here - I assume you want to display 'Class' (maybe better to edit your post to call this 'ExampleDialog' or something...) when the menu item is clicked, right? Or did you somehow add a menu to your CDialogEx-derived class? If the last, I guess this is what Mark is referring to - 'how are you displaying the menu'? How are you manually adding a menu to your dialog?