修复特定情况下打开文本框右键菜单会崩溃的问题 (#1152)

* feat: 新建配置时可以填入窗口标题

* feat: 添加 TextMenuFlyout

* fix: 修复 WinUI 右键菜单导致崩溃

* feat: 右键菜单本地化和代码优化

* feat: 实现撤销和重做

* feat: 扩展新建配置弹窗名称文本框的右键菜单

* chore: 修复编译

* feat: 删除无关功能
This commit is contained in:
Xu 2025-05-19 19:10:59 +08:00 committed by GitHub
commit 0df390c888
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
47 changed files with 545 additions and 208 deletions

View file

@ -21,8 +21,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct AboutPage : AboutPageT<AboutPage, implementation::AboutPage> {};
}
BASIC_FACTORY(AboutPage)

View file

@ -114,8 +114,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct App : AppT<App, implementation::App> {};
}
BASIC_FACTORY(App)

View file

@ -9,6 +9,7 @@ namespace Magpie {
#include "IsEqualStateTrigger.idl"
#include "IsNullStateTrigger.idl"
#include "TextBlockHelper.idl"
#include "TextMenuFlyout.idl"
#include "SimpleStackPanel.idl"
#include "WrapPanel.idl"
#include "CaptionButtonsControl.idl"

View file

@ -5,6 +5,4 @@ namespace winrt::Magpie::implementation {
struct BlueInfoBarStyle : BlueInfoBarStyleT<BlueInfoBarStyle> {};
}
namespace winrt::Magpie::factory_implementation {
struct BlueInfoBarStyle : BlueInfoBarStyleT<BlueInfoBarStyle, implementation::BlueInfoBarStyle> {};
}
BASIC_FACTORY(BlueInfoBarStyle)

View file

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "BoolNegationConverter.g.h"
namespace winrt::Magpie::implementation {
@ -10,9 +10,4 @@ struct BoolNegationConverter : BoolNegationConverterT<BoolNegationConverter> {
}
namespace winrt::Magpie::factory_implementation {
struct BoolNegationConverter : BoolNegationConverterT<BoolNegationConverter, implementation::BoolNegationConverter> {
};
}
BASIC_FACTORY(BoolNegationConverter)

View file

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "BoolToNegativeVisibilityConverter.g.h"
namespace winrt::Magpie::implementation {
@ -10,9 +10,4 @@ struct BoolToNegativeVisibilityConverter : BoolToNegativeVisibilityConverterT<Bo
}
namespace winrt::Magpie::factory_implementation {
struct BoolToNegativeVisibilityConverter : BoolToNegativeVisibilityConverterT<BoolToNegativeVisibilityConverter, implementation::BoolToNegativeVisibilityConverter> {
};
}
BASIC_FACTORY(BoolToNegativeVisibilityConverter)

View file

@ -35,9 +35,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct CaptionButtonsControl : CaptionButtonsControlT<CaptionButtonsControl, implementation::CaptionButtonsControl> {
};
}
BASIC_FACTORY(CaptionButtonsControl)

View file

@ -1,36 +0,0 @@
#pragma once
#include "XamlHelper.h"
namespace Magpie {
struct ComboBoxHelper {
// 用于修复 ComboBox 中存在的问题
// 因为官方毫无作为,我不得不使用这些 hack
template<typename T>
static void DropDownOpened(T const& page, winrt::IInspectable const& sender) {
using namespace winrt;
using namespace Windows::UI::Xaml::Controls;
// 修复下拉框不适配主题的问题
// https://github.com/microsoft/microsoft-ui-xaml/issues/6622
XamlHelper::UpdateThemeOfXamlPopups(page.XamlRoot(), page.ActualTheme());
// 修复下拉框位置不正确的问题
// https://github.com/microsoft/microsoft-ui-xaml/issues/4551
ComboBox comboBox = sender.as<ComboBox>();
IInspectable selectedItem = comboBox.SelectedItem();
if (!selectedItem) {
return;
}
if (std::optional<hstring> str = selectedItem.try_as<hstring>()) {
comboBox.PlaceholderText(*str);
} else if (ContentControl container = selectedItem.try_as<ContentControl>()) {
if (std::optional<hstring> strContent = container.Content().try_as<hstring>()) {
comboBox.PlaceholderText(*strContent);
}
}
}
};
}

View file

@ -0,0 +1,46 @@
#include "pch.h"
#include "ControlHelper.h"
#include "App.h"
#include "RootPage.h"
using namespace winrt;
using namespace winrt::Magpie::implementation;
using namespace Windows::UI::Xaml::Controls;
namespace Magpie {
void ControlHelper::ComboBox_DropDownOpened(const IInspectable& sender) {
// 修复下拉框不适配主题的问题
// https://github.com/microsoft/microsoft-ui-xaml/issues/6622
const auto& rootPage = App::Get().RootPage();
XamlHelper::UpdateThemeOfXamlPopups(rootPage->XamlRoot(), rootPage->ActualTheme());
// 修复下拉框位置不正确的问题
// https://github.com/microsoft/microsoft-ui-xaml/issues/4551
ComboBox comboBox = sender.as<ComboBox>();
IInspectable selectedItem = comboBox.SelectedItem();
if (!selectedItem) {
return;
}
if (std::optional<hstring> str = selectedItem.try_as<hstring>()) {
comboBox.PlaceholderText(*str);
} else if (ContentControl container = selectedItem.try_as<ContentControl>()) {
if (std::optional<hstring> strContent = container.Content().try_as<hstring>()) {
comboBox.PlaceholderText(*strContent);
}
}
}
void ControlHelper::NumberBox_Loaded(const IInspectable& sender) {
// 确保模板已应用
sender.as<MUXC::NumberBox>().ApplyTemplate();
// 设置内部 TextBox 的右键菜单
sender.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.ContextFlyout(winrt::Magpie::TextMenuFlyout());
}
}

View file

@ -0,0 +1,15 @@
#pragma once
#include "XamlHelper.h"
namespace Magpie {
// 用于修复 WinUI 控件存在的问题。因为官方毫无作为,我不得不使用这些 hack
struct ControlHelper {
// 修复 ComboBox 下拉框的主题和位置
static void ComboBox_DropDownOpened(const winrt::IInspectable& sender);
// 设置 NumberBox 内部 TextBox 的右键菜单
static void NumberBox_Loaded(const winrt::IInspectable& sender);
};
}

View file

@ -48,9 +48,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct ControlSizeTrigger : ControlSizeTriggerT<ControlSizeTrigger, implementation::ControlSizeTrigger> {
};
}
BASIC_FACTORY(ControlSizeTrigger)

