2.3 事件、委托与属性绑定
委托的三种写法
SNew(SButton)
// ① 无返回值的用 OnClicked_Lambda / OnPressed_Lambda
.OnClicked_Lambda([]() { DoSomething(); return FReply::Handled(); })
// ② 成员函数
.OnClicked(this, &FMyTool::OnButtonClicked) // 原生 C++ 类
.OnClicked_Raw(MyUObject, &UMyObject::HandleClicked) // UObject
// ③ 弱引用绑定(推荐:对象销毁后安全)
SNew(SButton).OnClicked(this, &FMyTool::OnClicked) // TSharedPtr 持有者安全
最常出的事故:OnClicked_Raw 绑了一个 UObject 指针,对象被 GC/销毁后按钮事件调到野指针 → 崩溃。规则:
- 绑 UObject 成员函数一律用
SPROPERTY的安全绑定或检查对象有效性 - 绑原生 C++ 对象用
OnClicked(this, ...)(持有 TSharedPtr 的场景)或确保生命周期长于 UI - lambda 捕获
this前,确认 UI 与对象的生命周期关系
常用事件
| 控件 | 事件 |
|---|---|
| SButton | OnClicked / OnPressed / OnHovered / OnReleased |
| SCheckBox | OnCheckStateChanged |
| SEditableTextBox | OnTextChanged / OnTextCommitted(回车/失焦) |
| SComboBox | OnSelectionChanged |
| SListView | OnSelectionChanged / OnMouseButtonDoubleClick / OnGenerateRow |
| SSlider | OnValueChanged |
ListView 的完整事件链
SListView 是编辑器工具里最复杂也最有用的控件,关键回调是 OnGenerateRow——为每个列表项生成一个行控件:
SNew(SListView<TSharedPtr<FListItem>>)
.ItemHeight(24.0f)
.ListItemsSource(&Items) // 数据源:TArray<TSharedPtr<Item>>
.OnGenerateRow_Lambda([](TSharedPtr<FListItem> Item, const TSharedRef<STableViewBase>& OwnerTable)
{
return SNew(STableRow<TSharedPtr<FListItem>>, OwnerTable)
[
SNew(STextBlock).Text(Item->DisplayName)
];
})
.OnSelectionChanged_Lambda([](TSharedPtr<FListItem> Item, ESelectInfo::Type)
{
if (Item.IsValid()) ShowDetails(Item);
})
要点:
- 数据源是外部 TArray 的指针,ListView 不持有数据——数据变化时调
ListView->RequestListRefresh()通知重绘 - Item 用
TSharedPtr<FStruct>而不是 UObject 指针——列表项频繁创建销毁,避免 GC 压力 - 大量数据(>100)必须确认开启了虚拟化(SListView 默认按需生成行)
属性绑定(TAttribute)
属性绑定的回归函数在每次 Tick/重绘时调用,所以:
// 对:轻量函数
SNew(STextBlock).Text_Lambda([](){ return GetCountText(); })
// 错:重计算(每帧扫资产)
SNew(STextBlock).Text_Lambda([](){ return CountAllAssetsSlowly(); }) // 卡
绑定函数必须快(O(1) 级别)。重活做成:数据变化时缓存结果 → 绑定读缓存。
双向同步:控件值 ↔ 数据
想「用户改输入框 → 更新数据;数据变 → 输入框刷新」,需要:
// 输入框
SNew(SEditableTextBox)
.Text_Lambda([]() { return DataToText(CachedValue); }) // 数据→UI
.OnTextChanged_Lambda([](const FText& T) { CachedValue = TextToData(T); }) // UI→数据
这套「双向绑定」得自己写,Slate 没有现成的 MVVM(UE5 有 MVVM 插件,但工具 UI 一般不值得引入)。UMG 的「属性绑定」蓝图等价于这里的 TAttribute。
Tick 事件
SWidget 默认不 Tick。需要逐帧逻辑时用 SNew(SWidget).Tick_Lambda(...) 或从 SCompoundWidget 派生重写 Tick()。工具 UI 的 Tick 是卡顿来源——能用事件驱动(OnTextChanged 等)就别 Tick。
心得:UI 代码崩溃 80% 来自「事件绑定的对象已销毁」。Slate 工具上线前必做的一步:故意在工具运行中删除资产/关闭关卡/热重载,看事件还有没有触发崩溃。能熬过这套「破坏测试」的工具,线上基本不炸。
