Skip to content

UE Editor Menu Extension

Record how to extend the editor menu in UE

Hook

Hook can be understood as an anchor point for extending menus. We can set the newly added menu commands to appear before or after the Hook. The built-in editor menu commands in UE generally come with a Hook. In UE5, open Edit - Editor Preferences - General - Others - Show UI extension points to display all menu Hooks.

Module dependency

You need to add the required modules LevelEditor, Slate, SlateCore, EditorStyle, EditorWidgets, UnrealEd, ToolMenus to the .Build.cs file of the project.

PrivateDependencyModuleNames.AddRange(
    new string[]
    {
        "Core",
        "Engine",
        "CoreUObject",
        "LevelEditor",
        "Slate",
        "SlateCore",
        "EditorStyle",
        "EditorWidgets",
        "UnrealEd",
        "ToolMenus",
    }
    );

Add menu bar

Directly enter the code

auto MenuExtender = MakeShared<FExtender>();

MenuExtender->AddMenuBarExtension(
    "Help", EExtensionHook::After,      // Create After Help
    nullptr,
    FMenuBarExtensionDelegate::CreateLambda([](FMenuBarBuilder& MenuBarBuilder)
    {
        MenuBarBuilder.AddPullDownMenu(
            TEXT("MenuTest"),       // Name
            TEXT("MenuTest"),       // Tips
            FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
            {
                // create sub menus
            }),
            TEXT("MenuText"));      // New Hook
    })
);
FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor").GetMenuExtensibilityManager()->AddExtender(MenuExtender);

Executing the above code, you can see that a menu bar MenuTest has been added after the Help.

Add Command

Using the MenuBuilder.AddMenuEntry interface:

// Inside MenuTest Lambda
MenuBuilder.AddMenuEntry(
    FText::FromName("MenuTestAction"), FText::FromName("MenuTestAction"),
    FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
    {
        // do action
    })));

Put the above code into CreateLambda to generate menu commands:

Use MenuBuilder.BeginSection and MenuBuilder.EndSection:

MenuBuilder.BeginSection(NAME_None, FText::FromName("MenuTestSection"));
// code to create action
MenuBuilder.EndSection();

Separator

MenuBuilder.AddMenuSeparator();

Submenus are similar to menus and need to be defined inside Lambda:

MenuBuilder.AddSubMenu(
    FText::FromName("MenuTestSub"),
    FText::FromName("MenuTestSub"),
    FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
    {
        MenuBuilder.AddMenuEntry(
            FText::FromName("MenuTestSubAction"), FText::FromName("MenuTestSubAction"),
            FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
            {
                // do action
            })));
    }));

SlateUI Control

You can also add UI controls:

MenuBuilder.AddWidget(
    SNew(SHorizontalBox)
        + SHorizontalBox::Slot()
        .AutoWidth()
        [
            SNew(SEditableTextBox)
            .MinDesiredWidth(50)
            .Text(FText::FromName("MenuTestWidget"))
        ]

        + SHorizontalBox::Slot()
        .AutoWidth()
        .Padding(5, 0, 0, 0)
        [
        SNew(SButton)
        .Text(FText::FromName("ExtendWidget"))
        .OnClicked(FOnClicked::CreateLambda([]()
        {
            // do action
            return FReply::Handled();
        }))
        ],
    FText::GetEmpty()
);

The content related to Slate UI is not elaborated here. If you are interested, you can look for another article to read.

Hook Add Menu

For example, add a command in Tools - Programming:

MenuExtender->AddMenuExtension(
    "Programming", EExtensionHook::After,
    nullptr,
    FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
    {
        MenuBuilder.AddMenuEntry(
        FText::FromName("MenuTestAction"), FText::FromName("MenuTestAction"),
        FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
        {
            // do action
        })));
    })
);

Similarly, you can add other types of menus.

Complete Code

void BuildTestMenu()
{
    auto MenuExtender = MakeShared<FExtender>();

    MenuExtender->AddMenuBarExtension(
        "Help", EExtensionHook::After,
        nullptr,
        FMenuBarExtensionDelegate::CreateLambda([](FMenuBarBuilder& MenuBarBuilder)
        {
            MenuBarBuilder.AddPullDownMenu(
                FText::FromName("MenuTest"),
                FText::FromName("MenuTest"),
                FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
                {
                    MenuBuilder.BeginSection(NAME_None, FText::FromName("MenuTestSection"));
                    MenuBuilder.AddMenuSeparator();
                    MenuBuilder.AddMenuEntry(
                        FText::FromName("MenuTestAction"), FText::FromName("MenuTestAction"),
                        FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
                        {
                            // do action
                        })));

                    MenuBuilder.AddSubMenu(
                        FText::FromName("MenuTestSubb"),
                        FText::FromName("MenuTestSubb"),
                        FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
                        {
                            MenuBuilder.AddMenuEntry(
                                FText::FromName("MenuTestSubAction"), FText::FromName("MenuTestSubAction"),
                                FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
                                {
                                    // do action
                                })));
                        }));
                    MenuBuilder.EndSection();

                    MenuBuilder.AddWidget(
                    SNew(SHorizontalBox)
                         + SHorizontalBox::Slot()
                         .AutoWidth()
                         [
                             SNew(SEditableTextBox)
                             .MinDesiredWidth(50)
                             .Text(FText::FromName("MenuTestWidget"))
                         ]
                         + SHorizontalBox::Slot()
                         .AutoWidth()
                         .Padding(5, 0, 0, 0)
                         [
                            SNew(SButton)
                            .Text(FText::FromName("ExtendWidget"))
                            .OnClicked(FOnClicked::CreateLambda([]()
                            {
                                // do action
                                return FReply::Handled();
                            }))
                         ],
                     FText::GetEmpty()
                    );
                }),
                "MenuTest");
        })
    );

    MenuExtender->AddMenuExtension(
        "Programming", EExtensionHook::After,
        nullptr,
        FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
        {
            MenuBuilder.AddMenuEntry(
            FText::FromName("MenuTestAction"), FText::FromName("MenuTestAction"),
            FSlateIcon(), FUIAction(FExecuteAction::CreateLambda([]()
            {
                // do action
            })));
        })
    );

    FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor").GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}

Original: https://wiki.disenone.site/en

This post is protected by CC BY-NC-SA 4.0 agreement, should be reproduced with attribution.

This post is translated using ChatGPT, please feedback if any omissions.