Final code fitting to the explanation in the OP under UPDATE & SOLUTION:
Program.cs is the main entry point of our tray app:
using System;
using System.Windows.Forms;
namespace Demo {
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext()); // we start with context instead of form
}
}
}
MyContext.cs prevents duplicate instanciation via Application.Idle
event method and has an Update()
method where it uses a handler value to update some UI elements:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Demo {
public class MyContext : ApplicationContext {
private MyHandler _Handler = null;
private NotifyIcon _TrayIcon = null;
public MyContext() {
// constructor is within the OnApplicationIdle method
// due to UI thread handling and preventing duplicates when having events
Application.ApplicationExit += new EventHandler(OnExit);
Application.Idle += new EventHandler(OnIdle);
}
new public void Dispose() {
_TrayIcon.Visible = false;
Application.Exit();
}
private void OnExit(object sender, EventArgs e) {
Dispose();
}
private void OnIdle(object sender, EventArgs e) {
// prevent duplicate initialization on each Idle event
if (_Handler == null) {
var context = TaskScheduler.FromCurrentSynchronizationContext();
_Handler = new MyHandler(
(f) => { // 1st parameter of MyHandler constructor
Task.Factory.StartNew(
() => {
f();
},
CancellationToken.None,
TaskCreationOptions.None,
context);
},
this // 2nd parameter of MyHandler constructor
);
_TrayIcon = new NotifyIcon() {
ContextMenu = new ContextMenu(new MenuItem[] {
new MenuItem("Toggle Something", ToggleSomething),
new MenuItem("-"),
new MenuItem("Exit", OnExit)
}),
Text = "My wonderful app",
Visible = true
};
_TrayIcon.MouseClick += new MouseEventHandler(_TrayIcon_Click);
Update(); // Handler is used and form is shown
}
}
public void Update() {
bool value = _Handler.GetValue();
// tray icon is updated
_TrayIcon.Icon = value ? path.to.icon.when.true
: path.to.icon.when.false;
// form is shown and closed by itself after a particular amount of time
MyForm form = new MyForm(value);
form.Show();
}
private void _TrayIcon_Click(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
ToggleSomething(sender, e);
}
}
private void ToggleSomething(object sender, EventArgs e) {
_Handler.ToggleValue();
}
// ...
}
}
MyHandler.cs needs a UI invoke reference by which it can directly call into the UI thread and thereby manipulate UI elements:
using System;
using System.Collections.Generic;
using System.Linq;
// ...
namespace Demo {
public class MyHandler {
private MyContext _Context = null;
private readonly Action<Action> _UIInvokeRef = null;
private bool _Value = false;
public MyHandler(Action<Action> uIInvokeRef, MyContext context) {
_Context = context;
_UIInvokeRef = uIInvokeRef;
// ...
Something something.OnSomething += Something_OnSomething; // an event that is triggered by something outside (e.g. a library that reacts to a system device)
}
private void Something_OnSomething(Data data) {
_Value = data.Value > 10 ? true : false; // data has been changed and value is set
// ...
_UIInvokeRef(() => { // UI thread is used
_Context.Update(); // update tray icon and show form
});
}
// ...
public bool GetValue() {
return _Value;
}
public void ToggleValue() {
_Value = !_Value;
// can also be used to manipulate a system device (e.g.)
// in order to trigger the Something_OnSomething event
// which then updates the UI elements
}
}
}
MyForm.cs uses a timer by which it can close itself:
using System;
using System.Windows.Forms;
namespace Demo {
public partial class MyForm : Form {
private System.Windows.Forms.Timer _Timer = null;
public FormImage(bool value) {
InitializeComponent();
pbx.Image = value ? path.to.picture.when.true
: path.to.picture.when.false;
}
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
this.FormBorderStyle = FormBorderStyle.None;
this.StartPosition = FormStartPosition.CenterScreen;
this.ShowInTaskbar = false;
this.TopLevel = true;
}
protected override void OnShown(EventArgs e) {
base.OnShown(e);
_Timer = new System.Windows.Forms.Timer();
_Timer.Interval = 500; // intervall until timer tick event is called
_Timer.Tick += new EventHandler(Timer_Tick); // timer tick event is registered
_Timer.Start(); // timer is started
}
private void Timer_Tick(object sender, EventArgs e) {
_Timer.Stop(); // timer is stopped
_Timer.Dispose(); // timer is discarded
this.Close(); // form is closed by itself
}
}
}
This works even when the handler event (system trigger) Something_OnSomething
is called faster again than the form timer event Timer_Tick
can close the form.