View file

@ -4,7 +4,7 @@
#include "HomePage.g.cpp"
#endif
#include "XamlHelper.h"
#include "ComboBoxHelper.h"
#include "ControlHelper.h"
#include "App.h"
using namespace ::Magpie;
@ -17,7 +17,7 @@ void HomePage::TimerSlider_Loaded(IInspectable const& sender, RoutedEventArgs co
}
void HomePage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const {
ComboBoxHelper::DropDownOpened(*this, sender);
ControlHelper::ComboBox_DropDownOpened(sender);
}
void HomePage::SimulateExclusiveFullscreenToggleSwitch_Toggled(IInspectable const& sender, RoutedEventArgs const&) {

View file

@ -21,8 +21,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct HomePage : HomePageT<HomePage, implementation::HomePage> {};
}
BASIC_FACTORY(HomePage)

View file

@ -123,8 +123,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct HomeViewModel : HomeViewModelT<HomeViewModel, implementation::HomeViewModel> {};
}
BASIC_FACTORY(HomeViewModel)

View file

@ -25,9 +25,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct IsEqualStateTrigger : IsEqualStateTriggerT<IsEqualStateTrigger, implementation::IsEqualStateTrigger> {
};
}
BASIC_FACTORY(IsEqualStateTrigger)

View file

@ -22,9 +22,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct IsNullStateTrigger : IsNullStateTriggerT<IsNullStateTrigger, implementation::IsNullStateTrigger> {
};
}
BASIC_FACTORY(IsNullStateTrigger)

View file

@ -40,9 +40,5 @@ struct KeyVisualStyle : KeyVisualStyleT<KeyVisualStyle> {};
}
namespace winrt::Magpie::factory_implementation {
struct KeyVisual : KeyVisualT<KeyVisual, implementation::KeyVisual> {};
struct KeyVisualStyle : KeyVisualStyleT<KeyVisualStyle, implementation::KeyVisualStyle> {};
}
BASIC_FACTORY(KeyVisual)
BASIC_FACTORY(KeyVisualStyle)

View file

@ -101,7 +101,7 @@
<DependentUpon>CaptionButtonsControl.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ComboBoxHelper.h" />
<ClInclude Include="ControlHelper.h" />
<ClInclude Include="ContentDialogHelper.h" />
<ClInclude Include="ControlSizeTrigger.h">
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
@ -233,6 +233,10 @@
<DependentUpon>TextBlockHelper.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="TextMenuFlyout.h">
<DependentUpon>TextMenuFlyout.idl</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="ThemeHelper.h" />
<ClInclude Include="TitleBarControl.h">
<DependentUpon>TitleBarControl.xaml</DependentUpon>
@ -291,6 +295,7 @@
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="ContentDialogHelper.cpp" />
<ClCompile Include="ControlHelper.cpp" />
<ClCompile Include="ControlSizeTrigger.cpp">
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
<SubType>Code</SubType>
@ -421,6 +426,10 @@
<DependentUpon>TextBlockHelper.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="TextMenuFlyout.cpp">
<DependentUpon>TextMenuFlyout.idl</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="ThemeHelper.cpp" />
<ClCompile Include="TitleBarControl.cpp">
<DependentUpon>TitleBarControl.xaml</DependentUpon>
@ -444,6 +453,9 @@
<DependentUpon>BlueInfoBarStyle.xaml</DependentUpon>
<SubType>Designer</SubType>
</None>
<None Include="TextMenuFlyout.idl">
<SubType>Designer</SubType>
</None>
<Midl Include="ToastPage.idl">
<DependentUpon>ToastPage.xaml</DependentUpon>
<SubType>Code</SubType>

