ue5 创建多列StreeView的方法与理解
创建StreeView的多列样式怎么就像是创建单行单列差不多?貌似就是在单行单列中加入了多列widget?
目录结构:
必备条件
StreeView的多列创建需要的必备条件:
数据基类
CustomItemBase
#pragma once
/*
----------------------------------
| Name | Value |
----------------------------------
| 香蕉 | 真正的香蕉 |
----------------------------------
*/
// 构建一个基类,相当于创建一个空的价签,其中包括(名字:价格:),具体怎么填由子类决定
class FCustomItemBase : public TSharedFromThis<FCustomItemBase>
{
public:
virtual ~FCustomItemBase() {}
// 比如要卖香蕉:名字:香蕉 ,Value :价格
virtual TSharedRef<SWidget> MakeNameWidget() = 0;
virtual TSharedRef<SWidget> MakeValueWidget() = 0;
//当展示时需要先获取到这个价签后才能知道这是为哪个商品准备的价签(即:名字,价格)
void GetChildrens(TArray<TSharedRef<FCustomItemBase>>& OutChildren) const{ OutChildren = Childrens;};
private:
//用于保存传入的参数并通过OutChildren传出去
TArray<TSharedRef<FCustomItemBase>> Childrens;
};
/*
----------------------------------
| Name | Value |
----------------------------------
FCustomItemBase中有两个属性(name、Value),所以制作价签时就需要预留两个空位,使用SMultiColumnTableRow多列样式
创建继承至class SMultiColumnTableRow的类,查看基类样式为SMultiColumnTableRow : public STableRow<ItemType>
解释为: S类名 :public 基类<价签的引用>,有几个属性就安排几个位置(即:有两个属性,就安排两列),这是S类,所以需要S类的构造方法
如果是单列:STableRow,每行就只能放一个属性,明白了吗?
----------
| Name |
----------
*/
class SMultiDetailTableRow : public SMultiColumnTableRow<TSharedRef<FCustomItemBase>>
{
SLATE_BEGIN_ARGS(SMultiDetailTableRow)
{
}
SLATE_END_ARGS()
// 父类SMultiColumnTableRow中的方法,动态生成不同列的显示内容,必须实现的方法
virtual TSharedRef<SWidget> GenerateWidgetForColumn( const FName& InColumnName ) override;
// 父类方法中默认两个参数,第三个参数是因为创建时必须有FCustomItemBase才能正确显示
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& OwnerTableView,TSharedRef<FCustomItemBase> InCustomItemBase);
private:
// 这个弱指针是为了初始化时将传进来的FCustomItemBase赋值给CustomItemBase用于保存
TWeakPtr<FCustomItemBase> CustomItemBase;
};
#include "CustomItemBase.h"
//一行多列
TSharedRef<SWidget> SMultiDetailTableRow::GenerateWidgetForColumn(const FName& InColumnName)
{
// 判断InColumnName,如果传入的是名字,就在价签的name中添加名字
if (InColumnName.IsEqual(TEXT("Name")))
{
return CustomItemBase.Pin()->MakeNameWidget();
}
// 判断InColumnName,如果传入的是Value,就在价签的Value中添加价格
if ((InColumnName.IsEqual(TEXT("Value"))))
{
return CustomItemBase.Pin()->MakeValueWidget();
}
// 空widget用于占位
return SNullWidget::NullWidget;
}
//还是创建了一行两列,streeview中的多个行还是需要在生成树的类中创建
void SMultiDetailTableRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& OwnerTableView,
TSharedRef<FCustomItemBase> InCustomItemBase)
{
//赋值保存
CustomItemBase = InCustomItemBase;
//STableRow::FArguments()是这个是原生的风格样式,生成时的样式布局(可替换)
STableRow::FArguments ParentArgs;
ParentArgs.Padding(FMargin(0,8,0,0));
//调用基类的构造函数,查看基类中的Construct方法
SMultiColumnTableRow::Construct(ParentArgs, OwnerTableView);
}
数据子类
CustomItemBool
#pragma once
#include "CustomItemBase.h"
//价签的子类,父类中的价签已经印刷好了
class FCustomItemBool : public FCustomItemBase
{
public:
//创建后默认就是 false
FCustomItemBool():Value(false){}
// 实现父类 CustomItemBase中的方法,做具体的事(即:具体的名字,具体的价格)
virtual TSharedRef<SWidget> MakeNameWidget() override;
virtual TSharedRef<SWidget> MakeValueWidget() override;
private:
void OnCheckStateChanged(ECheckBoxState CheckState);
ECheckBoxState GetCheckBoxState() const;
bool Value;
};
#include "CustomItemBool.h"
TSharedRef<SWidget> FCustomItemBool::MakeNameWidget()
{
//具体的名字Test Bool,显示中文必须用Text包裹:(TEXT("选中测试Bool")
return SNew(STextBlock).Text(FText::FromString(TEXT("选中测试Bool")));
}
TSharedRef<SWidget> FCustomItemBool::MakeValueWidget()
{
//这里创建的是选择框,可以是选择框、下拉框...Bool类的控件
return SNew(SCheckBox)
//绑定点击状态,是否点击了,这是一个事件SLATE_EVENT( FOnCheckStateChanged, OnCheckStateChanged )
//相当于点击了就触发点击事件,将这个事件与回调函数OnCheckStateChanged绑定,点击就触发
.OnCheckStateChanged(this, &FCustomItemBool::OnCheckStateChanged)
//点击后的状态,是选中了还是取消了,查看SLATE_ATTRIBUTE( ECheckBoxState, IsChecked )后不是知道是啥
//继续查看enum class ECheckBoxState : uint8,里面有三种状态,那这个IsChecked()的参数是不是就是要这个枚举?
//因为这里是绑定,需要回调函数,所以创建一个ECheckBoxState类型的函数试试,结果懵对了
.IsChecked(this, &FCustomItemBool::GetCheckBoxState);
}
void FCustomItemBool::OnCheckStateChanged(ECheckBoxState CheckState)
{
//既然是回调函数,那么当被触发后要做什么?这个类是bool类型,非真即假
//使用三目运算符 :给Value 赋值为 CheckState
//CheckState的状态如果与ECheckBoxState::Checked 的状态相同,就是真,否则是假
Value = CheckState == ECheckBoxState::Checked ? true : false;
}
ECheckBoxState FCustomItemBool::GetCheckBoxState() const
{
//上面已经给Value赋值了,这里就问Value是什么?如果是真就是 ECheckBoxState::Checked了,否则。。。
return Value? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
streetview组装类
CustomDetailPlane
#pragma once
#include "CustomItemBase.h"
//这里是使用typedef创建别名的方式,用FCustomItemBase中的数据,创建了一个树,
typedef STreeView<TSharedRef<FCustomItemBase>> CustomDetailTreeView;
class SCustomDetailPlane : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SCustomDetailPlane)
{
}
SLATE_END_ARGS()
SCustomDetailPlane();
virtual ~SCustomDetailPlane();
void Construct(const FArguments& InArgs);
//树形结构中的行,这里就是一行一列,这里可以再装入1行2列或是其他。。。
TSharedRef<ITableRow> CustomOnGenerateRow(TSharedRef<FCustomItemBase> Item,const TSharedRef< STableViewBase >& TableView);
//SLATE_EVENT( FOnGetChildren, OnGetChildren ),
//事件类型,进一步查看API DECLARE_DELEGATE_TwoParams (FOnGetChildren,ArgumentType, TArray<ArgumentType>& );
//TwoParams表示需要两个参数,没有返回值,因为是GetChildren,ArgumentType应该是Children父类,然后从父类获取包含的子类
void OnGetChildren(TSharedRef<FCustomItemBase> ParentItem, TArray<TSharedRef<FCustomItemBase>>& OutChildrens);
//当CustomItemBool类创建好后,这时已经在类中确定了要做的事,这里创建一个函数用于给TreeItemSources赋值测试
void SetItemData();
private:
TSharedPtr<SWidgetSwitcher> WidgetSwitcher;
//这里用的是上面创建的别名,生成指向STreeView<TSharedRef<FCustomItemBase>>的指针
//因为STreeView<TSharedRef<FCustomItemBase>>太长,所以使用别名,也方便修改
TSharedPtr<CustomDetailTreeView> OtherNameDetailTreeView;
//树结构需要的数据源,(即已经准备好的价签,展台已经准备好了,需要几层就放入几个价签)
TArray<TSharedRef<FCustomItemBase>> TreeItemSources;
};
#include "CustomDetailPlane.h"
#include "CustomItemBool.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
SCustomDetailPlane::SCustomDetailPlane()
{
}
SCustomDetailPlane::~SCustomDetailPlane()
{
}
void SCustomDetailPlane::Construct(const FArguments& InArgs)
{
ChildSlot
[
SAssignNew(WidgetSwitcher, SWidgetSwitcher)
+ SWidgetSwitcher::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString("Custom Detail Plane"))
]
+ SWidgetSwitcher::Slot()
.HAlign(HAlign_Fill)
.VAlign(VAlign_Fill)
[
//生成树控件,只有树干
SAssignNew(OtherNameDetailTreeView, CustomDetailTreeView)
//设置数据源(即:准备好树枝)
.TreeItemsSource(&TreeItemSources)
//绑定代理(将生成事件与实际生成方法绑定到一起)
.OnGenerateRow(this, &SCustomDetailPlane::CustomOnGenerateRow)
//绑定代理(将获取事件与实际获取方法绑定到一起)
.OnGetChildren(this, &SCustomDetailPlane::OnGetChildren)
//生成列
.HeaderRow(
SNew(SHeaderRow)
+ SHeaderRow::Column("Name")
.HeaderContentPadding(FMargin(0))
.FillWidth(0.4f)
[
SNew(SBorder)
.Padding(FMargin(20, 5, 5, 5))
[
SNew(STextBlock).Text(FText::FromString("Name"))
]
]
+ SHeaderRow::Column("Value")
.HeaderContentPadding(FMargin(0))
.FillWidth(0.6f)
[
SNew(SBorder)
.Padding(FMargin(20, 5, 5, 5))
[
SNew(STextBlock).Text(FText::FromString("Value"))
]
]
)
]
];
SetItemData();
}
TSharedRef<ITableRow> SCustomDetailPlane::CustomOnGenerateRow(TSharedRef<FCustomItemBase> Item,
const TSharedRef<STableViewBase>& TableView)
{
return SNew(SMultiDetailTableRow, TableView, Item);
}
void SCustomDetailPlane::OnGetChildren(TSharedRef<FCustomItemBase> ParentItem,
TArray<TSharedRef<FCustomItemBase>>& OutChildrens)
{
ParentItem->GetChildrens(OutChildrens);
}
void SCustomDetailPlane::SetItemData()
{
//相当于实例化了一个FCustomItemBool类型的小控件BoolItem,等待显示
TSharedRef<FCustomItemBool> BoolItem = MakeShareable(new FCustomItemBool());
//在FCustomItemBool中已经将水果放到了果篮中并将果篮摆在了层板上,这里将层板放到树上
TreeItemSources.Add(BoolItem);
//这里如果你又创建了新的控件,就还用上面的MakeShareable(new FCustomItemBool());方法
//然后用TreeItemSources.Add(BoolItem);添加
//刷新
OtherNameDetailTreeView->RequestTreeRefresh();
//显示WidgetSwitcher的那一页
WidgetSwitcher->SetActiveWidgetIndex(1);
}