# GetOptions - **功能描述:** 指定一个外部类的函数提供选项给FName或FString属性在细节面板中下拉选项框提供值列表。 - **使用位置:** UPARAM, UPROPERTY - **引擎模块:** String/Text Property - **元数据类型:** string="abc" - **限制类型:** FString,FName - **关联项:** [GetKeyOptions](../GetKeyOptions.md), [GetValueOptions](../GetValueOptions.md) - **常用程度:** ★★★★★ 指定一个外部类的函数提供选项给FName或FString属性在细节面板中下拉选项框提供值列表。 - 只作用于FName或FString属性,FText不支持。 - 也可以用在容器上,比如TArray,TMap,TSet。 - 也可用在内部结构的变量上。这里的关键点是在寻找函数的时候,是通过找到OuterObject::Function来的,因此即使是内部结构的变量,也可以找到外部class里的函数。但如果是另外一个不相关的类,就必须用“Module.Class.Function”这种方式才能找到,否则只能返回空。 - 函数的原型是TArray 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 MyArray_GetOptions; UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static")) TSet MySet_GetOptions; UPROPERTY(EditAnywhere, meta = (GetOptions = "MyGetOptions_Static")) TMap 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 MyArray_GetOptions; UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions")) TSet MySet_GetOptions; UPROPERTY(EditAnywhere, Category = GetOptions, meta = (GetOptions = "MyGetOptions")) TMap MyMap_GetOptions; UFUNCTION() static TArray MyGetOptions_Static() { return TArray{"Cat", "Dog"}; } UFUNCTION() TArray MyGetOptions() { return TArray{"First", "Second", "Third"}; } public: UPROPERTY(EditAnywhere, Category = GetOptionsStruct) FMyOptionsTest MyStruct_GetOptions; public: UPROPERTY(EditAnywhere, Category = GetKeyValueOptions, meta = (GetKeyOptions = "MyGetOptions",GetValueOptions="MyGetOptions_Static")) TMap MyMap_GetKeyValueOptions; } ``` ## 测试效果: 根据下图,可见FText并没有起作用。其他带有GetOptions标记的在细节面板上都有一个下拉选项框。 而另外当使用TMap的时候,还可以用GetKeyOptions 和GetValueOptions来分别单独为Key和Value提供不一样的选项列表,见MyMap_GetKeyValueOptions。 ![Untitled](Untitled.png) ## 原理: 大致流程是用GetPropertyOptionsMetaDataKey来判断一个属性是否支持选项框编辑,然后通过 GetPropertyOptions调用指定的函数来获得选项列表,最后根据这个列表的值BuildComboBoxWidget。 ```cpp void PropertyEditorUtils::GetPropertyOptions(TArray& InOutContainers, FString& InOutPropertyPath, TArray>& InOutOptions) { // Check for external function references if (InOutPropertyPath.Contains(TEXT("."))) { InOutContainers.Empty(); UFunction* GetOptionsFunction = FindObject(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 OptionIntersection; TSet OptionIntersectionSet; for (UObject* Target : InOutContainers) { TArray StringOptions; { FEditorScriptExecutionGuard ScriptExecutionGuard; FCachedPropertyPath Path(InOutPropertyPath); if (!PropertyPathHelpers::GetPropertyValue(Target, Path, StringOptions)) { TArray 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(StringOptions); } else { TSet 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(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(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 SGraphPinString::TryBuildComboBoxWidget() { PropertyEditorUtils::GetPropertyOptions(PropertyContainers, GetOptionsFunctionName, ComboBoxOptions); } ```