Using a class member function outside the class (as a callback)

Hello. I'm new to the forum, relatively inexperienced with C++, and really grateful for all the posts others have made here. They have been extraordinarily useful! I still have much to learn.

I'm trying to write a menu interface to use with a 16x2 LCD. Each Menu is a class that contains an array of pointers to MenuItems (another class), state variables, and a member function showMenu() that displays the current selected menuItem on the LCD when called.

Each MenuItem contains two strings (one for each line on the LCD), and a pointer to a callback function that is run when the MenuItem is activated (on a button press for example).

The piece I'm having trouble with is how to use the showMenu() member function of a menu as the callback for submenus (a MenuItem that is another Menu() instantiation). My failed attempt is at lines 89 and 90 of Menutest.ino (bracketed in //// below).

I've tried to strip the code down to a minimal example. Sorry if it's a little long. All the code needed to attempt compilation is attached.

compilation errors:

/home/pjohnson/Arduino/Menutest/Menutest.ino: In function 'void setup()':
Menutest:89: error: expected unqualified-id before '&' token
   topMenuItem0.callbackFunction = Menu::&showMenu(subMenu1);
                                         ^
Menutest:89: error: 'showMenu' was not declared in this scope
   topMenuItem0.callbackFunction = Menu::&showMenu(subMenu1);

Menutest.ino:

#include "menu.h"

boolean exampleCallback() { return false; }

Menu topMenu;  // has to be global based on the way arduino loops

void setup() {

  // set up the display (includes the buttons).
  MyDisplay dsply;

  // set up serial comm
  Serial.begin(38400);
  while (!Serial && (millis() <= 1000));

  // set up the menu structure (main, 2-subs, 2-items/sub)
  // start with the submenus and build towards the top 
  
  Menu subMenu1;
  MenuItem subMenu1Item0;
  MenuItem subMenu1Item1;

  // populate the menuItems with data
  strcpy(subMenu1Item0.displayText1, "Submenu1, Item0 ");
  strcpy(subMenu1Item0.displayText2, "Press any button");
  strcpy(subMenu1Item1.displayText1, "Submenu1, Item1 ");
  strcpy(subMenu1Item1.displayText2, "Press any button");

  // connect the callback functions to the menuItems
  subMenu1Item0.callbackFunction = &exampleCallback;
  subMenu1Item1.callbackFunction = &exampleCallback;
  
  // attach the menuItems to the menu
  subMenu1.menuItems[0] = subMenu1Item0;  // populate the pointer array
  subMenu1.menuItems[1] = subMenu1Item1;
  subMenu1.numMenuItems = 2;              // tell it how many there are
    
  // connect the menu to the display and associated lcd
  subMenu1.display = dsply;
  subMenu1.lcd = dsply.lcd;
  
  Menu subMenu2;
  MenuItem subMenu2Item0;
  MenuItem subMenu2Item1;

  // populate the menuItems with data
  strcpy(subMenu2Item0.displayText1, "Submenu2, Item0 ");
  strcpy(subMenu2Item0.displayText2, "Press any button");
  strcpy(subMenu2Item1.displayText1, "Submenu2, Item1 ");
  strcpy(subMenu2Item1.displayText2, "Press any button");

  // connect the callback functions to the menuItems
  subMenu2Item0.callbackFunction = &exampleCallback;
  subMenu2Item1.callbackFunction = &exampleCallback;
  
  // attach the menuItems to the menu
  subMenu2.menuItems[0] = subMenu2Item0;     // populate the pointer array
  subMenu2.menuItems[1] = subMenu2Item1;
  subMenu2.numMenuItems = 2;                // tell it how many there are

  // connect the menu to the display and associated lcd
  subMenu2.display = dsply;
  subMenu2.lcd = dsply.lcd;
  
  // build the top-level menu from the lower menus 
  Menu topMenu;
  MenuItem topMenuItem0;
  MenuItem topMenuItem1;

  ///////
  // set up the menuItems
  
  // populate the display text for the menu items.
  strcpy(topMenuItem0.displayText1, "Show Submenu1   ");
  strcpy(topMenuItem1.displayText1, "Show Submenu2   ");
 
  ///////////////////////////////////////////////////////////////////////
  // This is where I'm having trouble 
   
  // set the callback functions to display the submenu
  topMenuItem0.callbackFunction = Menu::&showMenu(subMenu1);
  topMenuItem1.callbackFunction = Menu::&showMenu(subMenu2);

  ////////////////////////////////////////////////////////////////////////

  // Set up the menu (attach menuItems and set the number of them
  topMenu.menuItems[0] = topMenuItem0;
  topMenu.menuItems[1] = topMenuItem1;
  topMenu.numMenuItems = 2;

  // connect the menu to the display and associated lcd
  topMenu.display = dsply;
  topMenu.lcd = dsply.lcd;
}

