diff --git a/src/Magpie.App/HomePage.cpp b/src/Magpie.App/HomePage.cpp index 6adf2eb2..eccd7344 100644 --- a/src/Magpie.App/HomePage.cpp +++ b/src/Magpie.App/HomePage.cpp @@ -25,7 +25,7 @@ void HomePage::SimulateExclusiveFullscreenToggleSwitch_Toggled(IInspectable cons } // 这个回调被触发时 UI 还没有更新,需要异步处理 - Dispatcher().TryRunAsync(CoreDispatcherPriority::Low, [weakThis(get_weak())]() { + Dispatcher().RunAsync(CoreDispatcherPriority::Low, [weakThis(get_weak())]() { auto strongThis = weakThis.get(); if (!strongThis) { return; diff --git a/src/Magpie.App/RootPage.cpp b/src/Magpie.App/RootPage.cpp index b1d45c1f..0fe5d5cd 100644 --- a/src/Magpie.App/RootPage.cpp +++ b/src/Magpie.App/RootPage.cpp @@ -185,9 +185,9 @@ fire_and_forget RootPage::NavigationView_ItemInvoked(MUXC::NavigationView const& _newProfileViewModel.PrepareForOpen(dpi, isLightTheme, Dispatcher()); // 同步调用 ShowAt 有时会失败 - co_await Dispatcher().TryRunAsync(CoreDispatcherPriority::Normal, [this]() { - NewProfileFlyout().ShowAt(NewProfileNavigationViewItem()); - }); + co_await Dispatcher(); + + NewProfileFlyout().ShowAt(NewProfileNavigationViewItem()); } } diff --git a/src/Magpie.App/ShortcutService.cpp b/src/Magpie.App/ShortcutService.cpp index 01b4d5df..038788af 100644 --- a/src/Magpie.App/ShortcutService.cpp +++ b/src/Magpie.App/ShortcutService.cpp @@ -200,15 +200,13 @@ LRESULT CALLBACK ShortcutService::_LowLevelKeyboardProc(int nCode, WPARAM wParam that._keyboardHookShortcutActivated = true; // 延迟执行回调以缩短钩子的处理时间 - [](ShortcutAction action) -> fire_and_forget { - co_await CoreWindow::GetForCurrentThread().Dispatcher().TryRunAsync( - CoreDispatcherPriority::Normal, - [action]() { - Logger::Get().Info(fmt::format("热键 {} 激活(Keyboard Hook)", ShortcutHelper::ToString(action))); - Get()._FireShortcut(action); - } - ); - }(action); + CoreWindow::GetForCurrentThread().Dispatcher().RunAsync( + CoreDispatcherPriority::Normal, + [action]() { + Logger::Get().Info(fmt::format("热键 {} 激活(Keyboard Hook)", ShortcutHelper::ToString(action))); + Get()._FireShortcut(action); + } + ); if (curKeys.win && !curKeys.ctrl && !curKeys.shift && !curKeys.alt) { // 防止激活开始菜单 diff --git a/src/Magpie.App/ToastPage.cpp b/src/Magpie.App/ToastPage.cpp index 9521ab66..cceb9cd7 100644 --- a/src/Magpie.App/ToastPage.cpp +++ b/src/Magpie.App/ToastPage.cpp @@ -9,61 +9,59 @@ using namespace Windows::UI::Xaml::Controls; namespace winrt::Magpie::App::implementation { -fire_and_forget ToastPage::ShowMessage(const hstring& message) { +MUXC::TeachingTip ToastPage::ShowMessage(const hstring& message) { // !!! HACK !!! // 重用 TeachingTip 有一个 bug: 前一个 Toast 正在消失时新的 Toast 不会显示。为了 // 规避它,我们每次都创建新的 TeachingTip,但要保留旧对象的引用,因为播放动画时销毁 // 会导致崩溃。oldToastTeachingTip 的生存期可确保动画播放完毕。 MUXC::TeachingTip oldTeachingTip = MessageTeachingTip(); if (oldTeachingTip) { + // 先卸载再关闭,始终关闭 TeachingTip 确保调用者可以检查是否可见 UnloadObject(oldTeachingTip); + oldTeachingTip.IsOpen(false); } - weak_ref weakTeachingTip; - { - // 创建新的 TeachingTip - MUXC::TeachingTip newTeachingTip = FindName(L"MessageTeachingTip").as(); - MessageTextBlock().Text(message); - newTeachingTip.IsOpen(true); + // 创建新的 TeachingTip + MUXC::TeachingTip curTeachingTip = FindName(L"MessageTeachingTip").as(); + MessageTextBlock().Text(message); + curTeachingTip.IsOpen(true); - // !!! HACK !!! - // 移除关闭按钮。必须在模板加载完成后做,TeachingTip 没有 Opening 事件,但可以监听 MessageTextBlock 的 - // LayoutUpdated 事件,它在 TeachingTip 显示前必然会被引发。 - MessageTextBlock().LayoutUpdated([weak(weak_ref(newTeachingTip))](IInspectable const&, IInspectable const&) { - auto teachingTip = weak.get(); - if (!teachingTip) { - return; - } - - IControlProtected protectedAccessor = teachingTip.as(); + // !!! HACK !!! + // 移除关闭按钮。必须在模板加载完成后做,TeachingTip 没有 Opening 事件,但可以监听 MessageTextBlock 的 + // LayoutUpdated 事件,它在 TeachingTip 显示前必然会被引发。 + MessageTextBlock().LayoutUpdated([weak(weak_ref(curTeachingTip))](IInspectable const&, IInspectable const&) { + auto teachingTip = weak.get(); + if (!teachingTip) { + return; + } - // 隐藏关闭按钮 - if (DependencyObject closeButton = protectedAccessor.GetTemplateChild(L"AlternateCloseButton")) { - closeButton.as().Visibility(Visibility::Collapsed); - } + IControlProtected protectedAccessor = teachingTip.as(); - // 减小 Flyout 尺寸 - if (DependencyObject container = protectedAccessor.GetTemplateChild(L"TailOcclusionGrid")) { - container.as().MinWidth(0.0); - } - }); + // 隐藏关闭按钮 + if (DependencyObject closeButton = protectedAccessor.GetTemplateChild(L"AlternateCloseButton")) { + closeButton.as().Visibility(Visibility::Collapsed); + } - weakTeachingTip = newTeachingTip; - } + // 减小 Flyout 尺寸 + if (DependencyObject container = protectedAccessor.GetTemplateChild(L"TailOcclusionGrid")) { + container.as().MinWidth(0.0); + } + }); - auto weakThis = get_weak(); - CoreDispatcher dispatcher = Dispatcher(); - // 显示时长固定 2 秒 - co_await 2s; - co_await dispatcher; + // 第三个参数用于延长 oldTeachingTip 的生存期,确保关闭动画播放完毕 + [](CoreDispatcher dispatcher, weak_ref weakCurTeachingTip, MUXC::TeachingTip) -> fire_and_forget { + // 显示时长固定 2 秒 + co_await 2s; + co_await dispatcher; - if (weakThis.get()) { - MUXC::TeachingTip curTeachingTip = MessageTeachingTip(); - if (curTeachingTip == weakTeachingTip.get()) { - // 如果已经显示新的 Toast 则无需关闭,因为 newTeachingTip 已被卸载(但仍在生存期内) + MUXC::TeachingTip curTeachingTip = weakCurTeachingTip.get(); + // 如果 curTeachingTip 已被卸载则无需关闭 + if (curTeachingTip && curTeachingTip.IsLoaded()) { curTeachingTip.IsOpen(false); } - } + }(Dispatcher(), curTeachingTip, oldTeachingTip); + + return curTeachingTip; } } diff --git a/src/Magpie.App/ToastPage.h b/src/Magpie.App/ToastPage.h index 42ec83b0..34e5d730 100644 --- a/src/Magpie.App/ToastPage.h +++ b/src/Magpie.App/ToastPage.h @@ -4,7 +4,7 @@ namespace winrt::Magpie::App::implementation { struct ToastPage : ToastPageT { - fire_and_forget ShowMessage(const hstring& message); + MUXC::TeachingTip ShowMessage(const hstring& message); }; } diff --git a/src/Magpie.App/ToastPage.idl b/src/Magpie.App/ToastPage.idl index e0b3a86e..42e652dd 100644 --- a/src/Magpie.App/ToastPage.idl +++ b/src/Magpie.App/ToastPage.idl @@ -2,7 +2,7 @@ namespace Magpie.App { runtimeclass ToastPage : Windows.UI.Xaml.Controls.Page { ToastPage(); - void ShowMessage(String message); + Microsoft.UI.Xaml.Controls.TeachingTip ShowMessage(String message); // https://github.com/microsoft/microsoft-ui-xaml/issues/7579 void UnloadObject(Windows.UI.Xaml.DependencyObject object); diff --git a/src/Magpie.App/ToastService.cpp b/src/Magpie.App/ToastService.cpp index e1fe5bc4..3262faa5 100644 --- a/src/Magpie.App/ToastService.cpp +++ b/src/Magpie.App/ToastService.cpp @@ -5,6 +5,7 @@ #include #include #include "Win32Utils.h" +#include "XamlUtils.h" using namespace winrt; using namespace Windows::UI::Xaml::Controls; @@ -38,12 +39,12 @@ void ToastService::Uninitialize() noexcept { } void ToastService::ShowMessageOnWindow(std::wstring_view message, HWND hWnd) noexcept { - _Dispatcher().TryRunAsync(CoreDispatcherPriority::Normal, [this, capturedMessage(std::wstring(message)), hWnd]() { + _Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [this, capturedMessage(std::wstring(message)), hWnd]() { RECT frameRect; if (!Win32Utils::GetWindowFrameRect(hWnd, frameRect)) { return; } - + // 更改所有者关系使弹窗始终在 hWnd 上方 SetWindowLongPtr(_hwndToast, GWLP_HWNDPARENT, (LONG_PTR)hWnd); // _hwndToast 的输入已被附加到了 hWnd 上,这是所有者窗口的默认行为,但我们不需要。 @@ -64,7 +65,34 @@ void ToastService::ShowMessageOnWindow(std::wstring_view message, HWND hWnd) noe SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW ); - _toastPage.ShowMessage(capturedMessage); + MUXC::TeachingTip teachingTip = _toastPage.ShowMessage(capturedMessage); + + // 定期更新弹窗位置 + [](CoreDispatcher dispatcher, MUXC::TeachingTip teachingTip, HWND hWnd, HWND hwndToast) -> fire_and_forget { + do { + co_await 10ms; + co_await dispatcher; + + if (!IsWindow(hwndToast)) { + break; + } + + RECT frameRect; + if (!Win32Utils::GetWindowFrameRect(hWnd, frameRect)) { + break; + } + + SetWindowPos( + hwndToast, + NULL, + (frameRect.left + frameRect.right) / 2, + (frameRect.top + frameRect.bottom * 4) / 5, + 0, + 0, + SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOREDRAW + ); + } while (teachingTip.IsOpen()); + }(_dispatcher, std::move(teachingTip), hWnd, _hwndToast); }); } @@ -78,7 +106,7 @@ void ToastService::_ToastThreadProc() noexcept { static Utils::Ignore _ = [] { WNDCLASSEXW wcex{ .cbSize = sizeof(wcex), - .lpfnWndProc = DefWindowProc, + .lpfnWndProc = _ToastWndProc, .hInstance = wil::GetModuleInstanceHandle(), .lpszClassName = CommonSharedConstants::TOAST_WINDOW_CLASS_NAME }; @@ -93,13 +121,15 @@ void ToastService::_ToastThreadProc() noexcept { WS_EX_NOREDIRECTIONBITMAP | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT, CommonSharedConstants::TOAST_WINDOW_CLASS_NAME, L"Toast", - WS_POPUP | WS_VISIBLE, + WS_POPUP, 0, 0, 0, 0, NULL, NULL, wil::GetModuleInstanceHandle(), nullptr ); + SetWindowPos(_hwndToast, NULL, 0, 0, 0, 0, + SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER); // DesktopWindowXamlSource 在控件之前创建则无需调用 WindowsXamlManager::InitializeForCurrentThread DesktopWindowXamlSource xamlSource; @@ -144,6 +174,19 @@ void ToastService::_ToastThreadProc() noexcept { xamlSource.Close(); } +LRESULT ToastService::_ToastWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == WM_MOVE) { + if (Get()._toastPage) { + // 使弹窗随窗口移动 + XamlUtils::RepositionXamlPopups(Get()._toastPage.XamlRoot(), false); + } + + return 0; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + const CoreDispatcher& ToastService::_Dispatcher() noexcept { if (!_dispatcherInitializedCache) { _dispatcherInitialized.wait(false, std::memory_order_acquire); diff --git a/src/Magpie.App/ToastService.h b/src/Magpie.App/ToastService.h index ee9d1a72..3996f847 100644 --- a/src/Magpie.App/ToastService.h +++ b/src/Magpie.App/ToastService.h @@ -24,7 +24,9 @@ class ToastService { void _ToastThreadProc() noexcept; - // 确保 _dispatcher 完成初始化 + static LRESULT CALLBACK _ToastWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + + // 确保 _dispatcher 完成初始化。供主线程使用,toast 线程应直接使用 _dispatcher const CoreDispatcher& _Dispatcher() noexcept; std::thread _toastThread; diff --git a/src/Magpie/XamlWindow.h b/src/Magpie/XamlWindow.h index b3750b49..481435c6 100644 --- a/src/Magpie/XamlWindow.h +++ b/src/Magpie/XamlWindow.h @@ -412,11 +412,9 @@ class XamlWindowT { PostMessage(hwndDWXS, WM_SIZE, wParam, lParam); } - [](C const& content)->winrt::fire_and_forget { - co_await content.Dispatcher().RunAsync(winrt::CoreDispatcherPriority::Normal, [xamlRoot(content.XamlRoot())]() { - XamlUtils::RepositionXamlPopups(xamlRoot, true); - }); - }(_content); + _content.Dispatcher().RunAsync(winrt::CoreDispatcherPriority::Normal, [xamlRoot(_content.XamlRoot())]() { + XamlUtils::RepositionXamlPopups(xamlRoot, true); + }); } }