跳至主要內容

ED04.ToolMenus|在任意扩展点插入自定义按钮

Mr.Si大约 5 分钟u++

回顾

头像
之前的章节我们对编辑器的一些图标有了初步的认识。同时将自己的类注册到了内容浏览器的右键中。

成果预览

UToolMenus

头像

ToolMenus 是编辑器中用于创建菜单和工具栏的工具类。

扩展点

头像

ToolMenus 可以在几乎所有的扩展点注册,包括主菜单、工具栏、右键菜单等。


Debug

头像
偷偷问一句!这些绿色的字是怎么搞出来的!
头像

编辑器偏好设置-->开发者工具-->显示UI扩展点

实践

建议先看看完下面的步骤,再回头来看看这个时序图。

引入模块

PrivateDependencyModuleNames.AddRange(new string[]{"ToolMenus",});

注册命令|Command

头像
声明和定义一个编辑器命令(Command)。
头像
干啥用的?
头像
命令,顾名思义,统一管理编辑器命令以及支持快捷键绑定(可以指定 FInputChord)。
头像
打住打住!我不知道你在说什么!什么是FInputChord?

FInputChord 是 UE5 中用于绑定快捷键的结构体,它定义了一组按键(FKey)和可选的修饰键(Ctrl、Shift、Alt、Cmd)。

头像
别急,先跟着敲一遍再说。

#pragma once

#include "Framework/Commands/Commands.h"
#include "DataSystemEditorStyle.h"

/**
 *  FDataSystemEditorCommands 继承自 TCommands,用于注册编辑器中的命令
 *  主要作用:
 *  - 统一管理工具栏、菜单的命令
 *  - 允许绑定快捷键
 *  - 使命令可复用,支持 Undo/Redo
 */
class FDataSystemEditorCommands : public TCommands<FDataSystemEditorCommands>
{
public:

	/**
	 * 构造函数
	 * @param ContextName 命令上下文名称(唯一标识)
	 * @param ContextDesc 本地化名称(UI 上显示)
	 * @param Parent 用于分组的命令名称(一般用 NAME_None)
	 * @param StyleSetName 样式名称(用于获取图标)
	 */
	FDataSystemEditorCommands()
		: TCommands<FDataSystemEditorCommands>(
			TEXT("DataSystemEditor"),  // 命令的唯一 ID
			NSLOCTEXT("Contexts", "DataSystemEditor", "Data System Plugin"), // UI 显示名称
			NAME_None,  // 没有父命令组
			FDataSystemEditorStyle::GetStyleSetName() // 样式(图标)
		)
	{
	}

	/** 继承的 TCommands<> 方法:注册所有命令 */
	virtual void RegisterCommands() override;

public:
	/** 代表插件操作的命令(用于工具栏按钮、菜单项) */
	TSharedPtr<FUICommandInfo> PluginAction;
};
#include "DataSystemEditorCommands.h"

#define LOCTEXT_NAMESPACE "FDataSystemEditorCommands"

void FDataSystemEditorCommands::RegisterCommands()
{
	// 定义 PluginAction 命令(工具栏按钮、菜单项使用)
	UI_COMMAND(
		PluginAction, // 变量
		"Open Data System Editor", // 按钮名称
		"Opens the Data System Editor window.", // 提示文本(鼠标悬停时显示)
		EUserInterfaceActionType::Button, // 按钮类型(普通按钮)
		FInputChord() // 快捷键(为空表示没有默认快捷键)
	);
}

#undef LOCTEXT_NAMESPACE


注册命令|MapAction

头像
定义完后回到模块中使用我们的命令
头像
这里的MapAction啥意思?
头像
一句话解释:把一个“按钮命令” 和 “你想做的事” 绑在一起的过程。

注意: 此MapAction并不是必须的,例如有的下拉菜单,没有按钮命令。

PluginCommands = MakeShareable(new FUICommandList);
	
PluginCommands->MapAction(
		FDataSystemEditorCommands::Get().PluginAction,
		FExecuteAction::CreateRaw(this, &FDataSystemEditorModule::PluginButtonClicked),
		FCanExecuteAction());

菜单|绑定回调

头像
接着咱们开始注册菜单,注册菜单之前先注册菜单的回调委托。
UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FDataSystemEditorModule::RegisterMenus));

菜单|注册菜单

UToolMenu* ToolbarMenu = UToolMenus::Get()->RegisterMenu("LevelEditor.LevelEditorToolBar.SettingsToolbar", NAME_None, EMultiBoxType::SlimHorizontalToolBar);

菜单|按键布局

UENUM(BlueprintType)
enum class EMultiBoxType : uint8
{
    MenuBar,                       // 水平菜单栏(如主菜单)
    ToolBar,                       // 标准工具栏
    VerticalToolBar,              // 垂直工具栏
    SlimHorizontalToolBar,        // 扁平水平工具栏(图标 + 文本水平对齐)
    UniformToolBar,               // 均匀布局的工具栏(目前仅支持水平)
    Menu,                         // 下拉菜单或右键菜单
    ButtonRow,                    // 多行按钮排列(最多N个按钮一行)
    SlimHorizontalUniformToolBar // 扁平均匀工具栏
};

菜单|显示结构

  • Section 和 Entry
概念作用
Section(区块)用于组织菜单选项的 分组,类似于 菜单分类
Entry(选项)具体的 菜单项,用户可以点击的按钮

