Coming back to answer this myself after a lot of headbashing…
We’re going to cover a few things here:
- Opening note
- ILayoutDetails
- Creating your (property type) customization
- Your IPropertyTypeCustomization class
- Customizing your property type
- Notes
Opening note
I was creating a customization for FName. For this reason, most of this is going to concern itself with IPropertyTypeCustomization.
If you need to see the difference between these, I suggest scanning over the DetailCustomizations.cpp. You’ll find that here:
C:\Program Files\Epic Games\UE_4.15\Engine\Source\Editor\DetailCustomizations\Private\DetailCustomizations.cpp
-
IPropertyType examples - check out FDetailCustomizationsModule::RegisterPropertyTypeCustomizations.
-
IDetailCustomization examples - check out FDetailCustomizationsModule::RegisterObjectCustomizations.
Make sure to study any class you think is relevant. It’s quite a headache to figure this out on your own.
ILayoutDetails
The class that the documentation asks you to use does not exist, and from what I can tell it hasn’t existed for a very long time. In its place, however, we have two new types: IPropertyTypeCustomization and IDetailCustomization.
IPropertyTypeCustomization allows you to define how default values are made for single variable types. For example, my FName customization allowed me to make a small change to FName:
vs
IDetailCustomization is for when you have more complex things to put together, and need a simpler interface for the entire ordeal. For this, let’s look at the actor picker.
Creating your (property type) customization
Firstly, you should have a Module - in my case, all it does is my binding. I suggest just creating a Blank module from Edit → Plugins → New Plugin if you do not already have one.
Within your module’s .uplugin, you’ll need to make the following change so that your module is loaded in the editor.
"Modules": [
{
"Name": "YourModuleName",
"Type": "Editor",
"LoadingPhase": "Default"
}
]
Within your module’s .h file, you’ll need the following:
#include "CoreMinimal.h"
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "PropertyEditorModule.h"
#include "PropertyEditorDelegates.h"
#include "DetailCustomizations.h"
//Your IPropertyTypeCustomization or IDetailCustomization
#include "YourDetailCustomization.h"
– You may not need all of them. I went a bit ham. If any of them are redundant, do tell!
In your .cpp file you should have a function called StartupModule, and ShutdownModule. Within both you’ll need to declare a PropertyModule - this is needed to register your customizations!
In my case, I’ll be describing IPropertyTypeCustomization’s process.
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
And the StartupModule and ShutdownModule need one more line, respectively.
In StartupModule()…
PropertyModule.RegisterCustomPropertyTypeLayout(
PropertyToCustomize,
FOnGetPropertyTypeCustomizationInstance::CreateStatic(
&YourCustomizationClass::MakeInstance)
);
And our ShutdownModule() needs its counterpart:
PropertyModule.UnregisterCustomPropertyTypeLayout(
PropertyToCustomize
);
It’ll be up to you to find the name of the property you want to change. In my case, I wanted to change FName, which required me to use NAME_NameProperty.
Your IPropertyTypeCustomization class
Firstly, we need a function that matches what we put into our above binding - MakeInstance. All it does is return an instance on request.
Additionally, we’ll need two functions to receive data from Unreal Engine’s back-end:
- CustomizeHeader
- CustomizeChildren
There is no real reason that I know as to why you shouldn’t just copy-paste the following and modify it to your liking.
class YourCustomization: public IPropertyTypeCustomization
{
public:
/**
* Creates a new instance.
*
* @return A new property type customization.
*/
static TSharedRef<IPropertyTypeCustomization> MakeInstance()
{
return MakeShareable(new YourCustomization());
}
public:
// IPropertyTypeCustomization interface
virtual void CustomizeHeader(
TSharedRef<IPropertyHandle> StructPropertyHandle,
class FDetailWidgetRow& HeaderRow,
IPropertyTypeCustomizationUtils& StructCustomizationUtils
) override;
virtual void CustomizeChildren(
TSharedRef<class IPropertyHandle> StructPropertyHandle,
class IDetailChildrenBuilder& StructBuilder,
IPropertyTypeCustomizationUtils& StructCustomizationUtils
) override;
private:
TSharedPtr<IPropertyHandle> PropertyHandle;
}
Customizing your property type
This is when we’ll finally go into your class’ .cpp file.
Firstly, we’ll need to include Slate. I just copy-pasted from another class, so mine looks like this:
//Copypaste from RangeStructCustomization
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Engine/GameViewportClient.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SComboBox.h"
#include "DetailWidgetRow.h"
#include "Editor.h"
#include "PropertyHandle.h"
#include "DetailLayoutBuilder.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "SlateBasics.h"
Again, I’m unlikely to need all of those. I just haven’t gone through and found which ones are redundant…
Remember the two functions, CustomizeHeader and CustomizeChildren? They’ll come in handy now:
void FNameDetailCustomization::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, FDetailWidgetRow & HeaderRow, IPropertyTypeCustomizationUtils & StructCustomizationUtils)
{
PropertyHandle = StructPropertyHandle;
HeaderRow.NameContent()
[
StructPropertyHandle->CreatePropertyNameWidget()
]
[
SNew(STextblock)
.Text(FText::FromString(TEXT("Your property has been replaced by plaintext!"))
]
}
void FNameDetailCustomization::CustomizeChildren(
TSharedRef<class IPropertyHandle> StructPropertyHandle,
IDetailChildrenBuilder & StructBuilder,
IPropertyTypeCustomizationUtils & StructCustomizationUtils
)
{
//do nothing
}
If you’ve made it this far, your challenge is now Slate, and what you can bind to those elements. For example, I have an SEditableTextBox…
SNew(SEditableTextBox)
.Text(this, &FNameDetailCustomization::GetNameValue)
.OnTextChanged(this,&FNameDetailCustomization::UpdateValue)
.OnTextCommitted(this,&FNameDetailCustomization::SetNameValue)
Notes
- You can find the code I wrote when learning how to do this [here][4].
- A PropertyType customization should be seen on your variable, and if it’s part of an array.
- However, a TMap will override your customization it seems.