7.9 KiB
Raw Blame History

AllowEditInlineCustomization

  • 功能描述: 允许EditInline的对象属性可以自定义属性细节面板来编辑该对象内的数据。
  • 使用位置: UPROPERTY
  • 元数据类型: string="abc"
  • 关联项: EditInline
  • 常用程度:

允许EditInline的对象属性可以自定义属性细节面板来编辑该对象内的数据。

测试代码:

UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyCommonObject :public UObject
{
	GENERATED_BODY()
public:
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	int32 MyInt = 123;
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	FString MyString;
};

UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyCustomAsset :public UObject
{
	GENERATED_BODY()
public:
public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline))
	UMyCommonObject* MyCommonObject;

	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditInline,AllowEditInlineCustomization))
	UMyCommonObject* MyCommonObject_Customization;
};

效果:

Untitled

要做到自定义EditInline的效果采用自定义的IPropertyTypeCustomization和RegisterCustomPropertyTypeLayout也能做到。区别是正如上面代码里的UMyCustomAsset里面有两个同类型的UMyCommonObject*对象假如用IPropertyTypeCustomization的方式就会导致两个变量都变成自定义的UI模式。而用AllowEditInlineCustomization就可以使得其中你想要的那个变成自定义方式而其他的不做改变。

在用法上AllowEditInlineCustomization必须配合自定义的FAssetEditorToolkit来自己定义一个DetailView而不是只自定义某个类型在引擎统一的DetailView的显示然后再自定义IDetailCustomization来提供具体的Widget最后用RegisterInstancedCustomPropertyLayout来关联起来。

DetailsView->RegisterInstancedCustomPropertyLayout(UMyCommonObject::StaticClass(),FOnGetDetailCustomizationInstance::CreateStatic(&FMyCommonObjectDetailsCustomization::MakeInstance));

这部分代码可参考MyCustomAsset的相关实现

源码:

FDetailPropertyRow::FDetailPropertyRow(TSharedPtr<FPropertyNode> InPropertyNode, TSharedRef<FDetailCategoryImpl> InParentCategory, TSharedPtr<FComplexPropertyNode> InExternalRootNode)
{
		static FName InlineCustomizationKeyMeta("AllowEditInlineCustomization");
		if (PropertyNode->AsComplexNode() && ExternalRootNode.IsValid()) // AsComplexNode works both for objects and structs
		{
			// We are showing an entirely different object inline.  Generate a layout for it now.
			if (IDetailsViewPrivate* DetailsView = InParentCategory->GetDetailsView())
			{
				ExternalObjectLayout = MakeShared<FDetailLayoutData>();
				DetailsView->UpdateSinglePropertyMap(InExternalRootNode, *ExternalObjectLayout, true);
			}
		}
		else if (PropertyNode->HasNodeFlags(EPropertyNodeFlags::EditInlineNew) && PropertyNode->GetProperty()->HasMetaData(InlineCustomizationKeyMeta))
		{
			// Allow customization of 'edit inline new' objects if the metadata key has been specified.
			// The child of this node, if set, will be an object node that we will want to treat as an 'external object layout'
			TSharedPtr<FPropertyNode> ChildNode = PropertyNode->GetNumChildNodes() > 0 ? PropertyNode->GetChildNode(0) : nullptr;
			TSharedPtr<FComplexPropertyNode> ComplexChildNode = StaticCastSharedPtr<FComplexPropertyNode>(ChildNode);
			if (ComplexChildNode.IsValid())
			{
				// We are showing an entirely different object inline.  Generate a layout for it now.
				if (IDetailsViewPrivate* DetailsView = InParentCategory->GetDetailsView())
				{
					ExternalObjectLayout = MakeShared<FDetailLayoutData>();
					DetailsView->UpdateSinglePropertyMap(ComplexChildNode, *ExternalObjectLayout, true);
				}
			}
		}
		
}

作用的原理是在创建FDetailPropertyRow的时候即一个属性的在细节面板里的一行如果有AllowEditInlineCustomization就会创建ExternalObjectLayout 之后在FDetailPropertyRow的创建孩子的时候就会判断是否有ExternalObjectLayout如果有就可以应用上我们之前的Customization如果没有就会应用默认的设置。如下是使用ExternalObjectLayout的代码

