Custom tray icon notification balloon - c#

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;
}

Related

Background Worker and Timer in System Tray App C#

This is an incredibly simple task tray app - using ApplicationContext and a few guides I found online.
The purpose of the app is to query a small REST API and show a message box to the user on a given result. I need to essentially have the API query in a background loop, running every 10 seconds or something similar. This is to report on data that I've made accessible via another service.
I've done some reading and it seems a BackgroundWorker and Timer is an appropriate option, but I'm lost on where to go next. How exactly can I achieve this? I initially tried adding a while(true) loop to the TaskTrayApplicationContext but it just created an infinite loop whereby you couldn't do anything else with the app.
namespace TaskTrayApplication
{
public class TaskTrayApplicationContext : ApplicationContext
{
NotifyIcon notifyIcon = new NotifyIcon();
Configuration configWindow = new Configuration();
public TaskTrayApplicationContext()
{
MenuItem configMenuItem = new MenuItem("Configuration", new EventHandler(ShowConfig));
MenuItem exitMenuItem = new MenuItem("Exit", new EventHandler(Exit));
notifyIcon.Icon = TaskTrayApplication.Properties.Resources.AppIcon;
notifyIcon.DoubleClick += new EventHandler(ShowMessage);
notifyIcon.ContextMenu = new ContextMenu(new MenuItem[] { configMenuItem, exitMenuItem });
notifyIcon.Visible = true;
}
void ShowMessage(object sender, EventArgs e)
{
// Only show the message if the settings say we can.
if (TaskTrayApplication.Properties.Settings.Default.ShowMessage)
MessageBox.Show("This is the Serenity TaskTray Agent.");
}
void ShowConfig(object sender, EventArgs e)
{
// If we are already showing the window meerly focus it.
if (configWindow.Visible)
configWindow.Focus();
else
configWindow.ShowDialog();
}
void Exit(object sender, EventArgs e)
{
// We must manually tidy up and remove the icon before we exit.
// Otherwise it will be left behind until the user mouses over.
notifyIcon.Visible = false;
Application.Exit();
}
}
}
And the Program.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace TaskTrayApplication
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// Instead of running a form, we run an ApplicationContext.
Application.Run(new TaskTrayApplicationContext());
}
}
}
Threading is hard, concurrency is hard. Background worker and System.Timers are both constructs that run in their own thread.
winforms won't allow for interaction between threads that own a control (read: that created a control) and threads that don't. This is a whole subject apart i wont get into now - theres good stuff to read out there why this is and how to go about it: https://visualstudiomagazine.com/articles/2010/11/18/multithreading-in-winforms.aspx
There are tools to help, one is the dispatchertimer:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer?view=netcore-3.1
This is a special timer that instead of its own thread, schedules tasks on the main thread. The main thread in a winforms application handles the drawing of controls, showing of the different windows etc. e.g. this 'owns' all controls.
A sample can be seen on msdn, i adopted it here to show you what you could do:
public class TaskTrayApplicationContext : ApplicationContext
{
...
DispatcherTimer dispatcherTimer;
public TaskTrayApplicationContext()
{
...
dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0,0,1);
dispatcherTimer.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
// Fetch your data via a rest api
var myData = MyDataFunction();
// check and show dialog if the data is not okay
if(myData.Result.Value = 'NOT_OKAY!')
ShowMessage(this, myData.Result); // or something.
}
...
Now since this does not utilize a second thread, this means the main ui thread could be blocked from drawing the windows, reacting to user input etc. because its busy doing work in the timer_tick function. This would for example happen if your rest call takes a long time.
This will make your application freeze and irresponsive. This could be a problem but most likely wont, so lets burn that bridge when we get to it.

(WPF) How to ping from a class in c#?

I'm creating a WPF tool to use on my HelpDesk team using XAML and C#, but I'd like to improve upon what I've done. I've got a textbox to enter a hostname, a button to start a ping, and a textbox that will keep pinging that hostname until I press the button again.
I'd like to keep the functionality the same, but I know there's a better way to write this code.
Currently, I have a class that pings the server and returns a response ONE single time. My main form calls this class, the logic is not written in the main form. When I tried to make this loop, it just kills the function after the first run through rather than looping. The way I got it to work was by using a timer to call the function/ping class every 500 MS or so, and stopping the timer by clicking the button again.
Here's my current way of doing this, t stands for "timer":
private void onTick(object sender, EventArgs e)
{
PingClass pingClass = new PingClass();
_richtext.AppendText(pingClass.PingHost(_textBox.Text));
_richtext.ScrollToEnd();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if ((string)_pingBtn.Content == "Ping")
{
_richtext.Visibility = Visibility.Visible;
_pingBtn.Content = "Stop Ping";
t.Interval = 500;
t.Tick += onTick;
t.Start();
}
else if ((string)_pingBtn.Content == "Stop Ping")
{
_richtext.Visibility = Visibility.Hidden;
_pingBtn.Content = "Ping";
t.Stop();
}
The class itself is pretty simple, it just returns a string of whatever the response from the host is. Unfortunately, it doesn't allow me to loop through inside the class because it stops at the first return. I'd like to gather an "average ping" and some other stats, but can't with the way it's currently set up. I don't want to have a timer, I want to be able to loop in the class itself until the button in the main form is pressed. Is this possible?

Event handler not fired when using Timer

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.

C# NofityIcon balloon tip doesn't always go away within specified time

I use a NotifyIcon in a rather simple fashion.
public class Popup
{
...
private static NotifyIcon ni;
static Popup()
{
ni = new NotifyIcon();
ni.Icon = SystemIcons.Information;
}
public Popup(string nexusKey)
{
...
}
public void make(string text)
{
try
{
...
}
catch
{
ni.Visible = true;
ni.ShowBalloonTip(1000, "Thats the title", text, ToolTipIcon.Info);
}
}
}
Problem is, it seems like the "stay alive" timer doesn't get started if I am focusing different windows than the one hosting the process that display the balloon. Any ideas on how to make sure the balloon goes away after 1 second no matter what ?
Part of the reason for this behaviour is that the timer used in ShowBalloonToolTip was designed to only run when the OS detects user input. Thus if you are just waiting for the balloon to disappear and not actually doing anything then it will never timeout.
I believe that the reasoning was that if you left your PC and came back an hour later then you wouldn't miss any notifications.
There is a way around it, and that is to run a separate timer that toggles the icon visibility.
For example:
private void ShowBalloonWindow(int timeout)
{
if (timeout <= 0)
return;
int timeoutCount = 0;
trayIcon.ShowBalloonTip(timeout);
while (timeoutCount < timeout)
{
Thread.Sleep(1);
timeoutCount++;
}
trayIcon.Visible = false;
trayIcon.Visible = true;
}
edit
Ah yes - I cobbled that together without thinking about how you were using it.
If you wish to run this asynchronously then I'd suggest that you place the timer within a worker thread that Invokes a method that toggles the trayIcon.Visible property on completion.

How to create a control and manage its events in one method?

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....
You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}
You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Categories