新增托盘图标
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
@@ -235,7 +235,7 @@ namespace Melskin.Controls
|
||||
}
|
||||
view.Refresh();
|
||||
|
||||
// !string.IsNullOrEmpty(Text) 的判断
|
||||
// !string.IsNullOrEmpty(Tip) 的判断
|
||||
if (!string.IsNullOrEmpty(Text) && !view.IsEmpty && textBox is { IsKeyboardFocused: true })
|
||||
{
|
||||
IsDropDownOpen = true;
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Melskin.Controls.Decorations;
|
||||
/// CornerRadius="10"
|
||||
/// Background="#40FF0000">
|
||||
/// <!-- 半透明红色背景 -->
|
||||
/// <TextBlock Text = "Mini Card" Foreground="White" FontSize="16"/>
|
||||
/// <TextBlock Tip = "Mini Card" Foreground="White" FontSize="16"/>
|
||||
///</GlassChromeDecorator>
|
||||
/// </code>
|
||||
/// <![CDATA[]]>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Melskin.Controls;
|
||||
@@ -193,7 +192,7 @@ public class MelWindow : Window
|
||||
if (maximizeRestoreButton is Visual maximizeButtonVisual)
|
||||
{
|
||||
var bounds = VisualTreeHelper.GetDescendantBounds(maximizeButtonVisual);
|
||||
Debug.WriteLine(bounds.ToString());
|
||||
//Debug.WriteLine(bounds.ToString());
|
||||
var buttonTransform = maximizeButtonVisual.TransformToAncestor(this);
|
||||
var buttonRect = buttonTransform.TransformBounds(bounds);
|
||||
|
||||
@@ -228,7 +227,7 @@ public class MelWindow : Window
|
||||
//VisualStateManager.GoToState(maximizeRestoreButton, "MouseOver", true);
|
||||
new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1A000000")),
|
||||
"Normal" => Brushes.Transparent,
|
||||
//VisualStateManager.GoToState(maximizeRestoreButton, "Normal", true);
|
||||
//VisualStateManager.GoToState(maximizeRestoreButton, "Normal", true);
|
||||
_ => maximizeRestoreButton?.Background
|
||||
};
|
||||
}
|
||||
@@ -322,7 +321,7 @@ public class MelWindow : Window
|
||||
/// <inheritdoc />
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
base.OnApplyTemplate();
|
||||
minimizeButton?.Click -= MinimizeButtonClickHandler;
|
||||
|
||||
minimizeButton = GetTemplateChild(VbMinimizeButtonName) as Button;
|
||||
|
||||
@@ -83,7 +83,7 @@ public partial class ToastControl
|
||||
// foreground = new SolidColorBrush(Color.FromRgb(245, 108, 108));
|
||||
// break;
|
||||
// }
|
||||
// control.IconTextBlock.Text = icon;
|
||||
// control.IconTextBlock.Tip = icon;
|
||||
// control.IconTextBlock.Foreground = foreground;
|
||||
// control.RootBorder.Background = background;
|
||||
// control.RootBorder.BorderBrush = background;
|
||||
|
||||
216
Melskin/Controls/TrayIcon.cs
Normal file
216
Melskin/Controls/TrayIcon.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Melskin.Controls
|
||||
{
|
||||
public class TrayIcon : FrameworkElement, IDisposable
|
||||
{
|
||||
// 1. 悬浮提示文本
|
||||
public static readonly DependencyProperty TipProperty =
|
||||
DependencyProperty.Register(nameof(Tip), typeof(string), typeof(TrayIcon),
|
||||
new PropertyMetadata(string.Empty, OnTextChanged));
|
||||
|
||||
public string Tip
|
||||
{
|
||||
get => (string)GetValue(TipProperty);
|
||||
set => SetValue(TipProperty, value);
|
||||
}
|
||||
|
||||
// 2. 双击命令
|
||||
public static readonly DependencyProperty DoubleClickCommandProperty =
|
||||
DependencyProperty.Register(nameof(DoubleClickCommand), typeof(ICommand), typeof(TrayIcon));
|
||||
|
||||
public ICommand DoubleClickCommand
|
||||
{
|
||||
get => (ICommand)GetValue(DoubleClickCommandProperty);
|
||||
set => SetValue(DoubleClickCommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ClickCommandProperty =
|
||||
DependencyProperty.Register(nameof(ClickCommand), typeof(ICommand), typeof(TrayIcon));
|
||||
|
||||
public ICommand ClickCommand
|
||||
{
|
||||
get => (ICommand)GetValue(ClickCommandProperty);
|
||||
set => SetValue(ClickCommandProperty, value);
|
||||
}
|
||||
|
||||
// 3. 【新增】图标本地路径 (例如: "app.ico")
|
||||
public static readonly DependencyProperty IconPathProperty =
|
||||
DependencyProperty.Register(nameof(IconPath), typeof(string), typeof(TrayIcon),
|
||||
new PropertyMetadata(string.Empty, OnIconPathChanged));
|
||||
|
||||
public string IconPath
|
||||
{
|
||||
get => (string)GetValue(IconPathProperty);
|
||||
set => SetValue(IconPathProperty, value);
|
||||
}
|
||||
|
||||
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctrl = (TrayIcon)d;
|
||||
if (ctrl._isAdded)
|
||||
{
|
||||
ctrl._nid.szTip = e.NewValue?.ToString() ?? "";
|
||||
Shell_NotifyIcon(NIM_MODIFY, ref ctrl._nid);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIconPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctrl = (TrayIcon)d;
|
||||
if (ctrl._isAdded)
|
||||
{
|
||||
ctrl._nid.hIcon = ctrl.LoadMyIcon(e.NewValue?.ToString());
|
||||
Shell_NotifyIcon(NIM_MODIFY, ref ctrl._nid);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Win32 API 声明 ---
|
||||
private const int NIM_ADD = 0x0000;
|
||||
private const int NIM_MODIFY = 0x0001;
|
||||
private const int NIM_DELETE = 0x0002;
|
||||
private const int NIF_MESSAGE = 0x0001;
|
||||
private const int NIF_ICON = 0x0002;
|
||||
private const int NIF_TIP = 0x0004;
|
||||
private const int WM_LBUTTONUP = 0x0202; // 左键弹起消息
|
||||
private const int WM_TRAYMOUSEMESSAGE = 0x0400 + 1024;
|
||||
private const int WM_LBUTTONDBLCLK = 0x0203;
|
||||
private const int WM_RBUTTONUP = 0x0205; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct NOTIFYICONDATA
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr hwnd;
|
||||
public int uID;
|
||||
public int uFlags;
|
||||
public int uCallbackMessage;
|
||||
public IntPtr hIcon; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
|
||||
public string szTip;
|
||||
public int dwState;
|
||||
public int dwStateMask; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
|
||||
public string szInfo;
|
||||
public int uTimeoutOrVersion;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
|
||||
public string szInfoTitle;
|
||||
public int dwInfoFlags;
|
||||
public Guid guidItem;
|
||||
public IntPtr hBalloonIcon;
|
||||
}
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern bool Shell_NotifyIcon(int dwMessage, ref NOTIFYICONDATA pnid);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetForegroundWindow(IntPtr hWnd);
|
||||
|
||||
// 【新增】纯 Win32 加载本地图标 API
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr LoadImage(IntPtr hInst, string lpszName, uint uType, int cxDesired, int cyDesired, uint fuLoad);
|
||||
|
||||
private IntPtr LoadMyIcon(string iconPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(iconPath)) return IntPtr.Zero;
|
||||
// 1 = IMAGE_ICON, 0x00000010 = LR_LOADFROMFILE
|
||||
return LoadImage(IntPtr.Zero, iconPath, 1, 0, 0, 0x00000010);
|
||||
}
|
||||
|
||||
// --- 内部状态 ---
|
||||
private HwndSource _messageSink;
|
||||
private int _uID = 100;
|
||||
private bool _isAdded = false;
|
||||
private NOTIFYICONDATA _nid;
|
||||
|
||||
public TrayIcon()
|
||||
{
|
||||
Visibility = Visibility.Collapsed;
|
||||
this.Loaded += OnLoaded;
|
||||
this.Unloaded += OnUnloaded;
|
||||
if (Application.Current != null)
|
||||
Application.Current.Exit += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isAdded) return;
|
||||
|
||||
HwndSourceParameters p = new HwndSourceParameters("TrayIconMessageSink")
|
||||
{
|
||||
Width = 0, Height = 0, WindowStyle = 0, ExtendedWindowStyle = 0x00000080
|
||||
};
|
||||
_messageSink = new HwndSource(p);
|
||||
_messageSink.AddHook(WndProc);
|
||||
|
||||
_nid = new NOTIFYICONDATA
|
||||
{
|
||||
cbSize = Marshal.SizeOf(typeof(NOTIFYICONDATA)),
|
||||
hwnd = _messageSink.Handle,
|
||||
uID = _uID,
|
||||
uFlags = NIF_MESSAGE | NIF_TIP | NIF_ICON,
|
||||
uCallbackMessage = WM_TRAYMOUSEMESSAGE,
|
||||
szTip = this.Tip ?? "",
|
||||
// 加载属性中指定的图标
|
||||
hIcon = LoadMyIcon(this.IconPath)
|
||||
};
|
||||
|
||||
Shell_NotifyIcon(NIM_ADD, ref _nid);
|
||||
_isAdded = true;
|
||||
}
|
||||
|
||||
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
if (msg == WM_TRAYMOUSEMESSAGE)
|
||||
{
|
||||
int mouseMsg = lParam.ToInt32();
|
||||
if (mouseMsg == WM_LBUTTONUP)
|
||||
{
|
||||
if (ClickCommand?.CanExecute(null) == true)
|
||||
{
|
||||
ClickCommand.Execute(null);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
else if (mouseMsg == WM_LBUTTONDBLCLK)
|
||||
{
|
||||
if (DoubleClickCommand?.CanExecute(null) == true)
|
||||
DoubleClickCommand.Execute(null);
|
||||
handled = true;
|
||||
}
|
||||
else if (mouseMsg == WM_RBUTTONUP)
|
||||
{
|
||||
ShowContextMenu();
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private void ShowContextMenu()
|
||||
{
|
||||
if (this.ContextMenu != null)
|
||||
{
|
||||
SetForegroundWindow(_messageSink.Handle);
|
||||
this.ContextMenu.PlacementTarget = null;
|
||||
this.ContextMenu.Placement = PlacementMode.MousePoint;
|
||||
this.ContextMenu.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnloaded(object sender, RoutedEventArgs e) => Dispose();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isAdded)
|
||||
{
|
||||
Shell_NotifyIcon(NIM_DELETE, ref _nid);
|
||||
_isAdded = false;
|
||||
}
|
||||
if (_messageSink != null)
|
||||
{
|
||||
_messageSink.RemoveHook(WndProc);
|
||||
_messageSink.Dispose();
|
||||
_messageSink = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Melskin.Markup;
|
||||
/// 使用此扩展时,需要确保已正确引用了Material Design图标字体资源。可以通过设置`FontFamily`属性来应用图标字体。
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// <code lang="xml"> <TextBlock Text="{enu:SymbolIcon Symbol=AddBox}" FontFamily="{StaticResource MaterialIconFont}"/>
|
||||
/// <code lang="xml"> <TextBlock Tip="{enu:SymbolIcon Symbol=AddBox}" FontFamily="{StaticResource MaterialIconFont}"/>
|
||||
/// </code>
|
||||
/// </example>
|
||||
[ContentProperty(nameof(Symbol))]
|
||||
|
||||
Reference in New Issue
Block a user