{"id":3109,"date":"2021-01-02T14:00:06","date_gmt":"2021-01-02T06:00:06","guid":{"rendered":"http:\/\/blog.coolcoding.cn\/?p=3109"},"modified":"2021-01-02T19:43:19","modified_gmt":"2021-01-02T11:43:19","slug":"inspecting-types-with-custom-details-panels","status":"publish","type":"post","link":"https:\/\/blog.coolcoding.cn\/?p=3109","title":{"rendered":"Inspecting types with custom Details panels"},"content":{"rendered":"\n<p>By default,&nbsp;UObject-derived UAssets open in the generic property editor. It looks as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/1cd21451-0090-40c5-b897-5525655f98ee.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>However, at times, you may wish for custom widgets so that you can edit the properties on your class. To facilitate this, Unreal supports\u00a0<strong>Details Customization<\/strong>, which is the focus of this recipe.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">How to do it&#8230;<\/h1>\n\n\n\n<ol><li>From the&nbsp;Chapter_10Editor&nbsp;folder, create two new files called&nbsp;MyCustomAssetDetailsCustomization.h&nbsp;and&nbsp;MyCustomAssetDetailsCustomization.cpp.<\/li><li>Return to your project file and update your Visual Studio project. Once finished, open up the project in Visual Studio.<\/li><li>Add the following&nbsp;#pragma&nbsp;and&nbsp;#includes&nbsp;to the header (MyCustomAssetDetailsCustomization.h):<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#pragma once<br><br>#include \"MyCustomAsset.h\" \n#include \"DetailLayoutBuilder.h\" \n#include \"IDetailCustomization.h\" \n#include \"IPropertyTypeCustomization.h\" <\/pre>\n\n\n\n<ol><li>Define our customization class, as follows:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">class FMyCustomAssetDetailsCustomization : public IDetailCustomization<br>{<br><br>public:<br>    virtual void CustomizeDetails(IDetailLayoutBuilder&amp; DetailBuilder) override;<br><br>    void ColorPicked(FLinearColor SelectedColor);<br>    <br>    static TSharedRef&lt;IDetailCustomization&gt; MakeInstance()<br>    {<br>        return MakeShareable(new FMyCustomAssetDetailsCustomization);<br>    }<br>    <br>    TWeakObjectPtr&lt;class UMyCustomAsset&gt; MyAsset;<br>};<\/pre>\n\n\n\n<ol><li>Below that, define the following additional class:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">class FMyCustomAssetPropertyDetails : public IPropertyTypeCustomization<br>{<br>public:<br>  void ColorPicked(FLinearColor SelectedColor);<br>  static TSharedRef&lt;IPropertyTypeCustomization&gt; MakeInstance()<br>  {<br>    return MakeShareable(new FMyCustomAssetPropertyDetails);<br>  }<br><br>  UMyCustomAsset* MyAsset;<br>  virtual void CustomizeChildren(TSharedRef&lt;IPropertyHandle&gt; PropertyHandle, IDetailChildrenBuilder&amp; ChildBuilder, IPropertyTypeCustomizationUtils&amp; CustomizationUtils) override;<br><br>  virtual void CustomizeHeader(TSharedRef&lt;IPropertyHandle&gt; PropertyHandle, FDetailWidgetRow&amp; HeaderRow, IPropertyTypeCustomizationUtils&amp; CustomizationUtils) override;<br><br>};<\/pre>\n\n\n\n<ol><li>In the implementation file, add the following includes at the top of the file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#include \"MyCustomAssetDetailsCustomization.h\" <br>#include \"Chapter_10Editor.h\" <br>#include \"IDetailsView.h\" <br>#include \"DetailLayoutBuilder.h\" <br>#include \"DetailCategoryBuilder.h\" <br>#include \"SColorPicker.h\" <br>#include \"SBoxPanel.h\" <br>#include \"DetailWidgetRow.h\" <\/pre>\n\n\n\n<ol><li>Afterwards, create an implementation for&nbsp;CustomizeDetails:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void FMyCustomAssetDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder&amp; DetailBuilder)<br>{<br>    const TArray&lt; TWeakObjectPtr&lt;UObject&gt; &gt;&amp; SelectedObjects = DetailBuilder.GetDetailsView()-&gt;GetSelectedObjects();<br><br>    for (int32 ObjectIndex = 0; !MyAsset.IsValid() &amp;&amp; ObjectIndex &lt; SelectedObjects.Num(); ++ObjectIndex)<br>    {<br>        const TWeakObjectPtr&lt;UObject&gt;&amp; CurrentObject = SelectedObjects[ObjectIndex];<br>        if (CurrentObject.IsValid())<br>        {<br>            MyAsset = Cast&lt;UMyCustomAsset&gt;(CurrentObject.Get());<br>        }<br>    }<br><br>    DetailBuilder.EditCategory(\"CustomCategory\", FText::GetEmpty(), ECategoryPriority::Important)<br>.AddCustomRow(FText::GetEmpty())<br>    [<br>    SNew(SVerticalBox)<br>    + SVerticalBox::Slot()<br>    .VAlign(VAlign_Center)<br>        [<br>            SNew(SColorPicker)<br>            .OnColorCommitted(this, &amp;FMyCustomAssetDetailsCustomization::ColorPicked)<br>        ]<br>    ];<br>}<\/pre>\n\n\n\n<ol><li>Also, create a definition for&nbsp;ColorPicked:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void FMyCustomAssetDetailsCustomization::ColorPicked(FLinearColor SelectedColor)<br>{<br>    if (MyAsset.IsValid())<br>    {<br>        MyAsset.Get()-&gt;ColorName = SelectedColor.ToFColor(false).ToHex();<br>    }<br>}<\/pre>\n\n\n\n<ol><li>Beneath all of the scripts in&nbsp;MyCustomAssetDetailsCustomization.cpp, add the following code:&nbsp;<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">void FMyCustomAssetPropertyDetails::CustomizeChildren(TSharedRef&lt;IPropertyHandle&gt; PropertyHandle, IDetailChildrenBuilder&amp; ChildBuilder, IPropertyTypeCustomizationUtils&amp; CustomizationUtils)<br>{<br>}<br><br>void FMyCustomAssetPropertyDetails::CustomizeHeader(TSharedRef&lt;IPropertyHandle&gt; PropertyHandle, FDetailWidgetRow&amp; HeaderRow, IPropertyTypeCustomizationUtils&amp; CustomizationUtils)<br>{<br>    UObject* PropertyValue = nullptr;<br>    auto GetValueResult = PropertyHandle-&gt;GetValue(PropertyValue);<br><br>    HeaderRow.NameContent()<br>        [<br>            PropertyHandle-&gt;CreatePropertyNameWidget()<br>        ];<br>    HeaderRow.ValueContent()<br>        [<br>            SNew(SVerticalBox)<br>            + SVerticalBox::Slot()<br>        .VAlign(VAlign_Center)<br>        [<br>            SNew(SColorPicker)<br>            .OnColorCommitted(this, &amp;FMyCustomAssetPropertyDetails::ColorPicked)<br>        ]<br>        ];<br>}<br><br>void FMyCustomAssetPropertyDetails::ColorPicked(FLinearColor SelectedColor)<br>{<br>    if (MyAsset)<br>    {<br>        MyAsset-&gt;ColorName = SelectedColor.ToFColor(false).ToHex();<br>    }<br>}<\/pre>\n\n\n\n<ol><li>In our editor module source file (Chapter_10Editor.cpp), add the following to your&nbsp;#includes&nbsp;in the&nbsp;Chapter_10Editor.cpp&nbsp;file:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">#include \"PropertyEditorModule.h\" <br>#include \"MyCustomAssetDetailsCustomization.h\"<br>#include \"MyCustomAssetPinFactory.h\"<\/pre>\n\n\n\n<ol><li>Add the following to the implementation of&nbsp;StartupModule:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">FPropertyEditorModule&amp; PropertyModule = FModuleManager::LoadModuleChecked&lt;FPropertyEditorModule&gt;(\"PropertyEditor\");<br>PropertyModule.RegisterCustomClassLayout(UMyCustomAsset::StaticClass()-&gt;GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&amp;FMyCustomAssetDetailsCustomization::MakeInstance));<br>PropertyModule.RegisterCustomPropertyTypeLayout(UMyCustomAsset::StaticClass()-&gt;GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&amp;FMyCustomAssetPropertyDetails::MakeInstance));<\/pre>\n\n\n\n<ol><li>Add the following to&nbsp;ShutdownModule:<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-preformatted\">FPropertyEditorModule&amp; PropertyModule = FModuleManager::LoadModuleChecked&lt;FPropertyEditorModule&gt;(\"PropertyEditor\");<br>PropertyModule.UnregisterCustomClassLayout(UMyCustomAsset::StaticClass()-&gt;GetFName());<\/pre>\n\n\n\n<ol><li>Compile your code and launch the editor. Create a new instance of&nbsp;MyCustomAsset&nbsp;via the&nbsp;Content Browser.<\/li><li>Double-click on it to verify that the default editor that comes up now shows your custom layout:<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"http:\/\/blog.coolcoding.cn\/wp-content\/uploads\/2021\/01\/27a2b2cf-7c4b-45ba-a188-6e3894e6c8e3.png\" alt=\"\"\/><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">How it works&#8230;<\/h1>\n\n\n\n<p>Details Customization is performed through the&nbsp;IDetailCustomization&nbsp;interface, which developers can inherit when defining a class that customizes the way assets of a certain class are displayed.<\/p>\n\n\n\n<p>The main function that&nbsp;IDetailCustomization&nbsp;uses to allow for this process to occur is as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">virtual void CustomizeDetails(IDetailLayoutBuilder&amp; DetailBuilder) override;<\/pre>\n\n\n\n<p>Within our implementation of this function, we use methods on&nbsp;DetailBuilder&nbsp;that are&nbsp;passed in as parameters to get an array of all selected objects. The loop then scans those to ensure that at least one selected object is of the correct type.<\/p>\n\n\n\n<p>Customizing the representation of a class is done by calling methods on the&nbsp;DetailBuilder&nbsp;object. We create a new category for our details view by using the&nbsp;EditCategory&nbsp;function.<\/p>\n\n\n\n<p>The first parameter of the&nbsp;EditCategory&nbsp;function is the name of the category we are going to manipulate.<\/p>\n\n\n\n<p>The second parameter is optional, and contains a potentially localized display name for the category.<\/p>\n\n\n\n<p>The third parameter is the priority of the category. A higher priority means it is displayed further up the list.<\/p>\n\n\n\n<p>EditCategory&nbsp;returns a reference to the category itself as&nbsp;CategoryBuilder, allowing us to chain additional method calls onto an invocation of&nbsp;EditCategory.<\/p>\n\n\n\n<p>As a result, we call&nbsp;AddCustomRow()&nbsp;on&nbsp;CategoryBuilder, which adds a new key-value pair to be displayed in the category.<\/p>\n\n\n\n<p>Using the Slate syntax, we then specify that the row will contain a Vertical Box with a single center-aligned slot.<\/p>\n\n\n\n<p>Inside the slot, we create a color picker control and bind its&nbsp;OnColorCommitted&nbsp;delegate to our local&nbsp;ColorPicked&nbsp;event handler.<\/p>\n\n\n\n<p>Of course, this requires us to define and implement&nbsp;ColourPicked. It has the following signature:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void FMyCustomAssetDetailsCustomization::ColorPicked(FLinearColor SelectedColor)<\/pre>\n\n\n\n<p>Inside the implementation of&nbsp;ColorPicked, we check to see if one of our selected assets was of the correct type, because, if at least one selected asset was correct, then&nbsp;MyAsset&nbsp;will be populated with a valid value.<\/p>\n\n\n\n<p>Assuming we have a valid asset, we set the&nbsp;ColorName&nbsp;property to the hex string value corresponding to the color that was selected by the user.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>By default,&nbsp;UObject-derived UAssets open in the ge [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[27,1],"tags":[26],"_links":{"self":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3109"}],"collection":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3109"}],"version-history":[{"count":1,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3109\/revisions"}],"predecessor-version":[{"id":3122,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=\/wp\/v2\/posts\/3109\/revisions\/3122"}],"wp:attachment":[{"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3109"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3109"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.coolcoding.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3109"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}