void loop() {

  topMenu.showMenu();

}

menu.h:

#ifndef __menu_h
#define __menu_h


#include <Arduino.h>
#include "display.h"

class MenuItem
{
 public:
  MenuItem();
  static const uint8_t lcdCols = 16;   // columns on the lcd display
  char displayText1[lcdCols];          // 1st line text to display 
  char displayText2[lcdCols];      // 2nd line text to display
  boolean (*callbackFunction)();   // pointer to callback function
 
  // callback functions for all items should return false when
  // execution is complete and control should be returned to the
  // next-higher menu. For sub-menus the callback should be the
  // showMenu() method for that sub-menu.
};


// the way the menu is supposed to work is that each level of menu is
// it's own menu that consists of menuitems.  Each menu keeps track of
// which menuitem is currently active and whether control is supposed
// to pass to the active menu or if the display text is supposed to be
// displayed.  If control is supposed to be passed, the function
// pointed to by the callbackFunction() for the selected menuItem is
// called.  The callbackFunction() should be either a function to
// drill down to the next submenu, or the final function to execute
// (like starting a measurement).
class Menu
{
 public:
  Menu();
  static const int maxMenuItems = 5;
  static boolean showMenu( );        // to show the menu on the display
  static MyDisplay display;          // display to draw the menu item on
  static LiquidCrystal lcd;          // the LCD associated with the display

  // state variables to keep track of where we are in the menu system
  
  static boolean fallThrough;          // pass control to a sub-menu
  static boolean redrawFlag;           // true if display needs updating
  static uint8_t activeItem;           // selected menu item (0 indexed)
  static uint8_t numMenuItems;         // number  of sub-menu items available. 

  MenuItem menuItems[maxMenuItems];    // each MenuItem contains a
       // displayText string and a
       // pointer to the menu item's
       // callback function
  
 private:
  void _buttonscan( Menu menu );
  
  
};

#endif

menu.cpp:

#include "menu.h"


// constructor for any given menu
Menu::Menu( )
{
 
  // set default parameters
  fallThrough = false;
  redrawFlag = true;
  activeItem = 0;
  numMenuItems = 1;

}

boolean Menu::showMenu(  )
{

  // this function displays the current menu.  It returns true when
  // control is supposed to return to the next higher menu, and false
  // when control is at this level or farther down the menu structure.
  
  MenuItem active = menuItems[activeItem];     // get the active menu item

  // bypass all this if fallThrough is set and go to the next level down
  if (fallThrough)
    {
      // drill down a level and set fallThrough based on the return value
      fallThrough = active.callbackFunction(); 
    }

  // otherwise display the activeItem text and look for a button press
  else
    {
      // redraw the display if the redrawFlag is set
      if (redrawFlag)
 {
  // send the displayText to the display
  display.displayText(lcd, active.displayText1, 0);
  display.displayText(lcd, active.displayText2, 1);

  // reset the redraw flag until something changes
  redrawFlag = false;
 }
   
      // Process the button input and set the activeItem and fallThrough
      _buttonscan(menu);
      
    }
}

display.cpp (4.74 KB)

display.h (1.94 KB)

menu.cpp (23.7 KB)

menu.h (2.91 KB)

Menutest.ino (3.22 KB)

Pointers to member functions is a complex topic. Have a look here: https://isocpp.org/wiki/faq/pointers-to-members

If you have a callback thats a plain function call you'll need to write a function that trampolines
to a method of an object stored in a static variable. The lack of closures in C makes this kind
of stuff very boring and boilerplatey.

If you are in control of the callback site, just have it take a pointer to an object, then you can
use a callback method.