 |
|
 |
Is the ON_NOTIFY event getting fired? Do you have the notification handler declaired for the owner of the toolbar?
Kirk Stowell
Codejock Software
www.codejock.com
|
|
|
|
 |
|
 |
This is the relevant code:
This is in my class:
BEGIN_MESSAGE_MAP(CKBToolBarCtrl, CToolBarCtrl)
//{{AFX_MSG_MAP(CKBToolBarCtrl, CToolBarCtrl)
ON_WM_SIZE()
ON_WM_KEYDOWN()
//ON_CONTROL_REFLECT(0, OnCommand)
ON_NOTIFY(TBN_DROPDOWN, IDR_IETOOLBAR , OnHandleMenu)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
This is in my .h file
afx_msg void OnHandleMenu(NMHDR *pNMHDR, LRESULT* pResult);
As you can see, I have IDR_IETOOLBAR in the middle. I have tried all kinds of constants and nothing works.
In the function, OnHandleMenu, the first line is ASSERT(0);. My intention is for that to alert me when the function gets called so I know the event is being captured. That has never happened. So my conclusion is that the event is not being captured correctly.
Thank you!
Mike
|
|
|
|
 |
|
 |
Hi Mike,
If you are handling the message in an owner window such as CMainFrame then you would use ON_NOTIFY because the toolbar sends the TBN_DROPDOWN message to the owner window to "notify" the owner of the event that occured. If you are handling the message in a derived class then you would need to use ON_NOTIFY_REFLECT instead of ON_NOTIFY. If you want to use a derived class then I would suggest using CToolBar instead of CToolBarCtrl.
Here is an example of how you would implement this.
ToolBarEx.h:
#if !defined(__TOOLBAREX_H__)
#define __TOOLBAREX_H__
#if _MSC_VER > 1000
#pragma once
#endif
#include <afxtempl.h>
class CToolBarEx : public CToolBar
{
public:
CToolBarEx();
virtual ~CToolBarEx();
public:
void SetDropArrow(UINT nCmdID, UINT nMenuID);
protected:
virtual void ShowPopupMenu(UINT nCmdID);
afx_msg void OnTBDropDown(NMTOOLBAR* pNMTB, LRESULT *pResult);
DECLARE_MESSAGE_MAP()
CMap<UINT, UINT, UINT, UINT> m_mapCmd2Mnu;
};
#endif
ToolBar.cpp:
#include "stdafx.h"
#include "Resource.h"
#include "ToolBarEx.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CToolBarEx::CToolBarEx()
{
}
CToolBarEx::~CToolBarEx()
{
}
BEGIN_MESSAGE_MAP(CToolBarEx, CToolBar)
ON_NOTIFY_REFLECT(TBN_DROPDOWN, OnTBDropDown)
END_MESSAGE_MAP()
void CToolBarEx::ShowPopupMenu(UINT nCmdID)
{
UINT nMenuID;
if (!m_mapCmd2Mnu.Lookup( nCmdID, nMenuID ))
return;
CMenu menu;
if ( menu.LoadMenu( nMenuID ))
{
CMenu* pPopup = menu.GetSubMenu( 0 );
ASSERT( pPopup );
CRect rc;
SendMessage( TB_GETRECT, nCmdID, ( LPARAM )&rc );
ClientToScreen( &rc );
pPopup->TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,
rc.left, rc.bottom, this, &rc );
}
}
void CToolBarEx::OnTBDropDown(NMTOOLBAR* pNMTB, LRESULT *pResult)
{
ShowPopupMenu( pNMTB->iItem );
*pResult = 0;
}
void CToolBarEx::SetDropArrow(UINT nCmdID, UINT nMenuID)
{
int iItem = CommandToIndex( nCmdID );
if (iItem != -1)
{
GetToolBarCtrl( ).SendMessage( TB_SETEXTENDEDSTYLE, 0,
( LPARAM )TBSTYLE_EX_DRAWDDARROWS );
SetButtonStyle( iItem,
GetButtonStyle( iItem ) | TBSTYLE_DROPDOWN );
m_mapCmd2Mnu.SetAt(nCmdID, nMenuID);
}
}
Now in your CMainFrame class all you need to do is call SetDropArrow for as many command + menu combinations you wish to add, for example:
m_wndToolBar.SetDropArrow( ID_FILE_NEW, IDR_MENU1 );
m_wndToolBar.SetDropArrow( ID_FILE_OPEN, IDR_MENU2 );
Let me know if this helps.
Kirk Stowell
Codejock Software
www.codejock.com
|
|
|
|
 |
