Programmatically set the state of ToggleMenuItem

I'm building a menu2 with toggle items using menu xml definitions. I cannot figure out how to programmatically set the toggle state of the menu item. When the user changes the toggle state, I am saving the state to Application.Storage, but when I load the menu, it reverts to the state from the xml definition. When instantiating the menu, I can not figure out how to identify the ToggleMenuItem to invoke the setEnabled method.

If I try to call Menu2.findItemById("item1").setEnabled(Application.Storage.getValue("booleanHolder")) I am told "Cannot find symbol ':setEnabled' on type '$.Toybox.Lang.Number'."

<menu2 id="SettingsMenu" title="Settings">
<toggle-menu-item id="item1" label="Item1" subLabel="Show" disabledSubLabel="Hide" checked="true"></toggle-menu-item>
</menu2>
class SettingsSubMenuDelegate extends WatchUi.Menu2InputDelegate {
function initialize() {
Menu2InputDelegate.initialize();
Menu2.findItemById("item1").setEnabled(Application.Storage.getValue("booleanHolder"));
}

function onSelect(item as WatchUi.ToggleMenuItem) {
if (item.getId() == :item1) {
Application.Storage.setValue("booleanHolder", item.isEnabled());
}
}
}
  • Extend your Menu2 like so

    class MyShineyMenu extends Rez.Menus.MyShineyMenu {
    
      function initialize() {
        Rez.Menus.MyShineyMenu.initialize();
        // change the menu here
      }
    
    }

    This way you can alter your menu very easily based upon the XML and various bits and pieces. You can set defaults, remove items etc etc.

    You don't want to do this in your delegate. Your delegate doesn't have access to the menu itself by default unless you pass it in there. And you'll probably need to request updates, but I'm not sure menu2 supports that. Eitherway, extend the Menu2 and you are golden.

    Also, findMenuItemByID returns the index of the menu item in the menu, so that is a number. You'll than need to get the item from menu. See https://gitlab.com/waterkip/opn-monkeyc/-/blob/master/source/X11/Menu2.mc#L52 for an example.

    Be aware the findMenuItemByID returns -1 if it cannot find the item in the menu. So you'll need to catch that.

  • I usually don't use xml for the menu, and build them as needed and can vary what's even in the menu based on the device.  and set the state there:

    Menu2.addItem(new WatchUi.ToggleMenuItem("Show Seconds When Possible", null,"sec",MySettings.showSecs, null));

    for menu items that might have more choices, the same (this is a case where the item is oonly used on AMOLED devices)

    if(canBurnIn) {
    Menu2.addItem(new WatchUi.MenuItem("Always On Mode",vmodes[MySettings.vMode],"am",{}));
    }

    In both cases here, MySettings is the current state of things.  I read the settings at start up, then only write them when they change, so I do that in the Menu2 delegate.

    for "Always On Mode", I change the sublabel in the delegate:

    item.setSubLabel(vmodes[MySettings.vMode]);

    for a toggle menu, the visual state is alreay set.

  • Thanks @jm_m_58,

    That's basically what has been done with the code originally. I'm trying to update it to follow the current methodology of abstracting the display elements into xml files, and then updating them as needed at render time. I am finding, though, that the documentation has a lot of holes in it when you get into these deeper level weeds.

  • Thanks @waterkip,

    I'm going to take a deeper dive into the example you linked to and see if I can work something out.

  • Take a look at the Menu2Sample in the SDK.  Using the xml or building the menu2 in your code have both been available all along, so there really isn't a "current methodology" involved here.  I find building them dynamically much more flexible as you can adapt things at runtime.  In one case I have an app with more options before a recording is started, and can just skip some things if I'm recording (you can select a sport before recording, but not while in the process of recording, for example)

  • You can change both at run time. The biggest difference IMO is that with a Menu2 without the XML you can subclass it and add common methods to it to make your life easier and with extending Rez.Menu you cannot.

    If Garmin would implement a Composition API than it would be super sweet and you can have all the cookies and ice cream with the Rez.Menu variant.

    I mix them btw, I have both XML and pure Menu2, the latter I primairly use for fully dynamic menus, the XML variant I use for those menus that have minor logic, just some hiding and stuff. The XML is easy and less code to setup a simple menu. You don't even need to extend it if you don't want to alter it, just call new Rez.Menu.whateverthename and you are done.

  • I tried your example of extending the Rez.Menu.MyMenu example, but I'm left with my original question. How do I identify the ToggleMenuItem I am trying to modify? 

    Still using the menu XML in the OP.

    WatchUi.pushView(new SettingsMenu(), new SettingsSubMenuDelegate(), WatchUi.SLIDE_UP );

    class SettingsMenu extends Rez.Menus.SettingsMenu {

    function initialize() {

    Rez.Menus.SettingsMenu.initialize();

    var itemId = me.findItemById("item1");

    System.println(itemId);

    }

    }


    Using the extended class the menu loads, but the above code simply returns -1 when the menu is rendered. I still can't figure out how to identify the menu item that I want to modify.

  • It becomes a symbol, so you should use `:item1` to find it.

    If you make items yourself the id can also be a string and so on. But the ID is in this case a symbol. It is the same symbol or item id as you can use in the delegate available via `item.getID()` (or `getId()`, I have a base delegate that just exposes it as itemId). 

  • I woul dsuggest creating the menu in the code and not to use XML.

    If you are willing to provide your app for devices which don't have Menu2 support, the XML won't be a good solution. The app export will fail if only one devies has not the needes SDK level (even a product number for the same device like asian variants).

    That's why I moved to do such things without XML and check in the code if the device is supporting the Menu2.

                var menu = new WatchUi.Menu2({:title=>WatchUi.loadResource(Rez.Strings.NotificationMenuTitle)});
                menu.addItem(
                    new WatchUi.MenuItem(
                        WatchUi.loadResource(Rez.Strings.NotificationMenuDeleteAll),
                        null,
                        "all",
                        {}
                    )
                );

    As benefit you can use the ID ("all" in the example) as string instead as symbol.

            if (id.equals("all")){....