Files
wpf-notifyicon/Source/NotifyIconWpf/Interop/WindowMessageSink.cs
Philipp Sumi 354ba1ca43 NotifyWPF
---------
CHG   Lot of plumbing, some fixes
CHG   Work on sample project.
CHG   Popups and ContextMenu now store coordinates - otherwise delayed opending may happen elsewhere.

git-svn-id: https://svn.evolvesoftware.ch/repos/evolve.net/WPF/NotifyIcon@55 9f600761-6f11-4665-b6dc-0185e9171623
2009-03-31 22:20:07 +00:00

359 lines
9.9 KiB
C#

using System;
using System.ComponentModel;
using System.Diagnostics;
namespace Hardcodet.Wpf.TaskbarNotification.Interop
{
/// <summary>
/// Receives messages from the taskbar icon through
/// window messages of an underlying helper window.
/// </summary>
public class WindowMessageSink : IDisposable
{
#region members
/// <summary>
/// The ID of messages that are received from the the
/// taskbar icon.
/// </summary>
public const int CallbackMessageId = 0x400;
/// <summary>
/// The ID of the message that is being received if the
/// taskbar is (re)started.
/// </summary>
private uint taskbarRestartMessageId;
/// <summary>
/// Used to track whether a mouse-up event is just
/// the aftermath of a double-click and therefore needs
/// to be suppressed.
/// </summary>
private bool isDoubleClick;
/// <summary>
/// A delegate that processes messages of the hidden
/// native window that receives window messages. Storing
/// this reference makes sure we don't loose our reference
/// to the message window.
/// </summary>
private WindowProcedureHandler messageHandler;
/// <summary>
/// Window class ID.
/// </summary>
internal string WindowId { get; private set; }
/// <summary>
/// Handle for the message window.
/// </summary
internal IntPtr MessageWindowHandle { get; private set; }
/// <summary>
/// The version of the underlying icon. Defines how
/// incoming messages are interpreted.
/// </summary>
public NotifyIconVersion Version { get; set; }
#endregion
#region events
/// <summary>
/// The custom tooltip should be closed or hidden.
/// </summary>
public event Action<bool> ChangeToolTipStateRequest;
/// <summary>
/// Fired in case the user clicked or moved within
/// the taskbar icon area.
/// </summary>
public event Action<MouseEvent> MouseEventReceived;
/// <summary>
/// Fired if a balloon ToolTip was either displayed
/// or closed (indicated by the boolean flag).
/// </summary>
public event Action<bool> BallonToolTipChanged;
/// <summary>
/// Fired if the taskbar was created or restarted. Requires the taskbar
/// icon to be reset.
/// </summary>
public event Action TaskbarCreated;
#endregion
#region construction
/// <summary>
/// Creates a new message sink that receives message from
/// a given taskbar icon.
/// </summary>
/// <param name="version"></param>
public WindowMessageSink(NotifyIconVersion version)
{
Version = version;
CreateMessageWindow();
}
private WindowMessageSink()
{
}
/// <summary>
/// Creates a dummy instance that provides an empty
/// pointer rather than a real window handler.<br/>
/// Used at design time.
/// </summary>
/// <returns></returns>
internal static WindowMessageSink CreateEmpty()
{
return new WindowMessageSink
{
MessageWindowHandle = IntPtr.Zero,
Version = NotifyIconVersion.Vista
};
}
#endregion
#region CreateMessageWindow
/// <summary>
/// Creates the helper message window that is used
/// to receive messages from the taskbar icon.
/// </summary>
private void CreateMessageWindow()
{
//generate a unique ID for the window
WindowId = "WPFTaskbarIcon_" + DateTime.Now.Ticks;
//register window message handler
messageHandler = OnWindowMessageReceived;
// Create a simple window class which is reference through
//the messageHandler delegate
WindowClass wc;
wc.style = 0;
wc.lpfnWndProc = messageHandler;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = IntPtr.Zero;
wc.hIcon = IntPtr.Zero;
wc.hCursor = IntPtr.Zero;
wc.hbrBackground = IntPtr.Zero;
wc.lpszMenuName = "";
wc.lpszClassName = WindowId;
// Register the window class
WinApi.RegisterClass(ref wc);
// Get the message used to indicate the taskbar has been restarted
// This is used to re-add icons when the taskbar restarts
taskbarRestartMessageId = WinApi.RegisterWindowMessage("TaskbarCreated");
// Create the message window
MessageWindowHandle = WinApi.CreateWindowEx(0, WindowId, "", 0, 0, 0, 1, 1, 0, 0, 0, 0);
if (MessageWindowHandle == IntPtr.Zero)
{
throw new Win32Exception();
}
}
#endregion
#region Handle Window Messages
/// <summary>
/// Callback method that receives messages from the taskbar area.
/// </summary>
private long OnWindowMessageReceived(IntPtr hwnd, uint messageId, uint wparam, uint lparam)
{
if (messageId == taskbarRestartMessageId)
{
//recreate the icon if the taskbar was restarted (e.g. due to Win Explorer shutdown)
TaskbarCreated();
}
//forward message
ProcessWindowMessage(messageId, wparam, lparam);
// Pass the message to the default window procedure
return WinApi.DefWindowProc(hwnd, messageId, wparam, lparam);
}
/// <summary>
/// Processes incoming system messages.
/// </summary>
/// <param name="msg">Callback ID.</param>
/// <param name="wParam">If the version is <see cref="NotifyIconVersion.Vista"/>
/// or higher, this parameter can be used to resolve mouse coordinates.
/// Currently not in use.</param>
/// <param name="lParam">Provides information about the event.</param>
private void ProcessWindowMessage(uint msg, uint wParam, uint lParam)
{
if (msg != CallbackMessageId) return;
switch (lParam)
{
case 0x200:
// Debug.WriteLine("MOVE");
MouseEventReceived(MouseEvent.MouseMove);
break;
case 0x201:
Debug.WriteLine("left down 1");
MouseEventReceived(MouseEvent.IconLeftMouseDown);
break;
case 0x202:
Debug.WriteLine("left up");
if (!isDoubleClick)
{
MouseEventReceived(MouseEvent.IconLeftMouseUp);
}
isDoubleClick = false;
break;
case 0x203:
Debug.WriteLine("left click 2");
isDoubleClick = true;
MouseEventReceived(MouseEvent.IconDoubleClick);
break;
case 0x204:
Debug.WriteLine("right click 1");
MouseEventReceived(MouseEvent.IconRightMouseDown);
break;
case 0x205:
Console.Out.WriteLine("right mouse up");
MouseEventReceived(MouseEvent.IconRightMouseUp);
break;
case 0x206:
//double click with right mouse button - do not trigger event
Debug.WriteLine("right click 2");
break;
case 0x207:
Debug.WriteLine("middle click 1");
MouseEventReceived(MouseEvent.IconMiddleMouseDown);
break;
case 520:
Debug.WriteLine("mouse up middle");
MouseEventReceived(MouseEvent.IconMiddleMouseUp);
break;
case 0x209:
//double click with middle mouse button - do not trigger event
Debug.WriteLine("middle click 2");
break;
case 0x402:
Debug.WriteLine("balloon shown");
BallonToolTipChanged(true);
break;
case 0x403:
case 0x404:
Debug.WriteLine("balloon close");
BallonToolTipChanged(false);
break;
case 0x405:
Debug.WriteLine("balloon clicked");
MouseEventReceived(MouseEvent.BalloonToolTipClicked);
break;
case 0x406:
Debug.WriteLine("show custom tooltip");
ChangeToolTipStateRequest(true);
break;
case 0x407:
Debug.WriteLine("close custom tooltip");
ChangeToolTipStateRequest(false);
break;
default:
Debug.WriteLine("Unhandled message ID: " + lParam);
break;
}
}
#endregion
#region Dispose
/// <summary>
/// Set to true as soon as <see cref="Dispose"/>
/// has been invoked.
/// </summary>
public bool IsDisposed { get; private set; }
/// <summary>
/// Disposes the object.
/// </summary>
/// <remarks>This method is not virtual by design. Derived classes
/// should override <see cref="Dispose(bool)"/>.
/// </remarks>
public void Dispose()
{
Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SupressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary>
/// This destructor will run only if the <see cref="Dispose()"/>
/// method does not get called. This gives this base class the
/// opportunity to finalize.
/// <para>
/// Important: Do not provide destructors in types derived from
/// this class.
/// </para>
/// </summary>
~WindowMessageSink()
{
Dispose(false);
}
/// <summary>
/// Removes the windows hook that receives window
/// messages and closes the underlying helper window.
/// </summary>
private void Dispose(bool disposing)
{
//don't do anything if the component is already disposed
if (IsDisposed || !disposing) return;
IsDisposed = true;
WinApi.DestroyWindow(MessageWindowHandle);
messageHandler = null;
}
#endregion
}
}