Files
WindowsMusicPlayer-TheUntam…/UntamedMusicPlayer/MainWindow.xaml.cs
2026-03-13 01:35:49 +08:00

425 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using System.Numerics;
using System.Runtime.InteropServices;
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
using UntamedMusicPlayer.Contracts.Services;
using UntamedMusicPlayer.Helpers;
using UntamedMusicPlayer.Messages;
using UntamedMusicPlayer.Models;
using UntamedMusicPlayer.Services;
using UntamedMusicPlayer.ViewModels;
using UntamedMusicPlayer.Views;
using Windows.System;
using WinRT.Interop;
using WinUIEx;
using ZLogger;
namespace UntamedMusicPlayer;
public sealed partial class MainWindow : WindowEx, IRecipient<LogMessage>
{
private readonly ILogger _logger = LoggingService.CreateLogger<MainWindow>();
private readonly InfoBarManager? _infoBarManager;
private bool _isClosingWorkDone;
private readonly Timer _playBarTimer;
private bool _playBarTimerEnabled = false;
private bool _isPlayBarHidden = false;
// 热键 ID
private const int HOTKEY_ID_VOLUME_UP = 1;
private const int HOTKEY_ID_VOLUME_DOWN = 2;
private nint _windowHandle;
private nint _oldWndProc;
// 必须保持委托引用防止 GC 回收
private readonly WndProcDelegate _wndProcDelegate;
public MainWindow()
{
InitializeComponent();
Data.MainWindow = this;
AppWindow.SetTaskbarIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AppIcon/Icon.ico"));
AppWindow.SetTitleBarIcon(
Path.Combine(AppContext.BaseDirectory, "Assets/AppIcon/Icon.ico")
);
AppWindow.SetIcon(Path.Combine(AppContext.BaseDirectory, "Assets/AppIcon/Icon.ico"));
Title = "AppDisplayName".GetLocalized();
ExtendsContentIntoTitleBar = true;
ShellFrame.Navigate(typeof(ShellPage));
// 初始化InfoBar管理器
_infoBarManager = new InfoBarManager(
ErrorInfoBar,
SendFeedbackButton,
ShowInfoBarStoryboard,
HideInfoBarStoryboard
);
// 注册日志消息接收
StrongReferenceMessenger.Default.Register(this);
ErrorInfoBar.Translation += new Vector3(0, 0, 40);
// 注册AppWindow.Closing事件来处理窗口关闭
AppWindow.Closing += AppWindow_Closing;
// 注册全局键盘和指针事件用于ESC键和鼠标侧键导航
RootGrid.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnGlobalKeyDown), true);
RootGrid.AddHandler(
UIElement.PointerPressedEvent,
new PointerEventHandler(OnGlobalPointerPressed),
true
);
// 创建委托并保持引用(防止被 GC 回收)
_wndProcDelegate = NewWindowProc;
// 注册系统级全局热键
Activated += MainWindow_Activated;
// 初始化播放栏隐藏定时器
_playBarTimer = new Timer(TimerTick, null, Timeout.Infinite, Timeout.Infinite);
Data.RootPlayBarViewModel?.PropertyChanged += RootPlayBarViewModelPropertyChanged;
}
private void RootPlayBarViewModelPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (
e.PropertyName
is nameof(RootPlayBarViewModel.IsDetail)
or nameof(RootPlayBarViewModel.IsFullScreen)
)
{
if (Data.RootPlayBarViewModel!.IsDetail && Data.RootPlayBarViewModel.IsFullScreen)
{
RootGrid.PointerMoved += RootGrid_PointerMoved;
}
else
{
RootGrid.PointerMoved -= RootGrid_PointerMoved;
if (_isPlayBarHidden)
{
ShellFrame.Margin = new Thickness(0, 0, 0, 117);
ShowPlayBarStoryboard.Begin();
_isPlayBarHidden = false;
StopPlayBarTimer();
}
}
}
}
private void RootGrid_PointerMoved(object sender, PointerRoutedEventArgs e)
{
var pointerPoint = e.GetCurrentPoint(RootGrid);
var position = pointerPoint.Position;
var height = RootGrid.ActualHeight;
if (position.Y > height - 117) // 如果鼠标在底部 117 像素范围内
{
ShellFrame.Margin = new Thickness(0, 0, 0, 117);
if (_isPlayBarHidden)
{
ShowPlayBarStoryboard.Begin();
_isPlayBarHidden = false;
}
StopPlayBarTimer();
}
else if (!_isPlayBarHidden) // 鼠标离开了底部,开始定时器
{
StartPlayBarTimer();
}
}
private void TimerTick(object? state)
{
StopPlayBarTimer();
DispatcherQueue.TryEnqueue(
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low,
() =>
{
if (
Data.RootPlayBarViewModel!.IsDetail
&& Data.RootPlayBarViewModel.IsFullScreen
&& !_isPlayBarHidden
)
{
ShellFrame.Margin = new Thickness(0);
HidePlayBarStoryboard.Begin();
_isPlayBarHidden = true;
}
}
);
}
private void StartPlayBarTimer()
{
if (!_playBarTimerEnabled)
{
_playBarTimer.Change(TimeSpan.FromSeconds(2), Timeout.InfiniteTimeSpan);
_playBarTimerEnabled = true;
}
}
private void StopPlayBarTimer()
{
if (_playBarTimerEnabled)
{
_playBarTimer.Change(Timeout.Infinite, Timeout.Infinite);
_playBarTimerEnabled = false;
}
}
/// <summary>
/// 窗口激活时注册全局热键
/// </summary>
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
Activated -= MainWindow_Activated; // 只在第一次激活时注册
_windowHandle = WindowNative.GetWindowHandle(this); // 获取窗口句柄
SubclassWindow(); // 子类化窗口以接收 WM_HOTKEY 消息
RegisterGlobalHotKeys(); // 注册全局热键
}
// 窗口过程委托
private delegate nint WndProcDelegate(nint hWnd, uint msg, nint wParam, nint lParam);
/// <summary>
/// 子类化窗口以拦截消息
/// </summary>
private void SubclassWindow()
{
const int GWL_WNDPROC = -4;
var newWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate); // 使用保存的委托引用
_oldWndProc = ExternFunction.SetWindowLong(_windowHandle, GWL_WNDPROC, newWndProc); // 替换窗口过程
}
/// <summary>
/// 新的窗口过程,用于拦截 WM_HOTKEY 消息
/// </summary>
private nint NewWindowProc(nint hWnd, uint msg, nint wParam, nint lParam)
{
if (msg == ExternFunction.WM_HOTKEY)
{
var hotkeyId = wParam.ToInt32();
DispatcherQueue.TryEnqueue(() => // 在 UI 线程上处理热键
{
switch (hotkeyId)
{
case HOTKEY_ID_VOLUME_UP: // Alt + Up: 增加音量
var currentVolumeUp = Data.PlayState.Volume;
Data.PlayState.Volume = Math.Min(100, currentVolumeUp + 5);
break;
case HOTKEY_ID_VOLUME_DOWN: // Alt + Down: 减少音量
var currentVolumeDown = Data.PlayState.Volume;
Data.PlayState.Volume = Math.Max(0, currentVolumeDown - 5);
break;
}
});
return nint.Zero;
}
return ExternFunction.CallWindowProc(_oldWndProc, hWnd, msg, wParam, lParam); // 调用原始窗口过程
}
/// <summary>
/// 注册系统级全局热键
/// </summary>
private void RegisterGlobalHotKeys()
{
try
{
// 注册 Alt + Up (增加音量)
var success1 = ExternFunction.RegisterHotKey(
_windowHandle,
HOTKEY_ID_VOLUME_UP,
ExternFunction.MOD_ALT,
ExternFunction.VK_UP
);
// 注册 Alt + Down (减少音量)
var success2 = ExternFunction.RegisterHotKey(
_windowHandle,
HOTKEY_ID_VOLUME_DOWN,
ExternFunction.MOD_ALT,
ExternFunction.VK_DOWN
);
if (!success1 || !success2)
{
_logger.ZLogWarning($"注册全局热键失败。上: {success1}, 下: {success2}");
}
}
catch (Exception ex)
{
_logger.ZLogWarning(ex, $"注册全局热键时出错");
}
}
/// <summary>
/// 注销系统级全局热键
/// </summary>
private void UnregisterGlobalHotKeys()
{
if (_windowHandle != nint.Zero)
{
ExternFunction.UnregisterHotKey(_windowHandle, HOTKEY_ID_VOLUME_UP);
ExternFunction.UnregisterHotKey(_windowHandle, HOTKEY_ID_VOLUME_DOWN);
}
}
/// <summary>
/// 处理全局键盘按键事件ESC键返回Alt+上/下键调音量)
/// </summary>
private void OnGlobalKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key == VirtualKey.Escape) // ESC 键返回
{
if (Data.RootPlayBarViewModel?.IsDetail == true)
{
Data.RootPlayBarViewModel.DetailModeUpdate();
}
else
{
Data.ShellPage?.GoBack();
}
e.Handled = true;
return;
}
}
/// <summary>
/// 处理全局鼠标按键事件(侧键返回)
/// </summary>
private void OnGlobalPointerPressed(object sender, PointerRoutedEventArgs e)
{
var properties = e.GetCurrentPoint(RootGrid).Properties;
if (properties.IsXButton1Pressed)
{
if (Data.RootPlayBarViewModel?.IsDetail == true)
{
Data.RootPlayBarViewModel.DetailModeUpdate();
}
else
{
Data.ShellPage?.GoBack();
}
e.Handled = true;
}
}
/// <summary>
/// 接收日志消息并在InfoBar中显示
/// </summary>
/// <param name="message">日志消息</param>
public void Receive(LogMessage message)
{
// 只处理Error和Critical级别的日志
if (message.Level >= LogLevel.Error)
{
// 确保在UI线程上执行
DispatcherQueue.TryEnqueue(() =>
{
_infoBarManager?.ShowMessage(message);
});
}
}
/// <summary>
/// 获取导航页(ShellFrame)
/// </summary>
/// <returns></returns>
public Frame GetShellFrame()
{
return ShellFrame;
}
public Grid GetBackgroundGrid()
{
return BackgroundGrid;
}
/// <summary>
/// 处理AppWindow关闭请求 - 在窗口实际关闭前处理数据保存
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private async void AppWindow_Closing(AppWindow sender, AppWindowClosingEventArgs args)
{
if (_isClosingWorkDone)
{
return;
}
try
{
args.Cancel = true;
sender.Hide(); // 立即隐藏窗口,提升视觉响应
Data.MusicPlayer.Pause(); // 立即停止音乐播放
Data.DesktopLyricWindow?.Close(); // 立即关闭桌面歌词
Settings.NotFirstUsed = true;
// 并行执行保存以缩短退出后的存活时间
await Task.WhenAll(
Data.MusicPlayer.SaveStateAsync(),
Data.PlaylistLibrary.SaveLibraryAsync()
);
}
catch (Exception ex)
{
_logger.ZLogInformation(ex, $"保存应用程序数据失败");
}
finally
{
PerformFinalCleanup();
_isClosingWorkDone = true;
Close(); // 此时真正触发关闭
}
}
/// <summary>
/// 窗口关闭时的工作
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void MainWindow_Closed(object sender, WindowEventArgs args)
{
LoggingService.Shutdown();
}
private void PerformFinalCleanup()
{
try
{
Data.MusicPlayer.Dispose();
Data.DesktopLyricWindow?.Dispose();
UnregisterGlobalHotKeys();
RootGrid.RemoveHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnGlobalKeyDown));
RootGrid.RemoveHandler(
UIElement.PointerPressedEvent,
new PointerEventHandler(OnGlobalPointerPressed)
);
StrongReferenceMessenger.Default.Unregister<LogMessage>(this);
_infoBarManager?.Dispose();
App.GetService<IMaterialSelectorService>().Dispose();
App.GetService<IDynamicBackgroundService>().Dispose();
_playBarTimer?.Dispose();
}
catch (Exception ex)
{
_logger.ZLogInformation(ex, $"后期清理资源失败");
}
}
}