|
 |
Kirk,
Thank you so much! That post was very enlightening! I have my drop down working, and I learned more about how to manage my code in my project.
Again, thank you so much for your time!
Mike
|
|
|
|
 |
|
 |
You are quite welcome, glad I could help.
Kirk Stowell
Codejock Software
www.codejock.com
|
|
|
|
 |
|
 |
Thanks for the help with this Kirk. One additional question. I have an Update CCmdUI handler for each item in my popup, since I want certain items checked. It looks like the handler isn't called unti I actually click on an item, rather before the menu is displayed. I tried adding the handler to my mainframe class, but that didn't get called at all. Any ideas?
TIA
Eric
|
|
|
|
 |
|
 |
Hi Eric,
I just tried this using the sample and it seems to work ok for me. I changed IDR_MENU1 to have 4 menu items "Item One", "Item Two", "Item Three" and "Item Four". I then added command handlers to CMainFrame. The command handlers _have_to_ be added to the owner of the popup menu, in this case CMainFrame.
In MainFrm.h add:
enum EnumItem { item1, item2, item3, item4 };
class CMainFrame : public CMDIFrameWnd
{
snip...
protected: // control bar embedded members
CStatusBar m_wndStatusBar;
CToolBar m_wndToolBar;
EnumItem m_item;
snip...
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnOntoolbardroparrowItemone();
afx_msg void OnUpdateOntoolbardroparrowItemone(CCmdUI* pCmdUI);
afx_msg void OnOntoolbardroparrowItemtwo();
afx_msg void OnUpdateOntoolbardroparrowItemtwo(CCmdUI* pCmdUI);
afx_msg void OnOntoolbardroparrowItemthree();
afx_msg void OnUpdateOntoolbardroparrowItemthree(CCmdUI* pCmdUI);
afx_msg void OnOntoolbardroparrowItemfour();
afx_msg void OnUpdateOntoolbardroparrowItemfour(CCmdUI* pCmdUI);
//}}AFX_MSG
snip...
};
In MainFrm.cpp add:
snip...
/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_item = item2;
}
snip...
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(ID_ONTOOLBARDROPARROW_ITEMONE, OnOntoolbardroparrowItemone)
ON_UPDATE_COMMAND_UI(ID_ONTOOLBARDROPARROW_ITEMONE, OnUpdateOntoolbardroparrowItemone)
ON_COMMAND(ID_ONTOOLBARDROPARROW_ITEMTWO, OnOntoolbardroparrowItemtwo)
ON_UPDATE_COMMAND_UI(ID_ONTOOLBARDROPARROW_ITEMTWO, OnUpdateOntoolbardroparrowItemtwo)
ON_COMMAND(ID_ONTOOLBARDROPARROW_ITEMTHREE, OnOntoolbardroparrowItemthree)
ON_UPDATE_COMMAND_UI(ID_ONTOOLBARDROPARROW_ITEMTHREE, OnUpdateOntoolbardroparrowItemthree)
ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnToolbarDropDown)
ON_COMMAND(ID_ONTOOLBARDROPARROW_ITEMFOUR, OnOntoolbardroparrowItemfour)
ON_UPDATE_COMMAND_UI(ID_ONTOOLBARDROPARROW_ITEMFOUR, OnUpdateOntoolbardroparrowItemfour)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
snip...
/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers
void CMainFrame::OnOntoolbardroparrowItemone() {
m_item = item1;
}
void CMainFrame::OnUpdateOntoolbardroparrowItemone(CCmdUI* pCmdUI) {
pCmdUI->SetCheck(m_item == item1);
}
void CMainFrame::OnOntoolbardroparrowItemtwo() {
m_item = item2;
}
void CMainFrame::OnUpdateOntoolbardroparrowItemtwo(CCmdUI* pCmdUI) {
pCmdUI->SetCheck(m_item == item2);
}
void CMainFrame::OnOntoolbardroparrowItemthree() {
m_item = item3;
}
void CMainFrame::OnUpdateOntoolbardroparrowItemthree(CCmdUI* pCmdUI) {
pCmdUI->SetCheck(m_item == item3);
}
void CMainFrame::OnOntoolbardroparrowItemfour() {
m_item = item4;
}
void CMainFrame::OnUpdateOntoolbardroparrowItemfour(CCmdUI* pCmdUI) {
pCmdUI->SetCheck(m_item == item4);
}
You will also need to add the 4 menu items to IDR_MENU1 but that should be all you need to do.
Cheers,
Kirk Stowell
Codejock Software
www.codejock.com
-- modified at 16:50 Friday 2nd September, 2005
|
|
|
|
 |