View file

@ -79,15 +79,15 @@
<ClCompile Include="SmoothResizeHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
<ClCompile Include="ControlHelper.cpp">
<Filter>Helpers</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ShortcutService.h">
<Filter>Services</Filter>
</ClInclude>
<ClInclude Include="ComboBoxHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="ShortcutHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
@ -166,6 +166,9 @@
<ClInclude Include="SmoothResizeHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
<ClInclude Include="ControlHelper.h">
<Filter>Helpers</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Pages">
@ -276,6 +279,9 @@
<None Include="SettingsExpanderCornerRadiusConverter.idl">
<Filter>Converters</Filter>
</None>
<None Include="TextMenuFlyout.idl">
<Filter>Helpers</Filter>
</None>
</ItemGroup>
<ItemGroup>
<Page Include="HomePage.xaml">

View file

@ -38,9 +38,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct PageFrame : PageFrameT<PageFrame, implementation::PageFrame> {
};
}
BASIC_FACTORY(PageFrame)

View file

@ -4,7 +4,7 @@
#include "ProfilePage.g.cpp"
#endif
#include "Win32Helper.h"
#include "ComboBoxHelper.h"
#include "ControlHelper.h"
#include "ProfileService.h"
#include "Profile.h"
@ -30,7 +30,11 @@ void ProfilePage::OnNavigatedTo(Navigation::NavigationEventArgs const& args) {
}
void ProfilePage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) {
ComboBoxHelper::DropDownOpened(*this, sender);
ControlHelper::ComboBox_DropDownOpened(sender);
}
void ProfilePage::NumberBox_Loaded(IInspectable const& sender, RoutedEventArgs const&) {
ControlHelper::NumberBox_Loaded(sender);
}
void ProfilePage::CursorScalingComboBox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const&) {

View file

@ -15,6 +15,8 @@ struct ProfilePage : ProfilePageT<ProfilePage> {
void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&);
void NumberBox_Loaded(IInspectable const& sender, RoutedEventArgs const&);
void CursorScalingComboBox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const&);
void RenameMenuItem_Click(IInspectable const&, RoutedEventArgs const&);
@ -39,8 +41,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct ProfilePage : ProfilePageT<ProfilePage, implementation::ProfilePage> {};
}
BASIC_FACTORY(ProfilePage)

View file

@ -58,7 +58,11 @@
Height="32"
Margin="0,8,0,20"
KeyDown="RenameTextBox_KeyDown"
Text="{x:Bind ViewModel.RenameText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{x:Bind ViewModel.RenameText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.ContextFlyout>
<local:TextMenuFlyout />
</TextBox.ContextFlyout>
</TextBox>
<Button x:Uid="Profile_MoreOptions_RenameFlyout_OK"
MinWidth="80"
HorizontalAlignment="Right"
@ -250,6 +254,7 @@
Spacing="8">
<muxc:NumberBox VerticalAlignment="Center"
LargeChange="10"
Loaded="NumberBox_Loaded"
Maximum="1000"
Minimum="10"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
@ -295,6 +300,7 @@
Spacing="8">
<muxc:NumberBox Foreground="{ThemeResource TextFillColorPrimaryBrush}"
LargeChange="10"
Loaded="NumberBox_Loaded"
Minimum="0"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
SmallChange="1"
@ -310,6 +316,7 @@
Spacing="8">
<muxc:NumberBox Foreground="{ThemeResource TextFillColorPrimaryBrush}"
LargeChange="10"
Loaded="NumberBox_Loaded"
Minimum="0"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
SmallChange="1"
@ -325,6 +332,7 @@
Spacing="8">
<muxc:NumberBox Foreground="{ThemeResource TextFillColorPrimaryBrush}"
LargeChange="10"
Loaded="NumberBox_Loaded"
Minimum="0"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
SmallChange="1"
@ -340,6 +348,7 @@
Spacing="8">
<muxc:NumberBox Foreground="{ThemeResource TextFillColorPrimaryBrush}"
LargeChange="10"
Loaded="NumberBox_Loaded"
Minimum="0"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
SmallChange="1"
@ -396,6 +405,7 @@
Margin="5,0,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Loaded="NumberBox_Loaded"
Maximum="10"
Minimum="0.1"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
@ -438,7 +448,11 @@
<FontIcon Glyph="&#xE756;" />
</local:SettingsCard.HeaderIcon>
<TextBox KeyDown="LaunchParametersTextBox_KeyDown"
Text="{x:Bind ViewModel.LaunchParameters, Mode=TwoWay}" />
Text="{x:Bind ViewModel.LaunchParameters, Mode=TwoWay}">
<TextBox.ContextFlyout>
<local:TextMenuFlyout />
</TextBox.ContextFlyout>
</TextBox>
</local:SettingsCard>
<local:SettingsCard x:Uid="Profile_Advanced_DisableDirectFlip">
<ToggleSwitch x:Uid="ToggleSwitch"

