События Qt и сигналы / слоты

97

В мире Qt в чем разница событий и сигналов / слотов?

Одно заменяет другое? События - это абстракция сигналов / слотов?

Рафаэль
источник

Ответы:

30

Документация Qt, вероятно, лучше всего объясняет это:

В Qt события - это объекты, производные от абстрактного QEventкласса, которые представляют вещи, которые произошли либо внутри приложения, либо в результате внешней активности, о которой приложению необходимо знать. События могут быть получены и обработаны любым экземпляром QObjectподкласса, но они особенно актуальны для виджетов. Этот документ описывает, как события доставляются и обрабатываются в типичном приложении.

Таким образом, события и сигналы / слоты - это два параллельных механизма, выполняющих одно и то же. Как правило, событие создается внешней сущностью (например, клавиатурой или колесом мыши) и доставляется через цикл событий в QApplication. В общем, если вы не настроите код, вы не будете генерировать события. Вы можете фильтровать их QObject::installEventFilter()или обрабатывать события в подклассе объекта, переопределив соответствующие функции.

Сигналы и слоты намного проще генерировать и получать, и вы можете соединить любые два QObjectподкласса. Они обрабатываются с помощью Metaclass (подробнее см. В файле moc_classname.cpp), но большая часть межклассового взаимодействия, которое вы будете производить, вероятно, будет использовать сигналы и слоты. Сигналы могут быть доставлены немедленно или отложены через очередь (если вы используете потоки).

Сигнал может быть сгенерирован.

Харальд Шейрих
источник
отложенная очередь такая же, как цикл событий очереди, или существует две очереди? один для отложенных сигналов и на событие?
Guillaume07,
29
@Raphael, позор тебе за то, что ты принял этот ответ. - В процитированном абзаце нет даже слов «сигнал» или «слот»!
Роберт Симер
1
@neuronet: абзац процитирован со словами «[он] объясняет это лучше всего», но это не так. Не за что. Нисколько.
Роберт Симер,
@Robert, так что если бы эту небольшую вступительную строку изменили на «Сначала давайте рассмотрим, как документы объясняют события, прежде чем переходить к рассмотрению того, как они связаны с сигналами», вы бы согласились с ответом? Если да, то мы можем просто отредактировать ответ, чтобы улучшить его, поскольку это второстепенный вопрос, и я согласен, что его можно было бы сформулировать лучше. [Править, это настоящий вопрос: поскольку я не уверен, что остальная часть намного лучше .... но только потому, что в цитируемой части не упоминаются сигналы, кажется поверхностным беспокойством, если остальная часть действительно хороша, а я не предполагая .]
eric
4
@neuronet, если вы удалите цитату полностью, это улучшит ответ в моих глазах. - Если вы удалите весь ответ, это улучшит весь QA.
Роберт Симер
152

В Qt сигналы и события являются реализациями паттерна Observer . Их используют в разных ситуациях, потому что у них разные сильные и слабые стороны.

Прежде всего, давайте определим, что мы подразумеваем под «событием Qt»: виртуальная функция в классе Qt, которую вы ожидаете переопределить в своем базовом классе, если вы хотите обработать событие. Это связано с шаблоном шаблонного метода .

Обратите внимание, как я использовал слово « ручка ». В самом деле, вот основная разница между назначением сигналов и событий:

  • Вы « обрабатываете » события
  • Вы " получаете уведомление" об излучении сигнала

Разница в том, что когда вы «обрабатываете» событие, вы берете на себя ответственность «ответить» поведением, которое полезно за пределами класса. Например, рассмотрим приложение, на котором есть кнопка с номером. Приложение должно позволить пользователю сфокусировать кнопку и изменить номер, нажимая клавиши «вверх» и «вниз». В противном случае кнопка должна работать как обычно QPushButton(ее можно нажимать и т. Д.). В Qt это делается путем создания вашего собственного маленького многоразового «компонента» (подкласса QPushButton), который реализует заново QWidget::keyPressEvent. Псевдокод:

class NumericButton extends QPushButton
    private void addToNumber(int value):
        // ...

    reimplement base.keyPressEvent(QKeyEvent event):
        if(event.key == up)
            this.addToNumber(1)
        else if(event.key == down)
            this.addToNumber(-1)
        else
            base.keyPressEvent(event)

