I want to show a balloon popup without adding a dependency on Windows.Forms. So I decided to use Shell_NotifyIcon
from Shell32
. Here is a code that I wrote (translated to C# from F#), which should create a notification icon and then show a balloon.
struct NotifyIconData {
public System.Int32 cbSize;
public System.IntPtr hWnd; // HWND
public System.Int32 uID; // UINT
public System.Int32 uFlags; // UINT
public System.Int32 uCallbackMessage; // UINT
public System.IntPtr hIcon; // HICON
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]
public System.String szTip; // char[128]
public System.Int32 dwState; // DWORD
public System.Int32 dwStateMask; // DWORD
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)]
public System.String szInfo; // char[256]
public System.Int32 uTimeoutOrVersion; // UINT
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)]
public System.String szInfoTitle; // char[64]
public System.Int32 dwInfoFlags; // DWORD
public Guid guidItem;
public IntPtr hBalloonIcon; //HIcon
}
[DllImport("shell32.dll", SetLastError = true)]
extern bool Shell_NotifyIcon(uint dwMessage, ref NotifyIconData pnid);
[DllImport("kernel32.dll")]
extern IntPtr GetConsoleWindow();
public NotifyIconData CreateNotify (IntPtr hwnd)
{
var data = new NotifyIconData();
data.cbSize = sizeof(NotifyIconData);
data.hWnd = hwnd;
data.uID = 0;
data.uFlags = 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000020 | 0x00000080; // NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_GUID | NIF_SHOWTIP;
data.szTip = "Tooltip";
data.dwState = 0;
data.dwStateMask = 0x00000001 ||| 0x00000002; // NIS_SHAREDICON | NIS_HIDDEN
data.guidItem = Guid.NewGuid ();
data.hIcon = PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512));
var result = Shell_NotifyIcon(0x00000000u, &(data));
data.uVersion = 4;
var versionResult = Shell_NotifyIcon(0x00000004u, &(data));
return data;
}
public bool CreateBalloon (string message, NotifyIconData data)
{
data.uFlags = 0x00000010 | 0x00000020; // NIF_INFO | NIF_GUID
data.dwInfoFlags = 0x00000000; // NO ICON
data.szInfo = message;
data.szInfoTitle = "Balloon title";
data.hBalloonIcon = PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512));
var result = Shell_NotifyIcon(0x000000001u, &(data));
return result
}
public bool ShowBalloon (string message)
{
let hwnd = GetConsoleWindow();
var notify = CreateNotify (hwnd);
return CreateBalloon (message, notify);
}
I can observe in logs, that everything seems to be fine, no errors could be seen.
2020-10-13T15:34:23.880+00 724 INFO create notify error 0, true
2020-10-13T15:34:23.889+00 724 INFO set version error 0, true
2020-10-13T15:34:23.889+00 724 INFO baloon error 0, true
But the balloon doesn't appear on a screen. I'm wondering where the error could be located (maybe I'm missing calling some function or the icon should be set (right now I ignore it)?). I also tried to:
SetThreadDesktop(OpenInputDesktop(0u, true, GENERIC_ALL)) from user32
before calling "show balloon".But it still doesn't work.
Here is the original code wrote in F#:
type NotifyIconData =
struct
val mutable cbSize: System.Int32 // DWORD
val mutable hWnd: System.IntPtr // HWND
val mutable uID: System.Int32 // UINT
val mutable uFlags: System.Int32 // UINT
val mutable uCallbackMessage: System.Int32 // UINT
val mutable hIcon: System.IntPtr // HICON
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)>]
val mutable szTip: System.String // char[128]
val mutable dwState: System.Int32 // DWORD
val mutable dwStateMask: System.Int32 // DWORD
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)>]
val mutable szInfo: System.String // char[256]
val mutable uTimeoutOrVersion: System.Int32 // UINT
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)>]
val mutable szInfoTitle: System.String // char[64]
val mutable dwInfoFlags: System.Int32 // DWORD
val mutable guidItem: Guid
val mutable hBalloonIcon: IntPtr //HIcon
end
[<DllImport("shell32.dll", SetLastError = true)>]
extern bool Shell_NotifyIcon(uint dwMessage, NotifyIconData& pnid)
[<DllImport("kernel32.dll")>]
extern IntPtr GetConsoleWindow();
let createNotify hwnd =
let mutable data = NotifyIconData()
data.cbSize <- sizeof<NotifyIconData>
data.hWnd <- hwnd
data.uID <- 0
data.uFlags <- 0x00000001 ||| 0x00000002 ||| 0x00000004 ||| 0x00000008 ||| 0x00000020 ||| 0x00000080
data.szTip <- "Tooltip"
data.dwState <- 0
data.dwStateMask <- 0x00000001 ||| 0x00000002
data.guidItem <- Guid.NewGuid ()
data.hIcon <- PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512))
let result = Shell_NotifyIcon(uint NotifyCommand.Add, &(data))
data.uTimeoutOrVersion <- 4
let versionResult = Shell_NotifyIcon(uint NotifyCommand.SetVersion, &(data))
data
let createBalloon message (d: NotifyIconData) =
let mutable data = d
data.uFlags <- 0x00000010 ||| 0x00000020
data.dwInfoFlags <- 4
data.hBalloonIcon <- PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512))
data.szInfo <- message
data.szInfoTitle <- "Balloon title"
data.uTimeoutOrVersion <- 50000
let result = Shell_NotifyIcon(uint NotifyCommand.Modify, &(data))
result
let showBallon hwnd message =
GetConsoleWindow ()
|> createNotify
|> createBalloon message
The sample you provide seems to work in C# console app:
public static NotifyIconData CreateNotify(IntPtr hwnd)
{
var data = new NotifyIconData();
data.cbSize = Marshal.SizeOf(typeof(NotifyIconData));
data.hWnd = hwnd;
data.uID = 0;
data.uFlags = 0x00000001 | 0x00000002 | 0x00000004 | 0x00000008 | 0x00000020 | 0x00000080; // NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_STATE | NIF_GUID | NIF_SHOWTIP;
data.szTip = "Tooltip";
data.dwState = 0;
data.dwStateMask = 0x00000001 | 0x00000002; // NIS_SHAREDICON | NIS_HIDDEN
data.guidItem = Guid.NewGuid();
var result = Shell_NotifyIcon(0x00000000u, ref data);
data.uTimeoutOrVersion = 4;
var versionResult = Shell_NotifyIcon(0x00000004u, ref data);
return data;
}
public static bool CreateBalloon(string message, NotifyIconData data)
{
data.uFlags = 0x00000010 | 0x00000020; // NIF_INFO | NIF_GUID
data.dwInfoFlags = 0x00000000; // NO ICON
data.szInfo = message;
data.szInfoTitle = "Balloon title";
var result = Shell_NotifyIcon(0x000000001u, ref data);
return result;
}
public static bool ShowBalloon(string message)
{
IntPtr hwnd = GetConsoleWindow();
var notify = CreateNotify(hwnd);
return CreateBalloon(message, notify);
}
static void Main(string[] args)
{
bool ret = ShowBalloon("test");
Console.WriteLine("Hello World!");
}
And there is also a simple sample work for me:
static void Main(string[] args)
{
//ShowBalloon("test");
var data = new NOTIFYICONDATA();
data.cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
data.uID = 0;
data.uFlags = 0x00000010; // NIF_INFO
data.dwInfoFlags = 0x00000000; // NO ICON
data.szInfo = "test";
data.szInfoTitle = "Balloon title";
bool result = Shell_NotifyIcon(0x00000001, ref data);
Console.WriteLine("Hello World!");
}
@Drake answer shows me where the error could be located so I added more robust logging to a working C# example and F# example (which is added at the end of the question I didn't at it at first since I thought that the code is 1:1 between C# and F#). The problem was with setting the cbSize
value. I used sizeof
to calculate the size of a NotifyIconData
structure. But as mentioned here, sizeof
would return the size of a System.Type
instead of NotifyIconData
. So I changed sizeof
to a Marshal.SizeOf(typedefof<NotifyIconData>)
and everything starts working as expected.
Here are the logs from a C# app (which as Drake mentioned is working just fine)
... when I create notify icon
cbSize: 528, hwnd: 983580, uid: 0, uflags: 175, sztip: Tooltip, dwState: 0, dwStateMask: 3, guidItem: 9367e9cd-1fc2-4312-83dd-22b6e03486b9
... when version is set
cbSize: 528, hwnd: 983580, uid: 0, uflags: 175, sztip: Tooltip, dwState: 0, dwStateMask: 3, guidItem: 9367e9cd-1fc2-4312-83dd-22b6e03486b9
... when we enter `createBalloon` function
cbSize: 528, hwnd: 983580, uid: 0, uflags: 175, sztip: Tooltip, dwState: 0, dwStateMask: 3, guidItem: 9367e9cd-1fc2-4312-83dd-22b6e03486b9
... after balloon creation
cbSize: 528, hwnd: 983580, uid: 0, uflags: 48, sztip: Tooltip, dwState: 0, dwStateMask: 3, guidItem: 9367e9cd-1fc2-4312-83dd-22b6e03486b9
Here are the logs from a F# version:
... when I create notify icon
cbSize: 96, hwnd: 591150n, uid: 0, uflags: 175, sztip: "Tooltip", dwState: 0, dwStateMask: 3, guidItem: 1ee87d18-ef8d-48ba-aee9-f197de6425a4
... when version is set
cbSize: 96, hwnd: 591150n, uid: 0, uflags: 175, sztip: "Tooltip", dwState: 0, dwStateMask: 3, guidItem: 1ee87d18-ef8d-48ba-aee9-f197de6425a4
... when we enter `createBalloon` function
cbSize: 96, hwnd: 591150n, uid: 0, uflags: 175, sztip: "Tooltip", dwState: 0, dwStateMask: 3, guidItem: 1ee87d18-ef8d-48ba-aee9-f197de6425a4
... after balloon creation
cbSize: 96, hwnd: 591150n, uid: 0, uflags: 48, sztip: "Tooltip", dwState: 0, dwStateMask: 3, guidItem: 1ee87d18-ef8d-48ba-aee9-f197de6425a4
And here is the fully working F# example:
type NotifyIconData =
struct
val mutable cbSize: System.Int32 // DWORD
val mutable hWnd: System.IntPtr // HWND
val mutable uID: System.Int32 // UINT
val mutable uFlags: System.Int32 // UINT
val mutable uCallbackMessage: System.Int32 // UINT
val mutable hIcon: System.IntPtr // HICON
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)>]
val mutable szTip: System.String // char[128]
val mutable dwState: System.Int32 // DWORD
val mutable dwStateMask: System.Int32 // DWORD
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=256)>]
val mutable szInfo: System.String // char[256]
val mutable uTimeoutOrVersion: System.Int32 // UINT
[<MarshalAs(UnmanagedType.ByValTStr, SizeConst=64)>]
val mutable szInfoTitle: System.String // char[64]
val mutable dwInfoFlags: System.Int32 // DWORD
val mutable guidItem: Guid
val mutable hBalloonIcon: IntPtr //HIcon
end
[<DllImport("shell32.dll", SetLastError = true)>]
extern bool Shell_NotifyIcon(uint dwMessage, NotifyIconData& pnid)
[<DllImport("kernel32.dll")>]
extern IntPtr GetConsoleWindow();
let createNotify hwnd =
let mutable data = NotifyIconData()
data.cbSize <- Marshal.SizeOf(typedefof<NotifyIconData>)
data.hWnd <- hwnd
data.uID <- 0
data.uFlags <- 0x00000001 ||| 0x00000002 ||| 0x00000004 ||| 0x00000008 ||| 0x00000020 ||| 0x00000080
data.szTip <- "Tooltip"
data.dwState <- 0
data.dwStateMask <- 0x00000001 ||| 0x00000002
data.guidItem <- Guid.NewGuid ()
data.hIcon <- PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512))
let result = Shell_NotifyIcon(uint NotifyCommand.Add, &(data))
data.uTimeoutOrVersion <- 4
let versionResult = Shell_NotifyIcon(uint NotifyCommand.SetVersion, &(data))
data
let createBalloon message (d: NotifyIconData) =
let mutable data = d
data.uFlags <- 0x00000010 ||| 0x00000020
data.dwInfoFlags <- 4
data.hBalloonIcon <- PInvoke.User32.LoadIcon(IntPtr.Zero, IntPtr(32512))
data.szInfo <- message
data.szInfoTitle <- "Balloon title"
data.uTimeoutOrVersion <- 50000
let result = Shell_NotifyIcon(uint NotifyCommand.Modify, &(data))
result
let showBallon hwnd message =
GetConsoleWindow ()
|> createNotify
|> createBalloon message
User contributions licensed under CC BY-SA 3.0