Announcement

Collapse
No announcement yet.

IDetailCustomization - any tips for using it?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    IDetailCustomization - any tips for using it?

    Good day!
    I'm trying to understand how IDetailCustomization actually works, but documentation of IDetailCustomization is extremely outdated. I've tried to understand it from the engine source code, but still can't get an idea how to work with it.

    I looked up with FSkeletalMeshComponentDetails and FSpriteComponentDetailsCustomization, but they both called in different way (the second one calls from MakeInstance but the first one from friend class, I guess?). The only thing that unites them is two functions: MakeInstance and CustomizeDetails.

    Can someone give advice on how to work with IDetailCustomization or point a simple example of using this class in source code on engine?
    Thanks in advance!

    #2
    Hey! Actually, I was able to get things going using the wiki: https://wiki.unrealengine.com/Customizing_detail_panels
    Also, the docs (https://docs.unrealengine.com/latest...Customization/) while outdated (the code won't work I think), it requires minor modifications to work.

    Here is some of my code which will help you find what exactly is outdated in the docs (and with what you should replace it!)
    Code:
    /*  .h */
    
    #include "CoreMinimal.h"
    #include "IDetailCustomization.h"
    class IPropertyHandle;
    class IDetailChildrenBuilder;
    
    class FResourceGeneratorDetails : public IDetailCustomization
    {
    public:
    
        static TSharedRef<IDetailCustomization> MakeInstance();
    
        virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
    protected:
    
        void GenerateRecipeArrayElementWidget(TSharedRef<IPropertyHandle> ChildHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout);
    
    private:
    
        void ShareRecipeKeys();
    
        TArray<TSharedPtr<FName>> RecipeKeys;
    
        //TSharedPtr<IPropertyHandle> CurrentHandle;
    };
    and the cpp:
    Code:
    ...
    #include "DetailCategoryBuilder.h"
    #include "DetailLayoutBuilder.h"
    #include "IDetailChildrenBuilder.h"
    #include "IDetailPropertyRow.h"
    
    #include "PropertyHandle.h"
    
    #include "DetailWidgetRow.h"
    #include "STextBlock.h"
    #include "SComboBox.h"
    #include "SWidget.h"
    #include "PropertyCustomizationHelpers.h"
    
    
    #define LOCTEXT_NAMESPACE "ResourceGeneratorDetails"
    
    
    TSharedRef<IDetailCustomization> FResourceGeneratorDetails::MakeInstance()
    {
        return MakeShareable(new FResourceGeneratorDetails);
    }
    
    void FResourceGeneratorDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
    {
        ShareRecipeKeys();
        // Create a category so this is displayed early in the properties
        IDetailCategoryBuilder& CraftingCategory = DetailBuilder.EditCategory("Crafting", FText::FromString(TEXT("Crafting")), ECategoryPriority::Important);
    
        //You can get properties using the detailbuilder
        //MyProperty= DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(MyClass, MyClassPropertyName));
        TSharedPtr<IPropertyHandle> RecipesProperty = DetailBuilder.GetProperty("Recipes");
        TSharedRef<FDetailArrayBuilder> RecipesArrayPropertyBuilder = MakeShareable(new FDetailArrayBuilder(RecipesProperty.ToSharedRef()));
    
        RecipesArrayPropertyBuilder->OnGenerateArrayElementWidget(FOnGenerateArrayElementWidget::CreateSP(this, &FResourceGeneratorDetails::GenerateRecipeArrayElementWidget, &DetailBuilder));
        CraftingCategory.AddCustomBuilder(RecipesArrayPropertyBuilder, false);
    }
    
    
    void FResourceGeneratorDetails::GenerateRecipeArrayElementWidget(TSharedRef<IPropertyHandle> ChildHandle, int32 ArrayIndex, IDetailChildrenBuilder& ChildrenBuilder, IDetailLayoutBuilder* DetailLayout)
    {
    
        IDetailPropertyRow& PropertyArrayRow = ChildrenBuilder.AddProperty(ChildHandle);
    
        PropertyArrayRow.CustomWidget()
        .NameContent()
        [
            ChildHandle->CreatePropertyNameWidget()
        ]
        .ValueContent()
        [
            SNew(SHorizontalBox)
            + SHorizontalBox::Slot()
            [
                SNew(SComboBox<TSharedPtr<FName>>)
                .OptionsSource(&RecipeKeys)
                .OnGenerateWidget_Lambda ( [](TSharedPtr<FName> InItem) {
                    return SNew(STextBlock)
                    .Text(FText::FromName(*InItem));
                })
                .OnSelectionChanged_Lambda([=](TSharedPtr<FName> Selection, ESelectInfo::Type){
                    if (ChildHandle->IsValidHandle()) {
                        ChildHandle->SetValue(*Selection);
                    }
                })
                .ContentPadding(FMargin(2, 0))
                [
                    SNew(STextBlock)
                    .Font(IDetailLayoutBuilder::GetDetailFont())
                    .Text_Lambda( [=]() -> FText
                    {
                        if (ChildHandle->IsValidHandle())
                        {
                            FName val;
                            ChildHandle->GetValue(val);
                            return FText::FromName(val);
                        }
    
                        return FText::GetEmpty();
                    } )
                ]
            ]
        ];
    
    }
    
    void FResourceGeneratorDetails::ShareRecipeKeys()
    {
        TArray<FName> AllKeys = UETPStatics::GetCraftingRecipeKeys();
        RecipeKeys.Empty(AllKeys.Num());
    
        for (FName key : AllKeys) {
            RecipeKeys.Add(MakeShareable(new FName(key)));
        }
    }
    
    
    #undef LOCTEXT_NAMESPACE
    Excuse me for not cleaning up the code; nevertheless, I think you will find this (more complicated) example more useful since it is a collection of various tutorials/digging through engine code.
    What it actually does is create a drop-down menu for values in an ARRAY. The values that are shown are found in GetCraftingRecipeKeys() so ignore (or replace) it.

    For something simple you should just keep the CustomizeDetails()

    Don't forget to register your customization through StartupModule() using:

    Code:
        PropertyModule.RegisterCustomClassLayout("ResourceGenerator", FOnGetDetailCustomizationInstance::CreateStatic(&FResourceGeneratorDetails::MakeInstance));
    If you don't have a module yet you should follow this:
    https://wiki.unrealengine.com/Creating_an_Editor_Module

    Hope I helped a bit, good luck :-)
    Last edited by Semitable; 09-18-2017, 06:47 AM.
    Check my User Interface Kit here: https://forums.unrealengine.com/show...-Interface-Kit
    A WIP Hex Planet Generation: https://forums.unrealengine.com/show...ral-Generation

    Comment


      #3
      So for using IDetailCustomization I need to create a new module?
      What about customizing a set of variables for Behavior Tree task? For example, I want to make enum and choosing one of his values will change list of available variables. It's not an editor extension, I guess, so maybe I don't need to create new module?

      Comment


        #4
        Any detail customization is an editor extension and should be done in an editor module.

        I have some info here. The source code might need some minor changes for 4.17, but it's mostly up to date and shows how to set up the module too.

        Comment

        Working...
        X