Видеть? Этот код представляет новую абстракцию: виджет, который действует как кнопка, но с некоторыми дополнительными функциями. Мы очень удобно добавили этот функционал:

  • Поскольку мы повторно реализовали виртуальный объект, наша реализация автоматически инкапсулировалась в наш класс. Если бы дизайнеры Qt сделалиkeyPressEvent сигнал, нам нужно было бы решить, наследовать ли QPushButtonсигнал или просто подключаться к нему извне. Но это было бы глупо, поскольку в Qt от вас всегда ожидается наследование при написании виджета с настраиваемым поведением (не зря - возможность повторного использования / модульность). Таким образом, создавая keyPressEventсобытие, они передают свое намерение, которое keyPressEventявляется всего лишь базовым строительным блоком функциональности. Если бы это был сигнал, это выглядело бы как вещь, обращенная к пользователю, хотя этого не должно было быть.
  • Поскольку доступна реализация функции базовым классом, мы легко реализуем шаблон цепочки ответственности , обрабатывая наши особые случаи (клавиши вверх и вниз) и оставляя остальное базовому классу. Как видите, это было бы почти невозможно, будь keyPressEventэто сигнал.

Дизайн Qt хорошо продуман - они заставили нас упасть в яму успеха , позволив легко делать правильные вещи и трудно делать неправильные (сделав keyPressEvent событием).

С другой стороны, рассмотрите простейшее использование QPushButton- просто создание его экземпляра и получение уведомлений при нажатии :

