Radek Chalupa - vývoj software, konzultace a školení programování
Radek Chalupa

Co je nového zde i jinde v IT

Nová aplikace "Klávesnice"
(9.6.2017)  V sekci software ke stažení můžete vyzkoušet nový nástroj "Klávesnice" více zde.

Aktualizace Visual Studia 2017
(12.5.2017)  Vyšla aktualizace Visal Studia 2017 na verzi 15.2 více zde.

Kamera 4.8 nyní zcela zdarma
(4.5.2017)  Aplikace Kamera 4.8 je nyní zdarma k volnému použití. více zde.

Nová verze SQLite
(1.3.2017)  V únoru vyšla nová verze databáze SQLite 3.17.0 více zde.

Nové Visual Studio 2017
(10.2.2017)  Finální verze nového Microsoft Visual Studia 2017 vyjde 7. března, tentokrát doprovázeno oslavou 20. výročí uvedení první veze Visual Studia. více zde.


Poslední články

NotifyIcon ve WPF aplikaci

Databáze SQLite v C/C++ - úvod

Buffering v GDI

Windows desktop aplikace s využitím ATL

WinForms a C++ - překreslování bez problikávání pozadí

WinForms a C++ - resources


NotifyIcon ve WPF aplikaci

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.


Copyright © 2017 Radek Chalupa