wpf c# tray icon app terminates after 30 minutes

500 Views Asked by At

The following app displays disk activity in a systray icon. It runs normally for about 30-40 minutes, and then terminates, leaving the icon on the desktop. It is as if it is being killed by the system as an unnecessary background task. Why is this happening, and how can I prevent it?

public partial class MainWindow : Window
{
    public System.Windows.Forms.NotifyIcon ni = new System.Windows.Forms.NotifyIcon();
    public MainWindow()
    {
        InitializeComponent();

        ni.Visible = true;
        ni.Text = "disktray"; // tooltip text show over tray icon
        CreateTextIcon("0");
        ni.DoubleClick +=
            delegate (object sender, EventArgs args)
            {
                //this.Show();
                //this.WindowState = WindowState.Normal;
                ni.Visible = false;
                ni.Dispose();
                System.Windows.Application.Current.Shutdown();
            };
    }
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        CreateTextIcon("0");
        DispatcherTimer timer = new DispatcherTimer()
        {
            Interval = TimeSpan.FromMilliseconds(1024)
        };
        timer.Tick += Timer_Tick;
        timer.Start();

        this.Hide();
    }
    private void Timer_Tick(object sender, EventArgs e)
    {
        Diskpercent();
        string iii = diskpercentvalue.ToString();
        CreateTextIcon(iii);
    }
    public PerformanceCounter myCounter =
       new PerformanceCounter("PhysicalDisk", "% Disk Time", "_Total");
    public int diskpercentvalue = 0;
    public void Diskpercent()
    {
        var d = Convert.ToInt32(myCounter.NextValue());
        if (d > 99) d = 99; // can go over 100%
        diskpercentvalue = d;
    }

    public System.Drawing.Font fontToUse = 
        new System.Drawing.Font("Microsoft Sans Serif", 16, System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel);
    public System.Drawing.Brush brushToUse = new SolidBrush(System.Drawing.Color.White);
    public Bitmap bitmapText = new Bitmap(16, 16);
    public IntPtr hIcon;
    public void CreateTextIcon(string str)
    {
        //System.Drawing.Font fontToUse = new System.Drawing.Font("Microsoft Sans Serif", 16, System.Drawing.FontStyle.Regular, GraphicsUnit.Pixel);
        //System.Drawing.Brush brushToUse = new SolidBrush(System.Drawing.Color.White);
        //Bitmap bitmapText = new Bitmap(16, 16);
        Graphics g = System.Drawing.Graphics.FromImage(bitmapText);
        //IntPtr hIcon;
        g.Clear(System.Drawing.Color.Transparent);
        g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixelGridFit;
        g.DrawString(str, fontToUse, brushToUse, -4, -2);
        hIcon = (bitmapText.GetHicon());
        ni.Icon = System.Drawing.Icon.FromHandle(hIcon);
    }

}
2

There are 2 best solutions below

0
On BEST ANSWER

This line is causing a memory leak:

hIcon = (bitmapText.GetHicon());

hIcon needs to be destroyed:

    hIcon = (bitmapText.GetHicon());
    ni.Icon = System.Drawing.Icon.FromHandle(hIcon);
    DestroyIcon(hIcon);

Add this code to the class to define DestroyIcon:

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool DestroyIcon(IntPtr hIcon);

see:

https://msdn.microsoft.com/en-us/library/system.drawing.icon.fromhandle(v=vs.110).aspx

1
On

Go to the App.xaml.cs and implement it like below. The trick is to never close the MainWindow, as a closed Window cannot be shown again. Instead cancel the closing and just hide it.

using System.ComponentModel;
using System.Windows;

namespace BackgroundApplication
{

    public partial class App : Application
    {
        private System.Windows.Forms.NotifyIcon _notifyIcon;
        private bool _isExit;

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
            MainWindow = new MainWindow();
            MainWindow.Closing += MainWindow_Closing;

            _notifyIcon = new System.Windows.Forms.NotifyIcon();
            _notifyIcon.DoubleClick += (s, args) => ShowMainWindow();
            _notifyIcon.Icon = BackgroundApplication.Properties.Resources.MyIcon;
            _notifyIcon.Visible = true;

            CreateContextMenu();
        }

        private void CreateContextMenu()
        {
            _notifyIcon.ContextMenuStrip =
              new System.Windows.Forms.ContextMenuStrip();
            _notifyIcon.ContextMenuStrip.Items.Add("MainWindow...").Click += (s, e) => ShowMainWindow();
            _notifyIcon.ContextMenuStrip.Items.Add("Exit").Click += (s, e) => ExitApplication();
        }

        private void ExitApplication()
        {
            _isExit = true;
            MainWindow.Close();
            _notifyIcon.Dispose();
            _notifyIcon = null;
        }

        private void ShowMainWindow()
        {
            if (MainWindow.IsVisible)
            {
                if (MainWindow.WindowState == WindowState.Minimized)
                {
                    MainWindow.WindowState = WindowState.Normal;
                }
                MainWindow.Activate();
            }
            else
            {
                MainWindow.Show();
            }
        }

        private void MainWindow_Closing(object sender, CancelEventArgs e)
        {
            if (!_isExit)
            {
                e.Cancel = true;
                MainWindow.Hide(); // A hidden window can be shown again, a closed one not
            }
        }
    }
}

Go in addition to the App.xaml and remove the Startup-Uri, so that when you start the application, only the NotifyIcon is added to the notification area

<Application x:Class="BackgroundApplication.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:BackgroundApplication">
  <Application.Resources>

    </Application.Resources>
</Application>

Taken from:

https://www.thomasclaudiushuber.com/2015/08/22/creating-a-background-application-with-wpf/

Note: If you want to acces the variable, you to create a public readonly property in App.xaml.cs:

public System.Windows.Forms.NotifyIcon NotifyIcon { get { return      _notifyIcon; } }

Then you can use it in your MainWindow like this:

((App)App.Current).NotifyIcon