【问题标题】:Qt: Trying to pass multiple references to a slot function after a signal is emittedQt:在发出信号后尝试将多个引用传递给槽函数
【发布时间】:2017-07-27 16:39:54
【问题描述】:

编辑:我已阅读 Passing and argument to a slot 它很有帮助,但没有解决我通过信号槽将多个引用传递给我调用的函数的问题。

我目前正在开发一个本质上是单位转换器的 Qt 应用程序。我正在使用 QDoubleSpinBoxes 作为输入和输出来实现它。我遇到了一个我正在寻求帮助的问题。实现的想法是,用户将输入他们想要转换的任何值,当用户失去对旋转框的关注或按下回车键时,它将用答案填充其他单元类型框。

这是我目前的做法:

// creates a custom spinbox for each option
modifiedSpinbox *FahrenheitDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *CelciusDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *KelvinDblSpinbox = new modifiedSpinbox();
modifiedSpinbox *RankineDblSpinbox = new modifiedSpinbox();

// Creates a signal mapper that allows passing of a parameter
// to the convert functions anytime a number is entered
QSignalMapper *tempMapper = new QSignalMapper(this);

// Connects the spinbox editing complete signal with the mapper
connect(tempMapper, SIGNAL(mapped(int)),
        this,SLOT(on_userInput(int whoSignaled)));
// Connects the mapper with the function that calls the convert class
connect(FahrenheitDblSpinbox, SIGNAL(editingFinished()),
        tempMapper, SLOT(map()));
tempMapper->setMapping(FahrenheitDblSpinbox, 1);

我想实现这个转换函数的方式是,在用户完成他们对旋转框的输入后,让代码发送一个信号 (editingFinished()) 到一个插槽函数,该函数调用一个具有所需功能的 converttools 类进行实际转换。我遇到的问题是我无法弄清楚如何将这些 spinbox 对象的引用传递给我的 converttools 类,以便我可以直接设置 spinbox 值。 我最接近的是使用 QSignalMapper(如上所示)将 single int 或 Qwidget 传递给我的插槽函数,而不是我想要的对象。

我想要一些关于如何在发出信号后将多个引用传递给我的自定义类的建议。我在这里查看了很多问题,但似乎仍然无法弄清楚如何做到这一点或我没有看到更好的方法。

谢谢!

【问题讨论】:

  • 你应该更喜欢new signal/slot syntax
  • @KubaOber 如果您建议使用 lambda 解决方案...那将是一个痛苦的解决方案,总共需要编写 12 个 lambda 连接:(
  • 我已经看过那个页面我的应该是重复的,我不同意。 QSignalMapper(据我所知)的问题在于它只允许您传递一个 int、QWdiget 或 QObject。据我所知,我不能使用它传递一个 doublespinbox,即使可以,我也需要为每个函数调用传递多个引用,而不仅仅是一个。也许这不是实现我正在尝试做的事情的最佳方式,但这是我能想到的唯一方式。
  • 我很抱歉,我的代码有点混乱,因为它显示我试图传递一个我想要做的部分 int。我正在玩弄我发布的代码。
  • @spartan228 我同意这不是重复的。你最好的选择不是QSignalMapper,因为你的信号必须是无参数的。相反,您将需要编写自己的 QObject,它接受映射 lambdas 并将它们与对象方法相关联。

标签: qt signals-slots


【解决方案1】:

QSignalMapper 在 C++11 中已过时。对于在 C++98 中声明函子的繁琐性,这只是一个创可贴。如果您使用的是 C++11 编译器,则永远不必使用 QSignalMapper

Lambdas 让它变得微不足道 - 你真的不需要传递太多。旁注:理想情况下,您应该使用现代 C++11 单元库。

这是一个完整的例子:

// https://github.com/KubaO/stackoverflown/tree/master/questions/tempconvert-42648860
#include <QtWidgets>

const char kUnit[] = "unit";
class MyWidget : public QWidget {
   QFormLayout m_layout{this};
   QDoubleSpinBox m_fahrenheit, m_celsius, m_kelvin;
   QList<QDoubleSpinBox*> const m_spinBoxes{&m_fahrenheit, &m_celsius, &m_kelvin};
   enum Unit { Fahrenheit, Celsius, Kelvin };

   static double Q_DECL_RELAXED_CONSTEXPR fromK(Unit to, double val) {
      if (to == Fahrenheit) return (val-273.15)*1.8 + 32.0;
      else if (to == Celsius) return val - 273.15;
      else return val;
   }
   static double Q_DECL_RELAXED_CONSTEXPR toK(Unit from, double val) {
      if (from == Fahrenheit) return (val-32.0)/1.8 + 273.15;
      else if (from == Celsius) return val + 273.15;
      else return val;
   }
   void setTemp(Unit unit, double temp,  QDoubleSpinBox * skip = nullptr) {
      for (auto spin : m_spinBoxes) if (spin != skip) {
         QSignalBlocker b{spin};
         spin->setValue(fromK(unitOf(spin), toK(unit, temp)));
      }
   }
   static Unit unitOf(QDoubleSpinBox * spin) {
      return static_cast<Unit>(spin->property(kUnit).toInt());
   }
public:
   MyWidget(QWidget * parent = nullptr) : QWidget{parent} {
      m_layout.addRow("Fahreneheit", &m_fahrenheit);
      m_layout.addRow("Celsius", &m_celsius);
      m_layout.addRow("Kelvin", &m_kelvin);
      m_fahrenheit.setProperty(kUnit, Fahrenheit);
      m_celsius.setProperty(kUnit, Celsius);
      m_kelvin.setProperty(kUnit, Kelvin);
      for (auto const spin : m_spinBoxes) {
         auto const unit = unitOf(spin);
         spin->setRange(fromK(unit, 0.), fromK(unit, 1000.));
         connect(spin, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged),
                 [=]{ setTemp(unit, spin->value(), spin); });
      }
      setTemp(Celsius, 20.);
   }
};

