Події Qt та сигнал / слоти


97

У світі Qt, в чому різниця подій та сигналу / слотів?

Чи замінює одне інше? Чи події є абстракцією сигналу / слотів?

Відповіді:


30

Це найкраще пояснює документація Qt :

У Qt події - це об'єкти, похідні від абстрактного QEventкласу, які представляють речі, що відбулися або в рамках програми, або в результаті зовнішньої діяльності, про яку програма повинна знати. Події можуть бути отримані та оброблені будь-яким екземпляром QObjectпідкласу, але вони особливо актуальні для віджетів. Цей документ описує спосіб доставки та обробки подій у типовому додатку.

Отже, події та сигнал / слоти - це два паралельні механізми, що здійснюють одне і те ж. Як правило, подія генерується зовнішнім об'єктом (наприклад, клавіатурою або коліщатком миші) і доставляється через цикл подій у QApplication. Загалом, якщо ви не встановите код, ви не будете створювати події. Ви можете фільтрувати їх QObject::installEventFilter()або обробляти події в підкласовому об'єкті, замінюючи відповідні функції.

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

Може генеруватися сигнал.


відкладена черга така сама, як цикл події черги, або існує дві черги? один для відкладених сигналів і для події?
Гійом07

29
@Raphael сором за те, що ти прийняв цю відповідь. - Цитований абзац навіть не містить слів "сигнал" або "слот"!
Роберт Сімер

1
@neuronet: абзац цитується "[він] пояснює це найкраще", чого він не робить. Зовсім ні. Навіть трохи.
Роберт Сімер

@ Роберт, так що якби цей маленький вступний рядок був змінений на "Спочатку давайте розглянемо, як документи пояснюють події, перш ніж переходити до розгляду, як вони пов'язані з сигналами", чи будете Ви з відповіддю? Якщо так, то ми можемо просто відредагувати відповідь, щоб покращити її, оскільки це другорядний момент, і я погоджуюся, що це могло б бути сформульовано краще. [Редагувати це справжнє запитання: оскільки я не впевнений, що решта набагато краща .... але те, що в цитованій частині не згадуються сигнали, здається поверхневим занепокоєнням, якщо решта справді хороша, що я і є не припускаючи .]
eric

4
@neuronet, якщо ви повністю видалите цитату, це покращить відповідь в моїх очах. - Якщо ви видалите повну відповідь, це покращить цілий контроль якості.
Роберт Сімер

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кожного разу, коли ми хочемо, щоб якась кнопка сповіщала нас про клацання, це вимагало б багато підкласів без поважних причин! Віджет, який завжди відображає "Привіт світ" 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 ()”). Сигнал може бути перетворений на подію (зателефонуйте обробнику події).

Що абстрагує те, що залежить від випадку: натиснуте () - сигнал абстрагує події миші (кнопка опускається і знову вгору, не надто рухаючись). Події на клавіатурі - це абстракції з нижчих рівнів (такі речі як 果 або é - це кілька клавішних обрисів у моїй системі).

Можливо focusInEvent () є прикладом протилежного: він міг би використовувати (і, отже, абстрактно) сигнал, на який клацнули (), але я не знаю, чи насправді це робить.


4
Я вважаю цю частину: "слот не має права голосу щодо того, чи будуть викликані інші слоти, зареєстровані на цей сигнал", дуже просвітницький для різниці між сигналами та подіями. Однак у мене пов'язане запитання: що таке Повідомлення і як повідомлення пов'язані із сигналами та подіями? (якщо вони пов'язані). Чи є повідомлення абстракцією для сигналів та подій? Чи можна їх використовувати для опису такої взаємодії між об'єктами
QObjects

@axeoth: У Qt немає такого поняття, як "повідомлення". Ну, є вікно повідомлення, але це все.
Відновити Моніку

@KubaOber: Дякую. Я мав на увазі "події".
користувач1284631

3
@axeoth: Тоді ваше питання - дурниця. Він говорить: "що таке події і як події, пов'язані з сигналами та подіями".
Відновити Моніку

@KubaOber: Я не пам’ятаю контексту через рік. У будь-якому випадку, схоже, я запитував майже те саме, що і в ОП: у чому різниця між рівними, парними і як події пов'язані з сигналами та слотами. Що тоді було насправді, IIRC, я шукав спосіб перетворити класи Qt на сигнал / слот, керовані слотами, в якісь класи, які не належать до Qt, тому я шукав певний спосіб командувати першим зсередини другим. Я думаю, що я розглядав можливість надсилати їм події, оскільки це означало, що мені потрібно було лише включати заголовки Qt, а не змушувати мої класи реалізовувати сигнал / слоти.
користувач1284631

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, нас більше цікавить його сигнал click (), ніж натискання миші на низькому рівні або події натискання клавіш, які спричинили випромінювання сигналу. Але якщо ми реалізуємо клас QPushButton, нас більше цікавить реалізація коду для миші та ключових подій. Крім того, ми зазвичай обробляємо події, але отримуємо повідомлення про випромінювання сигналу.

Здається, це звичайний спосіб говорити про це, оскільки прийнята відповідь використовує одні й ті ж фрази.


Зауважте, будь ласка, дивіться корисні коментарі нижче до цієї відповіді від Kuba Ober, що змушує мене замислитись, чи це може бути трохи спрощено.


Я не можу зрозуміти, як це два механізми, що використовуються для досягнення одного і того ж - я думаю, це марне узагальнення, яке нікому не допомагає.
Відновіть Моніку

@KubaOber ні, це не допомагає уникнути деталей нижчого рівня. Ви б сказали, що Python не корисний, оскільки ви можете зробити те ж саме, написавши в машинному коді? :)
eric

