2

I have a CMFCRibbonUndoButton on the ribbon of an MFC application. I have a handler for when its ID is clicked (ON_COMMAND(ID_EDIT_UNDO, ...)). However, when the button is also in the quick access toolbar (QAT), there are apparently two CMFCRubbonUndoButtons which each keep their own state. In the command handler, I don't know how to tell which got clicked, and if you call GetActionNumber() on the wrong one, you get the wrong number of undo actions returned.

Is there a way in my ON_COMMAND handler to get the CMFCRibbonBaseElement* that fired the event?

Edit: the answer is important to me, the question is a bit obscure but I'm putting a bounty up!

Edit: here's how it's added to the QAT:

CList<UINT, UINT> lstQATCmds;
lstQATCmds.AddTail(ID_EDIT_UNDO);
m_RibbonBar.SetQuickAccessCommands(lstQATCmds);
AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
  • Could you give an example of how you add the Undo button to the QAT? I haven't worked with MFC in a while, but it seems very strange that it wouldn't keep state consistent between the buttons. – Collin Dauphinee Aug 01 '10 at 02:21
  • Added the code I use, buttons are added to the QAT by ID. It's based off the sample code. – AshleysBrain Aug 01 '10 at 14:44
  • Do you want both undo buttons to show the same list of undo items? In my test application I can call AddUndoAction to add items to the undo button in the ribbon, but these items don't show up in the undo button in the quick access toolbar. I've had a look through the MFC source code, and it appears that a solution to your problem won't be straightforward. – ChrisN Aug 01 '10 at 21:44
  • ChrisN - that's exactly what I got, the QAT button remains empty - but you *can* fill it by using `CRibbonBar::GetElementsByID()` and passing `ID_EDIT_UNDO` - you get two controls back, which allows you to add the same list of actions to both of them. This led me to the conclusion MFC clones it somewhere - but then how do you tell which got clicked in the handler? – AshleysBrain Aug 01 '10 at 22:19
  • Another tip: CRibbonBar has an `m_Pressed` member which correctly identifies the button when left-clicked on the main button, but it doesn't work for the dropdown nor triggering via keyboard shortcut. – AshleysBrain Aug 02 '10 at 13:42

2 Answers2

1

Is there a way in my ON_COMMAND handler to get the CMFCRibbonBaseElement* that fired the event?

Not directly, no. The WM_COMMAND message is sent from CMFCRibbonBaseElement::NotifyCommand, and this message doesn't include the pointer in its parameters.

To be able to tell which Undo button was clicked from the ON_COMMAND handler, I wrote this class, which inherits CMFCRibbonUndoButton. What this code does is store a pointer to the last activated Undo button each time one of the buttons is clicked, or the popup menu activated.

// CMyMFCRibbonUndoButton.h

class CMyMFCRibbonUndoButton : public CMFCRibbonUndoButton
{
    DECLARE_DYNCREATE(CMyMFCRibbonUndoButton)

public:
    CMyMFCRibbonUndoButton();
    CMyMFCRibbonUndoButton(UINT nID, LPCTSTR lpszText,
        int nSmallImageIndex = -1, int nLargeImageIndex = -1);

    virtual void OnClick(CPoint point);
    virtual void OnShowPopupMenu();

    static CMyMFCRibbonUndoButton* GetLastActivated();

private:
    static CMyMFCRibbonUndoButton* s_pLastActivated;
};

// CMyMFCRibbonUndoButton.cpp

IMPLEMENT_DYNCREATE(CMyMFCRibbonUndoButton, CMFCRibbonUndoButton)

CMyMFCRibbonUndoButton* CMyMFCRibbonUndoButton::s_pLastActivated = NULL;

CMyMFCRibbonUndoButton::CMyMFCRibbonUndoButton()
{
}

CMyMFCRibbonUndoButton::CMyMFCRibbonUndoButton(UINT nID, LPCTSTR lpszText,
    int nSmallImageIndex, int nLargeImageIndex) :
    CMFCRibbonUndoButton(nID, lpszText, nSmallImageIndex, nLargeImageIndex)
{
}

void CMyMFCRibbonUndoButton::OnClick(CPoint point)
{
    s_pLastActivated = this;
    CMFCRibbonUndoButton::OnClick(point);
}

void CMyMFCRibbonUndoButton::OnShowPopupMenu()
{
    s_pLastActivated = this;
    CMFCRibbonUndoButton::OnShowPopupMenu();
}

CMyMFCRibbonUndoButton* CMyMFCRibbonUndoButton::GetLastActivated()
{
    return s_pLastActivated;
}

Use this class in place of CMFCRibbonUndoButton when initialising your ribbon bar. In your handler function, call GetLastActivated() to retrieve this pointer, for example:

void CMyTestDoc::OnEditUndo()
{
    CMyMFCRibbonUndoButton* pUndoButton =
        CMyMFCRibbonUndoButton::GetLastActivated();

    ASSERT_VALID(pUndoButton);

    if (pUndoButton != NULL)
    {
        int ActionNumber = pUndoButton->GetActionNumber();
        // etc.
    }
}

This is a bit of a hack, certainly, but it was about the only way I could find to solve the problem.

Anyway, I hope this helps,

Chris

ChrisN
  • 16,635
  • 9
  • 57
  • 81
  • Thanks, but when MFC clones the undo button behind-the-scenes, it creates an ordinary CMFCRibbonUndoButton - not the derived class, so it never updates `s_pLastActivated` from the QAT button! – AshleysBrain Aug 03 '10 at 23:11
  • Interesting, this code works nicely in my test application, and when MFC clones the button it creates a `CMyMFCRibbonUndoButton` object. MFC calls `GetRuntimeClass()->CreateObject()` on the button, so it should create an object of the right class. – ChrisN Aug 04 '10 at 08:26
  • Ah - I must've missed something - I'll try again. – AshleysBrain Aug 04 '10 at 14:30
0

Have a look at the MSOffice2007Demo in the Visual C++ 2008 Feature Pack examples

They use a different technique where they trap a registered message (AFX_WM_ON_BEFORE_SHOW_RIBBON_ITEM_MENU) in this handler they rebuild the undo list dynamically (similar to the old SDK WM_INITMENUPOPUP handling).

The CMFCRibbonUndoButton that provoked the message is passed in the LPARAM of the message.

Using this technique you would maintain your undo list independently of the ribbon controls and use the control as a view into your list.

Bruce Ikin
  • 885
  • 4
  • 6