Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
Topic: Custom dictionary with IPropertyTypeCustomization
Source for UE4.26: https://github.com/klauth86/UE4Cookery/tree/main/CPP005
Sometimes you need to customize visual representation of UClasses and UStructs to gain more control and comfort while working with them in Editor. Thanx to modular system this is rather simple task. We just take a look at this with custom list of values example. Imagine that you want to create a list of values, for which you can select item with ComboBox in Editor Details panel, very very similar to UEnums.
First of all, let's create some list of values and let it be a list of FName, that is rather common and popular case. So, it will be
MyDictionary.h
#pragma once #include "UObject/ObjectMacros.h" #include "UObject/NameTypes.h" #include "MyDictionary.generated.h" USTRUCT(BlueprintType) struct CPP005_API FMyDictionary { GENERATED_USTRUCT_BODY(); uint8 ItemIndex; static TArray<FName> Items; };
and don't forget to init static things in MyDictionary.cpp
#include "MyDictionary.h" TArray<FName> FMyDictionary::Items = { FName("Letter A"), FName("Letter B"), FName("Letter C") };
Now, just add Editor module for our project and create some IPropertyTypeCustomization for UMyDictionary. It will be like this
PropertyTypeCustomization_MyDictionary.h
#pragma once #include "IPropertyTypeCustomization.h" class SPinComboBox; class CPP005EDITOR_API FPropertyTypeCustomization_MyDictionary : public IPropertyTypeCustomization { public: static TSharedRef<IPropertyTypeCustomization> MakeInstance(); void CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) override; virtual void CustomizeChildren(TSharedRef<class IPropertyHandle> StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override {} protected: void GenerateComboBoxIndexes(TArray< TSharedPtr<int32> >& OutComboBoxIndexes); FString OnGetText() const; void ComboBoxSelectionChanged(TSharedPtr<int32> NewSelection, ESelectInfo::Type SelectInfo); FText OnGetFriendlyName(int32 itemIndex); FText OnGetTooltip(int32 itemIndex); template<typename T> T* GetPropertyAs() const { if (PropertyHandlePtr.IsValid()) { TArray<void*> RawData; PropertyHandlePtr->AccessRawData(RawData); return reinterpret_cast<T*>(RawData[0]); } return nullptr; } protected: TSharedPtr<IPropertyHandle> PropertyHandlePtr; TSharedPtr<SPinComboBox> ComboBox; };
PropertyTypeCustomization_MyDictionary.cpp
#include "PropertyTypeCustomization_MyDictionary.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "SGraphPinComboBox.h" #include "IPropertyUtilities.h" #include "MyDictionary.h" #define LOCTEXT_NAMESPACE "PropertyTypeCustomization_MyDictionary" TSharedRef<IPropertyTypeCustomization> FPropertyTypeCustomization_MyDictionary::MakeInstance() { return MakeShareable(new FPropertyTypeCustomization_MyDictionary); } void FPropertyTypeCustomization_MyDictionary::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { PropertyHandlePtr = PropertyHandle; TSharedPtr<IPropertyUtilities> PropertyUtils = CustomizationUtils.GetPropertyUtilities(); // Get list of MyDictionary indexes TArray< TSharedPtr<int32> > ComboItems; GenerateComboBoxIndexes(ComboItems); ComboBox = SNew(SPinComboBox) .ComboItemList(ComboItems) .VisibleText(this, &FPropertyTypeCustomization_MyDictionary::OnGetText) .OnSelectionChanged(this, &FPropertyTypeCustomization_MyDictionary::ComboBoxSelectionChanged) .OnGetDisplayName(this, &FPropertyTypeCustomization_MyDictionary::OnGetFriendlyName) .OnGetTooltip(this, &FPropertyTypeCustomization_MyDictionary::OnGetTooltip); HeaderRow.NameContent()[PropertyHandle->CreatePropertyNameWidget()] .ValueContent()[ComboBox.ToSharedRef()].IsEnabled(MakeAttributeLambda([=] { return !PropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); })); } void FPropertyTypeCustomization_MyDictionary::GenerateComboBoxIndexes(TArray< TSharedPtr<int32> >& OutComboBoxIndexes) { int32 i = 0; for (auto item : FMyDictionary::Items) { TSharedPtr<int32> EnumIdxPtr(new int32(i++)); OutComboBoxIndexes.Add(EnumIdxPtr); } } FString FPropertyTypeCustomization_MyDictionary::OnGetText() const { if (auto myDictionary = GetPropertyAs<FMyDictionary>()) { auto itemIndex = myDictionary->ItemIndex; return (FMyDictionary::Items.Num() < itemIndex || itemIndex < 0) ? "" : FMyDictionary::Items[itemIndex].ToString(); } return ""; } void FPropertyTypeCustomization_MyDictionary::ComboBoxSelectionChanged(TSharedPtr<int32> NewSelection, ESelectInfo::Type /*SelectInfo*/) { if (NewSelection.IsValid()) { if (auto myDictionary = GetPropertyAs<FMyDictionary>()) { myDictionary->ItemIndex = *NewSelection; } } } FText FPropertyTypeCustomization_MyDictionary::OnGetFriendlyName(int32 itemIndex) { return (FMyDictionary::Items.Num() < itemIndex || itemIndex < 0) ? FText::GetEmpty() : FText::FromName(FMyDictionary::Items[itemIndex]); } FText FPropertyTypeCustomization_MyDictionary::OnGetTooltip(int32 itemIndex) { return (FMyDictionary::Items.Num() < itemIndex || itemIndex < 0) ? FText::GetEmpty() : FText::FromName(FMyDictionary::Items[itemIndex]); } #undef LOCTEXT_NAMESPACE
Note, that this will require us to add PropertyEditor, SlateCore, CoreUObject and GraphEditor in our Editor.Build.cs file. After that we need only to implement some register and unregister code in our project Editor Module:
CPP005EditorModule.cpp
#include "CPP005EditorModule.h" #include "PropertyEditorModule.h" #include "MyDictionary.h" #include "DetailCustomizations/PropertyTypeCustomization_MyDictionary.h" DEFINE_LOG_CATEGORY(LogCPP005Editor); #define LOCTEXT_NAMESPACE "FCPP005EditorModule" void FCPP005EditorModule::StartupModule() { // Register the details customizer FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout(FMyDictionary::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FPropertyTypeCustomization_MyDictionary::MakeInstance)); } void FCPP005EditorModule::ShutdownModule() { // Unregister the details customization if (FModuleManager::Get().IsModuleLoaded("PropertyEditor")) { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor"); PropertyModule.UnregisterCustomPropertyTypeLayout(FMyDictionary::StaticStruct()->GetFName()); } } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FCPP005EditorModule, CPP005Editor);
Main part is done, we can create some AActor class that has our FMyDictionary as UProperty
MyActor.h
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "GameFramework/Actor.h" #include "MyDictionary.h" #include "MyActor.generated.h" UCLASS() class CPP005_API AMyActor : public AActor { GENERATED_BODY() protected: UPROPERTY(EditAnywhere, Category = "My Actor") FMyDictionary MyDict; };
And check its view in Blueprint Editor:
PS. You can also configure custom details for any UClass, just use RegisterCustomClassLayout method instead of RegisterCustomPropertyTypeLayout...
Read more about:
BlogsYou May Also Like