Statistics
| Branch: | Revision:

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
}