View file

@ -877,4 +877,22 @@
<data name="Home_Advanced_DeveloperOptions_BenchmarkMode.Content" xml:space="preserve">
<value>Benchmark mode</value>
</data>
<data name="TextMenuFlyout_Copy" xml:space="preserve">
<value>Copy</value>
</data>
<data name="TextMenuFlyout_Cut" xml:space="preserve">
<value>Cut</value>
</data>
<data name="TextMenuFlyout_Paste" xml:space="preserve">
<value>Paste</value>
</data>
<data name="TextMenuFlyout_Redo" xml:space="preserve">
<value>Redo</value>
</data>
<data name="TextMenuFlyout_SelectAll" xml:space="preserve">
<value>Select All</value>
</data>
<data name="TextMenuFlyout_Undo" xml:space="preserve">
<value>Undo</value>
</data>
</root>

View file

@ -877,4 +877,22 @@
<data name="Home_Advanced_DeveloperOptions_BenchmarkMode.Content" xml:space="preserve">
<value>性能测试模式</value>
</data>
<data name="TextMenuFlyout_Copy" xml:space="preserve">
<value>复制</value>
</data>
<data name="TextMenuFlyout_Cut" xml:space="preserve">
<value>剪切</value>
</data>
<data name="TextMenuFlyout_Paste" xml:space="preserve">
<value>粘贴</value>
</data>
<data name="TextMenuFlyout_Redo" xml:space="preserve">
<value>重新操作</value>
</data>
<data name="TextMenuFlyout_SelectAll" xml:space="preserve">
<value>全选</value>
</data>
<data name="TextMenuFlyout_Undo" xml:space="preserve">
<value>取消操作</value>
</data>
</root>

View file

@ -10,13 +10,15 @@
#include "ProfileService.h"
#include "AppXReader.h"
#include "IconHelper.h"
#include "ComboBoxHelper.h"
#include "ControlHelper.h"
#include "ThemeHelper.h"
#include "ContentDialogHelper.h"
#include "LocalizationService.h"
#include "App.h"
#include "TitleBarControl.h"
#include "MainWindow.h"
#include "CandidateWindowItem.h"
#include "CommonSharedConstants.h"
using namespace ::Magpie;
using namespace winrt;
@ -225,8 +227,8 @@ void RootPage::NavigationView_ItemInvoked(MUXC::NavigationView const&, MUXC::Nav
}
}
void RootPage::ComboBox_DropDownOpened(IInspectable const&, IInspectable const&) const {
XamlHelper::UpdateThemeOfXamlPopups(XamlRoot(), ActualTheme());
void RootPage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const {
ControlHelper::ComboBox_DropDownOpened(sender);
}
void RootPage::NewProfileConfirmButton_Click(IInspectable const&, RoutedEventArgs const&) {
@ -348,7 +350,8 @@ void RootPage::_UpdateIcons(bool skipDesktop) {
continue;
}
MUXC::NavigationViewItem item = navMenuItems.GetAt(FIRST_PROFILE_ITEM_IDX + i).as<MUXC::NavigationViewItem>();
MUXC::NavigationViewItem item = navMenuItems.GetAt(FIRST_PROFILE_ITEM_IDX + i)
.as<MUXC::NavigationViewItem>();
_LoadIcon(item, profiles[i]);
}
}
@ -390,4 +393,28 @@ void RootPage::_ProfileService_ProfileReordered(uint32_t profileIdx, bool isMove
menuItems.InsertAt(curIdx, otherItem);
}
void RootPage::_UpdateNewProfileNameTextBox(bool fillWithTitle) {
int idx = _newProfileViewModel->CandidateWindowIndex();
if (idx < 0) {
return;
}
CandidateWindowItem* selectedItem = get_self<CandidateWindowItem>(
_newProfileViewModel->CandidateWindows().GetAt(idx).as<winrt::Magpie::CandidateWindowItem>());
hstring text = fillWithTitle ? selectedItem->Title() : selectedItem->DefaultProfileName();
TextBox textBox = NewProfileNameTextBox();
if (textBox.Text() == text) {
return;
}
const int size = (int)text.size();
// 遗憾的是设置 Text 属性会导致撤销/重做历史丢失
textBox.Text(std::move(text));
// 修改文本后将光标移到最后
textBox.Select(size, 0);
// 如果文本太长,这个调用可以使视口移到光标位置
textBox.Focus(FocusState::Programmatic);
}
}

View file

