In UE, when developing editor plugins, automatically creating property panels through the reflection information of USTRUCT provides a very convenient way to offer a configurable solution. However, there are often some specific customization needs for property panels, such as providing special panel options or displaying different types of values based on parameters. Property panels in UE are applied in HotPatcher and ResScannerUE, allowing for straightforward configuration and customization of plugins. UE’s official documentation: Details Panel Customization .
This article starts with creating an independent Details
panel and offers implementation methods for customizing property panels and property entries through the specific case in ResScannerUE .
Property Panel StructureDetailsView
Taking ResScannerUE as an example, launching the plugin creates a Dock that includes some basic operation buttons and a property panel:
The elements displayed in the property panel are reflection properties of a certain USTRUCT structure. In the case mentioned above, it is a FScannerConfig
structure:
1 | USTRUCT(BlueprintType) |
Defining a structure like this allows UE’s property panel to automatically create corresponding types and substructure members, completely avoiding the need to write creation code for each property. The creation process is also very simple, requiring only the use of the PropertyEditor
module to create an SStructureDetailsView
in a certain Slate class’s Construct
method.
First, you need to include the PropertyEditor
module in build.cs
, and then you can create it using PropertyEditor
:
1 | // .h |
The created panel needs to bind a UStruct to obtain the reflection information of that structure, as well as bind a structure instance to obtain and store values. Additionally, the properties of FDetailsViewArgs
and FStructureDetailsViewArgs
can be configured to control the functionality of the property panel, such as whether to enable property name search, scroll bars, etc.
Once SettingsView
is created, the method to display it in a Slate control is as follows:
1 | void SResScannerConfigPage::Construct(const FArguments& InArgs) |
In fact, this is just putting the created SettingsView
Widget into a container for display; this Widget contains all editable reflection properties bound to the UStruct, as shown in the previous ResScannerUE panel display.
Property Panel Customization
In the previous code for creating SettingView
, there is a line:
1 | SettingsView->GetDetailsView()->RegisterInstancedCustomPropertyLayout(FScannerConfig::StaticStruct(),FOnGetDetailCustomizationInstance::CreateStatic(&FScannerSettingsDetails::MakeInstance)); |
Here, a DetailCustomization class instance for the current SettingsView
is specified, which is used for customization operations when creating the SettingView
control. The declaration of this class is as follows:
1 | // ScannerConfigDetails.h |
In the CustomizeDetails
interface, you can perform some customization operations, such as creating a new Slate control in the property panel. For example, in the Import
button from ResScannerUE:
1 | void FScannerSettingsDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) |
You can use the DetailBuilder
in the interface to access the currently bound structure instance, allowing you to obtain data from the configuration and execute operations.
The above code obtains the Category
of the property and creates a CustomRow
element under RulesTable
. In ValueContent()
, you can directly write Slate code to create the custom controls. Since you can directly access the bound structure instance, the implementation in the controls can also directly call functions of the structure, allowing the Import
button to import data from the DataTable into the configuration.
Customization of Properties in the Panel
The previous section mentioned that you can create custom Slate controls in the property panel to achieve customized behaviors. However, some requirements need to modify the way properties are displayed in the panel. For example, in the requirement for property matching in ResScannerUE:
- You can select UClass type.
- Based on the selected UClass type, list options for the properties, and dynamically create the value types of the selected properties to display in the panel.
- Property names and values form an array, where each element can specify different property names and create corresponding value types.
I have already implemented this, and the effect is as follows:
Here’s a description of the implementation. If it’s only about obtaining reflection data, it’s actually not difficult. However, displaying it in the property panel can be a bit more complex.
- Traverse all the properties based on UClass.
- Obtain FProperty to get the current property type.
- Create different FProperty in the property panel.
Since the property panel binds a UStruct and creates properties, my initial thought was to dynamically modify the UStruct. However, that’s not very reasonable because UStruct is fixed and does not allow for different types of elements in an array.
So, what other ways are there? After reviewing the engine code, I found that UE can also perform custom operations on certain types of properties in the property panel, primarily through the IPropertyTypeCustomization
.
Its usage is similar to the IDetailCustomization
interface, which also provides an interface for custom property panel controls, but the creation approach is slightly different:
- DetailCustomization must be specified when creating SettingView, while
PropertyTypeCustomization
does not require this. - DetailCustomization only takes effect in the created SettingView, while
PropertyTypeCustomization
can apply to all places where that property is created.
PropertyTypeCustomization
is more flexible. After completing the custom events, it can be correctly displayed in both SettingView and the structures created from DataTable.
In ResScannerUE, I created a structure FPropertyMatchMapping
to store property names and values:
1 | USTRUCT(BlueprintType) |
PropertyName
and MatchValue
are both strings. The reason for using strings is that they can store values of any type. The subsequent operations for property customization will target the FPropertyMatchMapping
structure type.
Thus, the implementation logic for property customization is as follows:
- Create a custom
PropertyTypeCustomization
. - When the property panel is created, replace the original property creation logic with
PropertyTypeCustomization
.
First, create the PropertyTypeCustomization class for FPropertyMatchMapping
:
1 | class FCustomPropertyMatchMappingDetails : public IPropertyTypeCustomization |
Then, during module startup, you need to register this class:
1 | void FResScannerEditorModule::StartupModule() |
This step informs the PropertyEditor
module that when creating a property panel for FPropertyMatchMapping
, an instance of FCustomPropertyMatchMappingDetails
will be created to perform the creation operation, transferring the control creation process for the specified class to your code.
Implementation of FCustomPropertyMatchMappingDetails
involves creating the property name and setting it up to capture the corresponding values. In CustomizeHeader
, I can create the property name directly, but I did not find a way to access the entire parent structure instance in the parameters passed into CustomizeHeader
, so I could not retrieve what the selected UClass is. If the property-value pair doesn’t rely on contextual properties, one could easily create it here.
However, what I wanted to implement depends on the selected UClass value, so I only created the property name in CustomizeHeader
, while leaving the value control creation to CustomizeChildren
:
1 | void FCustomPropertyMatchMappingDetails::CustomizeHeader(TSharedRef<IPropertyHandle> StructPropertyHandle, |
In CustomizeHeader
, I retrieved and stored the real element values (PropertyName/Value are both FString) of the FPropertyMatchMapping
instance, while placing these elements into CustomizeChildren
for creation because the IDetailChildrenBuilder
parameter passed into CustomizeChildren
allows access to the parent structure, thus retrieving the selected UClass:
1 | void FCustomPropertyMatchMappingDetails::CustomizeChildren(TSharedRef<IPropertyHandle> StructPropertyHandle, |
Having obtained the UClass, I can now get all reflection properties and create an SComboBox
:
1 |
|
The idea is to create an SWidget through Slate and place it into the container created by StructBuilder.AddCustomRow
in the ValueContent container.
Using the created SComboBox
, you can retrieve the property name selected in the editor. With the selected property name and the UClass, you can access the FProperty, which represents the reflection information of the property. Therefore, when the name selected in the ComboBox changes, the FProperty obtained will differ, leading to corresponding types. The next key step is to dynamically create the expected value type in the property panel based on the obtained FProperty:
First, you need to create a container to store the value controls in CustomizeChildren
:
1 | StructBuilder.AddCustomRow(FText::FromString(TEXT("PropertyValue"))) |
PropertyContent
is a TSharedPtr<SBox>
meant to display the controls created later.
Continuing from the previous discussion of dynamically creating the corresponding value controls based on FProperty, the IDetailChildrenBuilder
contains a function AddExternalStructureProperty
, allowing you to add a property of an external structure to the current Details.
For example, considering the CompressionSettings
property of UTexture2D
:
1 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Compression, AssetRegistrySearchable) |
If I want to create this enum in a property panel that doesn’t directly contain it and place it in the previously created PropertyContent
, it would look like this:
1 | // Class is UTexture2D |
You can store mStructBuilder
in the parameters of CustomizeChildren
for future access.
In fact, the above code implements the key part of creating the corresponding value type controls based on FProperty. By simply obtaining the UClass and the property name, you can create the corresponding SWidget
and place it into a designated container for display.
The code to retrieve the property name selected from the ComboBox, obtain the FProperty via the property name, and then create the Widget is extensive. Interested readers can refer to the ResScannerUE DetailCustomization/CustomPropertyMatchMappingDetails.cpp for the full implementation.
Generic Data Storage Method
Apart from needing to display different properties in the panel, a general method for storing these values is also necessary. Strings can be used to store information of all types of values, and UE offers Value Widget
methods for reading string values and setting values from strings:
1 | FString Value; |
With these two functions, it is achievable that all types of values can be stored and retrieved with strings.