【UE4】编辑器开发(四)属性面板(Details)拓展

一个蓝图类会有一个属性面板,即Details面板。在编辑蓝图类或实例化蓝图类后,都可以编辑这个Details面板,下面第一节就是介绍如何自定义此面板内容。当一个蓝图类声明一个蓝图变量时,其Details面板会出现对应的属性设置,如何自定义这个属性设置的内容和样式就是第二节的内容。
下图是一个蓝图类的属性(Details)面板,其中的CustomCategory目录内容是我们为这个蓝图类拓展(第一节)的UI内容。而CustObject中的两个内容是我们声明两个变量后,出现的变量自定义属性样式(第二节)。
自定义属性面板
下图是自定义的CustActor类的属性样式:
Actor属性样式
要实现下面的两个拓展功能,需要依赖一个模块:PropertyEditor

//获取模块
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");

一、自定义类的属性面板

1. 创建任意UI

首先,我们创建一个类并继承UObject,类名为UCustObject。此类蓝图的属性面板默认情形下为空内容。
UCustomObject类
空属性面板
下面,我们来为此类的属性面板添加内容。

创建一个继承IDetailCustomization抽象类的子类,并实现抽象方法。

void FCustDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	//返回指定目录的构造对象。第一个参数用于查找指定目录(必要时也可以作为目录名),第二个参数为显示目录名(可选),第三个参数为优先级,下面设置的是Important,所以会在顶层显示此目录。
	IDetailCategoryBuilder& CustCategory = DetailBuilder.EditCategory(FName("CustCategory"), LOCTEXT("CustCategory", "Custom Category"), ECategoryPriority::Important);
	//为此目录添加一行记录。第一个参数为用于过滤(搜索)的文本,第二个参数是是否为高级项。
	CustCategory.AddCustomRow(LOCTEXT("CustRow", "CustRow"), false).NameContent()	//NameContent是属性名的显示内容
		[
			SNew(STextBlock).Text(LOCTEXT("TextBlock", "TextBlock"))
		].ValueContent()
		[	//ValueContent是值的显示内容。如果不想区分Key和Value分割的话,可以直接使用WholeRowContent,下面有效果。
			SNew(SVerticalBox)+SVerticalBox::Slot().HAlign(HAlign_Fill).AutoHeight()
			[
				SNew(SCheckBox)
			]+ SVerticalBox::Slot().HAlign(HAlign_Fill).AutoHeight()
			[
				SNew(SEditableTextBox)
			]
		];
}

创建一个静态方法,可以通过此方法获取(创建)一个IDetailCustomization子类(FCustDetailCustomization)的实例对象。用于后面的注册绑定到指定对象。

TSharedRef<IDetailCustomization> FCustDetailCustomization::MakeInstance()
{
	return MakeShareable(new FCustDetailCustomization());
}

下面,我们将对象与上面创建的属性面板的实例对象进行绑定:

//注册绑定
PropertyEditorModule.RegisterCustomPropertyTypeLayout("CustObject", FOnGetDetailCustomizationInstance::CreateStatic(&FCustDetailCustomization::MakeInstance));
//通知自定义模块修改完成
PropertyEditorModule.NotifyCustomizationModuleChanged();

//模块卸载时,记得Unregister
PropertyEditorModule.UnregisterCustomPropertyTypeLayout("CustObject");

AddCustomRow第一个参数的作用:
搜索
AddCustomRow第二个参数的作用:
不显示高级选项
显示高级选项
整行显示(WholeRowContent):
WholeRowContent

2. 设置成员变量UI

下图为声明的属性在属性面板中的样式,下面
属性目录
将属性放置在任意目录:

void FCustDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{	//获取属性句柄,这里获取的就是上边的IntegerVar变量的句柄
	TSharedRef<IPropertyHandle> IntegerHandle = DetailBuilder.GetProperty("IntegerVar");
	//获取目录构造器
	IDetailCategoryBuilder& CustCategory = DetailBuilder.EditCategory(FName("CustCategory"), LOCTEXT("CustCategory", "Custom Category"), ECategoryPriority::Important);
	//在目录中添加一行,并将
	CustCategory.AddCustomRow(LOCTEXT("SearchText", "SearchText")).WholeRowContent()
		[	//可以使用SProperty通过句柄直接创建一个默认的UI
			SNew(SProperty, IntegerHandle)
		];
	//也可以通过下面的两个方法添加到目录,不过这个就和UPROPERTY宏的作用一样了
	//第一个参数是属性句柄,第二个是查询关键词,第三个是是否是高级选项(上面演示过了)
	DetailBuilder.AddCustomRowToCategory(IntegerHandle, LOCTEXT("SearchInt", "SearchInt"), false);
	DetailBuilder.AddPropertyToCategory(IntegerHandle);
}

使用SProperty创建样式
因为加了两次同一个属性到面板,所以这两个属性的修改是同步的
添加到默认目录
同样,成员变量的样式也可以自定义,下面我们使用CustomWidget方法自定义成员变量的UI

void FCustDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	TSharedRef<IPropertyHandle> IntegerHandle = DetailBuilder.GetProperty("IntegerVar");
	IDetailCategoryBuilder& CustCategory = DetailBuilder.EditCategory(FName("CustCategory"), LOCTEXT("CustCategory", "Custom Category"), ECategoryPriority::Important);
	CustCategory.AddCustomRow(LOCTEXT("SearchText", "SearchText")).WholeRowContent().HAlign(HAlign_Left)
		[
			SNew(SBox).MinDesiredWidth(200.f)
			[
				SNew(SProperty, IntegerHandle).CustomWidget()
				[
					SNew(SHorizontalBox) + SHorizontalBox::Slot()
					[
						SNew(SCheckBox)
					] + SHorizontalBox::Slot()
					[
						SNew(SEditableTextBox)
					]
				]
			]
		];
}

使用CustomWidget进行自定义样式的方式和下面的差不多:

void FCustDetailCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
	TSharedRef<IPropertyHandle> IntegerHandle = DetailBuilder.GetProperty("IntegerVar");
	IDetailCategoryBuilder& CustCategory = DetailBuilder.EditCategory(FName("CustCategory"), LOCTEXT("CustCategory", "Custom Category"), ECategoryPriority::Important);
	CustCategory.AddCustomRow(LOCTEXT("SearchText", "SearchText")).NameContent()
		[
			SNew(STextBlock).Text(IntegerHandle->GetPropertyDisplayName())
		].ValueContent()
		[
			SNew(SHorizontalBox)+SHorizontalBox::Slot()
			[
				SNew(SCheckBox)
			]+ SHorizontalBox::Slot()
			[
				SNew(SEditableTextBox)
			]
		];
}

自定义样式的成员变量:
自定义样式的成员变量

二、自定义类属性设置

上面我们修改的是类的成员变量的样式,只对该类生效。下面我们实现的是将一个类或结构体作为其他类的成员变量时,全部都使用一种自定义的样式。
下面演示的是将一个结构体在设置样式改为和bool类型一样,是一个CheckBox(True or False)样式。
当ColorStruct选中时,Color为“Red”;未选中Color值为“Not Red”。
打印字符串
对比:
对比

创建一个结构体,我们自定义其Property样式

USTRUCT(BlueprintType)
struct FColorStruct
{
	GENERATED_USTRUCT_BODY()
public:
	//字符串,就是上面要显示的内容,这里注意,UPROPERTY宏要加上EditAnywhere,这样PropertyHandle才能获取到反射数据
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FString Color;
	//用于记录Checkbox的状态
	UPROPERTY(EditAnywhere)
	bool bRed;

	FColorStruct() : Color("Red"), bRed(true){}
};

创建一个类并继承IPropertyTypeCustomization。实现两个抽象方法和一个创建实例的静态方法。

void FStructDetail::CustomizeHeader(TSharedRef<IPropertyHandle> PropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
	HeaderRow.NameContent()		//属性的Name样式
		[
			PropertyHandle->CreatePropertyNameWidget(LOCTEXT("UseRed", "Use Red"))
		].ValueContent()		//属性的Value样式
		[	//当CheckBox状态发生改变时,触发OnCheckBoxStateChanged方法
			SAssignNew(CheckBox, SCheckBox).OnCheckStateChanged_Raw(this, &FColorStructPropertyDetail::OnCheckBoxStateChanged)
		];
}

void FStructDetail::CustomizeChildren(TSharedRef<IPropertyHandle> PropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils)
{
	//获取结构体的两个属性句柄
	ColorHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FColorStruct, Color));
	BoolHandle = PropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FColorStruct, bRed));
	if (BoolHandle.IsValid() && CheckBox.IsValid())
	{	//当显示属性内容时,初始化样式内容,上面的初始化样式,此方法初始化值
		bool bRed;
		BoolHandle->GetValue(bRed);
		if (bRed)
			CheckBox->SetIsChecked(ECheckBoxState::Checked);
		else
			CheckBox->SetIsChecked(ECheckBoxState::Unchecked);
	}
}

void FColorStructPropertyDetail::OnCheckBoxStateChanged(ECheckBoxState State)
{
	if (ColorHandle.IsValid() && BoolHandle.IsValid())		//当CheckBox状态发生改变,且结构体属性句柄有效,将修改其内容
	{
		switch (State)
		{
		case ECheckBoxState::Unchecked:				//取消选中
			ColorHandle->SetValue(FString("Not Red"));	//设置Color的内容为“Not Red”,SetValue的参数必须要与属性参数一致,否则会赋值失败!必要时,可以通过UProperty通过内存地址修改值
			BoolHandle->SetValue(false);				//记录Checkbox状态的变量修改
			break;
		case ECheckBoxState::Checked:
			ColorHandle->SetValue(FString("Red"));
			BoolHandle->SetValue(true);
			break;
		case ECheckBoxState::Undetermined:
			ColorHandle->SetValue(FString("Red"));
			BoolHandle->SetValue(true);
			break;
		default:
			break;
		}
	}
}

将结构体和自定义属性样式类绑定

FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyEditorModule.RegisterCustomClassLayout("ColorStruct", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FColorStructPropertyDetail::MakeInstance));
PropertyEditorModule.NotifyCustomizationModuleChanged();
//卸载
PropertyEditorModule.UnregisterCustomPropertyTypeLayout("ColorStruct");

一个结构体拥有了一个类似于Bool设置的属性样式
结构体属性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章