一個藍圖類會有一個屬性面板,即Details面板。在編輯藍圖類或實例化藍圖類後,都可以編輯這個Details面板,下面第一節就是介紹如何自定義此面板內容。當一個藍圖類聲明一個藍圖變量時,其Details面板會出現對應的屬性設置,如何自定義這個屬性設置的內容和樣式就是第二節的內容。
下圖是一個藍圖類的屬性(Details)面板,其中的CustomCategory目錄內容是我們爲這個藍圖類拓展(第一節)的UI內容。而CustObject中的兩個內容是我們聲明兩個變量後,出現的變量自定義屬性樣式(第二節)。
下圖是自定義的CustActor類的屬性樣式:
要實現下面的兩個拓展功能,需要依賴一個模塊:PropertyEditor
//獲取模塊
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
一、自定義類的屬性面板
1. 創建任意UI
首先,我們創建一個類並繼承UObject,類名爲UCustObject。此類藍圖的屬性面板默認情形下爲空內容。
下面,我們來爲此類的屬性面板添加內容。
創建一個繼承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):
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);
}
因爲加了兩次同一個屬性到面板,所以這兩個屬性的修改是同步的
同樣,成員變量的樣式也可以自定義,下面我們使用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設置的屬性樣式