Radek Chalupa

Profesionální vývoj software na zakázku, konzultace a školení pro programátory.

Vývoj software

Profesionální vývoj software - kompletní aplikace, komponenty, knihovny a moduly.

Webové stránky na zakázku

Webové stránky na zakázku

Školení a konzultace

Nabídka školení a konzultací pro programátory - začátečníky i pokročilé.

Freeware

Několik ukázek z mých freewarových aplikací ke stažení.

Materiál pro vývojáře

Články, rady a tipy, názory a komentáře o programování a počítačích vůbec.

Kontaktní informace

Máte zájem o nabízené služby, potřebujete další informace, neváhejte mě kontaktovat.


Co je nového

Školení programování v PHP  (28.8.2017)  V nabídce školení nyní naleznete také možnost naučit se programovat webové aplikace v PHP

Nástroj Kontrolní součty ke stažení  (27.8.2017)  V rubrice freeware ke stažení jednoduchý nástroj na výpočet kontrolních součtů zadaného souboru.

Aktualizace Visual Studia 2017  (15.8.2017)  Byla uvolněna "větší" aktualizace Microsoft Visual Studia 2017 na verzi 15.3

.NET Core 2.0  (14.8.2017)  Byla vydána .NET Core 2.0


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.