vault backup: 2024-10-12 17:19:45

This commit is contained in:
2024-10-12 17:19:46 +08:00
parent ff94ddca61
commit 244c0c52f6
960 changed files with 31348 additions and 10 deletions

View File

@@ -0,0 +1,94 @@
# AllowedCharacters
- **功能描述:** 只允许文本框里可以输入这些字符。
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** string="abc"
- **限制类型:** FName/FString/Fext
- **常用程度:** ★★★
只允许文本框里可以输入这些字符。
## 测试代码:
```cpp
public:
UPROPERTY(EditAnywhere, Category = AllowedCharactersTest, meta = (AllowedCharacters = "abcde"))
FString MyString_AllowedCharacters;
UPROPERTY(EditAnywhere, Category = AllowedCharactersTest, meta = (AllowedCharacters = "你好"))
FString MyString_AllowedCharacters_Chinese;
```
## 测试效果:
可见第一个只能输入abcde而fgh产生了报错。在测试中文的时候如果粘贴进去对应的中文则是OK的。否则也会产生报错不允许输入进去。
![Untitled](Untitled.png)
## 原理:
SPropertyEditorText里实际保存了FCharRangeList 的AllowedCharacters用来限制字符。同样在字符串改变的时候验证字符是否合法。
```cpp
FCharRangeList AllowedCharacters;
AllowedCharacters.InitializeFromString(PropertyHandle->GetMetaData(NAME_AllowedCharacters));
bool SPropertyEditorText::OnVerifyTextChanged(const FText& Text, FText& OutError)
{
const FString& TextString = Text.ToString();
if (MaxLength > 0 && TextString.Len() > MaxLength)
{
OutError = FText::Format(LOCTEXT("PropertyTextTooLongError", "This value is too long ({0}/{1} characters)"), TextString.Len(), MaxLength);
return false;
}
if (!AllowedCharacters.IsEmpty())
{
if (!TextString.IsEmpty() && !AllowedCharacters.AreAllCharsIncluded(TextString))
{
TSet<TCHAR> InvalidCharacters = AllowedCharacters.FindCharsNotIncluded(TextString);
FString InvalidCharactersString;
for (TCHAR Char : InvalidCharacters)
{
if (!InvalidCharactersString.IsEmpty())
{
InvalidCharactersString.AppendChar(TEXT(' '));
}
InvalidCharactersString.AppendChar(Char);
}
OutError = FText::Format(LOCTEXT("PropertyTextCharactersNotAllowedError", "The value may not contain the following characters: {0}"), FText::FromString(InvalidCharactersString));
return false;
}
}
if (PropertyValidatorFunc.IsBound())
{
FText Result = PropertyValidatorFunc.Execute(TextString);
if (!Result.IsEmpty())
{
OutError = Result;
return false;
}
}
return true;
}
```
根据FCharRangeList的定义限制字符的格式是
```cpp
/** Initializes this instance with the character ranges represented by the passed definition string.
* A definition string contains characters and ranges of characters, one after another with no special separators between them.
* Characters - and \ must be escaped like this: \- and \\
*
* Examples:
* "aT._" <-- Letters 'a' and 'T', dot and underscore.
* "a-zT._" <-- All letters from 'a' to 'z', letter 'T', dot and underscore.
* "a-zA-Z0-9._" <-- All lowercase and uppercase letters, all digits, dot and underscore.
* "a-zA-Z0-9\-\\._" <-- All lowercase and uppercase letters, all digits, minus sign, backslash, dot and underscore.
*/
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,8 @@
# GetKeyOptions
- **功能描述:** 为TMap里的FName/FString作为Key提供细节面板里选项框的选项值
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** string="abc"
- **限制类型:** TMap里FName/FString作为Key
- **关联项:** [GetOptions](GetOptions/GetOptions.md)

View File

@@ -0,0 +1,194 @@
# GetOptions
- **功能描述:** 指定一个外部类的函数提供选项给FName或FString属性在细节面板中下拉选项框提供值列表。
- **使用位置:** UPARAM, UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** string="abc"
- **限制类型:** FString,FName
- **关联项:** [GetKeyOptions](../GetKeyOptions.md), [GetValueOptions](../GetValueOptions.md)
- **常用程度:** ★★★★★
指定一个外部类的函数提供选项给FName或FString属性在细节面板中下拉选项框提供值列表。
- 只作用于FName或FString属性FText不支持。
- 也可以用在容器上比如TArrayTMapTSet。
- 也可用在内部结构的变量上。这里的关键点是在寻找函数的时候是通过找到OuterObject::Function来的因此即使是内部结构的变量也可以找到外部class里的函数。但如果是另外一个不相关的类就必须用“Module.Class.Function”这种方式才能找到否则只能返回空。
- 函数的原型是TArray<FString> FuncName() 返回一个字符串类型即使类型是FName因为引擎内部会自己做转换。
- 函数可以是成员函数,有可以是静态函数。
## 测试代码:
```cpp
USTRUCT(BlueprintType)
struct INSIDER_API FMyOptionsTest
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static"))
FString MyString_GetOptions;
UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static"))
TArray<FString> MyArray_GetOptions;
UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static"))
TSet<FString> MySet_GetOptions;
UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static"))
TMap<FString, int32> MyMap_GetOptions;
};
UCLASS(BlueprintType)
class INSIDER_API UMyProperty_Text :public UObject
{
public:
UPROPERTY(EditAnywhere, Category = GetOptions)
FString MyString_NoOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
FString MyString_GetOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
FName MyName_GetOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
FText MyText_GetOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
TArray<FString> MyArray_GetOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
TSet<FString> MySet_GetOptions;
UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions"))
TMap<FString, int32> MyMap_GetOptions;
UFUNCTION()
static TArray<FString> MyGetOptions_Static() { return TArray<FString>{"Cat", "Dog"}; }
UFUNCTION()
TArray<FString> MyGetOptions() { return TArray<FString>{"First", "Second", "Third"}; }
public:
UPROPERTY(EditAnywhere, Category = GetOptionsStruct)
FMyOptionsTest MyStruct_GetOptions;
public:
UPROPERTY(EditAnywhere, Category = GetKeyValueOptions, meta = (GetKeyOptions = "MyGetOptions",GetValueOptions="MyGetOptions_Static"))
TMap<FString, FName> MyMap_GetKeyValueOptions;
}
```
## 测试效果:
根据下图可见FText并没有起作用。其他带有GetOptions标记的在细节面板上都有一个下拉选项框。
而另外当使用TMap的时候还可以用GetKeyOptions 和GetValueOptions来分别单独为Key和Value提供不一样的选项列表见MyMap_GetKeyValueOptions。
![Untitled](Untitled.png)
## 原理:
大致流程是用GetPropertyOptionsMetaDataKey来判断一个属性是否支持选项框编辑然后通过
GetPropertyOptions调用指定的函数来获得选项列表最后根据这个列表的值BuildComboBoxWidget。
```cpp
void PropertyEditorUtils::GetPropertyOptions(TArray<UObject*>& InOutContainers, FString& InOutPropertyPath,
TArray<TSharedPtr<FString>>& InOutOptions)
{
// Check for external function references
if (InOutPropertyPath.Contains(TEXT(".")))
{
InOutContainers.Empty();
UFunction* GetOptionsFunction = FindObject<UFunction>(nullptr, *InOutPropertyPath, true);
if (ensureMsgf(GetOptionsFunction && GetOptionsFunction->HasAnyFunctionFlags(EFunctionFlags::FUNC_Static), TEXT("Invalid GetOptions: %s"), *InOutPropertyPath))
{
UObject* GetOptionsCDO = GetOptionsFunction->GetOuterUClass()->GetDefaultObject();
GetOptionsFunction->GetName(InOutPropertyPath);
InOutContainers.Add(GetOptionsCDO);
}
}
if (InOutContainers.Num() > 0)
{
TArray<FString> OptionIntersection;
TSet<FString> OptionIntersectionSet;
for (UObject* Target : InOutContainers)
{
TArray<FString> StringOptions;
{
FEditorScriptExecutionGuard ScriptExecutionGuard;
FCachedPropertyPath Path(InOutPropertyPath);
if (!PropertyPathHelpers::GetPropertyValue(Target, Path, StringOptions))
{
TArray<FName> NameOptions;
if (PropertyPathHelpers::GetPropertyValue(Target, Path, NameOptions))
{
Algo::Transform(NameOptions, StringOptions, [](const FName& InName) { return InName.ToString(); });
}
}
}
// If this is the first time there won't be any options.
if (OptionIntersection.Num() == 0)
{
OptionIntersection = StringOptions;
OptionIntersectionSet = TSet<FString>(StringOptions);
}
else
{
TSet<FString> StringOptionsSet(StringOptions);
OptionIntersectionSet = StringOptionsSet.Intersect(OptionIntersectionSet);
OptionIntersection.RemoveAll([&OptionIntersectionSet](const FString& Option){ return !OptionIntersectionSet.Contains(Option); });
}
// If we're out of possible intersected options, we can stop.
if (OptionIntersection.Num() == 0)
{
break;
}
}
Algo::Transform(OptionIntersection, InOutOptions, [](const FString& InString) { return MakeShared<FString>(InString); });
}
}
FName GetPropertyOptionsMetaDataKey(const FProperty* Property)
{
// Only string and name properties can have options
if (Property->IsA(FStrProperty::StaticClass()) || Property->IsA(FNameProperty::StaticClass()))
{
const FProperty* OwnerProperty = Property->GetOwnerProperty();
static const FName GetOptionsName("GetOptions");
if (OwnerProperty->HasMetaData(GetOptionsName))
{
return GetOptionsName;
}
// Map properties can have separate options for keys and values
const FMapProperty* MapProperty = CastField<FMapProperty>(OwnerProperty);
if (MapProperty)
{
static const FName GetKeyOptionsName("GetKeyOptions");
if (MapProperty->HasMetaData(GetKeyOptionsName) && MapProperty->GetKeyProperty() == Property)
{
return GetKeyOptionsName;
}
static const FName GetValueOptionsName("GetValueOptions");
if (MapProperty->HasMetaData(GetValueOptionsName) && MapProperty->GetValueProperty() == Property)
{
return GetValueOptionsName;
}
}
}
return NAME_None;
}
TSharedPtr<SWidget> SGraphPinString::TryBuildComboBoxWidget()
{
PropertyEditorUtils::GetPropertyOptions(PropertyContainers, GetOptionsFunctionName, ComboBoxOptions);
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -0,0 +1,8 @@
# GetValueOptions
- **功能描述:** 为TMap里的FName/FString作Value提供细节面板里选项框的选项值
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** string="abc"
- **限制类型:** TMap里FName/FString作为Value
- **关联项:** [GetOptions](GetOptions/GetOptions.md)

View File

@@ -0,0 +1,46 @@
# MaxLength
- **功能描述:** 在文本编辑框里限制文本的最大长度
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** int32
- **限制类型:** FName/FString/Fext
- **常用程度:** ★★★★★
在文本编辑框里限制文本的最大长度。但在C++或蓝图层面还是可以自己任意写入值的。
## 测试代码:
```cpp
UPROPERTY(EditAnywhere, Category = MaxLengthTest, meta = (MaxLength = 10))
FString MyString_MaxLength10 = TEXT("Hello");
```
## 测试效果:
![Untitled](Untitled.png)
## 原理:
在文本框里字符串改变的时候,检查当前的长度,超过则报错。
如果是FName属性则会把MaxLength 继续限制在NAME_SIZE (1024)以内。
```cpp
MaxLength = PropertyHandle->GetIntMetaData(NAME_MaxLength);
if (InPropertyEditor->PropertyIsA(FNameProperty::StaticClass()))
{
MaxLength = MaxLength <= 0 ? NAME_SIZE - 1 : FMath::Min(MaxLength, NAME_SIZE - 1);
}
bool SPropertyEditorText::OnVerifyTextChanged(const FText& Text, FText& OutError)
{
const FString& TextString = Text.ToString();
if (MaxLength > 0 && TextString.Len() > MaxLength)
{
OutError = FText::Format(LOCTEXT("PropertyTextTooLongError", "This value is too long ({0}/{1} characters)"), TextString.Len(), MaxLength);
return false;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,70 @@
# MultiLine
- **功能描述:** 使得文本属性编辑框支持换行。
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** bool
- **限制类型:** FName/FString/Fext
- **常用程度:** ★★★★★
使得文本属性编辑框支持换行。换行后的字符串以”\r\n”分隔换行。
## 测试代码:
```cpp
UPROPERTY(EditAnywhere, Category = MultiLineTest, meta = (MultiLine = true))
FString MyString_MultiLine = TEXT("Hello");
UPROPERTY(EditAnywhere, Category = MultiLineTest, meta = (MultiLine = true))
FText MyText_MultiLine = INVTEXT("Hello");
UPROPERTY(EditAnywhere, Category = MultiLineTest, meta = (MultiLine = true, PasswordField = true))
FString MyString_MultiLine_Password = TEXT("Hello");
UPROPERTY(EditAnywhere, Category = MultiLineTest, meta = (MultiLine = true, PasswordField = true))
FText MyText_MultiLine_Password = INVTEXT("Hello");
```
## 测试结果:
按住Shift+Enter回车换行。
![Untitled](Untitled.png)
## 原理:
原理也很简单根据bIsMultiLine 创建特定的多行编辑控件SMultiLineEditableTextBox。
```cpp
void SPropertyEditorText::Construct( const FArguments& InArgs, const TSharedRef< class FPropertyEditor >& InPropertyEditor )
{
bIsMultiLine = PropertyHandle->GetBoolMetaData(NAME_MultiLine);
if(bIsMultiLine)
{
ChildSlot
[
SAssignNew(HorizontalBox, SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew(MultiLineWidget, SMultiLineEditableTextBox)
.Text(InPropertyEditor, &FPropertyEditor::GetValueAsText)
.Font(InArgs._Font)
.SelectAllTextWhenFocused(false)
.ClearKeyboardFocusOnCommit(false)
.OnTextCommitted(this, &SPropertyEditorText::OnTextCommitted)
.OnVerifyTextChanged(this, &SPropertyEditorText::OnVerifyTextChanged)
.SelectAllTextOnCommit(false)
.IsReadOnly(this, &SPropertyEditorText::IsReadOnly)
.AutoWrapText(true)
.ModiferKeyForNewLine(EModifierKey::Shift)
//.IsPassword( bIsPassword )
]
];
PrimaryWidget = MultiLineWidget;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,91 @@
# PasswordField
- **功能描述:** 使得文本属性显示为密码框
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** bool
- **限制类型:** FName/FString/Fext
- **常用程度:** ★★★★★
使得文本属性显示为密码框。注意该文本的值在内存里依然是直接保存的,并没有加密,因此要自己来处理安全问题。
## 测试代码:
```cpp
public:
UPROPERTY(EditAnywhere, Category = PasswordTest)
FString MyString = TEXT("Hello");
UPROPERTY(EditAnywhere, Category = PasswordTest)
FText MyText = INVTEXT("Hello");
public:
UPROPERTY(EditAnywhere, Category = PasswordTest, meta = (PasswordField = true))
FString MyString_Password = TEXT("Hello");
UPROPERTY(EditAnywhere, Category = PasswordTest, meta = (PasswordField = true))
FText MyText_Password = INVTEXT("Hello");
```
## 测试效果:
![Untitled](Untitled.png)
## 原理:
该属性会导致Widget的IsPassword为true从源码也可以得知需要注意PasswordField 不能和MultiLine共用会导致Password功能失效。
```cpp
void SPropertyEditorText::Construct( const FArguments& InArgs, const TSharedRef< class FPropertyEditor >& InPropertyEditor )
{
const bool bIsPassword = PropertyHandle->GetBoolMetaData(NAME_PasswordField);
if(bIsMultiLine)
{
ChildSlot
[
SAssignNew(HorizontalBox, SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew(MultiLineWidget, SMultiLineEditableTextBox)
.Text(InPropertyEditor, &FPropertyEditor::GetValueAsText)
.Font(InArgs._Font)
.SelectAllTextWhenFocused(false)
.ClearKeyboardFocusOnCommit(false)
.OnTextCommitted(this, &SPropertyEditorText::OnTextCommitted)
.OnVerifyTextChanged(this, &SPropertyEditorText::OnVerifyTextChanged)
.SelectAllTextOnCommit(false)
.IsReadOnly(this, &SPropertyEditorText::IsReadOnly)
.AutoWrapText(true)
.ModiferKeyForNewLine(EModifierKey::Shift)
//.IsPassword( bIsPassword )
]
];
PrimaryWidget = MultiLineWidget;
}
else
{
ChildSlot
[
SAssignNew(HorizontalBox, SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SAssignNew( SingleLineWidget, SEditableTextBox )
.Text( InPropertyEditor, &FPropertyEditor::GetValueAsText )
.Font( InArgs._Font )
.SelectAllTextWhenFocused( true )
.ClearKeyboardFocusOnCommit(false)
.OnTextCommitted( this, &SPropertyEditorText::OnTextCommitted )
.OnVerifyTextChanged( this, &SPropertyEditorText::OnVerifyTextChanged )
.SelectAllTextOnCommit( true )
.IsReadOnly(this, &SPropertyEditorText::IsReadOnly)
.IsPassword( bIsPassword )
]
];
PrimaryWidget = SingleLineWidget;
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,73 @@
# PropertyValidator
- **功能描述:** 用名字指定一个UFUNCTION函数来进行文本的验证
- **使用位置:** UPROPERTY
- **引擎模块:** String/Text Property
- **元数据类型:** string="abc"
- **限制类型:** FName/FString/Fext
- **常用程度:** ★★★
用名字指定一个UFUNCTION函数来进行文本的验证。
这个函数必须是用UFUNCTION修饰的这样才能通过名字找到。且因为搜索的范围在本类中因此该函数必须定义在本类中。否则会报错“LogPropertyNode: Warning: PropertyValidator ufunction 'MyValidateMyString' on UMyProperty_Text::MyString_PropertyValidator not found.”
函数的签名见如下代码。返回空的FText代表没有错误否则即是错误信息。
## 测试代码:
```cpp
UPROPERTY(EditAnywhere, Category = PropertyValidatorTest, meta = (PropertyValidator = "MyValidateMyString"))
FString MyString_PropertyValidator;
UFUNCTION()
static FText MyValidateMyString(const FString& Value)
{
if (Value.Len() <= 5 && Value.Contains("A"))
{
return FText();
}
return INVTEXT("This is invalid string");
}
```
## 测试效果:
![Untitled](Untitled.png)
## 原理:
原理也比较简单分为两部分。一是如何找到并创建该UFunction二是调用该函数来验证字符串。
```cpp
const FString PropertyValidatorFunctionName = PropertyHandle->GetMetaData(NAME_PropertyValidator);
const UClass* OuterBaseClass = PropertyHandle->GetOuterBaseClass();
if (!PropertyValidatorFunctionName.IsEmpty() && OuterBaseClass)
{
static TSet<FString> LoggedWarnings;
UObject* ValidatorObject = OuterBaseClass->GetDefaultObject<UObject>();
const UFunction* PropertyValidatorFunction = ValidatorObject->FindFunction(*PropertyValidatorFunctionName);
if (PropertyValidatorFunction)
{
if (PropertyValidatorFunction->FunctionFlags & FUNC_Static)
{
PropertyValidatorFunc = FPropertyValidatorFunc::CreateUFunction(ValidatorObject, PropertyValidatorFunction->GetFName());
}
bool SPropertyEditorText::OnVerifyTextChanged(const FText& Text, FText& OutError)
{
const FString& TextString = Text.ToString();
if (PropertyValidatorFunc.IsBound())
{
FText Result = PropertyValidatorFunc.Execute(TextString);
if (!Result.IsEmpty())
{
OutError = Result;
return false;
}
}
return true;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB