vault backup: 2024-10-12 17:19:45
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# ArraySizeEnum
|
||||
|
||||
- **功能描述:** 为固定数组提供一个枚举,使得数组元素按照枚举值来作为索引和显示。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Container Property
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** T Array[Size]
|
||||
- **常用程度:** ★★★
|
||||
|
||||
为固定数组提供一个枚举,使得数组元素按照枚举值来作为索引和显示。
|
||||
|
||||
- 所谓固定数组,是区别于TArray这种可以动态改变大小的数组,而是平凡的用[size]直接定义的数组。这种固定数组(static array)因为不会增删,因此才有时适合用枚举里的所有值用作下标,达成更高的便利性。
|
||||
- 枚举里一般会把最后一个枚举项(一般叫做Max或者Size,count之类)作为数据大小值。
|
||||
- 枚举里不想显示的枚举值可以用Hidden隐藏起来,但因为数组下标对应的是枚举项的下标(就是第几个枚举值)而不是枚举项的值,因此会发现数组的实际显示项目比定义的Size要小。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UENUM(BlueprintType)
|
||||
enum class EMyArrayEnumNormal :uint8
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Third,
|
||||
Max,
|
||||
};
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EMyArrayEnumHidden :uint8
|
||||
{
|
||||
First,
|
||||
Second,
|
||||
Cat = 5 UMETA(Hidden),
|
||||
Third = 2,
|
||||
Max = 3,
|
||||
};
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = ArraySizeEnumTest)
|
||||
int32 MyIntArray_NoArraySizeEnum[3];
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = ArraySizeEnumTest, meta = (ArraySizeEnum = "MyArrayEnumNormal"))
|
||||
int32 MyIntArray_Normal_HasArraySizeEnum[(int)EMyArrayEnumNormal::Max];
|
||||
|
||||
UPROPERTY(EditAnywhere, Category = ArraySizeEnumTest, meta = (ArraySizeEnum = "MyArrayEnumHidden"))
|
||||
int32 MyIntArray_Hidden_HasArraySizeEnum[(int)EMyArrayEnumHidden::Max];
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
都是大小为3的固定数组。
|
||||
|
||||
- MyIntArray_NoArraySizeEnum,是最普通的数组模样。
|
||||
- MyIntArray_Normal_HasArraySizeEnum,正统的使用枚举项来当数组下标的例子。可以发现下标名字不是012,而是枚举项名称了。
|
||||
- MyIntArray_Hidden_HasArraySizeEnum采用的枚举项里有隐藏的一项Cat,但它的下标是2(因为定义的顺序),因此数组的第3个被隐藏了起来。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
可以发现一开始判断是否是固定数组(ArrayDim>1其实就是固定数组了),然后通过名字找枚举,以及为数组的每一项去枚举里找枚举项从而生成细节面板里的子行。
|
||||
|
||||
```cpp
|
||||
void FItemPropertyNode::InitChildNodes()
|
||||
{
|
||||
if( MyProperty->ArrayDim > 1 && ArrayIndex == -1 )
|
||||
{
|
||||
// Do not add array children which are defined by an enum but the enum at the array index is hidden
|
||||
// This only applies to static arrays
|
||||
static const FName NAME_ArraySizeEnum("ArraySizeEnum");
|
||||
UEnum* ArraySizeEnum = NULL;
|
||||
if (MyProperty->HasMetaData(NAME_ArraySizeEnum))
|
||||
{
|
||||
ArraySizeEnum = FindObject<UEnum>(NULL, *MyProperty->GetMetaData(NAME_ArraySizeEnum));
|
||||
}
|
||||
|
||||
// Expand array.
|
||||
for( int32 Index = 0 ; Index < MyProperty->ArrayDim ; Index++ )
|
||||
{
|
||||
bool bShouldBeHidden = false;
|
||||
if( ArraySizeEnum )
|
||||
{
|
||||
// The enum at this array index is hidden
|
||||
bShouldBeHidden = ArraySizeEnum->HasMetaData(TEXT("Hidden"), Index );
|
||||
}
|
||||
|
||||
if( !bShouldBeHidden )
|
||||
{
|
||||
TSharedPtr<FItemPropertyNode> NewItemNode( new FItemPropertyNode);
|
||||
FPropertyNodeInitParams InitParams;
|
||||
InitParams.ParentNode = SharedThis(this);
|
||||
InitParams.Property = MyProperty;
|
||||
InitParams.ArrayOffset = Index*MyProperty->ElementSize;
|
||||
InitParams.ArrayIndex = Index;
|
||||
InitParams.bAllowChildren = true;
|
||||
InitParams.bForceHiddenPropertyVisibility = bShouldShowHiddenProperties;
|
||||
InitParams.bCreateDisableEditOnInstanceNodes = bShouldShowDisableEditOnInstance;
|
||||
|
||||
NewItemNode->InitNode( InitParams );
|
||||
AddChildNode(NewItemNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 261 KiB |
@@ -0,0 +1,63 @@
|
||||
# EditFixedOrder
|
||||
|
||||
- **功能描述:** 使数组的元素无法通过拖拽来重新排序。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Container Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** TArray
|
||||
- **常用程度:** ★★
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest)
|
||||
TArray<int32> MyIntArray_NoEditFixedOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest, meta = (EditFixedOrder))
|
||||
TArray<int32> MyIntArray_EditFixedOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest)
|
||||
TSet<int32> MyIntSet_NoEditFixedOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest, meta = (EditFixedOrder))
|
||||
TSet<int32> MyIntSet_EditFixedOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest)
|
||||
TMap<int32,FString> MyIntMap_NoEditFixedOrder;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = EditFixedOrderTest, meta = (EditFixedOrder))
|
||||
TMap<int32,FString> MyIntMap_EditFixedOrder;
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
- 可见只有第一个MyIntArray_NoEditFixedOrder,在数组元素上出现可拖拽的标记,然后可以改变顺序。
|
||||
- 加上EditFixedOrder的TArray就无法改变顺序了。
|
||||
- 其他TSet,TMap是不支持该meta的,因为其内部本身顺序也无关。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
可以看见,细节面板里一个属性行是否可重排序的判断是外部是个数组,且没有EditFixedOrder和ArraySizeEnum(固定数组)。当然本身这个属性也要在可编辑状态(比如被禁用灰掉就显然不可编辑了)
|
||||
|
||||
```cpp
|
||||
bool FPropertyNode::IsReorderable()
|
||||
{
|
||||
FProperty* NodeProperty = GetProperty();
|
||||
if (NodeProperty == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// It is reorderable if the parent is an array and metadata doesn't prohibit it
|
||||
const FArrayProperty* OuterArrayProp = NodeProperty->GetOwner<FArrayProperty>();
|
||||
|
||||
static const FName Name_DisableReordering("EditFixedOrder");
|
||||
static const FName NAME_ArraySizeEnum("ArraySizeEnum");
|
||||
return OuterArrayProp != nullptr
|
||||
&& !OuterArrayProp->HasMetaData(Name_DisableReordering)
|
||||
&& !IsEditConst()
|
||||
&& !OuterArrayProp->HasMetaData(NAME_ArraySizeEnum)
|
||||
&& !FApp::IsGame();
|
||||
}
|
||||
```
|
@@ -0,0 +1,55 @@
|
||||
# NoElementDuplicate
|
||||
|
||||
- **功能描述:** 去除TArray属性里数据项的Duplicate菜单项按钮。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Container Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** TArray
|
||||
- **常用程度:** ★
|
||||
|
||||
去除TArray属性里数据项的Duplicate菜单项按钮。
|
||||
|
||||
用于TArray属性,值可以是任何类型,可以是数值,结构,也可以是Object*。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = IntArray)
|
||||
TArray<int32> MyIntArray;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = IntArray, meta = (NoElementDuplicate))
|
||||
TArray<int32> MyIntArray_NoElementDuplicate;
|
||||
```
|
||||
|
||||
## 效果:
|
||||
|
||||
可以看到带有NoElementDuplicate的数组,在值的右侧下拉箭头的菜单项里只有两项。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
判断如有NoElementDuplicate,则只生成Insert_Delete 菜单,否则是默认的Insert_Delete_Duplicate 。当然要求当前属性是数组属性,且不是EditFixedSize固定大小的。
|
||||
|
||||
```cpp
|
||||
void GetRequiredPropertyButtons( TSharedRef<FPropertyNode> PropertyNode, TArray<EPropertyButton::Type>& OutRequiredButtons, bool bUsingAssetPicker )
|
||||
{
|
||||
const FArrayProperty* OuterArrayProp = NodeProperty->GetOwner<FArrayProperty>();
|
||||
|
||||
if( OuterArrayProp )
|
||||
{
|
||||
if( PropertyNode->HasNodeFlags(EPropertyNodeFlags::SingleSelectOnly) && !(OuterArrayProp->PropertyFlags & CPF_EditFixedSize) )
|
||||
{
|
||||
if (OuterArrayProp->HasMetaData(TEXT("NoElementDuplicate")))
|
||||
{
|
||||
OutRequiredButtons.Add( EPropertyButton::Insert_Delete );
|
||||
}
|
||||
else
|
||||
{
|
||||
OutRequiredButtons.Add( EPropertyButton::Insert_Delete_Duplicate );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,40 @@
|
||||
# ReadOnlyKeys
|
||||
|
||||
- **功能描述:** 使TMap属性的Key不能编辑。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Container Property
|
||||
- **元数据类型:** bool
|
||||
- **限制类型:** TMap属性
|
||||
- **常用程度:** ★★
|
||||
|
||||
使TMap属性的Key不能编辑。
|
||||
|
||||
意味着这个TMap里的元素是在这之前(构造函数里初始化等)就设置好的,但我们只希望用户更改值的内容,而不改Key的名字。这在某些情况下比较有用,比如以Platform作为Key,这样Platform的列表是固定的就不希望用户更改了。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ReadOnlyKeysTest)
|
||||
TMap<int32, FString> MyIntMap_NoReadOnlyKeys;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ReadOnlyKeysTest, meta = (ReadOnlyKeys))
|
||||
TMap<int32, FString> MyIntMap_ReadOnlyKeys;
|
||||
```
|
||||
|
||||
## 测试结果:
|
||||
|
||||
可见MyIntMap_ReadOnlyKeys的Key是灰色的,不可编辑。
|
||||
|
||||

|
||||
|
||||
## 源码里搜到:
|
||||
|
||||
```cpp
|
||||
void FDetailPropertyRow::MakeNameOrKeyWidget( FDetailWidgetRow& Row, const TSharedPtr<FDetailWidgetRow> InCustomRow ) const
|
||||
{
|
||||
if (PropertyHandle->HasMetaData(TEXT("ReadOnlyKeys")))
|
||||
{
|
||||
PropertyKeyEditor->GetPropertyNode()->SetNodeFlags(EPropertyNodeFlags::IsReadOnly, true);
|
||||
}
|
||||
}
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,163 @@
|
||||
# TitleProperty
|
||||
|
||||
- **功能描述:** 指定结构数组里的结构成员属性内容来作为结构数组元素的显示标题。
|
||||
- **使用位置:** UPROPERTY
|
||||
- **引擎模块:** Container Property
|
||||
- **元数据类型:** string="abc"
|
||||
- **限制类型:** TArray<FStruct>
|
||||
- **常用程度:** ★★
|
||||
|
||||
指定结构数组里的结构成员属性内容来作为结构数组元素的显示标题。
|
||||
|
||||
## 重点是:
|
||||
|
||||
- 作用目标为结构数组TArray<FStruct>,其他的TSet,TMap不支持。
|
||||
- TitleProperty顾名思义是用作标题的属性。但要更明确一些,标题指的是结构数组元素在细节面板里显示的标题。属性指的是结构数组里的结构里面的属性。
|
||||
- 然后下一步是TitleProperty的格式要怎么写。根据引擎源码,TitleProperty最后是用FTitleMetadataFormatter来解析计算内容。通过查看其内部代码,可知其TitleProperty格式可以为:
|
||||
- 如果TitleProperty里包含“{”,则会把里面的字符串当作一个FTextFormat(以“{ArgName}…”组织的格式字符串)。最终格式是以“{PropertyName}…”组织的字符串去找多个对应的属性。
|
||||
- 否则就会把TitleProperty的全部当作PropertyName,直接去找对应的属性名称。
|
||||
|
||||
## 测试代码:
|
||||
|
||||
```cpp
|
||||
USTRUCT(BlueprintType)
|
||||
struct INSIDER_API FMyArrayTitleStruct
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FMyArrayTitleStruct() = default;
|
||||
FMyArrayTitleStruct(int32 id) :MyInt(id) {}
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
int32 MyInt = 123;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
FString MyString=TEXT("Hello");
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
float MyFloat=456.f;
|
||||
};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = TitlePropertyTest)
|
||||
TArray<FMyArrayTitleStruct> MyStructArray_NoTitleProperty;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = TitlePropertyTest, meta = (TitleProperty="{MyString}[{MyInt}]"))
|
||||
TArray<FMyArrayTitleStruct> MyStructArray_HasTitleProperty;
|
||||
```
|
||||
|
||||
## 测试效果:
|
||||
|
||||
可以发现,下面的数组元素的标题变为了“Hello[x]”,而不是默认的“3 members”。
|
||||
|
||||

|
||||
|
||||
## 原理:
|
||||
|
||||
属性编辑器里属性节点如果发现有TitleProperty,则会生成一个FTitleMetadataFormatter的TitlePropertyFormatter 来进行字符串格式的解析和输出结果内容。其内部其实真正用的又是FTextFormat,这样才可以把多个属性的内容拼接成一个目标字符串。
|
||||
|
||||
当然SPropertyEditorTitle还注意到了如果有TitleProperty,可能会实时的改变,因此绑定了一个函数来进行Tick更新。
|
||||
|
||||
```cpp
|
||||
//绑定一个函数来每tick获取名字
|
||||
void SPropertyEditorTitle::Construct( const FArguments& InArgs, const TSharedRef<FPropertyEditor>& InPropertyEditor )
|
||||
{
|
||||
// If our property has title support we want to fetch the value every tick, otherwise we can just use a static value
|
||||
static const FName NAME_TitleProperty = FName(TEXT("TitleProperty"));
|
||||
const bool bHasTitleProperty = InPropertyEditor->GetProperty() && InPropertyEditor->GetProperty()->HasMetaData(NAME_TitleProperty);
|
||||
if (bHasTitleProperty)
|
||||
{
|
||||
NameTextBlock =
|
||||
SNew(STextBlock)
|
||||
.Text(InPropertyEditor, &FPropertyEditor::GetDisplayName)
|
||||
.Font(NameFont);
|
||||
}
|
||||
else
|
||||
{
|
||||
NameTextBlock =
|
||||
SNew(STextBlock)
|
||||
.Text(InPropertyEditor->GetDisplayName())
|
||||
.Font(NameFont);
|
||||
}
|
||||
}
|
||||
|
||||
FText FPropertyEditor::GetDisplayName() const
|
||||
{
|
||||
FItemPropertyNode* ItemPropertyNode = PropertyNode->AsItemPropertyNode();
|
||||
|
||||
if ( ItemPropertyNode != NULL )
|
||||
{
|
||||
return ItemPropertyNode->GetDisplayName();
|
||||
}
|
||||
|
||||
if (const FComplexPropertyNode* ComplexPropertyNode = PropertyNode->AsComplexNode())
|
||||
{
|
||||
const FText DisplayName = ComplexPropertyNode->GetDisplayName();
|
||||
|
||||
// Does this property define its own name?
|
||||
if (!DisplayName.IsEmpty())
|
||||
{
|
||||
return DisplayName;
|
||||
}
|
||||
}
|
||||
|
||||
FString DisplayName;
|
||||
PropertyNode->GetQualifiedName( DisplayName, true );
|
||||
return FText::FromString(DisplayName);
|
||||
}
|
||||
|
||||
//生成TitleFormatter来解析TitleProperty里面的内容,最后得出文字。发现不支持Map,Set,因此只支持array。签名还有个判断ArrayIndex()==1的分支,走进普通属性
|
||||
FText FItemPropertyNode::GetDisplayName() const
|
||||
{
|
||||
if (CastField<FSetProperty>(ParentProperty) == nullptr && CastField<FMapProperty>(ParentProperty) == nullptr)
|
||||
{
|
||||
// Check if this property has Title Property Meta
|
||||
static const FName NAME_TitleProperty = FName(TEXT("TitleProperty"));
|
||||
FString TitleProperty = PropertyPtr->GetMetaData(NAME_TitleProperty);
|
||||
if (!TitleProperty.IsEmpty())
|
||||
{
|
||||
// Find the property and get the right property handle
|
||||
if (PropertyStruct != nullptr)
|
||||
{
|
||||
const TSharedPtr<IPropertyHandle> ThisAsHandle = PropertyEditorHelpers::GetPropertyHandle(NonConstThis->AsShared(), nullptr, nullptr);
|
||||
TSharedPtr<FTitleMetadataFormatter> TitleFormatter = FTitleMetadataFormatter::TryParse(ThisAsHandle, TitleProperty);
|
||||
if (TitleFormatter)
|
||||
{
|
||||
TitleFormatter->GetDisplayText(FinalDisplayName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//生成一个TitlePropertyFormatter
|
||||
void SPropertyEditorArrayItem::Construct( const FArguments& InArgs, const TSharedRef< class FPropertyEditor>& InPropertyEditor )
|
||||
{
|
||||
static const FName TitlePropertyFName = FName(TEXT("TitleProperty"));
|
||||
|
||||
// if this is a struct property, try to find a representative element to use as our stand in
|
||||
if (PropertyEditor->PropertyIsA( FStructProperty::StaticClass() ))
|
||||
{
|
||||
const FProperty* MainProperty = PropertyEditor->GetProperty();
|
||||
const FProperty* ArrayProperty = MainProperty ? MainProperty->GetOwner<const FProperty>() : nullptr;
|
||||
if (ArrayProperty) // should always be true
|
||||
{
|
||||
TitlePropertyFormatter = FTitleMetadataFormatter::TryParse(PropertyEditor->GetPropertyHandle(), ArrayProperty->GetMetaData(TitlePropertyFName));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
源码中例子:
|
||||
|
||||
读者还可以在UPropertyEditorTestObject里找到应用的例子。用testprops命令行就可以打开。
|
||||
|
||||
```cpp
|
||||
UPROPERTY(EditAnywhere, Category=ArraysOfProperties, meta=(TitleProperty=IntPropertyInsideAStruct))
|
||||
TArray<FPropertyEditorTestBasicStruct> StructPropertyArrayWithTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category=ArraysOfProperties, meta=(TitleProperty="{IntPropertyInsideAStruct} + {FloatPropertyInsideAStruct}"))
|
||||
TArray<FPropertyEditorTestBasicStruct> StructPropertyArrayWithFormattedTitle;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category=ArraysOfProperties, meta=(TitleProperty=ErrorProperty))
|
||||
TArray<FPropertyEditorTestBasicStruct> StructPropertyArrayWithTitleError;
|
||||
|
||||
UPROPERTY(EditAnywhere, Category=ArraysOfProperties, meta=(TitleProperty="{ErrorProperty}"))
|
||||
TArray<FPropertyEditorTestBasicStruct> StructPropertyArrayWithFormattedTitleError;
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Reference in New Issue
Block a user