【UE5】新建Editor Standalone Window插件,之前(或当前)创建插件的按钮消失(被顶掉/占用)的问题
问题描述
当你在同一个项目里先后新建两个Editor Standalone Window的插件模板:
然后你会发现,菜单栏里并不会出现两个插件入口。先前创建的插件入口消失了。后一个插件把前一个的入口按钮给顶掉了:
之前:
新建一个TestToolBarWiondow2后:
此时发现TestToolBarWindow的按钮消失了(但是该插件并没有被我禁用)
或者新建的按钮消失了(如果没有指定依赖顺序和配置加载阶段之类的,那么插件会按照字典序先后加载。最终留下的按钮是字典序最后面的那个。。。)
原因分析
先来看下这个插件模板的按钮注册大致流程,以TestToolBarWindow为例,注册菜单的核心逻辑在TestToolBarWindow.cpp里的这个函数:
void FTestToolbarWindowModule::RegisterMenus()
{// Owner will be used for cleanup in call to UToolMenus::UnregisterOwnerFToolMenuOwnerScoped OwnerScoped(this);{UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");{FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");Section.AddMenuEntryWithCommandList(FTestToolbarWindowCommands::Get().OpenPluginWindow, PluginCommands);}}{UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");{FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");{FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FTestToolbarWindowCommands::Get().OpenPluginWindow));Entry.SetCommandList(PluginCommands);}}}
}
它这边直接写死了获取的"LevelEditor.MainMenu.Window"这个Menu下的名为"WindowLayout"的Section:
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
所以可以得知,我们创建的两个插件的按钮都是默认写到同一个Menu的同一个Section下的。
然而:
一个ToolMenu下会有多个Section(可以当成是逻辑分组),然后每个Section里会有Entry(可以直观理解为按钮入口);当你在同一个Section里Add一个已经有同名Entry的Entry,那么会把之前的同名的Entry顶替掉。
而我们注册的按钮的Entry的名字是由啥决定的呢?
点进AddMenuEntryWithCommandList,一路查看定义(这边就不贴全代码了),会发现Entry使用的名字,如果没有额外指定的话,实际上就是这个const TSharedPtr< const FUICommandInfo >& InCommand的CommandName字段:
FToolMenuEntry& FToolMenuSection::AddMenuEntryWithCommandList(const TSharedPtr< const FUICommandInfo >& InCommand, const TSharedPtr< const FUICommandList >& InCommandList, const TAttribute<FText>& InLabelOverride, const TAttribute<FText>& InToolTipOverride, const TAttribute<FSlateIcon>& InIconOverride, const FName InTutorialHighlightName, const TOptional<FName> InNameOverride)
{return AddEntry(FToolMenuEntry::InitMenuEntryWithCommandList(InCommand, InCommandList, InLabelOverride, InToolTipOverride, InIconOverride, InTutorialHighlightName, InNameOverride));
}
即我们的FTestToolbarWindowCommands的OpenPluginWindow的CommandName字段。
然后我们来追溯下这个字段是怎么设置的。
来到TestToolbarWindowCommands.cpp这里:
void FTestToolbarWindowCommands::RegisterCommands()
{UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());
}
看看UI_COMMAND宏的定义:
#define UI_COMMAND( CommandId, FriendlyName, InDescription, CommandType, InDefaultChord, ... ) \MakeUICommand_InternalUseOnly( this, CommandId, TEXT(LOCTEXT_NAMESPACE), TEXT(#CommandId), TEXT(#CommandId) TEXT("_ToolTip"), "." #CommandId, TEXT(FriendlyName), TEXT(InDescription), CommandType, InDefaultChord, ## __VA_ARGS__ );
再进去一层看MakeUICommand_InternalUseOnly的定义:
void MakeUICommand_InternalUseOnly( FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* InSubNamespace, const TCHAR* InCommandName, const TCHAR* InCommandNameUnderscoreTooltip, const ANSICHAR* DotCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord)
{static const FString UICommandsStr(TEXT("UICommands"));const FString Namespace = InSubNamespace && FCString::Strlen(InSubNamespace) > 0 ? UICommandsStr + TEXT(".") + InSubNamespace : UICommandsStr;FUICommandInfo::MakeCommandInfo(This->AsShared(),OutCommand,InCommandName,FText::AsLocalizable_Advanced( Namespace, InCommandName, FriendlyName ),FText::AsLocalizable_Advanced( Namespace, InCommandNameUnderscoreTooltip, InDescription ),FSlateIcon( This->GetStyleSetName(), ISlateStyle::Join( This->GetContextName(), DotCommandName ) ),CommandType,InDefaultChord,InAlternateDefaultChord);
}
再进去一层看看MakeCommandInfo的定义,即可看到CommandName的赋值:
void FUICommandInfo::MakeCommandInfo( const TSharedRef<class FBindingContext>& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord, const FName InBundle)
{ensureMsgf( !InCommandLabel.IsEmpty(), TEXT("Command labels cannot be empty") );LLM_SCOPE_BYTAG(UI_Slate);OutCommand = MakeShareable( new FUICommandInfo( InContext->GetContextName() ) );OutCommand->CommandName = InCommandName;OutCommand->Label = InCommandLabel;OutCommand->Description = InCommandDesc;OutCommand->Icon = InIcon;OutCommand->UserInterfaceType = InUserInterfaceType;OutCommand->DefaultChords[static_cast<uint8>(EMultipleKeyBindingIndex::Primary)] = InDefaultChord;OutCommand->DefaultChords[static_cast<uint8>(EMultipleKeyBindingIndex::Secondary)] = InAlternateDefaultChord;OutCommand->Bundle = InBundle;FInputBindingManager::Get().CreateInputCommand( InContext, OutCommand.ToSharedRef() );
}
那么真相已经浮出水面。 从MakeCommandInfo回溯可以得知,一个Command的CommandName就是UI_COMMAND宏的第一个变量的这个变量名!比如这里:
UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());
那这个Command的CommandNanme就是"OpenPluginWindow"
那怪不得会串号。。。。。。
解决方案
建议创建菜单插件的时候给你自己的插件建立一个独有的Section名字,来替换掉这里的"WindowLayout":
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");