improved notifyicon

added mouseenter and mouseleave events
This commit is contained in:
NaBian committed 2019-04-20 23:36:44 +08:00
1 parent 9aa6df87cb
commit 3aa0f09d39
6 files changed
+388 -16

No files matched your search

@@ -1,15 +1,31 @@
using System;
using System.Collections.Generic;
namespace HandyControlDemo.Tools
{
public class AssemblyHelper
internal class AssemblyHelper
{
private static readonly string NameSpaceStr = typeof(AssemblyHelper).Assembly.GetName().Name;
private static readonly Dictionary<string, object> CacheDic = new Dictionary<string, object>();
public static void Register(string name, object instance) => CacheDic[name] = instance;
public static object ResolveByKey(string key)
{
if (CacheDic.TryGetValue(key, out var value))
{
return value;
}
return null;
}
public static object CreateInternalInstance(string className)
{
try
{
var type = Type.GetType($"{NameSpaceStr}.{className}");
return type == null ? null : Activator.CreateInstance(type);
}
@@ -1,4 +1,6 @@
using System.Windows;
using HandyControlDemo.Tools;
using HandyControlDemo.ViewModel;
namespace HandyControlDemo.UserControl
{
@@ -7,8 +9,13 @@ namespace HandyControlDemo.UserControl
public NotifyIconDemoCtl()
{
InitializeComponent();
AssemblyHelper.Register(nameof(NotifyIconDemoCtl), this);
Unloaded += NotifyIconDemoCtl_Unloaded;
}
private void NotifyIconDemoCtl_Unloaded(object sender, RoutedEventArgs e) => ViewModelLocator.Instance.NotifyIconDemo.Cleanup();
private void ButtonPush_OnClick(object sender, RoutedEventArgs e) => NotifyIconContextContent.CloseContextControl();
}
}
@@ -12,6 +12,10 @@ namespace HandyControlDemo.ViewModel
{
public class NotifyIconDemoViewModel : ViewModelBase
{
private bool _isCleanup;
private bool _reversed;
private bool _contextMenuIsShow;
public bool ContextMenuIsShow
@@ -25,6 +29,12 @@ namespace HandyControlDemo.ViewModel
Set(ref _contextMenuIsShow, value);
#endif
GlobalData.NotifyIconIsShow = ContextMenuIsShow || ContextContentIsShow;
if (!_isCleanup && !_reversed)
{
_reversed = true;
ContextContentIsShow = !value;
_reversed = false;
}
}
}
@@ -53,6 +63,12 @@ namespace HandyControlDemo.ViewModel
Set(ref _contextContentIsShow, value);
#endif
GlobalData.NotifyIconIsShow = ContextMenuIsShow || ContextContentIsShow;
if (!_isCleanup && !_reversed)
{
_reversed = true;
ContextMenuIsShow = !value;
_reversed = false;
}
}
}
@@ -70,5 +86,18 @@ namespace HandyControlDemo.ViewModel
public RelayCommand<object> MouseCmd => new Lazy<RelayCommand<object>>(() =>
new RelayCommand<object>(str=> Growl.Info(str.ToString()))).Value;
public override void Cleanup()
{
base.Cleanup();
_isCleanup = true;
ContextMenuIsShow = false;
ContextMenuIsBlink = false;
ContextContentIsShow = false;
ContextContentIsBlink = false;
GlobalData.NotifyIconIsShow = false;
_isCleanup = false;
}
}
}
@@ -82,7 +82,7 @@ namespace HandyControlDemo.ViewModel
#endregion
#region 命令
#region 命令
/// <summary>
/// 切换例子命令
@@ -109,7 +109,8 @@ namespace HandyControlDemo.ViewModel
_listBoxItemCurrent = item;
ContentTitle = item.Content;
Messenger.Default.Send(false, MessageToken.FullSwitch);
Messenger.Default.Send(AssemblyHelper.CreateInternalInstance($"UserControl.{tag}"), MessageToken.LoadShowContent);
var obj = AssemblyHelper.ResolveByKey(tag);
Messenger.Default.Send(obj ?? AssemblyHelper.CreateInternalInstance($"UserControl.{tag}"), MessageToken.LoadShowContent);
}
else
{
@@ -1,4 +1,6 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@@ -14,6 +16,8 @@ namespace HandyControl.Controls
{
public class NotifyIcon : FrameworkElement, IDisposable
{
private bool _isMouseOver;
private bool _added;
private readonly object _syncObj = new object();
@@ -44,7 +48,9 @@ namespace HandyControl.Controls
private bool _doubleClick;
private DispatcherTimer _dispatcherTimer;
private DispatcherTimer _dispatcherTimerBlink;
private DispatcherTimer _dispatcherTimerPos;
private bool _isTransparent;
@@ -87,6 +93,12 @@ namespace HandyControl.Controls
OnIconChanged();
UpdateIcon(true);
}
_dispatcherTimerPos = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(200)
};
_dispatcherTimerPos.Tick += DispatcherTimerPos_Tick;
};
if (Application.Current != null) Application.Current.Exit += (s, e) => Dispose();
@@ -137,9 +149,9 @@ namespace HandyControl.Controls
private static void OnBlinkIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (NotifyIcon)d;
if (ctl._dispatcherTimer != null)
if (ctl._dispatcherTimerBlink != null)
{
ctl._dispatcherTimer.Interval = (TimeSpan) e.NewValue;
ctl._dispatcherTimerBlink.Interval = (TimeSpan) e.NewValue;
}
}
@@ -158,30 +170,175 @@ namespace HandyControl.Controls
if (ctl.Visibility != Visibility.Visible) return;
if ((bool) e.NewValue)
{
if (ctl._dispatcherTimer == null)
if (ctl._dispatcherTimerBlink == null)
{
ctl._dispatcherTimer = new DispatcherTimer
ctl._dispatcherTimerBlink = new DispatcherTimer
{
Interval = ctl.BlinkInterval
};
ctl._dispatcherTimer.Tick += ctl.DispatcherTimer_Tick;
ctl._dispatcherTimerBlink.Tick += ctl.DispatcherTimerBlinkTick;
}
ctl._dispatcherTimer.Start();
ctl._dispatcherTimerBlink.Start();
}
else
{
ctl._dispatcherTimer?.Stop();
ctl._dispatcherTimer = null;
ctl._dispatcherTimerBlink?.Stop();
ctl._dispatcherTimerBlink = null;
ctl.UpdateIcon(true);
}
}
private void DispatcherTimer_Tick(object sender, EventArgs e)
private void DispatcherTimerBlinkTick(object sender, EventArgs e)
{
if (Visibility != Visibility.Visible || _iconCurrentHandle == IntPtr.Zero) return;
UpdateIcon(true, !_isTransparent);
}
private bool CheckMouseIsEnter()
{
var isTrue = FindNotifyIcon(out var rectNotify);
if (!isTrue) return false;
NativeMethods.GetCursorPos(out var point);
if (point.X >= rectNotify.Left && point.X <= rectNotify.Right &&
point.Y >= rectNotify.Top && point.Y <= rectNotify.Bottom)
{
return true;
}
return false;
}
private void DispatcherTimerPos_Tick(object sender, EventArgs e)
{
if (CheckMouseIsEnter())
{
if (!_isMouseOver)
{
_isMouseOver = true;
RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, Environment.TickCount)
{
RoutedEvent = MouseEnterEvent
});
_dispatcherTimerPos.Interval = TimeSpan.FromMilliseconds(500);
}
}
else
{
_dispatcherTimerPos.Stop();
_isMouseOver = false;
RaiseEvent(new MouseEventArgs(Mouse.PrimaryDevice, Environment.TickCount)
{
RoutedEvent = MouseLeaveEvent
});
}
}
//referenced from http://www.cnblogs.com/sczmzx/p/5158127.html
private IntPtr FindTrayToolbarWindow()
{
var hWnd = NativeMethods.FindWindow("Shell_TrayWnd", null);
if (hWnd != IntPtr.Zero)
{
hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "TrayNotifyWnd", null);
if (hWnd != IntPtr.Zero)
{
hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "SysPager", null);
if (hWnd != IntPtr.Zero)
{
hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null);
}
}
}
return hWnd;
}
//referenced from http://www.cnblogs.com/sczmzx/p/5158127.html
private IntPtr FindTrayToolbarOverFlowWindow()
{
var hWnd = NativeMethods.FindWindow("NotifyIconOverflowWindow", null);
if (hWnd != IntPtr.Zero)
{
hWnd = NativeMethods.FindWindowEx(hWnd, IntPtr.Zero, "ToolbarWindow32", null);
}
return hWnd;
}
private bool FindNotifyIcon(out NativeMethods.RECT rect)
{
var rectNotify = new NativeMethods.RECT();
var hTrayWnd = FindTrayToolbarWindow();
var isTrue = FindNotifyIcon(hTrayWnd, ref rectNotify);
if (!isTrue)
{
hTrayWnd = FindTrayToolbarOverFlowWindow();
isTrue = FindNotifyIcon(hTrayWnd, ref rectNotify);
}
rect = rectNotify;
return isTrue;
}
//referenced from http://www.cnblogs.com/sczmzx/p/5158127.html
private bool FindNotifyIcon(IntPtr hTrayWnd, ref NativeMethods.RECT rectNotify)
{
NativeMethods.GetWindowRect(hTrayWnd, out var rectTray);
var count = (int)NativeMethods.SendMessage(hTrayWnd, NativeMethods.TB_BUTTONCOUNT, 0, IntPtr.Zero);
var isFind = false;
if (count > 0)
{
NativeMethods.GetWindowThreadProcessId(hTrayWnd, out var trayPid);
var hProcess = NativeMethods.OpenProcess(NativeMethods.ProcessAccess.VMOperation | NativeMethods.ProcessAccess.VMRead | NativeMethods.ProcessAccess.VMWrite, false, trayPid);
var address = NativeMethods.VirtualAllocEx(hProcess, IntPtr.Zero, 1024, NativeMethods.AllocationType.Commit, NativeMethods.MemoryProtection.ReadWrite);
var btnData = new NativeMethods.TBBUTTON();
var trayData = new NativeMethods.TRAYDATA();
var handel = Process.GetCurrentProcess().Id;
for (uint i = 0; i < count; i++)
{
NativeMethods.SendMessage(hTrayWnd, NativeMethods.TB_GETBUTTON, i, address);
var isTrue = NativeMethods.ReadProcessMemory(hProcess, address, out btnData, Marshal.SizeOf(btnData), out _);
if (!isTrue) continue;
if (btnData.dwData == IntPtr.Zero)
{
btnData.dwData = btnData.iString;
}
NativeMethods.ReadProcessMemory(hProcess, btnData.dwData, out trayData, Marshal.SizeOf(trayData), out _);
NativeMethods.GetWindowThreadProcessId(trayData.hwnd, out var dwProcessId);
if (dwProcessId == (uint)handel)
{
var rect = new NativeMethods.RECT();
var lngRect = NativeMethods.VirtualAllocEx(hProcess, IntPtr.Zero, Marshal.SizeOf(typeof(Rect)), NativeMethods.AllocationType.Commit, NativeMethods.MemoryProtection.ReadWrite);
NativeMethods.SendMessage(hTrayWnd, NativeMethods.TB_GETITEMRECT, i, lngRect);
NativeMethods.ReadProcessMemory(hProcess, lngRect, out rect, Marshal.SizeOf(rect), out _);
NativeMethods.VirtualFreeEx(hProcess, lngRect, Marshal.SizeOf(rect), NativeMethods.FreeType.Decommit);
NativeMethods.VirtualFreeEx(hProcess, lngRect, 0, NativeMethods.FreeType.Release);
var left = rectTray.Left + rect.Left;
var top = rectTray.Top + rect.Top;
var botton = rectTray.Top + rect.Bottom;
var right = rectTray.Left + rect.Right;
rectNotify = new NativeMethods.RECT
{
Left = left,
Right = right,
Top = top,
Bottom = botton
};
isFind = true;
break;
}
}
NativeMethods.VirtualFreeEx(hProcess, address, 0x4096, NativeMethods.FreeType.Decommit);
NativeMethods.VirtualFreeEx(hProcess, address, 0, NativeMethods.FreeType.Release);
NativeMethods.CloseHandle(hProcess);
}
return isFind;
}
public bool IsBlink
{
get => (bool) GetValue(IsBlinkProperty);
@@ -289,6 +446,13 @@ namespace HandyControl.Controls
ShowContextMenu();
WmMouseUp(MouseButton.Right);
break;
case NativeMethods.WM_MOUSEMOVE:
if (!_dispatcherTimerPos.IsEnabled)
{
_dispatcherTimerPos.Interval = TimeSpan.FromMilliseconds(200);
_dispatcherTimerPos.Start();
}
break;
}
}
}
@@ -390,9 +554,9 @@ namespace HandyControl.Controls
if (_isDisposed) return;
if (disposing)
{
if (_dispatcherTimer != null && IsBlink)
if (_dispatcherTimerBlink != null && IsBlink)
{
_dispatcherTimer.Stop();
_dispatcherTimerBlink.Stop();
}
UpdateIcon(false);
}
@@ -41,10 +41,165 @@ namespace HandyControl.Tools.Interop
WM_MBUTTONDOWN = 0x0207,
WM_MBUTTONUP = 0x0208,
WM_MBUTTONDBLCLK = 0x0209,
WM_USER = 0x0400;
WM_USER = 0x0400,
TB_GETBUTTON = WM_USER + 23,
TB_BUTTONCOUNT = WM_USER + 24,
TB_GETITEMRECT = WM_USER + 29,
STANDARD_RIGHTS_REQUIRED = 0x000F0000,
SYNCHRONIZE = 0x00100000,
PROCESS_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0xFFF,
MEM_COMMIT = 0x1000,
MEM_RELEASE = 0x8000,
PAGE_READWRITE = 0x04,
TBSTATE_HIDDEN = 0x08;
[Flags]
public enum ProcessAccess
{
AllAccess = CreateThread | DuplicateHandle | QueryInformation | SetInformation | Terminate | VMOperation | VMRead | VMWrite | Synchronize,
CreateThread = 0x2,
DuplicateHandle = 0x40,
QueryInformation = 0x400,
SetInformation = 0x200,
Terminate = 0x1,
VMOperation = 0x8,
VMRead = 0x10,
VMWrite = 0x20,
Synchronize = 0x100000
}
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TBBUTTON
{
public int iBitmap;
public int idCommand;
public IntPtr fsStateStylePadding;
public IntPtr dwData;
public IntPtr iString;
}
[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
[StructLayout(LayoutKind.Sequential)]
public struct TRAYDATA
{
public IntPtr hwnd;
public uint uID;
public uint uCallbackMessage;
public uint bReserved0;
public uint bReserved1;
public IntPtr hIcon;
}
[Flags]
public enum FreeType
{
Decommit = 0x4000,
Release = 0x8000,
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
}
[DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
public static extern int RegisterWindowMessage(string msg);
[DllImport(ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern int MapWindowPoints(IntPtr hWndFrom, IntPtr hWndTo, ref RECT lpPoints, uint cPoints);
[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out TBBUTTON lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport(ExternDll.Kernel32, SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out RECT lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport(ExternDll.Kernel32, SetLastError = true)]
public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, out TRAYDATA lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
public static extern uint SendMessage(IntPtr hWnd, uint Msg, uint wParam, IntPtr lParam);
[DllImport(ExternDll.User32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr OpenProcess(ProcessAccess dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwProcessId);
[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern int CloseHandle(IntPtr hObject);
[DllImport(ExternDll.Kernel32, SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, FreeType dwFreeType);
[DllImport(ExternDll.User32, SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport(ExternDll.User32, SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport(ExternDll.User32)]
public static extern int GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport(ExternDll.User32, CharSet = CharSet.Auto)]
public static extern bool GetCursorPos(out POINT pt);
}
}