I am trying to figure out how to update the Status bar menu title property from a timer type item.
This is what I have tried so far without success.
`
NSStatusItem _item; public AppDelegate() { } public override void DidFinishLaunching(NSNotification notification) { NSStatusBar _statusBar = NSStatusBar.SystemStatusBar; Random _rand = new Random(); _item = _statusBar.CreateStatusItem(NSStatusItemLength.Variable); _item.Title = $"Text {_rand.Next(1, 100).ToString()}"; Timer _timer = new Timer(2000); _timer.Elapsed += ATimer_Elapsed; _timer.Start(); } void ATimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { _item.Title = $" NEW Text"; }
`
I have used the Action on the menu item, but I do not want it to be a event based item.
The root problem is that your Timer proc is not on the UI thread when called:
MenuTimer.AppDelegate.ATimer_Elapsed(System.Timers.Timer sender, System.Timers.ElapsedEventArgs e) in /Users/donblas/Projects/MenuTimer/AppDelegate.cs:34 System.Timers.Timer.MyTimerCallback(object state) in /Library/Frameworks/Xamarin.Mac.framework/Versions/4.0.0.214/src/mono/mcs/class/referencesource/System/services/timers/system/timers/Timer.cs:324 System.Threading.Timer.Scheduler.TimerCB(System.Threading.Timer o) in /Library/Frameworks/Xamarin.Mac.framework/Versions/4.0.0.214/src/mono/mcs/class/corlib/System.Threading/Timer.cs:327 System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() in /Library/Frameworks/Xamarin.Mac.framework/Versions/4.0.0.214/src/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1279 System.Threading.ThreadPoolWorkQueue.Dispatch() in /Library/Frameworks/Xamarin.Mac.framework/Versions/4.0.0.214/src/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:856 ObjCRuntime.Runtime.ThreadPoolDispatcher(System.Func<bool> callback) in /Users/builder/data/lanes/5489/c4240f3f/source/xamarin-macios/src/ObjCRuntime/Runtime.cs:244 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() in /Library/Frameworks/Xamarin.Mac.framework/Versions/4.0.0.214/src/mono/mcs/class/referencesource/mscorlib/system/threading/threadpool.cs:1208
so you need to invoke back to the UI thread:
void ATimer_Elapsed (object sender, System.Timers.ElapsedEventArgs e) { BeginInvokeOnMainThread (() => { _item.Title = $" NEW Text"; }); }
As noted in the Apple documentation here unless specified in documentation it is not safe to invoke UI objects from non-UI threads. BeginInvokeOnMainThread forces that code to invoke on the correct thread.
However, a better way of structuring this in my opinion is to use the underlying NSTimer APIs, which do exactly what you want here:
NSStatusItem StatusItem; public override void DidFinishLaunching (NSNotification notification) { Random rand = new Random (); StatusItem = NSStatusBar.SystemStatusBar.CreateStatusItem (NSStatusItemLength.Variable); StatusItem.Title = $"Text {rand.Next (1, 100).ToString ()}"; NSTimer.CreateScheduledTimer (2, (obj) => { StatusItem.Title = $" NEW Text"; }); }
Answers
The root problem is that your Timer proc is not on the UI thread when called:
so you need to invoke back to the UI thread:
As noted in the Apple documentation here unless specified in documentation it is not safe to invoke UI objects from non-UI threads. BeginInvokeOnMainThread forces that code to invoke on the correct thread.
However, a better way of structuring this in my opinion is to use the underlying NSTimer APIs, which do exactly what you want here:
Thank you ChrisHamons. Made perfect sense after you explained it.