button = new QPushButton(this)
connect(button, SIGNAL(clicked()), SLOT(sayHello())

Это явно должно быть сделано пользователем класса:

  • если бы нам приходилось создавать подклассы QPushButtonкаждый раз, когда мы хотим, чтобы какая-то кнопка уведомляла нас о щелчке, для этого потребовалось бы много подклассов без уважительной причины! Виджет, который всегда показывает «Hello world» messageboxпри нажатии, полезен только в одном случае, поэтому его нельзя использовать повторно. Опять же, у нас нет выбора, кроме как поступать правильно - подключаться к нему извне.
  • мы можем захотеть подключить несколько слотов к clicked()- или подключить несколько сигналов кsayHello() . С сигналами суеты нет. При создании подклассов вам придется сесть и поразмышлять над некоторыми диаграммами классов, пока вы не выберете подходящий дизайн.

Обратите внимание, что одно из мест QPushButtonиспускания clicked()находится в его mousePressEvent()реализации. Это не значитclicked() , что и mousePressEvent()являются взаимозаменяемыми - только что они связаны между собой .

Итак, сигналы и события имеют разные цели (но связаны между собой тем, что оба позволяют «подписаться» на уведомление о том, что что-то происходит).

Стефан Монов
источник
Я понял. Событие для каждого класса, все экземпляры класса будут реагировать одинаково, все они будут вызывать одно и то же QClassName :: event. Но сигнал для каждого объекта, каждый объект может иметь свое уникальное соединение сигнал-слот.
炸鱼 薯条 德里克
39

Мне пока не нравятся ответы. - Позвольте мне сосредоточиться на этой части вопроса:

События - это абстракция сигналов / слотов?

Короткий ответ: нет. Длинный ответ поднимает вопрос «лучше»: как связаны сигналы и события?

Простой основной цикл (например, Qt) обычно «застревает» в вызове select () операционной системы. Этот вызов переводит приложение в «спящий» режим, в то время как оно передает кучу сокетов, файлов или чего-либо еще ядру, запрашивая: если что-то в них изменится, позвольте вызову select () вернуться. - И ядро, как хозяин мира, знает, когда это произойдет.

Результатом этого вызова select () может быть: новые данные в сокете, подключенном к X11, пакет на порт UDP, который мы слушаем, и т. Д. - Этот материал не является ни сигналом Qt, ни событием Qt, а Основной цикл Qt сам решает, превращает ли он свежие данные в одни, в другие или игнорирует их.

Qt может вызывать метод (или несколько), например keyPressEvent (), эффективно превращая его в событие Qt. Или Qt испускает сигнал, который фактически ищет все функции, зарегистрированные для этого сигнала, и вызывает их одну за другой.

Здесь видно одно различие этих двух концепций: слот не имеет права голоса по поводу того, будут ли вызваны другие слоты, зарегистрированные для этого сигнала. - События больше похожи на цепочку, и обработчик событий решает, прерывает он эту цепочку или нет. В этом отношении сигналы похожи на звезду или дерево.

Событие может запускаться или полностью превращаться в сигнал (просто испустить один, а не вызывать «super ()»). Сигнал можно превратить в событие (вызвать обработчик события).

Что абстрагирует то, что зависит от случая: сигнал clicked () абстрагирует события мыши (кнопка опускается и поднимается снова без особого перемещения). События клавиатуры - это абстракции от более низких уровней (такие вещи, как 果 или é - это несколько нажатий клавиш в моей системе).

Возможно, focusInEvent () является примером противоположного: он может использовать (и, таким образом, абстрагировать) сигнал clicked (), но я не знаю, действительно ли это так.

Роберт Симер
источник
4
Я нахожу эту часть: «слот не имеет права голоса по поводу того, будут ли вызваны другие слоты, зарегистрированные для этого сигнала», очень поучительно для разницы между сигналами и событиями. Однако у меня есть связанный с этим вопрос: что такое сообщения и как сообщения связаны с сигналами и событиями? (если они связаны). Являются ли сообщения абстракцией для сигналов и событий? Могут ли они использоваться для описания таких взаимодействий между QObject (или другими объектами?)
user1284631
@axeoth: В Qt нет такого понятия, как «сообщения». Ну, есть окно сообщения, но это все.
Unslander Monica
@KubaOber: Спасибо. Я имел в виду «события».
user1284631
3
@axeoth: Тогда твой вопрос чушь. Он гласит: «Что такое события и как события связаны с сигналами и событиями».
Unslander Monica
@KubaOber: Год спустя я не помню контекст. В любом случае, похоже, что я спрашивал почти то же самое, что и OP: в чем разница между событиями и событиями и как события связаны с сигналами и слотами. Что было на самом деле тогда, IIRC, я искал способ обернуть классы, управляемые сигналами / слотами Qt, в какие-то классы, не относящиеся к Qt, поэтому я искал способ управлять первым изнутри последнего. Я полагаю, что я рассматривал возможность отправки им событий, поскольку это означало, что мне нужно было только включить заголовки Qt, а не заставлять мои классы реализовывать сигнал / слоты.
user1284631
16

События отправляются циклом событий. Каждой программе с графическим интерфейсом требуется цикл событий, независимо от того, что вы пишете для Windows или Linux, используя Qt, Win32 или любую другую библиотеку графического интерфейса. Также каждый поток имеет свой собственный цикл событий. В Qt «GUI Event Loop» (который является основным циклом всех приложений Qt) скрыт, но вы запускаете его вызовом:

QApplication a(argc, argv);
return a.exec();

Сообщения, которые ОС и другие приложения отправляют вашей программе, отправляются как события.

Сигналы и слоты - это механизмы Qt. В процессе компиляции с использованием moc (компилятора метаобъектов) они меняются на функции обратного вызова.

У события должен быть один получатель, который должен его отправить. Никто другой не должен получить это событие.

Все слоты, связанные с излучаемым сигналом, будут выполнены.

Вы не должны думать о сигналах как о событиях, потому что, как вы можете прочитать в документации Qt:

Когда сигнал испускается, подключенные к нему слоты обычно выполняются немедленно, как и при обычном вызове функции. Когда это происходит, механизм сигналов и слотов полностью независим от любого цикла событий графического интерфейса.

Когда вы отправляете событие, оно должно подождать некоторое время, пока цикл событий не отправит все события, которые произошли ранее. Из-за этого выполнение кода после отправки события или сигнала отличается. Код после отправки события будет запущен немедленно. С механизмами сигналов и слотов это зависит от типа подключения. Обычно он выполняется после всех слотов. Используя Qt :: QueuedConnection, он будет выполнен немедленно, как и события. Проверьте все типы подключения в документации Qt .

фейерверк
источник
Это предложение дает мне краткое представление о различиях между сигнальным слотом Qt и событиями:When you send an event, it must wait for time when event loop dispatch all events that came earlier. Because of this, execution of the cod after sending event or signal is different
swdev
7

Есть статья, в которой более подробно обсуждается обработка событий: http://www.packtpub.com/article/events-and-signals

Здесь обсуждается разница между событиями и сигналами:

События и сигналы - это два параллельных механизма, используемых для достижения одного и того же. В целом, сигналы полезны при использовании виджета, тогда как события полезны при реализации виджета. Например, когда мы используем такой виджет, как QPushButton, нас больше интересует его сигнал clicked (), чем низкоуровневое нажатие мыши или события нажатия клавиши, которые вызвали передачу сигнала. Но если мы реализуем класс QPushButton, нас больше интересует реализация кода для событий мыши и клавиш. Кроме того, мы обычно обрабатываем события, но получаем уведомления посредством излучения сигнала.

Кажется, это обычный способ говорить об этом, поскольку в принятом ответе используются некоторые из тех же фраз.


Обратите внимание, пожалуйста, ознакомьтесь с полезными комментариями ниже к этому ответу Кубы Обера, которые заставляют меня задаться вопросом, может ли он быть немного упрощенным.

Эрик
источник
Я не понимаю, как это могут быть два механизма, используемых для достижения одного и того же - я думаю, что это бесполезное обобщение, которое никому не помогает.
Unslander Monica
@KubaOber нет, это помогает избежать деталей нижнего уровня. Вы бы сказали, что Python бесполезен, потому что вы можете делать то же самое, написав машинный код? :)
eric
2
Я говорю, что первое предложение цитаты носит обобщающий характер и бесполезно. Технически это не неверно, но только потому, что вы можете, если хотите, использовать передачу событий для выполнения того, что делает механизм сигнальных слотов, и наоборот. Это было бы очень громоздко. Итак, нет, сигнальные слоты - это не то же самое, что события, они не выполняют одно и то же, и тот факт, что оба существуют, критически важен для успеха Qt. Приведенный пример кнопки полностью игнорирует общую функциональность системы сигнал-слот.
Unslander Monica
1
«События и сигналы - это два параллельных механизма, используемых для достижения одного и того же в узких рамках некоторых виджетов / элементов управления пользовательского интерфейса ». Жирная часть критически отсутствует и делает цитату бесполезной. Даже в этом случае можно задаться вопросом, насколько «одно и то же» действительно выполняется: сигналы позволяют реагировать на события щелчка без установки фильтра событий на виджет (или производных от виджета). Выполнение этого без сигналов намного более обременительно и жестко связывает клиентский код с элементом управления. Плохой дизайн.
Unslander Monica
1
События - понятие повсеместное . Конкретная реализация в Qt конкретно устанавливает их как передаваемые структуры данных event. Сигналы и слоты, являются, конкретно , методы, в то время как соединительный механизм представляет собой структуру данных , которая позволяет сигнал вызова одной или нескольких слотов , перечисленных в подключенных к нему. Я надеюсь, вы понимаете, что говорить о сигнале / слотах как о некотором «подмножестве» или «варианте» событий - это чепуха, и наоборот. Они действительно разные вещи , которые происходят , которые будут использоваться для аналогичных целей в контексте некоторых виджетов. Вот и все. ИМХО, чем больше вы обобщаете, тем менее полезным вы становитесь.
Unslander Monica
5