@ -58,6 +58,8 @@ private:
void _ProfileService_ProfileReordered(uint32_t profileIdx, bool isMoveUp);
void _UpdateNewProfileNameTextBox(bool fillWithTitle);
::Magpie::MultithreadEvent<bool>::EventRevoker _appThemeChangedRevoker;
::Magpie::Event<uint32_t>::EventRevoker _dpiChangedRevoker;
@ -66,6 +68,7 @@ private:
::Magpie::Event<uint32_t>::EventRevoker _profileRenamedRevoker;
::Magpie::Event<uint32_t>::EventRevoker _profileRemovedRevoker;
::Magpie::Event<uint32_t, bool>::EventRevoker _profileMovedRevoker;
Primitives::FlyoutBase::Opening_revoker _contextFlyoutOpeningRevoker;
};
}

View file

@ -108,10 +108,15 @@
</ComboBox>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="Root_NewProfileFlyout_Name" />
<TextBox Height="32"
<TextBox x:Name="NewProfileNameTextBox"
Height="32"
HorizontalAlignment="Stretch"
KeyDown="NewProfileNameTextBox_KeyDown"
Text="{x:Bind NewProfileViewModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{x:Bind NewProfileViewModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.ContextFlyout>
<local:TextMenuFlyout />
</TextBox.ContextFlyout>
</TextBox>
</local:SimpleStackPanel>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="Root_NewProfileFlyout_CopyFrom" />

View file

@ -3,7 +3,7 @@
#if __has_include("ScalingModesPage.g.cpp")
#include "ScalingModesPage.g.cpp"
#endif
#include "ComboBoxHelper.h"
#include "ControlHelper.h"
#include "EffectsService.h"
#include <parallel_hashmap/phmap.h>
@ -19,7 +19,11 @@ ScalingModesPage::ScalingModesPage() {
}
void ScalingModesPage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) {
ComboBoxHelper::DropDownOpened(*this, sender);
ControlHelper::ComboBox_DropDownOpened(sender);
}
void ScalingModesPage::NumberBox_Loaded(IInspectable const& sender, RoutedEventArgs const&) {
ControlHelper::NumberBox_Loaded(sender);
}
void ScalingModesPage::EffectSettingsCard_Loaded(IInspectable const& sender, RoutedEventArgs const&) {

View file

@ -14,6 +14,8 @@ struct ScalingModesPage : ScalingModesPageT<ScalingModesPage> {
void ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&);
void NumberBox_Loaded(IInspectable const& sender, RoutedEventArgs const&);
void EffectSettingsCard_Loaded(IInspectable const& sender, RoutedEventArgs const&);
void AddEffectButton_Click(IInspectable const& sender, RoutedEventArgs const&);
@ -41,8 +43,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct ScalingModesPage : ScalingModesPageT<ScalingModesPage, implementation::ScalingModesPage> {};
}
BASIC_FACTORY(ScalingModesPage)

View file

@ -157,7 +157,11 @@
Margin="0,8,0,20"
KeyDown="{x:Bind RenameTextBox_KeyDown}"
SelectionStart="{x:Bind RenameTextBoxSelectionStart, Mode=OneWay}"
Text="{x:Bind RenameText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{x:Bind RenameText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.ContextFlyout>
<local:TextMenuFlyout />
</TextBox.ContextFlyout>
</TextBox>
<Button x:Uid="ScalingModes_RenameFlyout_OK"
MinWidth="80"
HorizontalAlignment="Right"
@ -372,12 +376,14 @@
</local:SimpleStackPanel.Resources>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="ScalingModes_ScaleFlyout_WidthFactor" />
<muxc:NumberBox NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
<muxc:NumberBox Loaded="NumberBox_Loaded"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
Value="{x:Bind ScalingFactorX, Mode=TwoWay}" />
</local:SimpleStackPanel>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="ScalingModes_ScaleFlyout_HeightFactor" />
<muxc:NumberBox NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
<muxc:NumberBox Loaded="NumberBox_Loaded"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
Value="{x:Bind ScalingFactorY, Mode=TwoWay}" />
</local:SimpleStackPanel>
</local:SimpleStackPanel>
@ -394,12 +400,14 @@
</local:SimpleStackPanel.Resources>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="ScalingModes_ScaleFlyout_WidthPixels" />
<muxc:NumberBox NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
<muxc:NumberBox Loaded="NumberBox_Loaded"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
Value="{x:Bind ScalingPixelsX, Mode=TwoWay}" />
</local:SimpleStackPanel>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="ScalingModes_ScaleFlyout_HeightPixels" />
<muxc:NumberBox NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
<muxc:NumberBox Loaded="NumberBox_Loaded"
NumberFormatter="{x:Bind local:App.DoubleFormatter, Mode=OneTime}"
Value="{x:Bind ScalingPixelsY, Mode=TwoWay}" />
</local:SimpleStackPanel>
</local:SimpleStackPanel>
@ -541,7 +549,11 @@
<TextBlock x:Uid="ScalingModes_NewScalingModeFlyout_Name" />
<TextBox HorizontalAlignment="Stretch"
KeyDown="NewScalingModeNameTextBox_KeyDown"
Text="{x:Bind ViewModel.NewScalingModeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Text="{x:Bind ViewModel.NewScalingModeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.ContextFlyout>
<local:TextMenuFlyout />
</TextBox.ContextFlyout>
</TextBox>
</local:SimpleStackPanel>
<local:SimpleStackPanel Spacing="8">
<TextBlock x:Uid="ScalingModes_NewScalingModeFlyout_CopyFrom" />

View file

@ -102,9 +102,5 @@ struct SettingsCardStyle : SettingsCardStyleT<SettingsCardStyle> {};
}
namespace winrt::Magpie::factory_implementation {
struct SettingsCard : SettingsCardT<SettingsCard, implementation::SettingsCard> {};
struct SettingsCardStyle : SettingsCardStyleT<SettingsCardStyle, implementation::SettingsCardStyle> {};
}
BASIC_FACTORY(SettingsCard)
BASIC_FACTORY(SettingsCardStyle)