Section:用于组织菜单选项的 分组

FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("DataSystem");

Entry:具体的 菜单项

// 添加一个工具栏按钮,该按钮绑定 UI_COMMAND 定义的 PluginAction 命令
FToolMenuEntry& Entry = Section.AddEntry(
    FToolMenuEntry::InitToolBarButton(FDataSystemEditorCommands::Get().PluginAction)
);

// 设置按钮的命令列表(用于支持快捷键、状态等)
Entry.SetCommandList(PluginCommands);

菜单|按钮方式

函数名用途使用场景
InitMenuEntry普通菜单项右键菜单、主菜单
InitMenuEntryWithCommandList菜单项 + 自定义命令列表多命令源场景
InitToolBarButton工具栏按钮顶部工具栏、自定义工具条
InitSubMenu子菜单项多级菜单
InitDynamicEntry动态生成的菜单项项目很多、运行时生成内容
InitComboButton复合按钮例如下拉菜单

菜单|系统样式

SlimHorizontalToolBar的样式用AssetEditorToolbar

UToolMenu* ToolbarMenu = UToolMenus::Get()->RegisterMenu(
			"LevelEditor.LevelEditorToolBar.SettingsToolbar",
			NAME_None,
			EMultiBoxType::SlimHorizontalToolBar
);
ToolbarMenu->StyleName = "AssetEditorToolbar";





 

Entry的样式用CalloutToolbar

FToolMenuEntry& Entry = Section.AddEntry(
    FToolMenuEntry::InitToolBarButton(FDataSystemEditorCommands::Get().PluginAction)
    
Entry.StyleNameOverride = "CalloutToolbar";



 
头像
这些系统样式参考有吗?
头像
用这个Fab里的插件看吧

官方Fabopen in new window


回到案例

头像
OK!有了这些基础知识我们可以来简单的实现一下我们的案例了!
void FDataSystemEditorModule::RegisterMenus()
{
	RegisterComboMenus();
	
	FToolMenuOwnerScoped OwnerScoped(this);
	{
		UToolMenu* ToolbarMenu = UToolMenus::Get()->RegisterMenu(
			"LevelEditor.LevelEditorToolBar.SettingsToolbar",
			NAME_None,
			EMultiBoxType::SlimHorizontalToolBar
		);
		ToolbarMenu->StyleName = "AssetEditorToolbar";
		FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("DataSystem");

		FToolMenuEntry& Entry = Section.AddEntry(
			FToolMenuEntry::InitComboButton(
				"DataSystemComboButton",
				FUIAction(),
				FOnGetContent::CreateStatic(&FDataSystemEditorModule::GenerateToolbarSettingsMenu, PluginCommands.ToSharedRef()),
				LOCTEXT("ComboButtonLabel", "Data System"),
				LOCTEXT("ComboButtonToolTip", "Data System Settings & Tooling"),
				FSlateIcon(FDataSystemEditorStyle::GetStyleSetName(), "DataSystemEditor.OpenPluginWindow"),
				false 
			)
		);
		Entry.StyleNameOverride = "CalloutToolbar";
		Section.AddEntry(Entry);
	}
}
void FDataSystemEditorModule::RegisterComboMenus() const
{
	UToolMenu* Menu = UToolMenus::Get()->RegisterMenu("LevelEditor.LevelEditorToolBar.DataSystemEditor.ComboMenu");

	struct Local
	{
		static void OpenSettings(const FName ContainerName, const FName CategoryName, const FName SectionName)
		{
			FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer(ContainerName, CategoryName, SectionName);
		}

		static void OpenDataDefinitionManager()
		{
			// TODO: 打开我们的DataDefinitionManager
		}

		static void OpenDocumentation()
		{
			const FString URL = TEXT("https://rendertool.github.io/RenderDoc/unreal/");
			FPlatformProcess::LaunchURL(*URL, nullptr, nullptr);
		}
	};
	{
		FToolMenuSection& Section = Menu->AddSection("ProjectSection", LOCTEXT("ProjectHeading", "Project"));
		Section.AddMenuEntry(
			"DataDefinitionManager",
			LOCTEXT("DataDefinitionManager", "Data Definition Manager"),
			LOCTEXT("DataDefinitionManagerTooltip", "The Data Definition Manager is used to convert a Datatable into a DataAsset."),
			FSlateIcon(FDataSystemEditorStyle::GetStyleSetName(), "ClassIcon.DataDefinition"),
			FUIAction(FExecuteAction::CreateStatic(&Local::OpenDataDefinitionManager))
		);
	}
	{
		FToolMenuSection& Section = Menu->AddSection("Documentation", LOCTEXT("DocumentationHeading", "Documentation"));
		Section.AddMenuEntry(
			"DataSystemDocumentation",
			LOCTEXT("DataSystemDocumentation", "DataSystem Documentation"),
			LOCTEXT("DataSystemDocumentationToolTip", "DataSystem documentation"),
			FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.BrowseDocumentation"),
			FUIAction(FExecuteAction::CreateStatic(&Local::OpenDocumentation))
		);
	}
}







 


































































总结

头像
细心的你应该已经发现了吧,其实就是改了扩展点的名称。
头像
可是,这些扩展点我去哪里得到啊?即便编辑器里是打开了UI扩展点,好像有没有一个样啊!
头像
这个确实比较棘手。