Any one have a working example of a hand coded button?

I have implemented a couple buttons in my app using an xml layout. I though I would also see if I could figure out how to build the same functionality using just the code just to make sure I am versed in both ways.

Following the instructions in WatchUi.Button, I have put together most of the parameters to build a button by hand.

In onLayout()
if ( mySettings.isTouchScreen ) {
var b_zout = {
:locX => 0,
:locY => 20,
:width => 50,
:height => 50,
:behavior => :btnZoomOut
};
var tsButton = new Button.initialize(b_zout);
setLayout(tsButton);
}


In MyBehaviorDelegate
class MyBehaviorDelegate extends Ui.BehaviorDelegate {

function initialize() {
BehaviorDelegate.initialize();
}

function btnZoomOut() {
if (true) {
// do stuff;
Ui.requestUpdate();
}
return true;
}
}


I appear to be having problems with the right format for the :behavior parameter in the dictionary. I have tried the following formats with the associated results.

:behavior => :btnZoomOut = UnexpectedTypeException: Expected Class definition, given Method
:behavior => MyBehaviorDelegate.btnZoomOut = UnexpectedTypeException: Expected Class definition, given Method
:behavior => MyBehaviorDelegate.btnZoomOut() = UnexpectedTypeException: Expected Class definition, given Method
:behavior => btnZoomOut() = Could not find symbol btnZoomOut.
:behavior => btnZoomOut = Could not find symbol btnZoomOut.

So there is obviously a proper way to format the behavior method that is not clear to me. Every example I can find is using the XML layout method, so I have not see an good example.

