Components added at runtime don't show up in editor details panel.

Id like to see components added at runtime in ue4’s details panel. Is this possible?

My code currently looks like this:


 UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>(Owner);
if (MeshComponent->bWantsInitializeComponent) MeshComponent->InitializeComponent();

if (Owner != nullptr)
{
MeshComponent->SetStaticMesh(mesh);
}

MeshComponent->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);

Owner->RegisterAllComponents();

2 Likes

They will not show up in the details panel no. They must be class-default objects to be shown there.

Added this on your other thread, but adding here too for future searches.

If you are adding a list of totally random components and want them to show in editor then keep a TArray and add them to it.

In .h file:



UPROPERTY(EditInstanceOnly)
TArray<UStaticMeshComponent*> _dynamicComponents;


In .cpp file:



UStaticMeshComponent* NewComponent = NewObject<UStaticMeshComponent>(actor);
NewComponent->RegisterComponent();
_dynamicComponents.AddUnique(NewComponent);


3 Likes

This certainly works. However, when you have a C++ only actor, you can still add components via the add component button in details panel of the selected actor without having any CDO information about the actor setup before hand in code. I for one am curious as to how the editor is adding it there. Obviously it’s going to be a member of the actors component hierarchy in terms of outer objects, but is it really just some specific slate code?

EDIT: I did find that if you make your creation state of the component
before registration and attachment setup, it will be added to the components tab in the actors details panel. However, the details panels must be refreshed in order to update the changes.

NewComponent->CreationMethod = EComponentCreationMethod::Instance;

EDIT2: SOLUTION:

UActorComponent* NewAttachPoint = NewObject<UActorComponent>(this);
	this->AddInstanceComponent(NewAttachPoint);
	NewAttachPoint->SetupAttachment(this->Scene); //Or your component of choice (I usually make my own scene compnents)
	NewAttachPoint->RegisterComponent();

#if WITH_EDITOR
	if(this->IsSelectedInEditor())
	{
		FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
		TArray<UObject*> List;
		List.Add(this);

		LevelEditor.BroadcastActorSelectionChanged(List, true);
	}
#endif
3 Likes

I would not recommend to change the component creation method using the following:

UActorComponent* MyNativeComponentDynamicInstance = NewObject<USceneComponent>(this, UMyNativeComponent::StaticClass());
MyNativeComponentDynamicInstance->SetupAttachment(GetRootComponent());
// AddInstanceComponent(MyNativeComponentDynamicInstance) is the same as the 2 lines below
MyNativeComponentDynamicInstance->CreationMethod = EComponentCreationMethod::Instance;
AddOwnedComponent(MyNativeComponentDynamicInstance);

The reason is that you will break lots of editor mechanisms, for example, setting ‘Instance’ creation method allows the user to delete the component manually in the detail panel, which will lead to crashs if you keep a reference to it somewhere in your native code (and you certainly do). Also, it could break other things such as undo/redo…

The solution if you have only one instanced component is to define a property to a USceneComponent with ‘EditInstanceOnly’ flag (or a UActorComponent, but my previous example use a scene component, so I continue my explanation with that!), set it to a category that you choose to hide in class, and assign the value you dynamically created to the property:

UCLASS(Blueprintable, BlueprintType, HideCategories="MyActorInternals")
class AMyActor : public AActor
{
GENERATED_UCLASS_BODY()
public:
    // Button in editor to create the dynamic instance
    UFUNCTION(BlueprintCallable, CallInEditor)
    void CreateMyDynamicComponentInstance()
    {
        UActorComponent* MyNativeComponentDynamicInstance = NewObject<USceneComponent>(this, UMyNativeComponent::StaticClass());
        MyNativeComponentDynamicInstance->SetupAttachment(GetRootComponent());
        AddOwnedComponent(MyNativeComponentDynamicInstance);

#if WITH_EDITOR
        // The trick to display the dynamically created component in details panel!
        DynamicComponent = MyNativeComponentDynamicInstance
#endif
    }

#if WITH_EDITORONLY_DATA
private:
    UPROPERTY(EditInstanceOnly, Category="MyActorInternals")
    USceneComponent* DynamicComponent { nullptr };
#endif
};

With this method, component cannot be deleted by user, and undo/redo works correctly.

If you have multiples dynamic components, well, it’s more complicated. [Shmoopy1701] mentioned that the solution was to define a property with TArray type (or TMap / TSet), but unfortunally, it do not work with UE v5.1, maybe it was working before, DK.

    // Nope, dynamically created components assigned to this property will not show in details panel :(
    UPROPERTY(EditInstanceOnly, Category="MyActorInternals")
    TArray<USceneComponent*> DynamicComponents;

The reason is that unlike ‘simple’ properties, details panel code looks in CDO’s array property if it contains the pointer to our component, which it do not have, as it’s the actor instance array property whose have it! See ‘SSCSEditor::BuildSubTreeForActorNode’ and the call to ‘FComponentEditorUtils::GetPropertyForEditableNativeComponent’ for implementation details.

The solution I ended up with is to reserve a bunch of UPROPERTY in the base class… This work if you know in advance what is the maximum number of dynamic components you will create, and if this number is not too big:

UCLASS(Blueprintable, BlueprintType, HideCategories="MyActorInternals")
class AMyActor : public AActor
{
GENERATED_UCLASS_BODY()
public:
    // Button in editor to create the dynamic instance
    UFUNCTION(BlueprintCallable, CallInEditor)
    void CreateMyDynamicComponentInstance()
    {
        UActorComponent* MyNativeComponentDynamicInstance = NewObject<USceneComponent>(this, UMyNativeComponent::StaticClass());
        MyNativeComponentDynamicInstance->SetupAttachment(GetRootComponent());
        AddOwnedComponent(MyNativeComponentDynamicInstance);

#if WITH_EDITOR
        if (DynamicComponent1 == nullptr) { DynamicComponent1 = MyNativeComponentDynamicInstance; }
	else if (DynamicComponent2 == nullptr) { DynamicComponent2 = MyNativeComponentDynamicInstance; }
	else if (DynamicComponent3 == nullptr) { DynamicComponent3 = MyNativeComponentDynamicInstance; }
       else { /* Log some warning/error to user, if no more free dynamic property is available, the component will still be created in actor, but it will not be visible in detail panel */ }
#endif
    }

#if WITH_EDITORONLY_DATA
private: /* Allows to display in detail panels the 3 first dynamically created components */
    UPROPERTY(EditInstanceOnly, Category="MyActorInternals")
    USceneComponent* DynamicComponent1 { nullptr };
    UPROPERTY(EditInstanceOnly, Category="MyActorInternals")
    USceneComponent* DynamicComponent2 { nullptr };
    UPROPERTY(EditInstanceOnly, Category="MyActorInternals")
    USceneComponent* DynamicComponent3 { nullptr };
#endif
};

Still, it’s kind of hackish way of doing…