【UE5】新建Editor Standalone Window插件,之前(或当前)创建插件的按钮消失(被顶掉/占用)的问题

问题描述

当你在同一个项目里先后新建两个Editor Standalone Window的插件模板:

然后你会发现,菜单栏里并不会出现两个插件入口。先前创建的插件入口消失了。后一个插件把前一个的入口按钮给顶掉了:

之前:

新建一个TestToolBarWiondow2后:

此时发现TestToolBarWindow的按钮消失了(但是该插件并没有被我禁用)

或者新建的按钮消失了(如果没有指定依赖顺序和配置加载阶段之类的,那么插件会按照字典序先后加载。最终留下的按钮是字典序最后面的那个。。。)

原因分析

先来看下这个插件模板的按钮注册大致流程,以TestToolBarWindow 为例,注册菜单的核心逻辑在TestToolBarWindow.cpp里的这个函数:

cpp 复制代码
void FTestToolbarWindowModule::RegisterMenus()
{
	// Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
	FToolMenuOwnerScoped 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

cpp 复制代码
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");

所以可以得知,我们创建的两个插件的按钮都是默认写到同一个Menu的同一个Section下的。

然而:

一个ToolMenu下会有多个Section(可以当成是逻辑分组),然后每个Section里会有Entry(可以直观理解为按钮入口);当你在同一个Section里Add一个已经有同名Entry的Entry,那么会把之前的同名的Entry顶替掉。

而我们注册的按钮的Entry的名字是由啥决定的呢?

点进AddMenuEntryWithCommandList ,一路查看定义(这边就不贴全代码了),会发现Entry使用的名字,如果没有额外指定的话,实际上就是这个const TSharedPtr< const FUICommandInfo >& InCommandCommandName字段:

cpp 复制代码
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));
}

即我们的FTestToolbarWindowCommandsOpenPluginWindowCommandName字段。

然后我们来追溯下这个字段是怎么设置的。

来到TestToolbarWindowCommands.cpp这里:

cpp 复制代码
void FTestToolbarWindowCommands::RegisterCommands()
{
	UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());
}

看看UI_COMMAND宏的定义:

cpp 复制代码
#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的定义:

cpp 复制代码
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的赋值:

cpp 复制代码
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 回溯可以得知,一个CommandCommandName 就是UI_COMMAND 宏的第一个变量的这个变量名!比如这里:

cpp 复制代码
UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());

那这个Command的CommandNanme就是"OpenPluginWindow"

那怪不得会串号。。。。。。

解决方案

建议创建菜单插件的时候给你自己的插件建立一个独有的Section名字,来替换掉这里的"WindowLayout"

cpp 复制代码
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
相关推荐
zhangzhangkeji4 天前
UE5 C++(71):文件是否存在,文件夹是否存在,FPaths :: FileExists( const FString & InPath) ;
ue5
妙为5 天前
UE5角色穿过石头穿模
ue5·unreal engine5·角色穿越石头·穿模
技术策划Boring6 天前
2025年工作复盘:开放世界3A项目配置管线与性能监控的探索
游戏·ue5·虚幻·p4·perforce
zhangzhangkeji7 天前
UE5 C++(70-2):定义成员函数 getCleanDirectory(..) 和枚举类 EFileDirectoryType,来获得目录
ue5
avi91118 天前
UE4-UE5虚幻引擎-前置学习三,优化,基础CPP
ue5·ue4·游戏开发·虚幻·游戏优化·游戏代码
zhangzhangkeji9 天前
UE5线程进阶(3-2):任务图的相关源码整理。 FGraphEvent 与 TGraphTask 的区别和联系
ue5
zhangzhangkeji10 天前
UE5线程进阶(3-1):
ue5
zhangzhangkeji10 天前
UE5线程进阶(2-3):enum ENamedThreads命名空间 :: Type : int32 { RHIThread = 0 } 是渲染硬件接口线程
ue5
zhangzhangkeji11 天前
UE5线程进阶(2-1):枚举类EAsyncExecution,作业类TAsyncRunnable、TAsyncQueuedWork,及全局线程函数 Async(..),及线程调用的 4 种方法总结
ue5
zhangzhangkeji12 天前
UE5线程进阶(1):
ue5