Short Take: Button Presses

I’ve been working on rewriting our Standard UI in SIMPL# Pro and have tried a couple different approaches to map button presses to program logic. I figured a short post about what I’ve been exploring might be helpful.

Button presses are treated much like any other device signal change:

  • User presses a button on touchpanel
  • BooleanOutputs[sig] changes to True
  • If a SigEventHandler is defined, it is called with the device and signal that caused the event
  • User eventually releases button on touchpanel
  • BooleanOutputs[sig] changes to False
  • SigEventHandler is called again

One way to handle button presses is to check the signal that caused the event handler to fire. You would typically create something like:

private void SigChange(BasicTriList dev, SigEventArgs args)
{
    // Presses are represented as BooleanOutput changes from dev
    if (args.Sig.Type == eSigType.Bool)
    {
        if (args.Sig.BoolValue) // Pressed = true
        {
            switch (args.Sig.Number)
            {
                // Handle specific join numbers...
            }
        }
        else // Released = false
        {
            switch (args.Sig.Number)
            {
                // ...
            }
        }
    }
}

This can be handy since all your logic is kept in one place, but it can quickly become overwhelming if you’re trying to separate out different subsystems.

Another approach that is frequently used is to attach a delegate to the joins on the touchpanel device. Then the SigEventHandler only needs to check if a delegate exists and call it. Like so:

private void SigChange(BasicTriList dev, SigEventArgs args)
{
    // Only joins we care about have an action attached to them
    if (args.Sig.UserObject != null)
    {
        // Cast UserObject to Action delegate type
        var act = args.Sig.UserObject as Action<BasicTriList, SigEventArgs>;
        
        // Call delegate with device and signal
        act(dev, args);
    }
}

You need to assign Action delegates to the joins on the touchpanel ahead of time. This one checks if the button was pressed before calling doSomething. Remember, this expression will be called once for button presses and once for releases:

// lambda expression wrapped in an Action delegate
tp.BooleanOutput[20].UserObject = new Action<BasicTriList, SigEventArgs>(
    (dev, args) => { if (args.Sig.BoolValue) doSomething();
});

I’d like to simplify creating Action delegates, since I’d like to keep similar logic grouped into separate modules within my program. What I’m experimenting with is a UiManager class, partially shown here:

public class UiManager
{
    private List<BasicTriListWithSmartObject> _panels;
    private Dictionary<uint, Action<bool>> _buttons;

    public UiManager()
    {
        // _panels tracks touchpanels in our program
        _panels = new List<BasicTriListWithSmartObject>();
        // _buttons tracks button actions in our UI
        _buttons = new Dictionary<uint,Action<bool>>();
    }

    public void Add(BasicTriListWithSmartObject tp)
    {
        tp.SigChange += SigChange;
        _panels.Add(tp);
    }

    public void Register()
    {
        foreach (var tp in _panels)
            tp.Register();
    }

    public void Button(uint sig, Action<bool> act)
    {
        // Make sure dictionary contains sig
        if (!_buttons.ContainsKey(sig))
            _buttons[sig] = null;

        // Chain multiple actions if necessary
        if (_buttons[sig] == null)
            _buttons[sig] = act;
        else
            _buttons[sig] += act;
    }

    private void SigChange(BasicTriList dev, SigEventArgs args)
    {
        if (args.Sig.Type == eSigType.Bool)
        {
            // Have we registered this button?
            if (_buttons.ContainsKey(args.Sig.Number))
            {
                var act = _buttons[args.Sig.Number];

                // Call action if defined
                if (act != null)
                    act(args.Sig.BoolValue);
            }
        }
    }
}

Our SigChange methods checks to see if the event fired because of a button press. Then it checks if we’ve registered anything for this join number. If we find a stored Action, we call it with the value of the button press (or release).

In my initialization code, I create a single UiManager that I then register all identical touchpanels with. A button press on any touchpanel will be treated the same and update all others (useful for having an XPanel that mimics an in-room TSW-1060):

var ui = new UiManager();
ui.Add(new Tsw1060(0x03, CS));
ui.Add(new XpanelForSmartGraphics(0x04, CS));
ui.Register();
ui.Button(20, x => { if (x) doPress(); });
ui.Button(20, x => { if (!x) doRelease(); });

Here I’ve shown that multiple actions can be chained onto the same button press. This might be useful if multiple subsystems need to hook into UI elements, say a room with video conferencing might want to wake up the video codec when the system turns on (in addition to all the normal system power functions).

I hope to be able to share a good chunk of my Standard UI code once I’ve got it working. Thanks for reading!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s