TL; DR: сигналы и слоты являются косвенными вызовами методов. События - это структуры данных. Так что это совершенно разные животные.

Единственный раз, когда они объединяются, - это когда вызовы слотов выполняются через границы потоков. Аргументы вызова слота упаковываются в структуру данных и отправляются как событие в очередь событий принимающего потока. В принимающем потокеQObject::event метод распаковывает аргументы, выполняет вызов и, возможно, возвращает результат, если это было блокирующее соединение.

Если мы хотим обобщить до забвения, можно думать о событиях как о способе вызова eventметода целевого объекта . Это в некотором роде косвенный вызов метода, но я не думаю, что это полезный способ думать об этом, даже если это истинное утверждение.

Незеленка Моника
источник
2

События (в общем смысле взаимодействия пользователя / сети) обычно обрабатываются в Qt с помощью сигналов / слотов, но сигналы / слоты могут делать множество других вещей.

QEvent и его подклассы в основном представляют собой небольшие стандартизованные пакеты данных, используемые фреймворком для взаимодействия с вашим кодом. Если вы хотите каким-то образом обратить внимание на мышь, вам нужно только взглянуть на API QMouseEvent, и разработчикам библиотеки не нужно изобретать колесо каждый раз, когда вам нужно выяснить, что мышь сделала в каком-то углу. Qt API.

Это правда, что если вы ждете каких-либо событий (опять же в общем случае), ваш слот почти наверняка примет подкласс QEvent в качестве аргумента.

С учетом сказанного, сигналы и слоты, безусловно, можно использовать без QEvents, хотя вы обнаружите, что исходным стимулом для активации сигнала часто будет какое-то взаимодействие с пользователем или другая асинхронная активность. Однако иногда ваш код просто достигает точки, когда запуск определенного сигнала будет правильным решением. Например, запуск сигнала, связанного с индикатором выполнения во время длительного процесса, до этого момента не требует QEvent.

