How to make a C++ event callable from within the editor?

I’ve created a customization to add a button to all static mesh actors

Here’s the code:

MyActorCustomization.h



#pragma once

#include "IDetailCustomization.h"
#include "IPropertyChangeListener.h"

DECLARE_LOG_CATEGORY_EXTERN(LogMyActorCustomization, Log, All);

class MYGAMEEDITOR_API FMyActorCustomization : public IDetailCustomization {
public:
	// IDetailCustomization interface
	virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
	// End of IDetailCustomization interface

	static TSharedRef<IDetailCustomization> MakeInstance();

	static FReply ExecuteCommand(IDetailLayoutBuilder* DetailBuilder);
};


MyActorCustomization.cpp


#include "MyGameEditorPrivatePCH.h"
#include "MyActorCustomization.h"
#include "PropertyEditing.h"
#include "IDetailsView.h"


#define LOCTEXT_NAMESPACE "DungeonArchitectEditorModule" 
DEFINE_LOG_CATEGORY(LogMyActorCustomization)

template<typename T>
T* GetBuilderObject(IDetailLayoutBuilder* DetailBuilder) {
	TArray<TWeakObjectPtr<UObject>> OutObjects;
	DetailBuilder->GetObjectsBeingCustomized(OutObjects);
	T* Obj = nullptr;
	if (OutObjects.Num() > 0) {
		Obj = Cast<T>(OutObjects[0].Get());
	}
	return Obj;
}

//////////////// FMyActorCustomization ////////////////////
void FMyActorCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	IDetailCategoryBuilder& Category = DetailBuilder.EditCategory("My Category");
	FText Caption = FText::FromString("My Custom Button");
	Category.AddCustomRow(Caption)
		.ValueContent()
		
			SNew(SButton)
			.Text(Caption)
			.OnClicked(FOnClicked::CreateStatic(&FMyActorCustomization::ExecuteCommand, &DetailBuilder))
		];
}

TSharedRef<IDetailCustomization> FMyActorCustomization::MakeInstance()
{
	return MakeShareable(new FMyActorCustomization);
}

FReply FMyActorCustomization::ExecuteCommand(IDetailLayoutBuilder* DetailBuilder)
{
	AStaticMeshActor* StaticMesh = GetBuilderObject<AStaticMeshActor>(DetailBuilder);
	if (StaticMesh) {
		UE_LOG(LogMyActorCustomization, Log, TEXT("Running custom code"));
		// TODO: Add your button logic here

	}
	return FReply::Handled();
}

#undef LOCTEXT_NAMESPACE


MyGameEditor.h


// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"
#include "ModuleManager.h"
#include "UnrealEd.h"

DECLARE_LOG_CATEGORY_EXTERN(MyGameEditor, All, All)

class FMyGameEditorModule : public IModuleInterface
{
public:
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;

};


MyGameEditor.cpp


// Fill out your copyright notice in the Description page of Project Settings.
#include "MyGameEditorPrivatePCH.h"
#include "MyGameEditor.h"
#include "ModuleManager.h"
#include "MyActorCustomization.h"
#include "PropertyEditorModule.h"

IMPLEMENT_GAME_MODULE(FMyGameEditorModule, MyGameEditor);

DEFINE_LOG_CATEGORY(MyGameEditor)

#define LOCTEXT_NAMESPACE "MyGameEditor"

void FMyGameEditorModule::StartupModule()
{
	UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Started"));

	// Register the details customization
	FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
	// Leave out the suffix "A" when specifying the name of your actor in the first param
	PropertyEditorModule.RegisterCustomClassLayout("StaticMeshActor", FOnGetDetailCustomizationInstance::CreateStatic(&FMyActorCustomization::MakeInstance));
}

void FMyGameEditorModule::ShutdownModule()
{
	UE_LOG(MyGameEditor, Warning, TEXT("MyGameEditor: Log Ended"));
}

#undef LOCTEXT_NAMESPACE

MyGameEditorPrivatePCH.h



#include "CoreUObject.h"

// You should place include statements to your module's private header files here.  You only need to
// add includes for headers that are used in most of your module's source files though.
#include "Engine.h"
#include "UnrealEd.h"




MyGameEditor.Build.cs


// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;

public class MyGameEditor : ModuleRules
{
	public MyGameEditor(TargetInfo Target)
	{
		
        PublicIncludePaths.AddRange(
            new string]{
                "MyGameEditor/Public"
            });
 
        PrivateIncludePaths.AddRange(
            new string] {
			"MyGameEditor/Private"
		});
        
        PublicDependencyModuleNames.AddRange(
            new string]
            {
                    "Core",
                    "CoreUObject",
                    "Engine",
                    "EditorStyle",
                    "UnrealEd",
                    "PropertyEditor",
                    "MyGame"
            }
        );

        // Uncomment if you are using Slate UI
        PrivateDependencyModuleNames.AddRange(new string] { "Slate", "SlateCore" });
    }
}


MyGame.Target.cs


// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;
using System.Collections.Generic;

public class MyGameTarget : TargetRules
{
	public MyGameTarget(TargetInfo Target)
	{
		Type = TargetType.Game;
	}

	//
	// TargetRules interface.
	//

	public override void SetupBinaries(
		TargetInfo Target,
		ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
		ref List<string> OutExtraModuleNames
		)
	{
		OutExtraModuleNames.AddRange( new string] { "MyGame" } );
        if (UEBuildConfiguration.bBuildEditor)
        {
            OutExtraModuleNames.Add("MyGameEditor");
        }
    }
}


MyGameEditor.Target.cs


// Fill out your copyright notice in the Description page of Project Settings.

using UnrealBuildTool;
using System.Collections.Generic;

public class MyGameEditorTarget : TargetRules
{
	public MyGameEditorTarget(TargetInfo Target)
	{
		Type = TargetType.Editor;
	}

	//
	// TargetRules interface.
	//

	public override void SetupBinaries(
		TargetInfo Target,
		ref List<UEBuildBinaryConfiguration> OutBuildBinaryConfigurations,
		ref List<string> OutExtraModuleNames
		)
	{
		OutExtraModuleNames.AddRange( new string] { "MyGameEditor" } );
	}
}


MyGame.uproject


{
	"FileVersion": 3,
	"EngineAssociation": "4.12",
	"Category": "",
	"Description": "",
	"Modules": 
		{
			"Name": "MyGame",
			"Type": "Runtime",
			"LoadingPhase": "Default"
		},
		{
			"Name": "MyGameEditor",
			"Type": "Editor",
            "LoadingPhase":  "PostEngineInit"
		}
	],
	"Plugins": 
		{
			"Name": "Substance",
			"Enabled": true,
			"MarketplaceURL": "com.epicgames.launcher://ue/marketplace/offers/2f6439c2f9584f49809d9b13b16c2ba4"
		}
	]
}

2 Likes