I would like to have a system tray icon appear only when I need to show a balloon tip, then hide the icon when the balloon tip is closed.
However, once the icon is shown, I can't get it to disappear because the event handler is not fired:
public partial class MainWindow : Window {
public static NotifyIcon trayIcon = new NotifyIcon();
public MainWindow() {
InitializeTrayIcon();
}
void InitializeTrayIcon() {
trayIcon.Text = "My App";
trayIcon.Icon = MyApp.Properties.Resources.myIcon;
trayIcon.Visible = false;
//the following never gets fired:
trayIcon.BalloonTipClosed += (sender, e) => {
trayIcon.Visible = false;
};
}
public static void ShowTrayNotification(ToolTipIcon icon, string title, string text, int duration) {
trayIcon.Visible = true;
trayIcon.ShowBalloonTip(duration, title, text, icon);
}
}
The ShowTrayNotification() is called from a method that is triggered by a timer:
public abstract class Watcher {
protected System.Timers.Timer myTimer = new System.Timers.Timer(1000);
//the following is called in a subclass of Watcher, which is instantiated in MainWindow
protected void SetupMyTimer() {
myTimer.AutoReset = true;
myTimer.Elapsed += myTimer_Elapsed;
myTimer.Start();
}
protected virtual void myTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) {
myTimer.Enabled = false;
MyTimerElapsedCallback();
myTimer.Enabled = true;
}
void MyTimerElapsedCallback() {
MainWindow.ShowTrayNotification(ToolTipIcon.Info, "Hello There!", "Balloon text here.", 5000);
}
}
So the balloon is shown. But BalloonTipClosed in MainWindow is never fired.
I have tried:
putting the (1) creation of the NotifyIcon, (2) displaying of balloon, and (3) setting BalloonTipClosed all in MainWindow, and it works fine (i.e. BalloonTipClosed is fired)
putting (1), (2), and (3) in SetupMyTimer() and it works fine as well
putting (1), (2), and (3) in MyTimerElapsedCallback() and it does not work (i.e. BalloonTipClosed is not fired)
changing BalloonTipClosed to BalloonTipClicked and it does not work as well.
using non-lambda BalloonTipClosed EventHandler, does not work.
with this, I am thinking the problem has to do with the Timer, but I don't know how it's affecting the event handler, nor how to fix.
Any ideas?
You have a threading bug in your code, the timer's Elapsed event is raised on a threadpool thread. You normally get an InvalidOperationException when you do this sort of thing but the check is not implemented for NotifyIcon.
The side-effect of making it visible on the wrong thread is that an otherwise hidden window that is used to receive the event notifications is created on that bad thread. It cannot receive any notifications at all, the threadpool thread does not pump a message loop. Lousy diagnostic, no exception and no good way to see why it goes wrong.
Your ShowTrayNotification() method must use the form's BeginInvoke() method so that the code runs on the UI thread. You make that difficult because the method is static, in an absolute pinch you could use Application.OpenForms[0].BeginInvoke(). But it would certainly be better to have your Watcher class raise an event instead of calling the form's method directly. Or consider using a plain Winforms' Timer, the one you find back in the toolbox. As posted, the Watcher class has no visible added value.
Related
I have a question about System.Windows.Forms.Timer. Is it possible to get Tick event after disposing it? For example, if the message is in the message loop and I dispose the timer meanwhile. If it is possible what is the best way to prevent against it. Do you now any good sources explaining it, because I couldn't find anything explaining it. Here is same code explaining my problem:
namespace TestTimer
{
public partial class Form1 : Form
{
ObjectWithTimer obj = null;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if(obj != null)
{
obj.Deinit();
obj = null;
}
obj = new ObjectWithTimer();
}
}
public class ObjectWithTimer
{
public Object o = new object();
public Timer timer = new Timer();
bool disposed = false;
public ObjectWithTimer()
{
timer.Interval = 10;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
}
public void Deinit()
{
timer.Enabled = false;
timer.Tick -= new EventHandler(timer_Tick);
timer.Dispose();
timer = null;
disposed = true;
}
private void timer_Tick(object sender, EventArgs e)
{
if (disposed)
{
//Is it possible to get here
if (timer.Enabled) return;
}
//doing something
}
}
}
Understanding how timers work can help you feel better about it. They are implemented by the operating system, the underlying winapi call to start a timer is SetTimer(). The OS then posts a notification whenever the timer ticks, you get a WM_TIMER message. The plumbing in Winforms ensures that your Tick event handler runs when this message is received.
These messages are stored in the message queue, an internal data structure associated with a window. This queue serializes messages, it is the basic mechanism that ensures that you for example can never lose a mouse click or a keyboard key press, even when the window is unresponsive because the UI thread is busy with something else.
This queue gives reason to be cautious, what happens when the queue stores a WM_TIMER message when you disposed the timer? Unless something drastic is done, you'd still get that message and your Tick event handler will fire.
But no need to worry, WM_TIMER belongs to a small group of messages that are generated in a special way. They are synthesized messages, it is only ever generated when your program asks for a message with GetMessage(). Other common messages that belong that group are WM_PAINT, it fires the Paint event. Note how you can call Invalidate() as often as you like, you still get only a single Paint event. WM_MOUSEMOVE is another one, it fires the MouseMove event. Something you can reason about, no matter how fast you move the mouse, you can never flood the message queue with mouse-move messages.
Another characteristic of these synthesized messages is that they appear to have a "low priority". Given is that they are only ever synthesized when the message queue is empty. Which is why keyboard messages and mouse clicks always generate an event ahead of a paint.
Long story short, you can only get a WM_TIMER message if you ask for a message and the timer is still alive. The Timer.Dispose() method calls KillTimer() under the hood. Which ends any opportunity to still get a Tick event. Only possible way that could get screwed up is when you call the Stop() or Dispose() methods from a worker thread. Don't do that.
The Windows Forms Timer is single threaded so is not possible that while disposing it you are in timer_Tick.
Also you are not detaching your event in deinit function.
This is very easy to test. I've modified your code a bit:
public class Form1 : Form
{
public Form1()
{
var button = new Button();
button.Click += button1_Click;
Controls.Add(button);
}
private void button1_Click(object sender, EventArgs e)
{
var obj = new ObjectWithTimer();
Thread.Sleep(2000);
obj.Deinit();
}
}
public class ObjectWithTimer
{
public System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
bool disposed = false;
public ObjectWithTimer()
{
timer.Interval = 100;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
}
public void Deinit()
{
timer.Enabled = false;
timer.Tick -= new EventHandler(timer_Tick);
timer.Dispose();
timer = null;
disposed = true;
}
private void timer_Tick(object sender, EventArgs e)
{
"Ticked".Dump();
}
}
The Thread.Sleep ensures the UI thread is occupied while the timer does its ticking.
The result? No, the Tick will not fire after the timer is disabled. Even the timer.Tick -= new EventHandler(timer_Tick); is unnecessary.
Usually, when you access controls in a Thread you end up with some cross thread exceptions. In my C# WinForms Application I have a picture box and a toolstriplabel which do not cause that exception. I don't understand why, can anybody explain this to me?
Here some code explanation:
In the main form I have a picturebox and a toolstriplabel. Also I have a reference to another Form, which has no controls and no additional source code. And then in the main form there is another object which works with a thread. This thread can raise three different events and the main form is subscribed to these three events.
Event1 causes the toolstriplabel to update (with some information from the thread).
Event2 causes the picturebox to update (with a new picture from the thread).
Event1 and Event2 work perfectly fine. I do not use any invoke methods, I directly change Text and BackgroundImage properties without cross thread exception.
Event3 though makes troubles. It is supposed to show the other form but I receive the cross therad exception. It works only if I use a BeginInvoke to show the form.
Why is that?
Edit:
The multithreading is done by an MJPEGStream object. I subscribe the NewFrame method of that MJPEGStream object.
public partial class Form1 : Form
{
private CAM cam;
private PeekWindow frmPeekWindow;
public Form1()
{
InitializeComponent();
cam = new CAM();
cam.NewImageMessageEvent += new NewImageEventHandler(cam_NewImageMessageEvent);
cam.DetectionEvent += new DetectionEventHandler(cam_DetectionEvent);
cam.FpsChangedMessageEvent += new FpsChangedEventHandler(cam_FpsChangedMessageEvent);
cam.DetectionThreshold = (float)this.numDetectionThreshold.Value;
frmPeekWindow = new PeekWindow();
// without the next two lines, frmPeekwindow.Show() won't work if called in an event
frmPeekWindow.Show();
frmPeekWindow.Hide();
}
void cam_FpsChangedMessageEvent(object sender, FpsChangedEventArgs e)
{
lblFPS.Text = string.Format("fps: {0:0.0}", e.FPS);
}
void cam_DetectionEvent(object sender, DetectionEventArgs e)
{
if (chkEnablePeakWindow.Checked)
{
if (frmPeekWindow.InvokeRequired)
{
frmPeekWindow.Invoke((MethodInvoker)delegate()
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
});
}
else
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
}
}
}
void cam_NewImageMessageEvent(object sender, NewImageEventArgs e)
{
picStream.BackgroundImage = e.Image;
}
}
And here's the CAM class:
class CAM
{
private object lockScale = new object();
private MJPEGStream stream;
private Bitmap image;
public event NewImageEventHandler NewImageMessageEvent;
public event FpsChangedEventHandler FpsChangedMessageEvent;
public event DetectionEventHandler DetectionEvent;
// configure (login, pwd, source)
public CAM()
{
this.stream = new MJPEGStream("...");
this.stream.Login = "...";
this.stream.Password = "...";
this.stream.NewFrame += new NewFrameEventHandler(OnNewFrame)
}
private void OnNewFrame(object sender, NewFrameEventArgs ev)
{
try
{
FpsChangedMessageEvent(this, new FpsChangedEventArgs(10));
// get image
image = ev.Frame;
NewImageMessageEvent(this, new NewImageEventArgs(new Bitmap(image)));
DetectionEvent(this, new DetectionEventArgs(new Bitmap(image)));
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.Message);
}
}
}
You won't get cross thread exception, but it doesn't mean that this is a safe operation. There is always a possibility for your control to go unstable. You just don't know when it will happen.
See the following explanation from Microsoft.
http://msdn.microsoft.com/en-us/library/ms171728.aspx
Access to Windows Forms controls is not inherently thread safe. If you
have two or more threads manipulating the state of a control, it is
possible to force the control into an inconsistent state. Other
thread-related bugs are possible, such as race conditions and
deadlocks. It is important to make sure that access to your controls
is performed in a thread-safe way.
I have these three possibilites in mind:
The action is already dispatched to the gui thread.
The action doesn't need to be dispatched currently.
The action is somehow executed from the gui thread.
It's most likely number 3.
You don't necessarily always have to call BeginInvoke/Invoke. Sometimes the operation is running on the foreground thread, sometimes it is in the background.
Per the microsoft samples that are everywhere, You can SHOULD check to see if calling BeginInvoke/Invoke is required.
private void SetTextStandardPattern()
{
if (this.InvokeRequired)
{
this.Invoke(SetTextStandardPattern);
return;
}
this.text = "New Text";
}
Here is a nice microsoft article that has a sample:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
and here is another article on how to "avoid" the pattern:
http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired
Problem:
I am working on a application where in for some time consuming operation, i am supposed to show a progress bar on a form (WinForm) with a cancel button. So obviously i am using BackgroundWorker thread for it. Below is the code which simulates roughly of what i am trying to achieve.
namespace WindowsFormsApplication1
{
public delegate void SomeDelegateHandler();
public partial class Form1 : Form
{
public event SomeDelegateHandler DoSomeAction;
BackgroundWorker bgWorker;
public Form1()
{
InitializeComponent();
bgWorker = new BackgroundWorker();
bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);
}
void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
//Some logic code here.
for (int i = 0; i < 100; i++)
{
DoSomeAction();
}
}
private void Form1_Shown(object sender, EventArgs e)
{
if (DoSomeAction != null)
bgWorker.RunWorkerAsync();
else throw new EventNotSubscribedException();//Is this a valid style??
}
}
public class EventNotSubscribedException : ApplicationException
{
//Some custom code here
}
}
My Solution
As per the above code, as soon as the form is displayed to the user (OnShown event) i am starting the backgroundworker thread. This is because, the user need not to initiate any action for this to happen. So onshown does time consuming operation job. But the issue is, as i have shown above, the main time consuming job is executed on other class/component where it is kind of tight bounded too (legacy code: cant refactor). Hence i have subscribed to the event DoSomeAction in that legacy code class which launches this form.
Doubt/Question:
Is it valid to throw exception as shown above? (Please read my justification below).
Justification:
The OnShown event does check for null on event handler object. This is because, to make this form usable, the event has to be subscribed by the subscriber (usage code), then only it shall work. If not, then the form just displays and does noting at all and usage code may not know why it is happenings so. The usage code may assume that subscribing to the event is option just like button click events per say.
Hope my post is clear and understandable.
Thanks & Happy Coding,
Zen :)
Do you mean that you need to throw an exception to the caller of the form? Is it called using showDialog or Show?
BTW, I dont prefer to generate an exception from an event. Rather it would be rather nice to keep it such that it returns from the place with some status set on the Form class.
for instance, I would prefer using
IsEventSubscribed = false
this.Close()
rather than EventNotSubscribedException
BTW, One problem I can see in the code, when the bgWorker_DoWork is called, you should check DoSomeAction to null, because otherwise it might cause NullReferenceException.
Preferably,
Start the run the RunWorkerAsync from Form_shown
Check Delegate to null in DoWork, if it is null, do not call DoSomeAction otherwise call it.
On RunWorkerCompleted of the BackgroundWorker, close the form.
Let me know if you need anything more.
I would suggest making the consuming code construct the BackgroundWorker and pass it to the form's constructor. You can do a null test in the constructor and side-step this whole issue. Alternatively, take the delegate as a constructor argument instead. I mean, how likely is it that the consuming code will need to change the worker delegate mid-operation?
Another approach is to have the dialog monitor a task, instead of having a dialog control a task (as you have here). For example, you could have an interface like this:
public interface IMonitorableTask {
void Start();
event EventHandler<TData> TaskProgress;
}
Where TData is a type that provides any information you might need to update the dialog (such as percent completed).
The downside to this is that each task needs to be a type of its own. This can lead to very ugly, cluttered code. You could mitigate that issue somewhat by creating a helper class, something like:
public class DelegateTask : IMonitorableTask {
private Action<Action<TData>> taskDelegate;
public event EventHandler<TData> TaskProgress;
public DelegateTask(Action<Action<TData>> taskDelegate) {
if (taskDelegate == null)
throw new ArgumentNullException("taskDelegate");
this.taskDelegate = taskDelegate;
}
protected void FireTaskProgress(TData data) {
var handler = TaskProgress;
if (handler != null)
handler(this, data);
}
public void Start() {
taskDelegate(FireTaskProgress);
}
}
Then your task methods become factories:
public IMonitorableTask CreateFooTask(object argument) {
return new DelegateTask(progress => {
DoStuffWith(argument);
progress(new TData(0.5));
DoMoreStuffWith(argument);
progress(new TData(1));
});
}
And now you can easily(*) support, say, a command-line interface. Just attach a different monitor object to the task's event.
(*) Depending on how clean your UI/logic separation already is, of course.
I have a C# program that sits in the system tray and pops up a notification balloon now and then. I'd like to provide 2-3 buttons on the notification balloon to allow the user to take various actions when the notification appears - rather than, for example, having to click the notification balloon to display a form containing buttons for each possible action.
I'm looking for suggestions on the best way to go about implementing this.
Edit: clarification, I want to provide buttons on the notification balloon so the user can take direct action on the notification rather than having to take action through some other part of the application (a form or menu for example).
There's no built-in method for this. I would suggest writing your own "balloon" and activating that instead of calling .ShowBalloon()
This is how I do it. It may not be the correct way of doing it. I do this way because .ShowBalloonTip(i) doesn't work as expected for me. It doesn't stay for i seconds and go off. So I do in another thread and forcefully dispose off.
private static NotifyIcon _notifyIcon;
//you can call this public function
internal static void ShowBalloonTip(Icon icon)
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync(icon);
}
private static void worker_DoWork(object sender, DoWorkEventArgs e)
{
Show(e);
Thread.Sleep(2000); //meaning it displays for 2 seconds
DisposeOff();
}
private static void Show(DoWorkEventArgs e)
{
_notifyIcon = new NotifyIcon();
_notifyIcon.Icon = (Icon)e.Argument;
_notifyIcon.BalloonTipTitle = "Environment file is opened";
_notifyIcon.BalloonTipText = "Press alt+tab to switch between environment files";
_notifyIcon.BalloonTipIcon = ToolTipIcon.Info;
_notifyIcon.Visible = true;
_notifyIcon.ShowBalloonTip(2000); //sadly doesnt work for me :(
}
private static void DisposeOff()
{
if (_notifyIcon == null)
return;
_notifyIcon.Dispose();
_notifyIcon = null;
}
The following code hides a form for 10 seconds. Nothing too crazy.
Each time the button is pressed, it creates a new timer object that doesn't stop and just keeps going. My intuition tells me that if you end up pressing this button many times, you'll have a bunch of timers that are running when only one is necessary (or is my assumption incorrect?). Also, if I do need to stop and dispose this timer, would I just send it as an argument in RevealForm or have the timer be a class level variable and just stop/reset it each time?
private void ButtonHide_Click(object sender, EventArgs e) {
this.Visible = false;
System.Timers.Timer t = new System.Timers.Timer();
t.Elapsed += new ElapsedEventHandler(RevealForm);
t.Interval = 10000;
t.AutoReset = false;
t.Start();
}
private void RevealForm(object source, ElapsedEventArgs e) {
InvokeReveal();
}
private void InvokeReveal() {
if (InvokeRequired) {
Invoke(new Action(InvokeReveal));
}
else {
this.Visible = true;
}
}
Thanks much!
Create the timer in the class then call t.start() on each click.
No need to destroy/cleanup/etc. Just recycle the one you have.
Your assumption is correct - testing would have asserted such for you.
You could either:
A) Disable the timer after each execution (per-interval) and enable on click, or,
B) Stop and destroy the timer and create a new one with each click.
Either option will require a little refactoring of your existing code.
As for the second part of the question - how you stop the timer is preferential. in such a small application (if this is its function in entirety) then simply stopping the timer within the event handler (or related method) would just do the trick, though in order to access the Timer instance you would declare it at a higher level in scope (i.e not bound within the scope of the click event handler).
Generally, the first thing you do is stop the timer in your event handler.
If you just want one timer then make it a form level variable, start it in your ButtonHide_Click, then at the top of your RevealForm method, stop the timer.