View file

@ -81,9 +81,5 @@ struct SettingsExpanderStyle : SettingsExpanderStyleT<SettingsExpanderStyle> {};
}
namespace winrt::Magpie::factory_implementation {
struct SettingsExpander : SettingsExpanderT<SettingsExpander, implementation::SettingsExpander> {};
struct SettingsExpanderStyle : SettingsExpanderStyleT<SettingsExpanderStyle, implementation::SettingsExpanderStyle> {};
}
BASIC_FACTORY(SettingsExpander)
BASIC_FACTORY(SettingsExpanderStyle)

View file

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "SettingsExpanderCornerRadiusConverter.g.h"
namespace winrt::Magpie::implementation {
@ -10,9 +10,4 @@ struct SettingsExpanderCornerRadiusConverter : SettingsExpanderCornerRadiusConve
}
namespace winrt::Magpie::factory_implementation {
struct SettingsExpanderCornerRadiusConverter : SettingsExpanderCornerRadiusConverterT<SettingsExpanderCornerRadiusConverter, implementation::SettingsExpanderCornerRadiusConverter> {
};
}
BASIC_FACTORY(SettingsExpanderCornerRadiusConverter)

View file

@ -32,9 +32,5 @@ struct SettingsGroupStyle : SettingsGroupStyleT<SettingsGroupStyle> {};
}
namespace winrt::Magpie::factory_implementation {
struct SettingsGroup : SettingsGroupT<SettingsGroup, implementation::SettingsGroup> {};
struct SettingsGroupStyle : SettingsGroupStyleT<SettingsGroupStyle, implementation::SettingsGroupStyle> {};
}
BASIC_FACTORY(SettingsGroup)
BASIC_FACTORY(SettingsGroupStyle)

View file

@ -3,7 +3,7 @@
#if __has_include("SettingsPage.g.cpp")
#include "SettingsPage.g.cpp"
#endif
#include "ComboBoxHelper.h"
#include "ControlHelper.h"
using namespace ::Magpie;
using namespace winrt;
@ -21,7 +21,7 @@ void SettingsPage::InitializeComponent() {
}
void SettingsPage::ComboBox_DropDownOpened(IInspectable const& sender, IInspectable const&) const {
ComboBoxHelper::DropDownOpened(*this, sender);
ControlHelper::ComboBox_DropDownOpened(sender);
}
}

View file

@ -19,8 +19,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct SettingsPage : SettingsPageT<SettingsPage, implementation::SettingsPage> {};
}
BASIC_FACTORY(SettingsPage)

View file

@ -53,9 +53,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct ShortcutControl : ShortcutControlT<ShortcutControl, implementation::ShortcutControl> {
};
}
BASIC_FACTORY(ShortcutControl)

View file

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "SimpleStackPanel.g.h"
namespace winrt::Magpie::implementation {
@ -27,9 +27,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct SimpleStackPanel : SimpleStackPanelT<SimpleStackPanel, implementation::SimpleStackPanel> {
};
}
BASIC_FACTORY(SimpleStackPanel)

View file

@ -7,7 +7,6 @@
#include "App.h"
using namespace ::Magpie;
using namespace winrt::Magpie::implementation;
using namespace winrt;
using namespace Windows::UI::Xaml::Controls;

View file

