root / trunk / NotifyIconWpf / Interop / WindowMessageSink.cs @ 7fcbf914
History | View | Annotate | Download (11.9 kB)
1 |
// hardcodet.net NotifyIcon for WPF |
---|---|
2 |
// Copyright (c) 2009 - 2013 Philipp Sumi |
3 |
// Contact and Information: http://www.hardcodet.net |
4 |
// |
5 |
// This library is free software; you can redistribute it and/or |
6 |
// modify it under the terms of the Code Project Open License (CPOL); |
7 |
// either version 1.0 of the License, or (at your option) any later |
8 |
// version. |
9 |
// |
10 |
// The above copyright notice and this permission notice shall be |
11 |
// included in all copies or substantial portions of the Software. |
12 |
// |
13 |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
14 |
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
15 |
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
16 |
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
17 |
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
18 |
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
20 |
// OTHER DEALINGS IN THE SOFTWARE. |
21 |
// |
22 |
// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE |
23 |
|
24 |
|
25 |
using System; |
26 |
using System.ComponentModel; |
27 |
using System.Diagnostics; |
28 |
using System.Runtime.InteropServices; |
29 |
|
30 |
namespace Hardcodet.Wpf.TaskbarNotification.Interop |
31 |
{ |
32 |
/// <summary> |
33 |
/// Receives messages from the taskbar icon through |
34 |
/// window messages of an underlying helper window. |
35 |
/// </summary> |
36 |
public class WindowMessageSink : IDisposable |
37 |
{ |
38 |
#region members |
39 |
|
40 |
/// <summary> |
41 |
/// The ID of messages that are received from the the |
42 |
/// taskbar icon. |
43 |
/// </summary> |
44 |
public const int CallbackMessageId = 0x400; |
45 |
|
46 |
/// <summary> |
47 |
/// The ID of the message that is being received if the |
48 |
/// taskbar is (re)started. |
49 |
/// </summary> |
50 |
private uint taskbarRestartMessageId; |
51 |
|
52 |
/// <summary> |
53 |
/// Used to track whether a mouse-up event is just |
54 |
/// the aftermath of a double-click and therefore needs |
55 |
/// to be suppressed. |
56 |
/// </summary> |
57 |
private bool isDoubleClick; |
58 |
|
59 |
/// <summary> |
60 |
/// A delegate that processes messages of the hidden |
61 |
/// native window that receives window messages. Storing |
62 |
/// this reference makes sure we don't loose our reference |
63 |
/// to the message window. |
64 |
/// </summary> |
65 |
private WindowProcedureHandler messageHandler; |
66 |
|
67 |
/// <summary> |
68 |
/// Window class ID. |
69 |
/// </summary> |
70 |
internal string WindowId { get; private set; } |
71 |
|
72 |
/// <summary> |
73 |
/// Handle for the message window. |
74 |
/// </summary> |
75 |
internal IntPtr MessageWindowHandle { get; private set; } |
76 |
|
77 |
/// <summary> |
78 |
/// The version of the underlying icon. Defines how |
79 |
/// incoming messages are interpreted. |
80 |
/// </summary> |
81 |
public NotifyIconVersion Version { get; set; } |
82 |
|
83 |
#endregion |
84 |
|
85 |
#region events |
86 |
|
87 |
/// <summary> |
88 |
/// The custom tooltip should be closed or hidden. |
89 |
/// </summary> |
90 |
public event Action<bool> ChangeToolTipStateRequest; |
91 |
|
92 |
/// <summary> |
93 |
/// Fired in case the user clicked or moved within |
94 |
/// the taskbar icon area. |
95 |
/// </summary> |
96 |
public event Action<MouseEvent> MouseEventReceived; |
97 |
|
98 |
/// <summary> |
99 |
/// Fired if a balloon ToolTip was either displayed |
100 |
/// or closed (indicated by the boolean flag). |
101 |
/// </summary> |
102 |
public event Action<bool> BalloonToolTipChanged; |
103 |
|
104 |
/// <summary> |
105 |
/// Fired if the taskbar was created or restarted. Requires the taskbar |
106 |
/// icon to be reset. |
107 |
/// </summary> |
108 |
public event Action TaskbarCreated; |
109 |
|
110 |
#endregion |
111 |
|
112 |
#region construction |
113 |
|
114 |
/// <summary> |
115 |
/// Creates a new message sink that receives message from |
116 |
/// a given taskbar icon. |
117 |
/// </summary> |
118 |
/// <param name="version"></param> |
119 |
public WindowMessageSink(NotifyIconVersion version) |
120 |
{ |
121 |
Version = version; |
122 |
CreateMessageWindow(); |
123 |
} |
124 |
|
125 |
|
126 |
private WindowMessageSink() |
127 |
{ |
128 |
} |
129 |
|
130 |
|
131 |
/// <summary> |
132 |
/// Creates a dummy instance that provides an empty |
133 |
/// pointer rather than a real window handler.<br/> |
134 |
/// Used at design time. |
135 |
/// </summary> |
136 |
/// <returns></returns> |
137 |
internal static WindowMessageSink CreateEmpty() |
138 |
{ |
139 |
return new WindowMessageSink |
140 |
{ |
141 |
MessageWindowHandle = IntPtr.Zero, |
142 |
Version = NotifyIconVersion.Vista |
143 |
}; |
144 |
} |
145 |
|
146 |
#endregion |
147 |
|
148 |
#region CreateMessageWindow |
149 |
|
150 |
/// <summary> |
151 |
/// Creates the helper message window that is used |
152 |
/// to receive messages from the taskbar icon. |
153 |
/// </summary> |
154 |
private void CreateMessageWindow() |
155 |
{ |
156 |
//generate a unique ID for the window |
157 |
WindowId = "WPFTaskbarIcon_" + DateTime.Now.Ticks; |
158 |
|
159 |
//register window message handler |
160 |
messageHandler = OnWindowMessageReceived; |
161 |
|
162 |
// Create a simple window class which is reference through |
163 |
//the messageHandler delegate |
164 |
WindowClass wc; |
165 |
|
166 |
wc.style = 0; |
167 |
wc.lpfnWndProc = messageHandler; |
168 |
wc.cbClsExtra = 0; |
169 |
wc.cbWndExtra = 0; |
170 |
wc.hInstance = IntPtr.Zero; |
171 |
wc.hIcon = IntPtr.Zero; |
172 |
wc.hCursor = IntPtr.Zero; |
173 |
wc.hbrBackground = IntPtr.Zero; |
174 |
wc.lpszMenuName = ""; |
175 |
wc.lpszClassName = WindowId; |
176 |
|
177 |
// Register the window class |
178 |
WinApi.RegisterClass(ref wc); |
179 |
|
180 |
// Get the message used to indicate the taskbar has been restarted |
181 |
// This is used to re-add icons when the taskbar restarts |
182 |
taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated"); |
183 |
|
184 |
// Create the message window |
185 |
MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, IntPtr.Zero, IntPtr.Zero, |
186 |
IntPtr.Zero, IntPtr.Zero); |
187 |
|
188 |
if (MessageWindowHandle == IntPtr.Zero) |
189 |
{ |
190 |
#if SILVERLIGHT |
191 |
throw new Exception("Message window handle was not a valid pointer."); |
192 |
#else |
193 |
throw new Win32Exception("Message window handle was not a valid pointer"); |
194 |
#endif |
195 |
} |
196 |
} |
197 |
|
198 |
#endregion |
199 |
|
200 |
#region Handle Window Messages |
201 |
|
202 |
/// <summary> |
203 |
/// Callback method that receives messages from the taskbar area. |
204 |
/// </summary> |
205 |
private IntPtr OnWindowMessageReceived(IntPtr hwnd, uint messageId, IntPtr wparam, IntPtr lparam) |
206 |
{ |
207 |
if (messageId == taskbarRestartMessageId) |
208 |
{ |
209 |
//recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown) |
210 |
TaskbarCreated(); |
211 |
} |
212 |
|
213 |
//forward message |
214 |
ProcessWindowMessage(messageId, wparam, lparam); |
215 |
|
216 |
// Pass the message to the default window procedure |
217 |
return WinApi.DefWindowProc(hwnd, messageId, wparam, lparam); |
218 |
} |
219 |
|
220 |
|
221 |
/// <summary> |
222 |
/// Processes incoming system messages. |
223 |
/// </summary> |
224 |
/// <param name="msg">Callback ID.</param> |
225 |
/// <param name="wParam">If the version is <see cref="NotifyIconVersion.Vista"/> |
226 |
/// or higher, this parameter can be used to resolve mouse coordinates. |
227 |
/// Currently not in use.</param> |
228 |
/// <param name="lParam">Provides information about the event.</param> |
229 |
private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam) |
230 |
{ |
231 |
if (msg != CallbackMessageId) return; |
232 |
|
233 |
switch (lParam.ToInt32()) |
234 |
{ |
235 |
case 0x200: |
236 |
MouseEventReceived(MouseEvent.MouseMove); |
237 |
break; |
238 |
|
239 |
case 0x201: |
240 |
MouseEventReceived(MouseEvent.IconLeftMouseDown); |
241 |
break; |
242 |
|
243 |
case 0x202: |
244 |
if (!isDoubleClick) |
245 |
{ |
246 |
MouseEventReceived(MouseEvent.IconLeftMouseUp); |
247 |
} |
248 |
isDoubleClick = false; |
249 |
break; |
250 |
|
251 |
case 0x203: |
252 |
isDoubleClick = true; |
253 |
MouseEventReceived(MouseEvent.IconDoubleClick); |
254 |
break; |
255 |
|
256 |
case 0x204: |
257 |
MouseEventReceived(MouseEvent.IconRightMouseDown); |
258 |
break; |
259 |
|
260 |
case 0x205: |
261 |
MouseEventReceived(MouseEvent.IconRightMouseUp); |
262 |
break; |
263 |
|
264 |
case 0x206: |
265 |
//double click with right mouse button - do not trigger event |
266 |
break; |
267 |
|
268 |
case 0x207: |
269 |
MouseEventReceived(MouseEvent.IconMiddleMouseDown); |
270 |
break; |
271 |
|
272 |
case 520: |
273 |
MouseEventReceived(MouseEvent.IconMiddleMouseUp); |
274 |
break; |
275 |
|
276 |
case 0x209: |
277 |
//double click with middle mouse button - do not trigger event |
278 |
break; |
279 |
|
280 |
case 0x402: |
281 |
BalloonToolTipChanged(true); |
282 |
break; |
283 |
|
284 |
case 0x403: |
285 |
case 0x404: |
286 |
BalloonToolTipChanged(false); |
287 |
break; |
288 |
|
289 |
case 0x405: |
290 |
MouseEventReceived(MouseEvent.BalloonToolTipClicked); |
291 |
break; |
292 |
|
293 |
case 0x406: |
294 |
ChangeToolTipStateRequest(true); |
295 |
break; |
296 |
|
297 |
case 0x407: |
298 |
ChangeToolTipStateRequest(false); |
299 |
break; |
300 |
|
301 |
default: |
302 |
Debug.WriteLine("Unhandled NotifyIcon message ID: " + lParam); |
303 |
break; |
304 |
} |
305 |
} |
306 |
|
307 |
#endregion |
308 |
|
309 |
#region Dispose |
310 |
|
311 |
/// <summary> |
312 |
/// Set to true as soon as <c>Dispose</c> has been invoked. |
313 |
/// </summary> |
314 |
public bool IsDisposed { get; private set; } |
315 |
|
316 |
|
317 |
/// <summary> |
318 |
/// Disposes the object. |
319 |
/// </summary> |
320 |
/// <remarks>This method is not virtual by design. Derived classes |
321 |
/// should override <see cref="Dispose(bool)"/>. |
322 |
/// </remarks> |
323 |
public void Dispose() |
324 |
{ |
325 |
Dispose(true); |
326 |
|
327 |
// This object will be cleaned up by the Dispose method. |
328 |
// Therefore, you should call GC.SupressFinalize to |
329 |
// take this object off the finalization queue |
330 |
// and prevent finalization code for this object |
331 |
// from executing a second time. |
332 |
GC.SuppressFinalize(this); |
333 |
} |
334 |
|
335 |
/// <summary> |
336 |
/// This destructor will run only if the <see cref="Dispose()"/> |
337 |
/// method does not get called. This gives this base class the |
338 |
/// opportunity to finalize. |
339 |
/// <para> |
340 |
/// Important: Do not provide destructors in types derived from |
341 |
/// this class. |
342 |
/// </para> |
343 |
/// </summary> |
344 |
~WindowMessageSink() |
345 |
{ |
346 |
Dispose(false); |
347 |
} |
348 |
|
349 |
|
350 |
/// <summary> |
351 |
/// Removes the windows hook that receives window |
352 |
/// messages and closes the underlying helper window. |
353 |
/// </summary> |
354 |
private void Dispose(bool disposing) |
355 |
{ |
356 |
//don't do anything if the component is already disposed |
357 |
if (IsDisposed) return; |
358 |
IsDisposed = true; |
359 |
|
360 |
//always destroy the unmanaged handle (even if called from the GC) |
361 |
WinApi.DestroyWindow(MessageWindowHandle); |
362 |
messageHandler = null; |
363 |
} |
364 |
|
365 |
#endregion |
366 |
} |
367 |
} |