NotifyIcon ve WPF aplikaci
13.7.2017
Pokud vytváříte v Microsoft Visual Studiu aplikaci typu WPF (Windows Presentation Foundation), zjistíte ve vizuálním návrháři se nenabízí komponenta NotifyIcon, která je k disposici v aplikaci typu Windows Forms.
V tomto článku a ukázkové aplikaci si ukážeme jednoduchý způsob jak vytvořit NotifyIcon včetně fukcionality skrývání okna do této ikony.
Vytvoření takovéto ikony je standardní součástí Windows API a v .NET aplikaci využijeme proto volání příslušných funkcí pomocí DllImport
Po založení projektu (Visual C# - WPF aplikace pro klasický desktop) si nejprve připravíme potřebné deklarace hodnot, struktur a funkcí z Windows API, které budeme využívat. V ukázkovém projektu jsem tento soubor nazval WinApi.cs a zde je jeho kompletní výpis:
using System;
using System.Runtime.InteropServices;
namespace WinApi
{
internal static class UnsafeNativeMethods
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "NOTIFYICONDATA.szTip")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "NOTIFYICONDATA.szInfoTitle")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "NOTIFYICONDATA.szInfo")]
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
public static extern int Shell_NotifyIcon(int zprava, NativeMethods.NOTIFYICONDATA pNID);
}
internal static class NativeMethods
{
public const int WM_LBUTTONUP = 0x0202;
public const int WM_APP = 0x8000;
public const int wm_notify_ikona = WM_APP + 2001;
public const int
NIF_ICON = 0x00000002,
NIF_MESSAGE = 0x00000001,
NIF_TIP = 0x00000004,
NIF_INFO = 0x00000010,
NIF_STATE = 0x00000008,
NIF_SHOWTIP = 0x00000080;
public const int
NIM_ADD = 0x00000000,
NIM_MODIFY = 0x00000001,
NIM_DELETE = 0x00000002,
NIM_SETFOCUS = 0x00000003,
NIM_SETVERSION = 0x00000004;
public const int NOTIFYICON_VERSION_4 = 4;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class NOTIFYICONDATA
{
public int cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA));
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible")]
public IntPtr hWnd = IntPtr.Zero;
public int uID = 0;
public int uFlags = 0;
public int uCallbackMessage = 0;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible")]
public IntPtr hIcon = IntPtr.Zero;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szTip;
public int dwState = 0;
public int dwStateMask = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string szInfo;
public int uTimeoutOrVersion = 0;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string szInfoTitle;
public int dwInfoFlags = 0;
public Guid guidItem;
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2111:PointersShouldNotBeVisible")]
public IntPtr hBalloonicon = IntPtr.Zero;
}
}
}
Nyní trochu teorie: Ikona v oznamovací oblasti panelu nástrojů se vytváří, ruší a modifikuje funkcí Shell_NotifyIcon, v jejímž parametru (je to adresa struktury NOTIFYICONDATA) se nastaví požadovaná funkčnost. Jedním z nutných parametrů je handle okna kterému ikona patří. Toto získáme s využitím třídy System.Windows.Interop.HwndSource. Do třídy hlavního okna si přidáme následující proměnnou:
private System.Windows.Interop.HwndSource hwndSource;
V obsluze události Loaded ji pak přiřadíme k okno a přidáme si vlastní funkci která bude jakýmsi bypassem procedury okna, tzn. budeme v ním moci zachytávat (a nějak na ně reagovat) všechny zprávy Windows které přijdou tomuto oknu:
this.hwndSource = (HwndSource)PresentationSource.FromVisual(this); this.hwndSource.AddHook(WindowProc);
Dalším nutným parametrem při vytvoření NotifyIcon je číslo vlastní zprávy, přes kterou bude tato ikona komunikovat se svým oknem. Tuto zprávu máme deklarovanou ve výše zmíněném souboru WinApi.cs takto:
public const int WM_APP = 0x8000; public const int wm_notify_ikona = WM_APP + 1;
Hodnota WM_APP je číslo od kterého začínají možné uživatelské zprávy tak aby nekolidovaly se standardními zprávami Windows.
Nyní se již můžeme podívat na funkce které vytvoří a zruší ikonu v oznamovací oblasti:
private void VytvoritNotifyIcon()
{
WinApi.NativeMethods.NOTIFYICONDATA nid = new WinApi.NativeMethods.NOTIFYICONDATA();
nid.hIcon = ikona.Handle;
nid.hWnd = hwndSource.Handle;
nid.uID = 1;
nid.szTip = "Vývoj WMP";
nid.uFlags = WinApi.NativeMethods.NIF_ICON | WinApi.NativeMethods.NIF_MESSAGE | WinApi.NativeMethods.NIF_TIP;
nid.uCallbackMessage = WinApi.NativeMethods.wm_notify_ikona;
nid.uFlags |= WinApi.NativeMethods.NIF_SHOWTIP;
if (WinApi.UnsafeNativeMethods.Shell_NotifyIcon(WinApi.NativeMethods.NIM_ADD, nid) == 0)
throw new System.ComponentModel.Win32Exception();
nid.uTimeoutOrVersion = WinApi.NativeMethods.NOTIFYICON_VERSION_4;
if (WinApi.UnsafeNativeMethods.Shell_NotifyIcon(WinApi.NativeMethods.NIM_SETVERSION, nid) == 0)
throw new System.ComponentModel.Win32Exception();
}
private void ZrusitNotifyIcon()
{
WinApi.NativeMethods.NOTIFYICONDATA nid = new WinApi.NativeMethods.NOTIFYICONDATA();
nid.hWnd = hwndSource.Handle;
nid.uID = 1;
if (WinApi.UnsafeNativeMethods.Shell_NotifyIcon(WinApi.NativeMethods.NIM_DELETE, nid) == 0)
throw new System.ComponentModel.Win32Exception();
}
Funkci VytvoritNotifyIcon() zavoláme nejlépe na konci kódu obsluhy události Loaded a naopak funkci ZrusitNotifyIcon() při zrušení okna, tj. v obsluze události Closing.
Nyní zbývá podívat se jak vypadá zachycení a zpracování zprávy od ikony v proceduře okna:
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WinApi.NativeMethods.wm_notify_ikona:
int llp = lParam.ToInt32() & 0x0000FFFF;
switch (llp)
{
case WinApi.NativeMethods.WM_LBUTTONUP:
if (IsVisible)
Hide();
else
Show();
break;
}
break;
}
return IntPtr.Zero;
}
Jak je zřejmé z kódu, je realizováno pouze to základní zachycení kliknutí myši, přesně řečeno reakce na puštění levého tlačítka. V reakci na toto otestujeme zda je okno právě viditelné a podle toho ho skryjeme nebo naopak znovu zobrazíme.
Ukázkový projekt (Microsoft Visual Studio Community 2017) si můžete stáhnout zde.