1

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

}
pollaris
  • 1,281
  • 17
  • 20
  • There is no such thing as system killing unnecessary background apps in Windows. – n0rd Jun 02 '18 at 15:09
  • I'd guess your app is leaking handles. Open the task manager, add the "handles" column to it, find your process and check if the number grows over time. – n0rd Jun 02 '18 at 15:13
  • handles varies between 630 and 780. – pollaris Jun 02 '18 at 16:10
  • 1
    `Font`, `Brush`, `Bitmap` and `Graphics` all implement `IDisposable` and you don't properly dispose any of those. Also, you'd probably want to keep the instance of `Bitmap` around for some longer time as currently GC can kill it right after the `GetHicon` call, you should make it a class member. – n0rd Jun 02 '18 at 17:18
  • I made all local variables global. The number of handles is now constant, however the app still quits after 50 minutes. – pollaris Jun 02 '18 at 18:25
  • 1
    `Graphics` still have to be explicitly disposed. Check what else grows if not handles. Memory? – n0rd Jun 02 '18 at 23:14

2 Answers2

0

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
pollaris
  • 1,281
  • 17
  • 20
0

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

pollaris
  • 1,281
  • 17
  • 20