Update SubLabels of Menu2 when a data change

Hello everyone and thanks in advance,

I am doing a Menu for a watch using the Menu2 method. In this menu the item SubLable should display the value of the Position.heading which is supposed to change during time.

I can set the item SubLable correctly when i call the menu with the heading of that moment, however I am having trouble trying to update it while i am on the menu to make it match the actual Position.heading.

I tried with the timer method but it seems to work only for pages and not for delegates.

Thank you very much

Top Replies

  • If you are looking for something more dynamic, why are you using  Menu2 and not just updating a "normal" view?

    Maybe bc they still want all the UI/functionality of Menu2? I wonder…

All Replies

  • I set them when creating the menu, and change them in the menu delegate when that menu item is selected.

    If you are looking for something more dynamic, why are you using  Menu2 and not just updating a "normal" view?

  • My aim is to define a global variable that i use for calculations on the main page

    So in order to set the variable i have to go to the menu. The idea is that the user can set that global variabile equal to the heading of the moment in which he clicks on the menuitem.

    In order to give to the user a more idea of what he is setting, i was thinking to have the sublable of that item that shows the live heading.

    I ended up on the Menu2 just becouse it seems to be easy to manage and use.

  • If I understand, it still strikes me as something that would be easier to do without Menu2.  Your own view updating on a timer, showing the current value, and then something like onSelect to set the variable, and if you have more than one thing, use up/down to move between them.  Then onBack to exit

  • If you are looking for something more dynamic, why are you using  Menu2 and not just updating a "normal" view?

    Maybe bc they still want all the UI/functionality of Menu2? I wonder why Menu2 provides the updateItem() method if it's not meant to be dynamic...

    If I understand, it still strikes me as something that would be easier to do without Menu2.  Your own view updating on a timer, showing the current value, and then something like onSelect to set the variable, and if you have more than one thing, use up/down to move between them.  Then onBack to exit

    Yes clearly it is far simpler to completely reimplement the functionality, look, and feel of Menu2 in a custom view, instead of figuring out how to update Menu2 dynamically using a timer.

    I tried with the timer method but it seems to work only for pages and not for delegates.

    Use a timer to programmatically update the menu using Menu2.updateItem() (followed by a call to WatchUi.requestUpdate())

    Here's an example that modifies the SDK Menu2Sample sample so that the sublabel of the Toggles menu item displays a counter that increments every second. I've tested this in the sim (fr955), and a real Forerunner 955.

    In the Menu2Sample, the MenuTestDelegate class creates a Menu2 when the user "presses the Menu button" at the top level (e.g. hold UP on 5-button watches). I modified the class to:

    - store references to the menu it creates and the first item of the menu ("Toggles")

    - store an integer counter (starting at 0)

    - start a timer when the user presses Menu

    - when the timer expires, counter is incremented and the label of the Toggles item is updated with the current value of counter

    Complete MenuTestDelegate.mc source:

    //
    // Copyright 2018-2021 by Garmin Ltd. or its subsidiaries.
    // Subject to Garmin SDK License Agreement and Wearables
    // Application Developer Agreement.
    //
    
    import Toybox.Graphics;
    import Toybox.Lang;
    import Toybox.WatchUi;
    
    
    //! This delegate is for the main page of our application that pushes the menu
    //! when the onMenu() behavior is received.
    class Menu2TestDelegate extends WatchUi.BehaviorDelegate {
        var timer;
        var menu;
        var dynamicItem;
        var counter = 0;
        //! Constructor
        public function initialize() {
            BehaviorDelegate.initialize();
            timer = new Timer.Timer();
        }
    
        //! Handle the menu event
        //! @return true if handled, false otherwise
        public function onMenu() as Boolean {
            // Generate a new Menu with a drawable Title
            menu = new WatchUi.Menu2({:title=>new $.DrawableMenuTitle()});
            timer.start(method(:onTimer), 1000, true);
            // Add menu items for demonstrating toggles, checkbox and icon menu items
            dynamicItem = new WatchUi.MenuItem("Toggles", counter.toString(), "toggle", null);
            menu.addItem(dynamicItem);
            menu.addItem(new WatchUi.MenuItem("Checkboxes", null, "check", null));
            menu.addItem(new WatchUi.MenuItem("Icons", null, "icon", null));
            menu.addItem(new WatchUi.MenuItem("Custom", null, "custom", null));
            WatchUi.pushView(menu, new $.Menu2TestMenu2Delegate(), WatchUi.SLIDE_UP);
            return true;
        }
    
        // if this function is protected or private, then it's inappropriately optimized away.
        // seems like a compiler bug
        public function onTimer() as Void {
            counter++;
            System.println(counter);
            if (dynamicItem != null && menu != null) {
                dynamicItem.setSubLabel(counter.toString());
                menu.updateItem(dynamicItem, 0);
                WatchUi.requestUpdate();
            }
        }
    }
    
    //! This is the custom drawable we will use for our main menu title
    class DrawableMenuTitle extends WatchUi.Drawable {
    
        //! Constructor
        public function initialize() {
            Drawable.initialize({});
        }
    
        //! Draw the application icon and main menu title
        //! @param dc Device Context
        public function draw(dc as Dc) as Void {
            var spacing = 2;
            var appIcon = WatchUi.loadResource($.Rez.Drawables.LauncherIcon) as BitmapResource;
            var bitmapWidth = appIcon.getWidth();
            var labelWidth = dc.getTextWidthInPixels("Menu2", Graphics.FONT_MEDIUM);
    
            var bitmapX = (dc.getWidth() - (bitmapWidth + spacing + labelWidth)) / 2;
            var bitmapY = (dc.getHeight() - appIcon.getHeight()) / 2;
            var labelX = bitmapX + bitmapWidth + spacing;
            var labelY = dc.getHeight() / 2;
    
            dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_BLACK);
            dc.clear();
    
            dc.drawBitmap(bitmapX, bitmapY, appIcon);
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
            dc.drawText(labelX, labelY, Graphics.FONT_MEDIUM, "Menu2", Graphics.TEXT_JUSTIFY_LEFT | Graphics.TEXT_JUSTIFY_VCENTER);
        }
    }
    

    Patch file with changes:

    diff -r -u Menu2Sample/monkey.jungle Menu2Sample.new/monkey.jungle
    --- Menu2Sample/monkey.jungle	2024-01-11 13:36:00.207965000 -0500
    +++ Menu2Sample.new/monkey.jungle	2024-06-08 17:51:26.616639500 -0400
    @@ -1 +1,2 @@
     project.manifest = manifest.xml
    +project.typecheck = 0
    \ No newline at end of file
    diff -r -u Menu2Sample/source/MenuTestDelegate.mc Menu2Sample.new/source/MenuTestDelegate.mc
    --- Menu2Sample/source/MenuTestDelegate.mc	2024-01-11 13:36:00.242975100 -0500
    +++ Menu2Sample.new/source/MenuTestDelegate.mc	2024-06-08 18:01:41.767740300 -0400
    @@ -8,29 +8,45 @@
     import Toybox.Lang;
     import Toybox.WatchUi;
     
    +
     //! This delegate is for the main page of our application that pushes the menu
     //! when the onMenu() behavior is received.
     class Menu2TestDelegate extends WatchUi.BehaviorDelegate {
    -
    +    var timer;
    +    var menu;
    +    var dynamicItem;
    +    var counter = 0;
         //! Constructor
         public function initialize() {
             BehaviorDelegate.initialize();
    +        timer = new Timer.Timer();
         }
     
         //! Handle the menu event
         //! @return true if handled, false otherwise
         public function onMenu() as Boolean {
             // Generate a new Menu with a drawable Title
    -        var menu = new WatchUi.Menu2({:title=>new $.DrawableMenuTitle()});
    -
    +        menu = new WatchUi.Menu2({:title=>new $.DrawableMenuTitle()});
    +        timer.start(method(:onTimer), 1000, true);
             // Add menu items for demonstrating toggles, checkbox and icon menu items
    -        menu.addItem(new WatchUi.MenuItem("Toggles", "sublabel", "toggle", null));
    +        dynamicItem = new WatchUi.MenuItem("Toggles", counter.toString(), "toggle", null);
    +        menu.addItem(dynamicItem);
             menu.addItem(new WatchUi.MenuItem("Checkboxes", null, "check", null));
             menu.addItem(new WatchUi.MenuItem("Icons", null, "icon", null));
             menu.addItem(new WatchUi.MenuItem("Custom", null, "custom", null));
             WatchUi.pushView(menu, new $.Menu2TestMenu2Delegate(), WatchUi.SLIDE_UP);
             return true;
         }
    +
    +    public function onTimer() as Void {
    +        counter++;
    +        System.println(counter);
    +        if (dynamicItem != null && menu != null) {
    +            dynamicItem.setSubLabel(counter.toString());
    +            menu.updateItem(dynamicItem, 0);
    +            WatchUi.requestUpdate();
    +        }
    +    }
     }
     
     //! This is the custom drawable we will use for our main menu title
    

    Simulator output:

  • Hi, thank you very much! It works perfectly, I'll try it soon also on the real device