jkerian
источник
2
«События обычно обрабатываются в Qt с помощью сигналов / слотов» - на самом деле нет ... Объекты QEvent всегда передаются через перегруженные виртуальные объекты! Например, в QWidget нет сигнала «keyDown» - вместо этого keyDown является виртуальной функцией.
Стефан Монов
Согласитесь со Стефаном, на самом деле довольно запутанная часть Qt
Харальд Шейрих
1
@Stefan: Я бы сказал, что несколько приложений Qt переопределяют keyDown (и вместо этого в основном используют такие сигналы, как QAbstractButton :: clicked и QLineEdit :: editFinished), чтобы оправдать «обычно». Раньше я конечно ловил ввод с клавиатуры, но это не обычный способ обработки событий.
jkerian
после вашего редактирования (разъяснения того, что вы имеете в виду под словом «событие»), ваше сообщение теперь верное. Однако я воздерживаюсь от повторного использования подобных слов. Вещи достаточно мутные и без того, чтобы сигнализировать о «типе событий».
Стефан Монов
2

Я нашел этот вопрос, читая «Обработка событий» Леоу Ви Кхенга. В нем также говорится:

введите описание изображения здесь

Жасмин Бланшетт говорит:

Основная причина, по которой вы должны использовать события, а не стандартные вызовы функций или сигналы и слоты, заключается в том, что события могут использоваться как синхронно, так и асинхронно (в зависимости от того, вызываете ли вы sendEvent () или postEvents ()), при этом вызывая функцию или вызывая слот всегда синхронный. Еще одно преимущество событий в том, что их можно фильтровать.

Франсек
источник
1

Еще одно незначительное прагматическое соображение: для испускания или приема сигналов требуется наследование, QObjectтогда как объект любого наследования может публиковать или отправлять событие (поскольку вы вызываете QCoreApplication.sendEvent()или postEvent()). Обычно это не проблема, но: для использования сигналов PyQt странным образом требует, QObjectчтобы он был первым суперклассом, и вы, возможно, не захотите изменять порядок наследования только для того, чтобы иметь возможность отправлять сигналы.)

бутчк
источник
-1

На мой взгляд, события полностью повторяются и их можно выбросить. Нет никаких причин, по которым сигналы нельзя было бы заменить событиями или событиями сигналами, за исключением того, что Qt уже настроен как есть. Сигналы в очереди обертываются событиями, а события могут быть обернуты сигналами, например:

connect(this, &MyItem::mouseMove, [this](QMouseEvent*){});

Заменил бы удобную mouseMoveEvent()функцию, найденную в QWidget(но QQuickItemбольше не в ), и будет обрабатывать mouseMoveсигналы, которые менеджер сцены будет выдавать для элемента. Тот факт, что сигнал испускается от имени элемента какой-либо внешней сущностью, не важен и довольно часто случается в мире компонентов Qt, даже если это предположительно не разрешено (компоненты Qt часто обходят это правило). Но Qt представляет собой конгломерат множества различных дизайнерских решений и в значительной степени высечен из камня из-за страха сломать старый код (что в любом случае случается достаточно часто).

user1095108
источник
При необходимости события имеют то преимущество, что они распространяются по QObjectиерархии родитель-потомок. Соединения сигнал / слот - это просто обещание вызвать функцию, прямо или косвенно, при выполнении определенного условия. Нет связанной иерархии обработки с сигналами и слотами.
аноним
Вы можете реализовать аналогичные правила распространения с сигналами. Нет никаких правил. что определенный сигнал нельзя переоформить на другой объект (потомок). Вы можете добавить acceptedссылочный параметр к сигналу и обрабатывающим его слотам, или у вас может быть структура в качестве ссылочного параметра с acceptedполем. Но Qt сделала выбор дизайна, который он сделал, и теперь они высечены в камне.
user1095108
1
По сути, вы просто перерабатываете события, если делаете это так, как вы предлагаете.
Фабио А.
@FabioA. В этом суть вопроса ОП. События и сигналы могут заменять друг друга.
user1095108
Если ваше переопределение событий, то вы не заменяете события. Более того: сигналы реализуются поверх событий. Вам нужна своего рода «система событий», чтобы сообщить некоторым другим циклам событий, возможно, в потоке, который не принадлежит вам, что когда-нибудь в будущем он должен где-то выполнить заданную функцию.
Фабио А.