You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修正Gtk++ WidgetConnector模板,适配带参及非void返回的回调函数

问题:Gtk++ WidgetConnector类如何支持带参数或非void返回值的回调函数

我需要实现一个类来管理Gtk++控件与回调函数的sigc::connection连接,实现延迟建立连接、批量断开/重连等功能。目前编写的WidgetConnector类中,getConnectedWidget模板函数仅支持无返回值且无参数的回调,当使用带参数(如void onButtonPressed(int x))或返回非void类型(如bool onButtonPressed())的回调时无法编译。请问需要如何修正该类及模板函数以解决此问题?

原代码示例

原Gtk++连接代码

Gtk::Button* button; 
builder->get_widget("fooButton", button);
button->signal_clicked().connect(sigc::mem_fun(this, &ThisClassName::onButtonPressed)); // ThisClassName为当前类名

原WidgetConnector类代码

#ifndef WIDGETCONNECTOR_H
#define WIDGETCONNECTOR_H

#include <functional>
#include <map>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <sigc++/sigc++.h>
#include <gtkmm.h>

class WidgetConnector {
public:
    using ConnectionProxy = std::function<sigc::connection()>;

    WidgetConnector(const Glib::RefPtr<Gtk::Builder>& builder)
        : mBuilder(builder) { assert(mBuilder); }
    virtual ~WidgetConnector() {}

    template<class T_Widget, auto S, typename... T>
    T_Widget& getConnectedWidget(std::string label, const typename sigc::slot< void(T...)>& slot) {
        T_Widget* w = nullptr;
        mBuilder->get_widget(label, w);
        assert(w);

        using signal_type = decltype(std::mem_fun(S));
        const signal_type& widgetSignal = std::mem_fun(S);

        // 延迟连接,避免构建期间触发回调
        mConnectRefList[label] = std::make_unique<ConnectionProxy>([w, slot, widgetSignal]() {
            return widgetSignal(w).connect(slot);
        });
        return *w;
    }

    void connectWidgetsWithSignals() {
        for (auto const& [label, functor] : mConnectRefList)
        {
            connectWidgetWithSignal(label);
        }
    }

    void connectWidgetWithSignal(std::string label, bool doOverwrite = false) {
        auto connectionRef = (*mConnectRefList.at(label))();
        addDisconnectRef(label, connectionRef, doOverwrite);
    }

    template<typename CONN>
    void addDisconnectRef(std::string label, CONN connectRef, bool doOverwrite = false) {
        try {
            if (mDisconnectRefList[label].connected() && doOverwrite)
                mDisconnectRefList[label].disconnect();
            if (! mDisconnectRefList[label].connected()) {
                mDisconnectRefList[label] = connectRef;
                mIsConnected = true;
            }
        }
        catch (...) {
            std::cerr << "No connection set up for " << label << std::endl;
        }
    }

    void disconnectWidgetsWithSignals() {
        for (auto const& [label, functor] : mDisconnectRefList)
        {
            disconnectWidgetWithSignal(label);
        }
        mIsConnected = false;
    }

    void disconnectWidgetWithSignal(std::string label) {
        try {
            if (mDisconnectRefList.at(label).connected())
                mDisconnectRefList.at(label).disconnect();
        }
        catch (...) {
            std::cerr << "No connection set up for " << label << std::endl;
        }
    }

private:
    const Glib::RefPtr<Gtk::Builder> mBuilder;
    std::map<std::string, std::unique_ptr<ConnectionProxy>> mConnectRefList;
    std::map<std::string, sigc::connection> mDisconnectRefList;
    bool mIsConnected = false; // 原代码缺失此成员,需补充
};

#endif // WIDGETCONNECTOR_H

原使用示例

auto& w = getConnectedWidget<Gtk::Button, &Gtk::Button::signal_clicked>("fooButton", 
                       static_cast<const typename sigc::slot<void()>>(sigc::mem_fun(this,   
                       &ThisClassName::onButtonPressed)));
// 对w进行操作,如w.set_sensitive()
connectWidgetsWithSignals(); // 实际建立连接

解决方案

核心修改点

  • 泛化槽类型:将getConnectedWidget的槽参数从固定的sigc::slot<void(T...)>改为模板参数,让编译器自动推导回调的返回值和参数类型,不再限制为void无参。
  • 简化信号调用:直接通过成员函数指针获取信号,无需std::mem_fun,使用(widget->*SignalMember)()直接调用信号成员函数得到信号对象。
  • 移除冗余类型转换:使用示例中不需要手动static_cast槽类型,模板会自动匹配回调的签名。

修改后的完整WidgetConnector类代码

