4

I would like to know the location of my NotifyIcon in the system tray (before performing any click on it).

In this other question @Hans Passant made a comment saying NO, it's not possible, but I think that almost all the things which can be done internally by the OS also can be reproduced by the developer, if I'm not right then why the SO can get the NotifyIcon location to show popups on it?.

In the same question above there is a C# example illustrating how to locate the systemtray rectangle, I wonder whether maybe that's a start.

Can this task can be realized?

If yes, then how?

If not, why the OS can? In what way we are limited to be unable to reproduce the same thing?

Community
  • 1
  • 1
ElektroStudios
  • 19,105
  • 33
  • 200
  • 417
  • Of course the OS can do things that applications can't. You can only do things the OS lets you do. That's how an OS works. – Blorgbeard Oct 02 '14 at 02:51
  • 1
    Anyway, looks like those older questions may be out of date. See: http://msdn.microsoft.com/en-us/library/windows/desktop/dd378426(v=vs.85).aspx – Blorgbeard Oct 02 '14 at 02:54
  • Seems good info, thankyou!, but I see the same big problem, the structure used in that function needs a notifyicon handle and the icon position, I can't imagine how to determinate the current position, and more important how to get the handle of my NotifyIcon if a NotifyIcon doesns't expose a handle, I suppose that maybe FindWindowEx and EnumChildWindows functions will be involved to determinate which of all icons is my icon, but what is supposed that I need to search to associate this info?. thanks again! – ElektroStudios Oct 02 '14 at 03:05
  • 1
    The position on that function is an *out* parameter: that's the result, you don't pass it in. See [pinvoke.net](http://pinvoke.net/default.aspx/shell32/Shell_NotifyIconGetRect.html). Not sure about how to get the notifier ID; you could use reflection to access the [private fields of NotifyIcon](http://referencesource.microsoft.com/#System.Windows.Forms/ndp/fx/src/winforms/Managed/System/WinForms/NotifyIcon.cs), or you could throw out NotifyIcon and do all the icon stuff via pinvoke. – Blorgbeard Oct 02 '14 at 03:14

2 Answers2

7

You need these declarations:

    public const Int32 WM_MYMESSAGE = 0x8000; //WM_APP
    public const Int32 NOTIFYICON_VERSION_4 = 0x4;

    //messages
    public const Int32 WM_CONTEXTMENU = 0x7B;
    public const Int32 NIN_BALLOONHIDE = 0x403;
    public const Int32 NIN_BALLOONSHOW = 0x402;
    public const Int32 NIN_BALLOONTIMEOUT = 0x404;
    public const Int32 NIN_BALLOONUSERCLICK = 0x405;
    public const Int32 NIN_KEYSELECT = 0x403;
    public const Int32 NIN_SELECT = 0x400;
    public const Int32 NIN_POPUPOPEN = 0x406;
    public const Int32 NIN_POPUPCLOSE = 0x407;

    public const Int32 NIIF_USER = 0x4;
    public const Int32 NIIF_NONE = 0x0;
    public const Int32 NIIF_INFO = 0x1;
    public const Int32 NIIF_WARNING = 0x2;
    public const Int32 NIIF_ERROR = 0x3;
    public const Int32 NIIF_LARGE_ICON = 0x20;

    public enum NotifyFlags { 
        NIF_MESSAGE = 0x01, 
        NIF_ICON = 0x02,
        NIF_TIP = 0x04,
        NIF_INFO = 0x10,
        NIF_STATE = 0x08, 
        NIF_GUID = 0x20, 
        NIF_SHOWTIP = 0x80 
        }

    public enum NotifyCommand { NIM_ADD = 0x0, NIM_DELETE = 0x2, NIM_MODIFY = 0x1, NIM_SETVERSION = 0x4}
    [StructLayout(LayoutKind.Sequential)]
    public struct NOTIFYICONDATA
    {
        public Int32 cbSize;
        public IntPtr hWnd;
        public Int32 uID;
        public NotifyFlags uFlags;
        public Int32 uCallbackMessage;
        public IntPtr hIcon;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public String szTip;
        public Int32 dwState;
        public Int32 dwStateMask;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
        public String szInfo;
        public Int32 uVersion;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
        public String szInfoTitle;
        public Int32 dwInfoFlags;
        public Guid guidItem; //> IE 6
        public IntPtr hBalloonIcon;
    }

    [DllImport("shell32.dll")]
    public static extern System.Int32 Shell_NotifyIcon(NotifyCommand cmd, ref NOTIFYICONDATA data);


    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public Int32 left;
        public Int32 top;
        public Int32 right;
        public Int32 bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct NOTIFYICONIDENTIFIER
    {
        public Int32 cbSize;
        public IntPtr hWnd;
        public Int32 uID;
        public Guid guidItem;
    }

    //Works with Shell32.dll (version 6.1 or later)
    [DllImport("shell32.dll", SetLastError = true)]
    public static extern int Shell_NotifyIconGetRect([In]ref NOTIFYICONIDENTIFIER identifier, [Out]out RECT iconLocation);

Add the icon:

//you only need this guid to identify your icon
private Guid guid;

//call only once to set the icon and create the guid.
private void AddIcon()
{
    guid = Guid.NewGuid();

    NOTIFYICONDATA data = new NOTIFYICONDATA();

    data.cbSize = Marshal.SizeOf(data);
    data.hWnd = this.Handle;
    data.guidItem = guid;
    data.uCallbackMessage = WM_MYMESSAGE; //This is the message sent to our app
    data.hIcon = Properties.Resources.myIcon;
    data.szTip = "Your text";

    data.uFlags = NotifyFlags.NIF_ICON | NotifyFlags.NIF_GUID | NotifyFlags.NIF_MESSAGE | NotifyFlags.NIF_TIP | 
                  NotifyFlags.NIF_SHOWTIP;

    Shell_NotifyIcon(NotifyCommand.NIM_ADD, ref data);

    data.uVersion = NOTIFYICON_VERSION_4;
    Shell_NotifyIcon(NotifyCommand.NIM_SETVERSION, ref data);
}

Get position of icon in screen coordinates:

private void GetRectIcon()
{
    RECT rect = new RECT();
    NOTIFYICONIDENTIFIER notifyIcon = new NOTIFYICONIDENTIFIER();

    notifyIcon.cbSize = Marshal.SizeOf(notifyIcon);
    //only guid is needed
    notifyIcon.guidItem = guid;

    int hresult = Shell_NotifyIconGetRect(ref notifyIcon, out rect);

    //rect now has the position and size of icon
}

To delete the notification icon:

private void DeleteIcon()
{
    NOTIFYICONDATA data = new NOTIFYICONDATA();
    data.cbSize =   Marshal.SizeOf(data);
    data.uFlags = NotifyFlags.NIF_GUID;
    data.guidItem = guid;

    Shell_NotifyIcon(NotifyCommand.NIM_DELETE, ref data);
}

To add a baloon

private void AddBalloon()
{
    NOTIFYICONDATA data;
    data = new NOTIFYICONDATA();

    data.cbSize = Marshal.SizeOf(data);
    data.guidItem = guid;

    //Set custom icon for balloon or NIIF_NONE for no icon. You can use all the other 
    //NIIF_... for system icons
    data.dwInfoFlags = NIIF_USER;
    data.hBalloonIcon = Properties.Resources.myNewIcon;
    //text in balloon
    data.szInfo = "My text in balloon";
    //balloon title
    data.szInfoTitle = "Balloon title";
    //set the flags to be modified
    data.uFlags = NotifyFlags.NIF_INFO | NotifyFlags.NIF_SHOWTIP | NotifyFlags.NIF_GUID;

    Shell_NotifyIcon(NotifyCommand.NIM_MODIFY, ref data);
}

Catch messages

protected override void WndProc(ref Message m)
{

    if (m.Msg == WM_MYMESSAGE)
    {
        //(Int32)m.LParam & 0x0000FFFF get the low 2 bytes of LParam, we dont need the high ones. 
        //(Int32)m.WParam & 0x0000FFFF is the X coordinate and 
        //((Int32)m.WParam & 0xFFFF0000) >> 16 the Y
        switch ((Int32)m.LParam & 0x0000FFFF) 
            {
            case NIN_BALLOONHIDE:

                break;
            case NIN_BALLOONSHOW:

                break;
            case NIN_BALLOONTIMEOUT:

                break;
            case NIN_BALLOONUSERCLICK:
                //user clicked on balloon

                break;
            case NIN_SELECT:
                //user left click on icon

                break;
            case WM_CONTEXTMENU:
                //user right click on icon

                break;

            //get what mouse messages you want
            //case WM_LBUTTONDOWN:
            //....

            default:

                break;
        }
    }

    base.WndProc(ref m);
}

The power of unmanaged code

fusi
  • 330
  • 2
  • 14
  • First of all Thankyou, but I've asked for: 'how to get the location/rect of my application's NotifyIcon', not 'how to add a custom icon and get the location', is too different approach because using your suggested solution I can't add/use a NotifyIcon in my application then I can't subscribe to a NotifyIcon mouse events to know when I need to show a contextmenu, or to the ballontip events. If I'm wrong please recrify me and let me know ho to use it. Thankyou anyways! – ElektroStudios Oct 05 '14 at 07:08
  • 1
    @ElektroStudios It is the same thing. This way you do it manualy instead of the "easy" way using the designer. *...I can't subscribe to a NotifyIcon mouse events* this example is a part only that gets the rectangle. Of course you can get the mouse event and everything you want. I will edit the anwser – γηράσκω δ' αεί πολλά διδασκόμε Oct 05 '14 at 16:32
  • I think the value for NIN_POPUPCLOSE is incorrect - in the windows headers it is defined as: `#define NIN_POPUPCLOSE (WM_USER + 7)` – fusi Jun 04 '16 at 14:34
7

With the help of γηράσκω δ' αεί πολλά διδ's answer I was able to find a solution that let's you use the standard NotifyIcon control to determine the location of the icon. It requires the use of Reflection because it must access private fields and is therefore dependend on the current implementation of the NotifyIcon class. I tested it with .NET Framework 2.0 and 4.0.

using System;
using System.Reflection;
using System.Windows.Forms;
using System.Runtime.InteropServices;

sealed class NotifyIconHelper
{

    public static Rectangle GetIconRect(NotifyIcon icon)
    {
        RECT rect = new RECT();
        NOTIFYICONIDENTIFIER notifyIcon = new NOTIFYICONIDENTIFIER();

        notifyIcon.cbSize = Marshal.SizeOf(notifyIcon);
        //use hWnd and id of NotifyIcon instead of guid is needed
        notifyIcon.hWnd = GetHandle(icon);
        notifyIcon.uID = GetId(icon);

        int hresult = Shell_NotifyIconGetRect(ref notifyIcon, out rect);
        //rect now has the position and size of icon

        return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public Int32 left;
        public Int32 top;
        public Int32 right;
        public Int32 bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct NOTIFYICONIDENTIFIER
    {
        public Int32 cbSize;
        public IntPtr hWnd;
        public Int32 uID;
        public Guid guidItem;
    }

    [DllImport("shell32.dll", SetLastError = true)]
    private static extern int Shell_NotifyIconGetRect([In]ref NOTIFYICONIDENTIFIER identifier, [Out]out RECT iconLocation);

    private static FieldInfo windowField = typeof(NotifyIcon).GetField("window", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance);
    private static IntPtr GetHandle(NotifyIcon icon)
    {
        if (windowField == null) throw new InvalidOperationException("[Useful error message]");
        NativeWindow window = windowField.GetValue(icon) as NativeWindow;

        if (window == null) throw new InvalidOperationException("[Useful error message]");  // should not happen?
        return window.Handle;
    }

    private static FieldInfo idField = typeof(NotifyIcon).GetField("id", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance);
    private static int GetId(NotifyIcon icon)
    {
        if (idField == null) throw new InvalidOperationException("[Useful error message]");
        return (int)idField.GetValue(icon);
    }

}
Karsten
  • 1,814
  • 2
  • 17
  • 32