247 lines
9.9 KiB
Markdown
Raw Normal View History

2024-10-12 17:19:46 +08:00
# ShowInnerProperties
- **功能描述:** 在属性细节面板中显示对象引用的内部属性
- **使用位置:** UPROPERTY
- **引擎模块:** Object Property
- **元数据类型:** bool
- **限制类型:** UObject*
- **关联项:** [ShowOnlyInnerProperties](../ShowOnlyInnerProperties/ShowOnlyInnerProperties.md), [FullyExpand](../FullyExpand.md), [CollapsableChildProperties](../CollapsableChildProperties.md)
- **常用程度:** ★★★★★
在属性细节面板中显示对象引用的内部属性。
默认情况下对象引用属性的内部属性在细节面板里是不会显示出来的只是孤零零的显示一个对象名字。但你如果想直接显示出其内部属性然后可以编辑的话就需要ShowInnerProperties这个meta的作用。
但ShowInnerProperties作用有两个限定条件一是这个属性得是UObject*,二是这个属性不是个容器。
同时也注意到Struct属性是默认就会显示内部属性的因此也不需要再设置ShowInnerProperties。
**和EditInineNew的区别是什么**
这种效果和在UCLASS上设置EditInineNew配合其对象引用属性上设置Instanced达成的效果很相似。区别是UCLASS上设置EditInineNew会使得一个类的对象属性引用可以在属性面板里创建对象 而UPROPERTY上的Instanced会使得这个属性自动的增加EditInline的meta因此也会产生显示内部属性的同样效果。因此结论上来说和ShowInnerProperties像的是本质是EditInline这个meta。但EditInline的效果多了一层是它支持对象容器而ShowInnerProperties只支持单个对象引用属性。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct FMyPropertyInner
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 StructInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString StructInnerString;
};
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_InnerSub :public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ObjectInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ObjectInnerString;
};
UCLASS(BlueprintType, EditInlineNew)
class INSIDER_API UMyProperty_InnerSub_EditInlineNew :public UObject
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 ObjectInnerInt = 123;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString ObjectInnerString;
};
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyProperty_Inner :public UDataAsset
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FMyPropertyInner InnerStruct;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ShowInnerProperties))
FMyPropertyInner InnerStruct_ShowInnerProperties;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UMyProperty_InnerSub* InnerObject;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ShowInnerProperties))
UMyProperty_InnerSub* InnerObject_ShowInnerProperties;
//(Category = MyProperty_Inner, EditInline = , ModuleRelativePath = Property/MyProperty_Inner.h)
//CPF_Edit | CPF_BlueprintVisible | CPF_ZeroConstructor | CPF_NoDestructor | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline))
UMyProperty_InnerSub* InnerObject_EditInline;
//(Category = MyProperty_Inner, EditInline = true, ModuleRelativePath = Property/MyProperty_Inner.h)
//CPF_Edit | CPF_BlueprintVisible | CPF_ExportObject | CPF_ZeroConstructor | CPF_InstancedReference | CPF_NoDestructor | CPF_PersistentInstance | CPF_HasGetValueTypeHash | CPF_NativeAccessSpecifierPublic
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced)
UMyProperty_InnerSub* InnerObject_Instanced;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline))
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass_EditInline;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Instanced)
UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass_Instanced;
public:
UFUNCTION(CallInEditor)
void ClearInnerObject();
UFUNCTION(CallInEditor)
void InitInnerObject();
};
void UMyProperty_Inner::ClearInnerObject()
{
InnerObject = nullptr;
InnerObject_ShowInnerProperties = nullptr;
InnerObject_EditInline = nullptr;
InnerObject_Instanced = nullptr;
InnerObject_EditInlineNewClass = nullptr;
InnerObject_EditInlineNewClass_EditInline = nullptr;
InnerObject_EditInlineNewClass_Instanced = nullptr;
Modify();
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.NotifyCustomizationModuleChanged();
}
void UMyProperty_Inner::InitInnerObject()
{
InnerObject = NewObject<UMyProperty_InnerSub>(this);
InnerObject_ShowInnerProperties = NewObject<UMyProperty_InnerSub>(this);
InnerObject_EditInline = NewObject<UMyProperty_InnerSub>(this);
InnerObject_Instanced = NewObject<UMyProperty_InnerSub>(this);
InnerObject_EditInlineNewClass = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
InnerObject_EditInlineNewClass_EditInline = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
//InnerObject_EditInlineNewClass_Instanced = NewObject<UMyProperty_InnerSub_EditInlineNew>(this);
Modify();
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.NotifyCustomizationModuleChanged();
}
```
## 蓝图效果:
![Untitled](Untitled.png)
可以观察到:
- 结构的属性默认支持展开内部属性
- 带有ShowInnerProperties的UMyProperty_InnerSub* InnerObject_ShowInnerProperties;支持展开属性
- 带有EditInline和Instanced的UMyProperty_InnerSub* 也都支持展开内部属性也可以观察到他们的meta是一致的都带有EditInline=true
- 只有EditInlineNew的UCLASS的UMyProperty_InnerSub_EditInlineNew* InnerObject_EditInlineNewClass;其对象引用不支持展开属性说明在类上设置EditInlineNew并没有作用。
- 但是我们也观察到InnerObject_EditInlineNewClass_Instanced的设置里支持直接创建对象因为其类上有EditInlineNew。而InnerObject_Instanced上并不支持直接创建对象因为其类UMyProperty_InnerSub上并没有EditInlineNew因此不会出现在可选框里。
## 扩展例子:
在源码中搜索观察到UChildActorComponent::ChildActorTemplate上也会带有ShowInnerProperties则就是一个典型的应用以便让我们直接在熟悉细节面板里直接编辑ChildActor的属性数据。
但假如我们去掉这个ShowInnerProperties我们可以来前后对比一下效果
```cpp
class UChildActorComponent : public USceneComponent
{
UPROPERTY(VisibleDefaultsOnly, DuplicateTransient, Category=ChildActorComponent, meta=(ShowInnerProperties))
TObjectPtr<AActor> ChildActorTemplate;
}
void UMyProperty_Inner::RemoveActorMeta()
{
FProperty* prop = UChildActorComponent::StaticClass()->FindPropertyByName(TEXT("ChildActorTemplate"));
prop->RemoveMetaData(TEXT("ShowInnerProperties"));
}
void UMyProperty_Inner::AddActorMeta()
{
FProperty* prop = UChildActorComponent::StaticClass()->FindPropertyByName(TEXT("ChildActorTemplate"));
prop->SetMetaData(TEXT("ShowInnerProperties"), TEXT(""));
}
```
## 对比效果:
![Untitled](Untitled%201.png)
可以发现去除ShowInnerProperties后ChildActorTemplate属性退化成一个普通的对象引用我们无法在上面直接编辑对象的内部属性。
## 原理:
源码里最典型的例子是ChildActorTemplate这样就可以直接显示出内部的属性。
```cpp
class UChildActorComponent : public USceneComponent
{
UPROPERTY(VisibleDefaultsOnly, DuplicateTransient, Category=ChildActorComponent, meta=(ShowInnerProperties))
TObjectPtr<AActor> ChildActorTemplate;
}
```
作用的源码:
```cpp
void FPropertyNode::InitNode(const FPropertyNodeInitParams& InitParams)
{
const bool bIsObjectOrInterface = CastField<FObjectPropertyBase>(MyProperty) || CastField<FInterfaceProperty>(MyProperty);
// we are EditInlineNew if this property has the flag, or if inside a container that has the flag.
bIsEditInlineNew = GotReadAddresses && bIsObjectOrInterface && !MyProperty->HasMetaData(Name_NoEditInline) &&
(MyProperty->HasMetaData(Name_EditInline) || (bIsInsideContainer && OwnerProperty->HasMetaData(Name_EditInline)));
bShowInnerObjectProperties = bIsObjectOrInterface && MyProperty->HasMetaData(Name_ShowInnerProperties);
if (bIsEditInlineNew)
{
SetNodeFlags(EPropertyNodeFlags::EditInlineNew, true);
}
else if (bShowInnerObjectProperties)
{
SetNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties, true);
}
}
void FItemPropertyNode::InitExpansionFlags(void)
{
FProperty* MyProperty = GetProperty();
if (TSharedPtr<FPropertyNode>& ValueNode = GetOrCreateOptionalValueNode())
{
// This is a set optional, so check its SetValue instead.
MyProperty = ValueNode->GetProperty();
}
bool bExpandableType = CastField<FStructProperty>(MyProperty)
|| (CastField<FArrayProperty>(MyProperty) || CastField<FSetProperty>(MyProperty) || CastField<FMapProperty>(MyProperty));
if (bExpandableType
|| HasNodeFlags(EPropertyNodeFlags::EditInlineNew)
|| HasNodeFlags(EPropertyNodeFlags::ShowInnerObjectProperties)
|| (MyProperty->ArrayDim > 1 && ArrayIndex == -1))
{
SetNodeFlags(EPropertyNodeFlags::CanBeExpanded, true);
}
}
void FPropertyNode::RebuildChildren()
{
if (HasNodeFlags(EPropertyNodeFlags::CanBeExpanded) && (ChildNodes.Num() == 0))
{
InitChildNodes();
if (ExpandedPropertyItemSet.Size() > 0)
{
FPropertyNodeUtils::SetExpandedItems(ThisAsSharedRef, ExpandedPropertyItemSet);
}
}
}
```
特别注意这里的bShowInnerObjectProperties的判断条件是bIsObjectOrInterface 且有meta因此该特性只作用于对象引用上。然后如果判断有EPropertyNodeFlags::ShowInnerObjectProperties则继续设置EPropertyNodeFlags::CanBeExpanded最后导致展开对象的属性。