#ifndef WIDGETCONNECTOR_H
#define WIDGETCONNECTOR_H

#include <functional>
#include <map>
#include <cassert>
#include <iostream>
#include <sigc++/sigc++.h>
#include <gtkmm.h>

class WidgetConnector {
public:
    using ConnectionProxy = std::function<sigc::connection()>;

    WidgetConnector(const Glib::RefPtr<Gtk::Builder>& builder)
        : mBuilder(builder), mIsConnected(false) { assert(mBuilder); }
    virtual ~WidgetConnector() {}

    // 泛化模板,支持任意签名的槽
    template<class T_Widget, auto SignalMember, typename SlotType>
    T_Widget& getConnectedWidget(std::string label, const SlotType& slot) {
        T_Widget* widget = nullptr;
        mBuilder->get_widget(label, widget);
        assert(widget && "Widget not found in builder");

        // 存储延迟连接的lambda,直接调用信号成员函数
        mConnectRefList[label] = std::make_unique<ConnectionProxy>(
            [widget, slot]() {
                return (widget->*SignalMember)().connect(slot);
            }
        );
        return *widget;
    }

    void connectWidgetsWithSignals() {
        for (const auto& [label, proxy] : mConnectRefList) {
            connectWidgetWithSignal(label);
        }
    }

    void connectWidgetWithSignal(const std::string& label, bool doOverwrite = false) {
        try {
            auto conn = (*mConnectRefList.at(label))();
            addDisconnectRef(label, conn, doOverwrite);
        } catch (const std::out_of_range&) {
            std::cerr << "No connection proxy found for widget: " << label << std::endl;
        }
    }

    template<typename ConnType>
    void addDisconnectRef(const std::string& label, ConnType conn, bool doOverwrite = false) {
        try {
            auto& existing_conn = mDisconnectRefList[label];
            if (existing_conn.connected() && doOverwrite) {
                existing_conn.disconnect();
            }
            if (!existing_conn.connected()) {
                existing_conn = std::move(conn);
                mIsConnected = true;
            }
        } catch (const std::out_of_range&) {
            mDisconnectRefList[label] = std::move(conn);
            mIsConnected = true;
        }
    }

    void disconnectWidgetsWithSignals() {
        for (auto& [label, conn] : mDisconnectRefList) {
            if (conn.connected()) {
                conn.disconnect();
            }
        }
        mIsConnected = false;
    }

    void disconnectWidgetWithSignal(const std::string& label) {
        try {
            auto& conn = mDisconnectRefList.at(label);
            if (conn.connected()) {
                conn.disconnect();
            }
        } catch (const std::out_of_range&) {
            std::cerr << "No active connection found for widget: " << label << std::endl;
        }
    }

    bool isConnected() const { return mIsConnected; }

private:
    const Glib::RefPtr<Gtk::Builder> mBuilder;
    std::map<std::string, std::unique_ptr<ConnectionProxy>> mConnectRefList;
    std::map<std::string, sigc::connection> mDisconnectRefList;
    bool mIsConnected;
};

#endif // WIDGETCONNECTOR_H

修改后的使用示例

1. 无参数无返回值回调

// 回调函数
void onButtonClicked() {
    std::cout << "Button clicked!" << std::endl;
}

// 使用WidgetConnector
WidgetConnector connector(builder);
auto& button = connector.getConnectedWidget<Gtk::Button, &Gtk::Button::signal_clicked>(
    "fooButton", sigc::mem_fun(this, &ThisClassName::onButtonClicked)
);
button.set_sensitive(true);
connector.connectWidgetsWithSignals();

2. 带参数的回调(如Gtk::Entry的signal_activate带字符串参数)

// 回调函数
void onEntryActivated(const Glib::ustring& text) {
    std::cout << "Entry text: " << text << std::endl;
}

// 使用WidgetConnector
auto& entry = connector.getConnectedWidget<Gtk::Entry, &Gtk::Entry::signal_activate>(
    "fooEntry", sigc::mem_fun(this, &ThisClassName::onEntryActivated)
);

3. 返回非void类型的回调(如Gtk::Window的signal_delete_event返回bool)

// 回调函数
bool onWindowDelete(GdkEventAny* event) {
    std::cout << "Window closing..." << std::endl;
    return true; // 返回true阻止窗口关闭,false允许关闭
}

// 使用WidgetConnector
auto& window = connector.getConnectedWidget<Gtk::Window, &Gtk::Window::signal_delete_event>(
    "mainWindow", sigc::mem_fun(this, &ThisClassName::onWindowDelete)
);

内容的提问来源于stack exchange,提问作者Markus K.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 09:15:32