Сигнально-слотові з'єднання

Одною з фундаментальних можливостей у Qt є можливість взаємодії об'єктів з допомогою сигнально-слотових з'єднань. Розглянемо, як влаштувати взаємодію між об'єктами у програмі більш детально.

Оргазінація взаємодії між віджетами та класами у програмі

Будь-яка об'єктно-орієнтована програма складається з об'єктів, які взаємодіють між собою. Кожен з об'єктів володіє станом, який визначає сукупність даних, які зберігає об'єкт у даний момент. У відповідь на взаємодію з об'єктом, його стан може змінитися. Наприклад, об'єкт який реалізує мережеве з'єднання може отримати нові переслані дані, а об'єкт який реалізує кнопку на вікні користувацького інтерфейсу, може бути натиснутий користувачем. Таким чином об'єкт змінив свій стан — і він може повідомити про це інший об'єкт надсилаючи йому повідомлення про зміну. Практично, цю взаємодію, зазвичай, реалізують з допомогою виклику методу об'єкту, який необхідно повідомити. Цей метод має виконувати відповідні дії у відповідь на зміну. Кожен об'єкт виконує свою роль, а взаємодія між ними відбувається за рахунок взаємного виклику методів (прямого чи опосередкованого), кожен з яких має виконувати власну чітку функцію. Таким розділенням отримують зменшення складності у програмному коді — за рахунок чіткого розподілу відповідальності між об'єктами — а, отже, і гнучкість, здатність класів до повторного використання, простоту супроводження програмного коду. Дуже важливе хороше розуміння цих ключових ідей — розділення відповідальності між об'єктами у програмі та організації взаємодії між ними на основі чітко визначених інтерфейсів (наборів методів), які випливають з фундаментальних понять ООП: абстракції та інкапсуляції.

Отже, для реалізації такої взаємодії, програмісту необхідно викликати метод іншого об'єкту у відповідь на зміну стану. Об'єкт який змінив стан може для цього просто зберігати вказівник на інший об'єкт, метод якого має викликатися. У відповідь на зміну стану від буде доступатися через вказівник та викликати метод. У такому разі втрачається гнучкість, адже кожен об'єкт має містити поле для збереження вказівника на інший об'єкт, з яким він взаємодіє. Наприклад, у випадку з елементами управління на формі, доведеться програмувати кожен елемент окремо — успадковувати від класу елемента управління, додавати вказівник та перевизначити метод для обробки дій користувача задля взаємодії з іншими об'єктами у програмі. До того ж такий зв'язок задається жорстко на етапі компіляції.

Звичайно можна передавати вказівник на метод, який буде викликатися у відповідь (callback), але такий підхід теж володіє деякими недоліками (менш безпечно, завжди працює як прямий виклик методу, і т. п.). Саме тому у Qt використовується концепція сигнально-слотових з'єднань.

Поняття про сигнально-слотові з'єднання

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

Для задання з'єднання використовують метод connect() класу QObject. Метод приймає п'ять параметрів:

  • вказівник на об'єкт, який посилає сигнал (sender);

  • назва сигналу (signal) та його параметри, які задаються з допомогою макроса SIGNAL();

  • вказівник на об'єкт, який отримує сигнал (receiver);

  • назва слота (slot) та його параметри, які задаються з допомогою макроса SLOT(), або ж назва іншого сигналу, який буде емітуватися (випускатися) у відповідь;

  • тип сигнально слотового з'єднання (має значення за замовчуванням Qt::AutoConnection).

У наступному прикладі ми демонструємо сигнально-слотове з'єднання між кнопкою lPushButton та віджетом-вікном lWindow. У відповідь на натискання кнопки (сигнал clicked()), викликається метод-слот close(), який закриває вікно. Зауважимо, що слоти мають специфікатор доступу (private/protected/public) та викликають їх як і звичайні методи класу. Цим вони не відрізняються від інших методів.

    connect(lPushButton, SIGNAL(clicked()), lWindow, SLOT(close()));

Передача параметрів

Також з допомогою з'єднань між слотами та сигналами може відбуватися передача параметрів. Наприклад, візуальний елемент QCheckBox випускає сигнал toggled() кожен раз при встановленні та знятті прапорця. Сигнал toggled() передає один параметр — булеве значення: true – якщо прапорець втрановлено, false – якщо ні. Ми зможемо з'єднати його з зі слотом setChecked() кнопки, який також приймає булеве значення та встановлює кнопку у ввімкнений (true) або вимкнений (false) стан. Зауважте: ми використали метод setCheckable(), який встановлює для кнопки режим перемикання між двома станами.

     lPushButton->setCheckable(true);

    connect(lCheckBox, SIGNAL(toggled(bool)),
        lPushButton, SLOT(setChecked(bool)), Qt::UniqueConnection);

Сигнально-слотове з'єднання з передачею параметрів відбувається за правилами:

  • порядок та тип параметрів має співпадати у об'єкта який передає сигнал та у об'єкта-отримувача;

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

Можливість перехресного сигнально-слотового з'єднання зображено у наступному прикладі.

    connect(lCheckBox, SIGNAL(toggled(bool)),
        lPushButton, SLOT(setChecked(bool)), Qt::UniqueConnection);
    connect(lPushButton, SIGNAL(toggled(bool)),
        lCheckBox, SLOT(setChecked(bool)), Qt::UniqueConnection);

Тут ми бачимо взаємодію між обома елементами управління. При встановленні\скиданні прапорця буде встановлюватися у ввімкнений стан чи скидатися кнопка, та навпаки.

Типи сигнально-слотових з'єднань

Сигнально-слотові з'єднання можуть різнитися за методом виклику слота, або за механізмом з'єднання. Тип з'єднання можна вказати при його створенні:

  • Qt::AutoConnection – тип з'єднання за замовчуваннням. При з'єднанні об'єктів в межах потоку поводить себе як Direct Connection, інакше — як Queued Connection;

  • Qt::DirectConnection — слот викликається негайно після того як було випущено сигнал. По-суті це нагадує звичайний виклик слота як метода;

  • Qt::QueuedConnection — слот виконується, як тільки управління перейде до черги обробки повідомлень потоку-отримувача. Також використовують для організації взаємодії об'єктів у багатопоточній програмі;

  • Qt::BlockingQueuedConnection — теж саме, що і Queued Connection, але потік, з якого було випущено сигнал блокується допоки виконання слота не буде завершено. Цей тип з'єднання має використовуватись тільки коли об'єкти, які взаємодіють, знаходятся у різних потоках.

  • Qt::UniqueConnection — цей тип з'єднання таки же як і Qt:AutoConnection, але з'єднання відбувається тільки тоді коли воно унікальне (тобто таке, яке не дублює інше вже існуюче).

Один і той же сигнал об'єкта може бути приєднаний до кількох різних сигналів та слотів іншого об'єкту, та навпаки. При цьому послідовність у якій будуть викликатися приєднані сигнали та слоти наперед не відома, тому не варто розраховувати на послідовність з'єднання. Також варто пам'ятати, що одне і те ж з'єднання може бути виконане кілька разів підряд. У такому разі під час виклику сигналу, слот спрацює стільки ж разів, скільки разів було повторно виконано з'єднання. Для того, щоб уникнути цього, необхідно передавати тип з'єднання Qt::UniqueConnection кожного разу у випадках, коли інший тип з'єднання не потрібний.

Використання вказівників на функції у з'єднаннях (Qt5)

todo

Last updated