📘 本篇我们将结合之前的 SignalHub 与 Dispatcher 机制,构建一个适合 GUI 场景的响应式信号系统。以按钮点击为例,构建一个跨线程安全的事件响应系统,配合协程挂起/恢复,让 UI 编程也能更优雅易读。本篇以通俗方式讲解,适合初学者入门 GUI 驱动信号机制。
✅ 目标概览
模块 | 功能 |
---|---|
SignalHub GUI 版 | 管理 UI 控件的事件信号 |
GUIThreadDispatcher | 保障协程 resume 回到主线程执行(GUI 安全) |
按钮信号绑定 | 点击按钮 emit 信号,触发等待协程执行 |
协程结构 | UI 写法更清晰:等事件,再执行响应逻辑 |
✅ 一、简单 GUI 场景抽象(模拟)
为了不引入真实 GUI 库(如 Qt/SDL/ImGui),我们先模拟一个 UI 控件:
struct Button {
std::string label;
std::function<void()> onClick;
void click() {
if (onClick) onClick();
}
};
📌 假设你点击一个按钮,就会触发 onClick()
回调。
✅ 二、构建 GUI 主线程 Dispatcher
class GUIThreadDispatcher : public Dispatcher {
public:
void dispatch(std::function<void()> task) override {
tasks.push(task); // 假设 GUI 主线程会轮询执行这些任务
}
void runAll() {
while (!tasks.empty()) {
auto fn = tasks.front();
tasks.pop();
fn();
}
}
private:
std::queue<std::function<void()>> tasks;
};
📌 实际 GUI 框架中,主线程 loop 会负责 runAll()
。
✅ 三、构建 GUI 按钮信号中心
template<typename T>
class ButtonSignal {
public:
ButtonSignal(Dispatcher* d) : dispatcher(d) {}
void emit(const T& value) {
for (auto* w : waiters) {
w->resume(value);
}
waiters.clear();
}
HubAwaiter<T>* wait() {
auto* w = new HubAwaiter<T>();
w->dispatcher = dispatcher;
waiters.push_back(w);
return w;
}
private:
Dispatcher* dispatcher;
std::vector<HubAwaiter<T>*> waiters;
};
✅ 四、绑定协程处理逻辑
Task<void> onButtonClicked(ButtonSignal<std::string>& sig) {
std::string label = co_await *sig.wait();
std::cout << "🟢 你点击了按钮:" << label << std::endl;
co_return;
}
✅ 五、完整主函数模拟:模拟 UI 信号触发
int main() {
GUIThreadDispatcher guiDispatcher; // GUI 主线程调度器
ButtonSignal<std::string> signal(&guiDispatcher); // 按钮信号中心
Button btn { "登录" };
auto task = onButtonClicked(signal); // 协程等待按钮点击
btn.onClick = [&]() {
signal.emit(btn.label); // 点击按钮发出信号
};
// 模拟 UI 用户点击按钮
std::cout << "📋 用户点击按钮...\n";
btn.click();
// 主线程调度 resume
guiDispatcher.runAll();
return 0;
}
输出:
📋 用户点击按钮...
🟢 你点击了按钮:登录
✅ 总结回顾
点位 | 说明 |
---|---|
按钮.onClick | 发出信号(emit) |
协程 co_await | 挂起,等待按钮被点击 |
Dispatcher | 保证协程 resume 在 GUI 主线程 |
UI逻辑更清晰 | 不再回调地狱,线性协程代码更可维护 |
🧠 小测试:你掌握了吗?
Q1:为何需要 Dispatcher?
保证协程 resume 执行在主线程(尤其是 GUI 框架必须的线程安全要求)。
Q2:多个按钮可否共用一个事件中心?
可以,用 string 区分 label,或给每个按钮各自一个 signal。
Q3:协程比传统回调有什么优势?
更清晰、结构更顺、避免嵌套 callback 地狱。
Q4:实际 GUI 是否可用?
是的,此结构可直接套用于 ImGui/SDL/Qt 等 GUI 回调逻辑。
🔭 下一步 Day 17:组合 UI、异步加载与动画驱动
- 点击按钮后异步加载数据
- 加载过程中显示加载中动画
- 信号驱动界面状态切换(Loading → Ready)