I'm trying to create a simple counter app for a Samsung Galaxy Watch that responds to the bezel and has plus/minus buttons to increment/decrement the counter, respectively. When the counter value is below certain thresholds, its color should change (for example, going from white to orange to red as it gets closer to 0). The buttons should be able to be held down to cause the counter to rapidly increment/decrement after a slight delay until they are released. This functionality mostly works, except I sometimes get nondeterministic errors when I hold the buttons down. From inspection of my code, there shouldn't be any problem, but maybe I'm missing something. Here's an SCCM that replicates the issue:
using System;
using System.Collections.Generic;
using System.Timers;
using Tizen.Wearable.CircularUI.Forms;
using Xamarin.Forms;
namespace RepeatableCounter
{
class Program : global::Xamarin.Forms.Platform.Tizen.FormsApplication
{
protected override void OnCreate()
{
base.OnCreate();
LoadApplication(new App());
}
static void Main(string[] args)
{
var app = new Program();
Forms.Init(app);
global::Tizen.Wearable.CircularUI.Forms.FormsCircularUI.Init();
app.Run(args);
}
}
class App : Application
{
public App()
{
MainPage = new MainPage { Value = 20, Thresholds = { (5, Color.Red), (10, Color.Orange) }};
}
}
class MainPage : BezelInteractionPage, IRotaryEventReceiver
{
private int value;
private int ticks;
private readonly Label counter;
private readonly Timer resetTicks;
public MainPage() : base()
{
value = 0;
ticks = 0;
counter = new Label
{
Text = value.ToString(),
FontSize = 32,
BackgroundColor = Color.Transparent,
HorizontalTextAlignment = TextAlignment.Center
};
RepeatButton plusButton = new RepeatButton
{
Text = "+",
Delay = 500,
Interval = 100,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = 60
};
RepeatButton minusButton = new RepeatButton
{
Text = "\u2212",
Delay = 500,
Interval = 100,
HorizontalOptions = LayoutOptions.Center,
WidthRequest = 60
};
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
Spacing = -24,
Children = {
plusButton,
counter,
minusButton
}
};
RotaryFocusObject = this;
resetTicks = new Timer
{
Interval = 500,
Enabled = false,
AutoReset = false,
};
resetTicks.Elapsed += (sender, e) => ticks = 0;
plusButton.Pressed += (sender, e) => Value++;
plusButton.Held += (sender, e) => Value++;
minusButton.Pressed += (sender, e) => Value--;
minusButton.Held += (sender, e) => Value--;
}
public int Value
{
get { return value; }
set
{
this.value = value;
counter.Text = this.value.ToString();
Color selected = Color.Default;
foreach ((int threshold, Color color) in Thresholds)
{
if (value <= threshold)
{
selected = color;
break;
}
}
counter.TextColor = selected;
}
}
public int TickThreshold { get; set; } = 10;
public int FastTickStep { get; set; } = 5;
public IList<(int, Color)> Thresholds { get; set; } = new List<(int, Color)>();
public void Rotate(RotaryEventArgs args)
{
if (args.IsClockwise)
{
if (ticks >= 0)
ticks++;
else
ticks = 1;
if (ticks <= TickThreshold)
Value++;
else
Value += FastTickStep;
}
else
{
if (ticks <= 0)
ticks--;
else
ticks = -1;
if (ticks >= -TickThreshold)
Value--;
else
Value -= FastTickStep;
}
resetTicks.Stop();
resetTicks.Start();
}
}
public class RepeatButton : Button
{
private readonly Timer timer;
public RepeatButton() : base()
{
timer = new Timer
{
Enabled = false,
AutoReset = true
};
timer.Elapsed += (sender, e) => {
timer.Interval = Interval;
Held.Invoke(timer, e);
};
Pressed += (sender, e) => {
timer.Interval = Delay;
timer.Start();
};
Released += (sender, e) => timer.Stop();
}
public double Delay { get; set; } = 100;
public double Interval { get; set; } = 100;
public event EventHandler Held;
}
}
To replicate the issue, hold down the +
and/or -
buttons. Since the issue is nondeterministic, you might have to occasionally switch buttons. I've mostly seen it occur after a color change.
There are three "symptoms" that I've encountered:
Release
event (the counter keeps counting even after the button is released)Adding more UI elements seems to exacerbate the issue and cause the errors to happen faster, and this occurs both in the emulator and on an actual watch. Is there some sort of synchronization or threading issue I'm missing? What could be causing these errors?
Please use Device.StartTimer
instead System.Threading.Timer
.
System.Threading.Timer
use worker thread, so it should not directly access to UI
To execute your code on UI thread, you can use Device.BeginInvokeOnMainThread