2
Я кажу, що перше речення цитати узагальнює настільки, що є марним. Технічно це не неправильно, але лише тому, що ви могли б, за бажанням, використовувати передачу подій, щоб виконати те, що робить механізм сигналу-слота, і навпаки. Це було б дуже громіздко. Так, ні, слоти сигналу - це не те саме, що події, вони не виконують одне і те ж, і той факт, що обидва існують, є критичним для успіху Qt. Наведений приклад кнопки повністю ігнорує загальну функціональність системи сигнальних слотів.
Відновіть Моніку

1
"Події та сигнали - це два паралельних механізми, які використовуються для досягнення одного і того ж у вузькій області деяких віджетів / елементів управління користувачем " Жирна частина критично відсутня і робить цитату непотрібною. Навіть тоді можна поставити під сумнів, наскільки «те саме» насправді досягнуто: сигнали дозволяють реагувати на клікові події, не встановлюючи на віджет фільтр подій (або похідний з віджета). Робити це без сигналів набагато громіздкіше і з'єднує клієнтський код щільно до управління. Це поганий дизайн.
Відновіть Моніку

1
Події є всюдисущим поняттям . Конкретна реалізація в Qt конкретно встановлює їх як передані структури даних event. Конкретно , сигнали та слоти - це методи, тоді як механізм з'єднання - це структура даних, яка дозволяє сигналу викликати один або кілька слотів, перелічених як пов'язані з ним. Я сподіваюся, ви бачите, що говорити про сигнал / слоти як про якийсь "підмножина" або "варіант" подій - це нісенітниця, і навпаки. Вони справді різні речі, які, як правило, використовуються для подібних цілей у контексті деяких віджетів. Це воно. Чим більше ви узагальнюєте, тим менш корисним ви стаєте ІМХО.
Поновити Моніку

5

TL; DR: Сигнали та слоти - це непрямі виклики методу. Події - це структури даних. Тож вони зовсім різні тварини.

Єдиний час, коли вони збираються разом, це коли виклики слотів здійснюються через межі потоку. Аргументи виклику слотів упаковуються в структуру даних і надсилаються як подія до черги подій приймаючого потоку. У приймальному потоці QObject::eventметод розпаковує аргументи, виконує виклик і, можливо, повертає результат, якщо це було блокувальне з'єднання.

Якщо ми готові узагальнити забуття, можна подумати про події як спосіб виклику eventметоду цільового об’єкта . Це непрямий виклик методу, після моди - але я не думаю, що це корисний спосіб думати про це, навіть якщо це правдиве твердження.


2

Події (у загальному розумінні взаємодії користувача / мережі), як правило, обробляються в Qt сигналами / слотами, але сигнали / слоти можуть робити багато іншого.

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

Це правда, що якщо ви чекаєте якихось подій (знову ж у загальному випадку), ваш слот майже напевно прийме підклас QEvent як аргумент.

З огляду на це, сигнали та слоти, безсумнівно, можна використовувати без QEvents, хоча ви виявите, що початковим поштовхом для активації сигналу часто буде якась взаємодія користувача або інша асинхронна діяльність. Однак іноді ваш код просто досягає точки, коли спрацьовування певного сигналу буде правильним. Наприклад, спрацьовування сигналу, підключеного до індикатора прогресу під час тривалого процесу, не включає QEvent до цього моменту.


2
"Події, як правило, обробляються в Qt за допомогою сигналів / слотів" - насправді немає ... QEvent об'єкти завжди передаються навколо за допомогою перевантажених віртуалів! Наприклад, у QWidget немає сигналу "keyDown" - натомість keyDown - це віртуальна функція.
Стефан Монов

Погодьтеся зі стефаном, насправді досить заплутаним твором Qt
Харальд Шейріх,

1
@Stefan: Я б стверджував, що мало достатньо програм Qt замінює keyDown (і замість цього в основному використовують такі сигнали, як QAbstractButton :: clicked і QLineEdit :: editingFinished), щоб виправдати "типово". Я, звичайно, раніше зачепив введення з клавіатури, але це не звичайний спосіб обробки подій.
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 - це конгломерат безлічі різноманітних дизайнерських рішень, який в значній мірі кидається в камінь через страх зламати старий код (що і так трапляється досить часто).


Перевага подій полягає у поширенні QObjectієрархії батьків та дітей, коли це необхідно. З'єднання сигнал / слот - це лише обіцянка викликати функцію, прямо чи опосередковано, коли виконується певна умова. Не існує пов’язаної ієрархії обробки з сигналами та слотами.
анонімно

Ви можете реалізувати подібні правила розповсюдження за допомогою сигналів. Правила немає. що певний сигнал не може бути перевиданий іншому об’єкту (дитині). Ви можете додати acceptedопорний параметр до сигналу та слоти, що обробляють його, або у вас може бути структура як опорний параметр зaccepted полем. Але Qt зробив вибір дизайну, який він зробив, і тепер вони встановлені в камені.
user1095108

1
Ви в основному просто реалізовуєте події, якщо робите це так, як пропонуєте.
Фабіо А.

@FabioA. У цьому полягає суть питання ОП. Події та сигнали можуть / можуть замінити одне одного.
користувач1095108

Якщо ваші події відновлення, то ви не замінюєте події. Більше цього: сигнали реалізовані на вершині подій. Вам потрібна своєрідна "система подій" для того, щоб повідомляти деякі інші цикли подій, можливо, в потоці, який не є вашим, що колись у майбутньому вона повинна десь виконувати задану функцію.
Фабіо А.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.