@ -5,7 +5,6 @@ namespace winrt::Magpie::implementation {
// 当 TextBlock 被截断时自动设置 Tooltip
// https://stackoverflow.com/questions/21615593/how-can-i-automatically-show-a-tooltip-if-the-text-is-too-long
struct TextBlockHelper {
static void RegisterDependencyProperties();
static DependencyProperty IsAutoTooltipProperty() { return _isAutoTooltipProperty; }
@ -28,9 +27,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct TextBlockHelper : TextBlockHelperT<TextBlockHelper, implementation::TextBlockHelper> {
};
}
BASIC_FACTORY(TextBlockHelper)

View file

@ -0,0 +1,238 @@
#include "pch.h"
#include "TextMenuFlyout.h"
#if __has_include("TextMenuFlyout.g.cpp")
#include "TextMenuFlyout.g.cpp"
#endif
#include "CommonSharedConstants.h"
using namespace winrt::Windows::UI::Xaml::Input;
namespace winrt::Magpie::implementation {
TextMenuFlyout::TextMenuFlyout() {
// 大部分初始化推迟到 MenuFlyout_Opening
Opening({ this, &TextMenuFlyout::MenuFlyout_Opening });
}
void TextMenuFlyout::MenuFlyout_Opening(IInspectable const&, IInspectable const&) {
FrameworkElement target = Target();
if (!target) {
return;
}
bool hasSelection = false;
bool hasText = false;
bool canUndo = false;
bool canRedo = false;
// 目前使用的 TextBox 和 NumberBox 都支持修改
constexpr bool writable = true;
{
TextBox textBox = target.try_as<TextBox>();
if (!textBox) {
if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
textBox = numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>();
}
}
if (textBox) {
hasSelection = !textBox.SelectedText().empty();
hasText = !textBox.Text().empty();
canUndo = textBox.CanUndo();
canRedo = textBox.CanRedo();
}
}
// 延迟初始化
if (!_copy) {
std::vector<MenuFlyoutItemBase> items;
ResourceLoader resourceLoader =
ResourceLoader::GetForCurrentView(CommonSharedConstants::APP_RESOURCE_MAP_ID);
if (writable) {
_cut = items.emplace_back(_CreateMenuItem(
Symbol::Cut,
resourceLoader.GetString(L"TextMenuFlyout_Cut"),
{ this, &TextMenuFlyout::Cut_Click },
VirtualKeyModifiers::Control,
VirtualKey::X
));
}
_copy = items.emplace_back(_CreateMenuItem(
Symbol::Copy,
resourceLoader.GetString(L"TextMenuFlyout_Copy"),
{ this, &TextMenuFlyout::Copy_Click },
VirtualKeyModifiers::Control,
VirtualKey::C
));
if (writable) {
items.emplace_back(_CreateMenuItem(
Symbol::Paste,
resourceLoader.GetString(L"TextMenuFlyout_Paste"),
{ this, &TextMenuFlyout::Paste_Click },
VirtualKeyModifiers::Control,
VirtualKey::V
));
_undo = _CreateMenuItem(
Symbol::Undo,
resourceLoader.GetString(L"TextMenuFlyout_Undo"),
{ this, &TextMenuFlyout::Undo_Click },
VirtualKeyModifiers::Control,
VirtualKey::Z
);
items.emplace_back(_undo);
_redo = _CreateMenuItem(
Symbol::Redo,
resourceLoader.GetString(L"TextMenuFlyout_Redo"),
{ this, &TextMenuFlyout::Redo_Click },
VirtualKeyModifiers::Control,
VirtualKey::Y
);
items.emplace_back(_redo);
}
_selectAll = _CreateMenuItem(
Symbol{},
resourceLoader.GetString(L"TextMenuFlyout_SelectAll"),
{ this, &TextMenuFlyout::SelectAll_Click },
VirtualKeyModifiers::Control,
VirtualKey::A
);
items.emplace_back(_selectAll);
Items().ReplaceAll({ items.data(), (uint32_t)items.size() });
}
_copy.Visibility(hasSelection ? Visibility::Visible : Visibility::Collapsed);
_selectAll.Visibility(hasText ? Visibility::Visible : Visibility::Collapsed);
if (writable) {
_cut.Visibility(hasSelection ? Visibility::Visible : Visibility::Collapsed);
_undo.Visibility(canUndo ? Visibility::Visible : Visibility::Collapsed);
_redo.Visibility(canRedo ? Visibility::Visible : Visibility::Collapsed);
}
}
void TextMenuFlyout::Cut_Click(IInspectable const&, RoutedEventArgs const&) {
// 右键菜单关闭后仍会接收到文本框上的快捷键事件,可以安全忽略,因为 TextBox
// 仍会正常处理,除了 Ctrl+A
FrameworkElement target = Target();
if (!target) {
return;
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.CutSelectionToClipboard();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>().GetTemplateChild(L"InputBox").as<TextBox>().CutSelectionToClipboard();
}
}
void TextMenuFlyout::Copy_Click(IInspectable const&, RoutedEventArgs const&) {
FrameworkElement target = Target();
if (!target) {
return;
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.CopySelectionToClipboard();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.CopySelectionToClipboard();
}
}
void TextMenuFlyout::Paste_Click(IInspectable const&, RoutedEventArgs const&) {
FrameworkElement target = Target();
if (!target) {
return;
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.PasteFromClipboard();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.PasteFromClipboard();
}
}
void TextMenuFlyout::Undo_Click(IInspectable const&, RoutedEventArgs const&) {
FrameworkElement target = Target();
if (!target) {
return;
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.Undo();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.Undo();
}
}
void TextMenuFlyout::Redo_Click(IInspectable const&, RoutedEventArgs const&) {
FrameworkElement target = Target();
if (!target) {
return;
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.Redo();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.Redo();
}
}
void TextMenuFlyout::SelectAll_Click(IInspectable const&, RoutedEventArgs const&) {
// !!! HACK !!!
// 由于 WinUI 的 bug一旦右键菜单被打开过一次TextBox 就不再处理 Ctrl+A而我们
// 仍会收到回调。这里必须自己实现全选功能,否则 Ctrl+A 会永久失效。
IInspectable target = Target();
if (!target) {
target = FocusManager::GetFocusedElement(XamlRoot());
if (!target) {
return;
}
}
if (TextBox textBox = target.try_as<TextBox>()) {
textBox.SelectAll();
} else if (MUXC::NumberBox numberBox = target.try_as<MUXC::NumberBox>()) {
numberBox.as<IControlProtected>()
.GetTemplateChild(L"InputBox")
.as<TextBox>()
.SelectAll();
}
}
MenuFlyoutItemBase TextMenuFlyout::_CreateMenuItem(
Symbol symbol,
hstring text,
RoutedEventHandler click,
VirtualKeyModifiers modifiers,
VirtualKey key
) {
KeyboardAccelerator accel;
accel.Modifiers(modifiers);
accel.Key(key);
MenuFlyoutItem item;
if (symbol != Symbol{}) {
item.Icon(SymbolIcon{ std::move(symbol) });
}
item.Text(std::move(text));
item.Click(std::move(click));
item.KeyboardAccelerators().Append(std::move(accel));
return item;
}
}

