I’ve actually done this a while ago when trying to get arrays to display using enum names instead of indices. (I was later told this can just be done with a static array using the enum MAX value, but the process is still valid.)
Adding and handling metadata is pretty easy, the hard part is registering an editor customization that covers the scope you need. FPropertyEditorModule allows registering customizations either for a whole class, or for a specific property type:
virtual void RegisterCustomClassLayout( FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate );
virtual void RegisterCustomPropertyTypeLayout( FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate, TSharedPtr<IPropertyTypeIdentifier> Identifier = nullptr, TSharedPtr<IDetailsView> ForSpecificInstance = nullptr );
Now here’s the clincher: You cannot register multiple customizations on the same class or property type. So if you’re looking to customize your own stuff, you should have smooth sailing. But if you’re looking to customize built-in types that already have editor customization (such as TArray, in my case), you’re going to have a bad time.
You can find lots of examples of registering editor customizations across the code, just search for one of the above functions for inspiration. The jist of it is, you’ll need to create an Editor-specific module if you haven’t already (as this is editor-specific code that will not compile in a game build) and do registration in its StartupModule.
For the actual metadata, I’ll just go ahead and share the EnumArrayDetails customization that I had going. (I made it work by customizing UObject – it doesn’t have editor customizations! – but don’t try this at home. ;)) Like I said earlier, this particular customization isn’t necessary after all but it should help you get started.
#include "MyEditorCustomizationPrivatePCH.h"
#include "EnumArrayDetails.h"
#include "ObjectEditorUtils.h"
TSharedRef<IDetailCustomization> FEnumArrayDetails::MakeInstance()
{
return MakeShareable( new FEnumArrayDetails );
}
void FEnumArrayDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder )
{
TArray< TWeakObjectPtr<UObject> > CustomObjects;
DetailBuilder.GetObjectsBeingCustomized( CustomObjects );
for( auto CustomObject : CustomObjects )
{
if( CustomObject.IsValid() )
{
UClass* Class = CustomObject->GetClass();
for( TFieldIterator<UArrayProperty> PropIt( Class ); PropIt; ++PropIt )
{
**UArrayProperty* Property = *PropIt;
if( !Property->HasMetaData( "EnumArray" ) )
{
continue;
}
const FName EnumName = FName( *Property->GetMetaData( "EnumArray" ) );**
UEnum* EnumClass = (UEnum*)StaticFindObjectFast( UEnum::StaticClass(), nullptr, EnumName, true, true );
if( EnumClass == nullptr )
{
continue;
}
UClass* PropertyClass = Property->GetOwnerClass();
TSharedRef<IPropertyHandle> Handle = DetailBuilder.GetProperty( Property->GetFName(), PropertyClass );
if( !Handle->IsValidHandle() )
{
continue;
}
FName CategoryName = FObjectEditorUtils::GetCategoryFName( Property );
IDetailCategoryBuilder& Category = DetailBuilder.EditCategory( CategoryName );
TSharedRef<FDetailArrayBuilder> EnumArrayBuilder = MakeShareable( new FDetailArrayBuilder( Handle ) );
EnumArrayBuilder->OnGenerateArrayElementWidget( FOnGenerateArrayElementWidget::CreateSP( this, &FEnumArrayDetails::OnGenerateArrayRow ) );
ArrayEnumMap.Add( Property->GetFName(), EnumClass );
Category.AddCustomBuilder( EnumArrayBuilder );
}
}
}
}
void FEnumArrayDetails::OnGenerateArrayRow( TSharedRef<IPropertyHandle> ElementHandle, int32 Index, IDetailChildrenBuilder& ChildrenBuilder )
{
TSharedPtr<IPropertyHandle> ArrayHandle = ElementHandle->GetParentHandle();
FName ArrayName = ArrayHandle->GetProperty()->GetFName();
UEnum* EnumClass = ArrayEnumMap.FindChecked( ArrayName );
if( EnumClass->HasMetaData( TEXT("Hidden"), Index ) )
{
return;
}
FText DisplayText = EnumClass->GetDisplayNameText( Index );
FText DisplayName = ( !DisplayText.IsEmpty() ) ? DisplayText : FText::FromString( EnumClass->GetEnumName( Index ) );
ChildrenBuilder.AddChildContent( TEXT("") )
.NameContent()
SNew( STextBlock )
.Text( DisplayName )
.Font( IDetailLayoutBuilder::GetDetailFont() )
]
.ValueContent()
ElementHandle->CreatePropertyValueWidget()
];
}
I bolded the relevant lines, that’s how you inspect metadata to make decisions on it.
Cheers,