void FDetailPropertyRow::GenerateChildrenForPropertyNode( TSharedPtr<FPropertyNode>& RootPropertyNode, FDetailNodeList& OutChildren )
{
	// Children should be disabled if we are disabled
	TAttribute<bool> ParentEnabledState = TAttribute<bool>::CreateSP(this, &FDetailPropertyRow::GetEnabledState);

	if( PropertyTypeLayoutBuilder.IsValid() && bShowCustomPropertyChildren )
	{
		const TArray< FDetailLayoutCustomization >& ChildRows = PropertyTypeLayoutBuilder->GetChildCustomizations();

		for( int32 ChildIndex = 0; ChildIndex < ChildRows.Num(); ++ChildIndex )
		{
			TSharedRef<FDetailItemNode> ChildNodeItem = MakeShared<FDetailItemNode>(ChildRows[ChildIndex], ParentCategory.Pin().ToSharedRef(), ParentEnabledState);
			ChildNodeItem->Initialize();
			OutChildren.Add( ChildNodeItem );
		}
	}
	else if (ExternalObjectLayout.IsValid() && ExternalObjectLayout->DetailLayout->HasDetails())
	{
		OutChildren.Append(ExternalObjectLayout->DetailLayout->GetAllRootTreeNodes());
		//自定义的面板
	}
	else if ((bShowCustomPropertyChildren || !CustomPropertyWidget.IsValid()) && RootPropertyNode->GetNumChildNodes() > 0)
{
	//正常的默认创建孩子
}

源码里使用的一个例子是LevelSequence上Bind Actor上的Binding Property的细节面板。

假如我们采用一些代码去掉

USTRUCT()
struct FMovieSceneBindingPropertyInfo
{
	GENERATED_BODY()

	// Locator for the entry
	UPROPERTY(EditAnywhere, Category = "Default", meta=(AllowedLocators="Actor", DisplayName="Actor"))
	FUniversalObjectLocator Locator;

	// Flags for how to resolve the locator
	UPROPERTY()
	ELocatorResolveFlags ResolveFlags = ELocatorResolveFlags::None;

	UPROPERTY(Instanced, VisibleAnywhere, Category = "Default", meta=(EditInline, AllowEditInlineCustomization, DisplayName="Custom Binding Type"))
	UMovieSceneCustomBinding* CustomBinding = nullptr;
};

//自己Hack 代码
UObject* obj = UInsiderLibrary::FindObjectWithNameSmart(TEXT("MovieSceneBindingPropertyInfo"));
UScriptStruct* ss = Cast<UScriptStruct>(obj);
FProperty* prop = ss->FindPropertyByName(TEXT("CustomBinding"));
prop->RemoveMetaData(TEXT("AllowEditInlineCustomization"));

效果就会从左变到右边:

Untitled

注册的方式也不同:

void ULevelSequenceEditorSubsystem::AddBindingDetailCustomizations(TSharedRef<IDetailsView> DetailsView, TSharedPtr<ISequencer> ActiveSequencer, FGuid BindingGuid)
{
	// TODO: Do we want to create a generalized way for folks to add instanced property layouts for other custom binding types so they can have access to sequencer context?
	if (ActiveSequencer.IsValid())
	{
		UMovieSceneSequence* Sequence = ActiveSequencer->GetFocusedMovieSceneSequence();
		UMovieScene* MovieScene = Sequence ? Sequence->GetMovieScene() : nullptr;
		if (MovieScene)
		{
			FPropertyEditorModule& PropertyEditor = FModuleManager::Get().LoadModuleChecked<FPropertyEditorModule>(TEXT("PropertyEditor"));
			DetailsView->RegisterInstancedCustomPropertyTypeLayout(FMovieSceneBindingPropertyInfo::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateLambda([](TWeakPtr<ISequencer> InSequencer, UMovieScene* InMovieScene, FGuid InBindingGuid, ULevelSequenceEditorSubsystem* LevelSequenceEditorSubsystem)
				{
					return MakeShared<FMovieSceneBindingPropertyInfoDetailCustomization>(InSequencer, InMovieScene, InBindingGuid, LevelSequenceEditorSubsystem);
				}, ActiveSequencer.ToWeakPtr(), MovieScene, BindingGuid, this));
			
			DetailsView->RegisterInstancedCustomPropertyLayout(UMovieSceneSpawnableActorBinding::StaticClass(), FOnGetDetailCustomizationInstance::CreateStatic(&FMovieSceneSpawnableActorBindingBaseCustomization::MakeInstance, ActiveSequencer.ToWeakPtr(), MovieScene, BindingGuid));
		}
	}
}