У меня есть форма с двумя спинбоксами, которые должны быть связаны из-за соотношения сторон для ширины и высоты. Когда я нажимаю на первый спинбокс и увеличиваю / уменьшаю значение, другой, второй спинбокс должен изменить свое значение в соответствии с первым спинбоксом. Я уже установил отношения отношений, но есть проблема, потому что я соединяю обе спин-боксы на SLOT valueChanged (int) и этот метод блокирует всю программу из-за бесконечного цикла. Это означает, что когда я увеличиваю значение до первого spinbox, значение изменяется сначала для этого, а затем для второго, который снова вызывает первый.
Я хотел бы решить эту проблему, поэтому, когда я нажимаю на один из спинбоксов, чтобы изменить оба значения правильно без бесконечного цикла.
Итак, есть код:
void MainWindow::on_sbHeight_valueChanged(int arg1)
{
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbWidth->setValue((arg1/8)*2);
} else if (ui->radioRatio2->isChecked()) {
ui->sbWidth->setValue((arg1/14)*3);
}
} else {
ui->sbWidth->setValue(arg1);
}
}
void MainWindow::on_sbWidth_valueChanged(int arg1)
{
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbHeight->setValue((arg1/2)*8);
} else if (ui->radioRatio2->isChecked()) {
ui->sbHeight->setValue((arg1/3)*14);
}
} else {
ui->sbHeight->setValue(arg1);
}
}
Я думаю, что лучшее решение здесь было бы блокировать сигналы из спинбоксов, до изменения их значений в слотах:
Я обычно использую вспомогательный класс следующим образом:
class SignalsBlocker
{
public:
SignalsBlocker(QObject* ptr):
_ptr(ptr)
{
_b = ptr->blockSignals(true);
}
~SignalsBlocker()
{
_ptr->blockSignals(_b);
}
private:
QObject* _ptr;
bool _b;
};
Так что вы можете написать
void MainWindow::on_sbHeight_valueChanged(int arg1)
{
SignalsBlocker block(ui->sbWidth);
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbWidth->setValue((arg1/8)*2);
//.....
}
void MainWindow::on_sbWidth_valueChanged(int arg1)
{
SignalsBlocker block(ui->sbHeight);
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbHeight->setValue((arg1/2)*8);
//....
}
Простое решение, которое можно предложить, это
void foo(QObject* object)
{
object->blockSignals(true);
// some stuff
object->blockSignals(false);
}
Однако это решение неверно: представьте следующую ситуацию
QObject* obj;
obj->blockSignals(true);
foo(obj);
//some other stuff
obj->blockSignals(false);
Можно надеяться, что сигналы будут разблокированы после some otherstuff
, но на самом деле они будут разблокированы внутри foo
функция, которая не предназначена для поведения. Вот почему вы должны сохранить состояние блока, а затем восстановить его.
Но опять же RAII Вспомогательный класс является наиболее удобным решением, которое уменьшает сложность кода.
Также обратите внимание, что ваши вычисления с целыми числами, как
(arg1/8)*2
не совсем точны.
Например, пусть arg1 = 6
, затем arg1/8
является 0
, а также (arg1/8)*2
результаты в 0
,
Простое изменение порядка расчетов может повысить точность:
(arg1 * 2) / 8
arg1 = 6
arg1 * 2 = 12
(arg1 * 2 / 8) = 1
Решение @ lol4t0 теперь в Qt, начиная с версии 5.3 с QSignalBlocker.
Значение QSignalBlocker может использоваться точно так же, как SignalsBlocker от @ lol4t0. Он блокирует сигналы при создании и восстанавливает предыдущий статус блока, когда он уничтожается.
Пример из @ lol4t0, но с классом Qt:
void MainWindow::on_sbHeight_valueChanged(int arg1)
{
QSignalBlocker block(ui->sbWidth);
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbWidth->setValue((arg1/8)*2);
//.....
}
void MainWindow::on_sbWidth_valueChanged(int arg1)
{
QSignalBlocker block(ui->sbHeight);
if (arg1 != 0) {
if (ui->radioRatio1->isChecked()) {
ui->sbHeight->setValue((arg1/2)*8);
//....
}