View file

@ -0,0 +1,46 @@
#pragma once
#include "TextMenuFlyout.g.h"
namespace winrt::Magpie::implementation {
// GH#1070
//
// 移植自 https://github.com/microsoft/terminal/pull/18854
//
// 之所以使用自定义右键菜单,是因为当一个线程中创建了多个 XAML Islands 窗口,默认的
// 右键菜单会导致崩溃。应确保覆盖所有右键菜单,目前包括 TextBox 和 MUXC::NumberBox。
//
// 我还尝试过其他方案(如 MUXC::TextCommandBarFlyout但都不尽人意。目前看来对每个
// TextBox 和 NumberBox 单独设置 TextMenuFlyout 是最稳妥的方案。
struct TextMenuFlyout : TextMenuFlyoutT<TextMenuFlyout> {
TextMenuFlyout();
void MenuFlyout_Opening(IInspectable const&, IInspectable const&);
void Cut_Click(IInspectable const&, RoutedEventArgs const&);
void Copy_Click(IInspectable const&, RoutedEventArgs const&);
void Paste_Click(IInspectable const&, RoutedEventArgs const&);
void Undo_Click(IInspectable const&, RoutedEventArgs const&);
void Redo_Click(IInspectable const&, RoutedEventArgs const&);
void SelectAll_Click(IInspectable const&, RoutedEventArgs const&);
private:
MenuFlyoutItemBase _CreateMenuItem(
Symbol symbol,
hstring text,
RoutedEventHandler click,
VirtualKeyModifiers modifiers,
VirtualKey key
);
// 始终存在的条目
MenuFlyoutItemBase _copy{ nullptr };
MenuFlyoutItemBase _selectAll{ nullptr };
// 只适用于可编辑的控件的条目
MenuFlyoutItemBase _cut{ nullptr };
MenuFlyoutItemBase _undo{ nullptr };
MenuFlyoutItemBase _redo{ nullptr };
};
}
BASIC_FACTORY(TextMenuFlyout)

View file

@ -0,0 +1,6 @@
namespace Magpie {
[default_interface]
runtimeclass TextMenuFlyout : Windows.UI.Xaml.Controls.MenuFlyout {
TextMenuFlyout();
}
}

View file

@ -30,9 +30,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct TitleBarControl : TitleBarControlT<TitleBarControl, implementation::TitleBarControl> {
};
}
BASIC_FACTORY(TitleBarControl)

View file

@ -109,9 +109,4 @@ private:
}
namespace winrt::Magpie::factory_implementation {
struct WrapPanel : WrapPanelT<WrapPanel, implementation::WrapPanel> {
};
}
BASIC_FACTORY(WrapPanel)

View file

@ -99,3 +99,10 @@ using namespace std::chrono_literals;
// 导入 winrt 命名空间的 co_await 重载
// https://devblogs.microsoft.com/oldnewthing/20191219-00/?p=103230
using winrt::operator co_await;
// 简化工厂类的创建
#define BASIC_FACTORY(className) \
namespace winrt::Magpie::factory_implementation { \
struct className : className##T<className, implementation::className> {}; \
}