一个蓝图类会有一个属性面板,即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.RegisterCustomPropertyTypeLayout("ColorStruct", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FColorStructPropertyDetail::MakeInstance));
PropertyEditorModule.NotifyCustomizationModuleChanged();
//卸载
PropertyEditorModule.UnregisterCustomPropertyTypeLayout("ColorStruct");
一个结构体拥有了一个类似于Bool设置的属性样式