【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設置的屬性樣式
結構體屬性

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