|
 |
Thanks for the quick reply Kirk!
It does seem to work fine on the sample, but for some reason in my application, the update handler is called only AFTER I click on the menu item.
I have
ON_UPDATE_COMMAND_UI(IDM_PLAN_VIEW_SLATS, OnUpdatePlanViewSlats)
ON_COMMAND(IDM_PLAN_VIEW_SLATS, OnPlanViewSlats)
in my message map for my CMainFrame class. OnPlanViewSlats never gets called at all, when I click on the menu item. It's almost as if the mapping of the command handler is going to the update handler. I'll let you know what I find out.
Thanks again,
Eric
-- modified at 8:54 Tuesday 6th September, 2005
|
|
|
|
 |
|
 |
Doesn´t work in a docking toolbar, so how can I add a WM_NOTIFY, if my frame doens´t have this option?
|
|
|
|
 |
|
 |
Hi,
Does anyone know how to do this in C# for a button that is on a form (not in a toolbar)?
Thanks!
|
|
|
|
 |
|
 |
Made the modifications needed for Visual C++ .NET and it worked perfect.
Been trying to implement this for awhile with no success.
Thanks for the article
Everybody gotta be somebody
|
|
|
|
 |
|
 |
When trying to use this code under Visual C++ .NET, I get the error:
f:\Program Development\NET Working Development\NTPDevStd\MainFrm.cpp(28): error C2440: 'static_cast' : cannot convert from 'void (__thiscall CMainFrame::* )(NMTOOLBARA *,LRESULT *)' to 'void (__thiscall CCmdTarget::* )(NMHDR *,LRESULT *)'
Could anyone please help me here?
|
|
|
|
 |
|
 |
The problem is the prototype for OnToolbarDropDown is incorrect for .NET, change NMTOOLBAR to NMHDR in CMainFrame for example:
mainfrm.h:
afx_msg void OnToolbarDropDown(NMHDR* pNMHDR, LRESULT *pResult);
mainfrm.cpp:
void CMainFrame::OnToolbarDropDown(NMHDR* pNMHDR, LRESULT *pResult)
{
NMTOOLBAR* pNMTBar = (NMTOOLBAR*)pNMHDR;
CWnd *pWnd = NULL;
UINT nID = 0;
switch (pNMTBar->iItem)
{
case ID_FILE_OPEN:
pWnd = &m_wndToolBar;
nID = IDR_MENU1;
break;
default:
return;
}
CMenu menu;
menu.LoadMenu(nID);
CMenu* pPopup = menu.GetSubMenu(0);
ASSERT(pPopup);
CRect rc;
pWnd->SendMessage(TB_GETRECT, pNMTBar->iItem, (LPARAM)&rc);
pWnd->ClientToScreen(&rc);
pPopup->TrackPopupMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,
rc.left, rc.bottom, this, &rc);
*pResult = 0;
}
Hope this helps...
Best regards,
Kirk Stowell
Looking for great GUI Tools?
http://www.codejock.com/
|
|
|
|
 |