Any one have an idea? I know I can stick with the XML, but now it is bugging me to figure this one out.
  • The documentation for Button.initialize() says:

    :behavior (Toybox::Lang::Method) — A Method object to call when the Button is selected; set to null to use a SelectableEvent (optional)


    So, the value associated with the :behavior parameter should be a Lang.Method object. The documentation for the behavior member (which is initialized from the :behavior option) says:

    This method must be a member of the active View object's registered BehaviorDelegate, such as :onBack, but may also be a method of an extended class.


    I'm not sure why it says this method as the docs aren't talking about a method, they're talking about a member. Additionally, I see no reason that this has to be a method of the behavior delegate. Since it is a Lang.Method it should be able to be any Lang.Method instance. When you define the value via the resource file, it looks like it has to be a method on the delegate, but if you do it directly, this should not be a requirement.

    Clearly there is some inconsistency in the documentation around this, so I'll file a bug to get it documented correctly and to provide some sample code.

    Regardless, given the documentation that I'm reading (and without looking into the actual implementation), it seems that this is how you should define your options dict:

    var b_zout = {
    :locX => 0,
    :locY => 20,
    :width => 50,
    :height => 50,
    :behavior => self.method(:btnZoomOut) // this creates a Lang.Method
    };


    Additionally, the View.setLayout() method takes an array of WatchUi.Drawables, and you're passing a single drawable. If the only thing in your layout is the button, you'd write this...

    var tsButton = new WatchUi.Button(b_zout);

    setLayout([ tsButton ]);


    Travis
  • Also, those error messages don't make sense for the code that you've written. It seems that the first few messages should have said

    UnexpectedTypeException: Expected Method, given Class definition
  • Okay, after a few minutes of tinkering...

    class MyView extends WatchUi.View {

    function initialize() {
    View.initialize();
    }

    function onLayout(dc) {
    var b_zout = {
    :locX => 100,
    :locY => 100,
    :width => 100,
    :height => 70,
    :stateDefault => Graphics.COLOR_ORANGE,
    :stateHighlighted => Graphics.COLOR_YELLOW,
    :stateSelected => Graphics.COLOR_BLUE,
    :stateDisabled => Graphics.COLOR_ORANGE,
    :background => Graphics.COLOR_WHITE,
    :behavior => :btnZoomOut
    };

    var tsButton = new WatchUi.Button(b_zout);

    setLayout([ tsButton ]);
    }
    }

    class MyViewBehaviorDelegate extends WatchUi.BehaviorDelegate {

    function initialize() {
    BehaviorDelegate.initialize();
    }

    function btnZoomOut() {
    System.println("zoom out");
    return true;
    }
    }


    This works.

    So it looks like the documentation that I was citing is wrong and the other bit needs to be fixed. Either way the docs need fixing. I'll get something filed on this on Monday. I'll also add a note that we should give a snippet that shows how to programmatically create a button.

    Travis
  • Travis,

    Thanks for the quick feedback. With a working code example, I now have it working in my test setup also. :cool:

    Looks like my primary error was trying specify Button.initialize() when I should have been using WatchUi.Button(). Couple that with some documentation errors, no wonder I did not get it working.

    I have been playing around with the CIQ 3.0.0 Beta2 when I was trying to programmatically create the buttons. So the latest beta generated those error messages, but all is working now.

  • When I do screen buttons, I take a different approach.

    You know where the button goes on the screen, draw it on the screen in onUpdate() (I use dc calls, and change the color/label based on the app state), and then in the delegate, look for an onTap() in the bounds of the button. On the Oregon and Rino that don't have a start button, I draw one on the screen and when the correct tap is seen, I just call the same code as I do for key_enter/key_start on other devices. In onUpdate then, I use something like "Start", "Pause" and "Resume" for the label based on the current state.
  • jim_m_58,

    Thanks for the feedback. Now that I have code that works, I was thinking about moving it around. To see if I can optimize things a little. I tried to tackle this because how I implimented my buttons on the touch screen non watch devices broke on the watch devices when trying build in the CIQ3 beta.

    I has a if statement to check for has watchface, if false I would do my extra features for the bike computers and handhelds. Inside that I had a second statement to check for touchscreen. If true load the buttons resource. My problem was that iicreated a seperate resource folder and only added it to the devices I knew were touchscreen in my build file that way I would not compile in unnecessary resources on everything that would not use the buttons. It worked fine in CIQ2.x, but on becuse the resource was not available, the new symbol checking caused a build error in CIQ3.

    I started to look at ways to exclude that section of code from being included in the non-touchscreen devices, but my options were leading me to having to maintain large sections of nearly identical code and an even more complicated build file, or look at building the buttons in code instead of the resources.

    I think I am leaning towards the latter option once CIQ3 leaves beta.
  • I usually only do screen buttons on devices that don't have the real ones for a function (namely the Oregon and Rino), so a jungle based on that could be used, but with the small amount of code involved (like I said I use the same code for key_enter/Key_start with screen buttons), I do a check at runtime (I figure it out when the app starts, not all the time). In the Jungle, you could use excluseAnnotaions and just have one source file. That's what I'm doing with menu vs menu2 these days. Just two functions with the same name, where one gets excluded to build the menu and the delegate for the menu for the app). For devices with native maps, I'm using different code than those without maps for that screen, and that's based on a jungle.
  • As far as efficency, Travis woulkd know best, but my gut says they are close, but the way I do it might be a bit more efficient (I'm not doing things I don't need)

    for the jungles this, here is the jungle I'm using I mentioned in my last post. Menu2 and map logic is commented out as I've working with 2.4.6 right now, so I'm forcing those to the default.
    project.manifest = manifest.xml
    srcBase= source

    base.sourcePath=$(srcBase);source-nomaps

    #fenix5x.sourcePath = $(srcBase);source-maps
    #edge1030.sourcePath= $(srcBase);source-maps

    base.excludeAnnotations=menu2
    #fr645m.excludeAnnotations=menu1

  • sabeard to get rid of the symbol checking errors and turn them into symbol checking warnings instead use the compile time option in Window -> Preferences -> Connect IQ -> compile time options:
    --Eno-invalid-symbol
    (this only works as of the 3.0 beta as it's a new compile time option)

    I really like the new symbol checking option, but I'd really wish it were warnings by default and not errors as it are now....
  • sabeard - while you can turn the error into a warning, you may want to look at how to eliminate them all together. In the case where on some devices you use a new source file, the quickest way may be to have a source file for the other devices that's just a dummy with just the symbols but no real code. I think you can also make the compiler happy by using a few has statements at the proper place. That worked in a couple of my apps.