You can change the way the Unreal editor displays structs or other data types by registering a IPropertyTypeCustomization with the FPropertyEditorModule. This allows you to use Slate to render an entirely custom property editor.

This can be used to improve the usability of data types which might not have a very good default representation otherwise.

Creating a property type customization class

The basic class skeleton

The below example shows a skeleton for a property customization class:

//.h
class MY_API FMyCustomization : public IPropertyTypeCustomization
{
public:
	static TSharedRef<IPropertyTypeCustomization> MakeInstance();
 
	virtual void CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override;
	virtual void CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) override;
};
 
//.cpp
TSharedRef<IPropertyTypeCustomization> FMyCustomization::MakeInstance()
{
	return MakeShared<FMyCustomization>();
}
 
void FMyCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
	HeaderRow
		.NameContent()
		[
			//Create Slate widgets to display in the Name column of the
			//property editor here - a good default is to call the
			//CreatePropertyNameWidget like below, as it generates
			//the standard name column for the property.
			PropertyHandle->CreatePropertyNameWidget()
		]
		.ValueContent()
		[
			//Create Slate widgets to display the value of the property here.
			//What you need to put here depends entirely on what you are doing.
			SNew(STextBlock).Text(INVTEXT("Hello"))
		];
}
 
void FMyCustomization::CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
}

Reading or changing the property value within the property customizer

The PropertyHandle value passed to the CustomizeHeader function can be used to access the value of the property being customized.

For example, if we were using this to customize the following struct…

USTRUCT()
struct FMyStruct
{
	GENERATED_BODY()
 
	UPROPERTY(EditAnywhere)
	FName IconName;
};

We can use the following code to read the IconName property’s value:

TSharedPtr<IPropertyHandle> IconNameProp = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMyStruct, IconName));
 
FName IconName;
IconNameProp->GetValue(IconName);

The GetValue function can read various different types of data from the property handle, its behavior depends on the type of variable you pass as a parameter. If you wanted to instead read a float, you would pass a float as its parameter instead.

Once you’ve done some edits using the property customizer, you can write the data back in a similar fashion:

TSharedPtr<IPropertyHandle> IconNameProp = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FMyStruct, IconName));
 
IconNameProp->SetValue(FName("Amazing new FName"));

Everything else is done using Slate widgets.

Registering the property customizer

Typically you would register the customizer in the StartupModule function of an Editor Module:

FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomPropertyTypeLayout(
	FMyStruct::StaticStruct()->GetFName(),
	FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FMyCustomization::MakeInstance)
);

This would register the customizer for the struct type FMyStruct.

To unregister the customizer, typically in ShutdownModule:

FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.UnregisterCustomPropertyTypeLayout(FMyStruct::StaticStruct()->GetFName());