|
 |
Thank you very much.
|
|
|
|
 |
|
 |
You have to declare and then cast the pointer to NMTOOLBAR*
void CMainFrame::OnDropDown(NMHDR* pnmtb, LRESULT *plr)
{
NMTOOLBAR* value = (NMTOOLBAR*) pnmtb;
....
....
}
|
|
|
|
 |
|
 |
change line:
afx_msg void OnToolbarDropDown(NMTOOLBAR* pnmh, LRESULT* plRes);
to
afx_msg void OnToolbarDropDown(NMHDR* pnmhdr, LRESULT* plRes);
and changed the function :
void CMainFrame::OnToolbarDropDown(NMTOOLBAR* pnmhdr, LRESULT *plr)
{
...
}
to
void CMainFrame::OnToolbarDropDown(NMHDR* pnmhdr, LRESULT *plr)
{
//insert the line
NMTOOLBAR* pnmtb = (NMTOOLBAR*)pnmhdr;
...
}
it's working.
or removing line:
//ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnToolbarDropDown)
and override OnNotify(...)
BOOL CMainFrame::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
// TODO: 在此添加专用代码和/或调用基类
if( wParam == AFX_IDW_TOOLBAR )
{
NMTOOLBAR* pnmtb = (NMTOOLBAR*)lParam;
if( pnmtb->hdr.code == TBN_DROPDOWN )
{
OnToolbarDropDown( pnmtb,pResult );
}
}
return CFrameWnd::OnNotify(wParam, lParam, pResult);
}
it's working too.
Visual C++
|
|
|
|
 |
|
 |
I am also getting the same problem...
|
|
|
|
 |
|
 |
I had the same problem.
Here's how you fix it:
Change your toolbar declaration to be like this:
void CMainFrame::OnToolbarDropDown(NMHDR * pnmh, LRESULT *plr)
Then on the first line on the OnToolbarDropDown, cast it back...
NMTOOLBAR* pnmtb = (NMTOOLBAR*)pnmh;
You'll also have to update the definition in the header file, but it seems to work.
|
|
|
|
 |
|
 |
Well done, just followed your steps and got a nice little drop-down. Brilliant. Easy to read articles like this are exactly why I love CodeProject!
|
|
|
|
 |
|
 |
In Visual Studio, clicking the dropdown buttons for the redo/undo will bring up a window which allows you to redo/undo multiple steps. Does anyone know how to implement this functionality? Thanks!
Raymond
|
|
|
|
 |
|
 |
Better you should use your IDR_MAIN_FRAME's submenus but not separate menu resource.
|
|
|
|
 |
|
 |
This is great, but using a drop down text menus defeats the whole purpose of the drop arrow. What would be more useful is how to add a drop down toolbar which would show more buttons vertically.
cheers,
swine
====================================
Check out Aephid Photokeeper, the POWERFUL digital
photo album solution at http://www.aephid.com
|
|
|
|
 |
|
 |
The ON_NOTIFY message map won't work in a CDialog window so....
Use classwizard to override WM_NOTIFY, and generate OnNotify().
Then in OnNotify add...
switch (((LPNMHDR)lParam)->code)
{
case TBN_DROPDOWN:
etc.
etc.
}
For more info see "Using Drop-down Buttons in a Toolbar Control" in MSDN
|
|
|
|
 |
|
 |
When I try to compile this when place in a CDialog, the ON_NOTIFY bit brings up an error saying something like...
Can't convert from CMyDialogClass to CCmdTarget.
So the CDialog has to be a CmdTarget?
Any idea how this could be done
|
|
|
|
 |
|
 |
If you implement a button with a dropdown array with the MFC implementation of VC6, be sure to read the article Q190501 "BUG: Resizing CToolbar with Dropdown Arrow Buttons Freezes Apps" in the MS Knowledge Base
|
|
|
|
 |