Download

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

Custom events that you create in Blueprint can be exposed to the details panel of an actor to run some logic. I want to do a similar thing for a C++ defined event but I’m having trouble figuring out how to do so?

The blueprint equivalent:
4ec3665172e9739503815a324f7f783ab857d32b.jpeg
2acb9ce91c259b601eae706226cea6896d8ecb59.jpeg

All I really want is a couple of buttons in the details panel for one of my actors, when I press one it’ll generated a load of instances meshes, and when I press another it’ll do something else. Any ideas?

It looks like the only other way to do this using C++ is using details panel customization, which is an absolute headache because there’s zero documentation or examples…

@TheJamsh You need to create a Customization class in the editor module of your game

Create an editor module using the following steps:

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"
		}
	]
}

Thanks for the example code Ali - I started work on the editor module but it was a minefield… hopefully this will get it working.

Just to add for the devs reading this (coming from google search), if you make a multicast event then customization of Details Panel isn’t needed; they show up on parent blueprint like this:



//...
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FHSM_ExitState, uint8, StateID, FName, StateName);


I was just thinking about this the other day actually. The details customizations are great and really flexible, but it’s also a total pain to create one when you just want to do something simple. I was thinking that this particular case - adding a button to execute a function - should really have a UFUNCTION metadata to make this super easy to do. I’ll have a look into how feasible it might be for a PR. Problem is the property code is a monster and I’m not sure where would be the right place to insert this.

[MENTION=434]BrUnO XaVIeR[/MENTION]: I think @TheJamsh is talking about a button to run code at the point it’s clicked in-editor, not for binding a function to execute at runtime.

Yes, I just added that because the OP title; ppl looking for blueprint delegates will drop here because title.

You’re welcome :wink:

I’m thinking of creating a plugin that, when installed, would help extend the editor in various ways without writing C++. You would specify the mapping from some UI (e.g. target actor type and a blueprint that needs to be executed when pressed), adding a button to the toolbar etc

AHHHH IT’S WORKING. What a absolute nightmare that was… Now to make the good stuff happen.
@Ali so I’ve got functions hooked up to buttons, I guess it’s relatively easy to add properties here as well that I can tweak? Currently the variables in that category are part of my custom World Settings class.

EditoModule.JPG

Kind of a shame the Foliage Tool doesn’t work all that well for things like this… oh well.

Alright not all finish yet. I now can’t package the game because of the old HACD_64.dll linker error.

Something tells me the build.cs files aren’t setup correctly…

EDIT: Figured it out, had ‘UnrealEd’ as a dependency in the Uproject file. Fixed now.

You can add custom property rows like this in the CustomizeDetails function:


	TSharedPtr<IPropertyHandle> PropertyHandle = DetailBuilder.GetProperty("bStaticMeshReplicateMovement");
	Category.AddCustomRow(Caption)
	.WholeRowContent()
		
			SNew(SProperty, PropertyHandle)
		];


The property handle you search for has to be a UPROPERTY and should be a member of the class you are customizing

You can also add properties of external UObjects like this (provided you somehow get a reference of it in the CustomizeDetails function)


Category.AddExternalProperty(...)

Adding editor button which will execute some C++ function is now as simple as it should be.
Just add:


UFUNCTION(BlueprintCallable, CallInEditor)

macro above your function, recompile and button will be there.

Once again: Sorry for necro-ing old thread, but it’s easier to find this than actual answer.