int main(int argc, char ** argv) {
   QApplication app{argc, argv};
   MyWidget ui;
   ui.show();
   return app.exec();
}

【讨论】:

  • 这会重置盒子上的值已更改,我发现这特别成问题,因为不能保证往返。例如在不合理的大摄氏温度下,这可能会导致无限循环 0.0
  • 我的意思是我希望你能告诉我一个我没有想到的解决方法。 (我不知道Q_DECL_RELAXED_CONSTEXPR,所以这已经是东西了。)无论如何,这仍然会触发无限循环,因为setValue 发送valueChanged 信号,这意味着即使摄氏盒不会继续告诉自己要更新,Kelvin/Fahrenheit 盒子会,并且往返仍然会导致无限循环。我不得不通过阻止信号来解决这个问题:(
  • 由于信号阻塞,无法循环。往返误差远远超过任何可实现的温度测量。 53 位尾数是 lot 的分辨率。如果我们能够很好地测量温度,我会把代码打印出来吃掉。
  • "setValue 发送 valueChanged 信号" 不,它没有。这就是你错过的。它从来没有发送过信号——不在这段代码中。从代码的第一个版本开始就存在信号阻塞。 “我必须通过阻止信号来解决这个问题”这也是我的代码所做的。这就是你应该做的。这不是黑客攻击。这就是存在信号阻塞的原因。
  • 您缺少QSignalBlocker。无论如何,这是编译和运行并执行它应该正确执行的代码。只需从 github 下载它,在调试器下运行它,然后自己看看它是如何工作的。设置断点并进行实验。
【解决方案2】:

您正在寻找的是:

  1. 携带QDoubleSpinBox指定温度的信号
  2. 转换为常用温度单位
  3. 通知所有其他 QDoubleSpinBoxs 以更新它们
  4. 从通用温度单位到每个QDoubleSpinBox 的特定温度单位的转换

QSignalMapper is a bad choice here, because:

该类收集一组无参数信号,并用与发送信号的对象对应的整数、字符串或小部件参数重新发出它们

所以我们不能接受指定的温度。相反,让我们从 map&lt;QDoubleSpinBox*, pair&lt;function&lt;double(double)&gt;, function&lt;double(double)&gt;&gt;&gt; 开始,它将分别用于从给定的 QDoubleSpinBox 映射到其“转换为共同温度单位”和“转换为共同温度单位”。

然后我们将围绕这个 map 构建一个对象,看起来像这样:

class SlotMapper : public QObject
{
    Q_OBJECT
    map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>> mapping;
public:
    SlotMapper() = default;
    SlotMapper(const map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>> mapping) : mapping(mapping) {};
    AddMapping(QDoubleSpinBox* key, function<double(double)> valueFirst, function<double(double)> valueSecond) { mapping.insert_or_assign(key, make_pair(valueFirst, valueSecond)); }
    void map(const double assignedTemperature) const {
        const auto commonTemperatureUnit = mapping.at(QObject()::sender).first(assignedTemperature);

        for(auto it = cbegin(mapping); it != cend(mapping); ++it) {
            if(it->first != QObject()::sender) {
                it->first->blockSignals(true);
                it->first->setValue(it->second.second(commonTemperatureUnit));
                it->first->blockSignals(false);
            }
        }
    }
};

这个对象应该用所有必要的转换函数来构造。在您的情况下,可能看起来像:

SlotMapper mySlotMapper(map<QDoubleSpinBox*, pair<function<double(double)>, function<double(double)>>>{ {FahrenheitDblSpinbox, make_pair([](const double param){ return (param - 32.0) * 5.0 / 9.0; }, [](const double param){ return param * 9.0 / 5.0 + 32.0; })},
                                                                                                        {CelciusDblSpinbox, make_pair([](const double param){ return param; }, [](const double param){ return param; })},
                                                                                                        {KelvinDblSpinbox, make_pair([](const double param){ return param - 273.15; }, [](const double param){ return param + 273.15; })},
                                                                                                        {RankineDblSpinbox, make_pair([](const double param){ return (param - 491.67) * 5.0 / 9.0; }, [](const double param){ return (param + 273.15) * 9.0 / 5.0; })} });

就您的连接而言,它们看起来像:

connect(FahrenheitDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(CelciusDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(KelvinDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);
connect(RankineDblSpinbox, static_cast<void(QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), &mySlotMapper, &SlotMapper::map);

【讨论】:

  • 只是一个评论我现在无法访问 Qt,并且其中有很多移动部分,所以我想我在这里有一些语法错误。如果您能帮我指出任何问题,我将不胜感激。
  • 非常感谢你发布这个,我有时间会研究这个,我会告诉你我的想法(我是 C++/Qt 的新手,所以这将花点时间来处理和理解)。
  • @spartan228 如果您有具体问题,请告诉我。当map 发出信号时,我基本上只是调用转换函数并将值传递给所有其他QDoubleSpinBoxes。
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 2014-08-20
  • 1970-01-01
  • 2015-04-08
  • 2016-10-21
  • 1970-01-01
  • 1970-01-01
  • 2016-09-05
相关资源
最近更新 更多