한국어
Qt
 

Qt 에서 이벤트는 3가지 카테고리로 나눌 수 있다.

 

- Spontaneous 이벤트는 윈도우 시스템에 의해 생성된다. 이 이벤트는 시스템 큐에 들어간 후 이벤트 루프에서 차례대로 처리된다.

- Posted 이벤트들은 Qt 또는 응용 프로그램에 의해 생성된다. Qt에 의해 큐에 들어간 후 이벤트 루프에서 처리된다.

- Sent 이벤트들은 Qt 또는 응용 프로그램에 의해 생성된다. 이 이벤트들은 큐를 거치지 않고 직접 대상이 되는 오브젝트에 전달되어 즉시 처리된다.

 

우리가 흔히 Qt 용 프로젝트를 생성하면 main() 안에서 QApplication::exec()가 호출되는 것을 볼 수 있는 데 이때 비로소 응용프로그램은 이벤트 루프에 들어가게 된다.

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec(); // 이벤트 루프 시작
}

 

한마디로 이 함수가 호출 되기 전까지 응용프로그램은 유저의 입력을 받을 수 없다.

(예외적으로 QMessageBox 와 같은 Modal 위젯은 자체적으로 이벤트 루프를 실행하기 때문에 exec() 호출 전이라도 이벤트를 받을 수 있다.)

개념적으로 이벤트 루프는 다음과 같이 처리된다고 생각할 수 있다.

while ( !exit ) {
   while ( !posted_event_queue_is_empty) { ////////// (1)
      process_next_posted_event();
   }

   while ( !spontaneous_event_queue_is_empty) { ///////////// (2)
      process_next_spontaneous_event();
   }

   while ( !posted_event_qeueu_is_empty) { /////////////// (3)
      process_next_posted_event();
   }
}

 

먼저 (1) 에서 큐가 빌 때 까지 포스트 된 이벤트들이 처리된다.

처리가 완료되면 다음 단계(2)로 넘어가 역시 큐가 빌 때 까지 Spontaneous 이벤트들이 처리된다.

이때 이 이벤트들이 처리(Dispatch)되면서 새로운 이벤트들로 변환이 되어 다시 Post 큐에 들어가게 된다. 

마지막으로(3) Spontaneous 이벤트들에 의해 다시 채워진 이벤트 큐를 처리하며 한번의 이벤트 루프를 완료하게 된다.

 

Sent 로 보내진 이벤트들은 즉시 처리되기 때문에 이벤트 루프에서는 존재하지 않게 된다.

 

페인트 이벤트 처리를 예로 들며 자세히 살펴 보도록 하자.

위젯이 만들어 지고 처음 Visible이 되거나 Hidden 상태에서 Visible이 될 때 윈도우 시스템은 프로그램에게 위젯을 redraw하기 위해 (Spontaneous) 페인트 이벤트를 생성하게 된다.

이벤트 루프에서 이 이벤트를 꺼내어 디스패치를 하면 위젯이 redraw 된다.

 

그런데 윈도우 시스템에 의해서만 페인트 이벤트가 발생하는 것은 아니다.

우리가 흔히 사용하는 update()를 호출하면 위젯은 그 자신에게 페인트 이벤트를 Post하여 큐에 집어 넣고 이후 이벤트 루프에서 디스패치 되게 된다.

(디스패치 되면 그 자신에게 QPaintEvent 객체를 생성하여 QWidget::paintEvent(QPaintEvent*)의 인자로 넘겨 호출할 것이다.)

물론 이벤트루프에서의 처리를 기다리지 않고 즉시 위젯을 redraw하고자 한다면 paintEvent()를 직접 호출하는 방법도 있다.

다만 이 맴버함수는 protected로 선언되었기 때문에 자신이 아닌 외부에서 호출은 불가능하다.

어쨌든 직접호출하게 되면 이벤트 필터에 의해 해당 이벤트가 걸러지는 부분도 피할 수 있다.

 

이런 이유로 Qt에서는 Post하지 않고 즉시 이벤트 처리를 하기 위한 매커니즘(Send event)을 만들어 놓았는데 QWidget::repaint()가 대표적이다. 

 

그렇다면 Post 된 이벤트들이 즉시 처리되는 Send 이벤트에 비해 가지는 장점은 무엇일까?

 

이벤트를 Post 했을 때의 장점은 이벤트들을 최적화 할 수 있다는 것이다.

 

우리가 자주 사용하는 update()가 좋은 예가 되는 데 10번의 update()를 호출했다고 하면 이것들을 최적화 하여 갱신되는 영역을 union연산으로 합쳐서 하나로 만든 후 하나의 이벤트로 압축할 수 있다는 것이다.

 

paint, move, resize, layout hint, 그리고 language changed 와 같은 이벤트들이 이 범주에 들어간다.

 

이벤트 생성(Synthetic Events)

 

응용 프로그램은 직접 이벤트(객체)를 생성할 수도 있다.

QEvent 또는 이를 상속한 클래스의 인스턴스를 생성하고 QApplication::poseEvent(QObject *receiver, QEvent *e), QApplication::sendEvent(QObject *receiver, QEvent *e)를 호출해 처리 할 수 있는 것이다.

이 두함수들은 QObject와 QEvent 각각의 포인터를 인자로 받는다.

 

즉시 또는 지연 처리할 수 있느냐의 차이점 외에 호출 시의 차이점도 있는 데

 

postEvent()를 호출 할 때 QEvent 를 new로 생성하여 넘기면 이후의 이벤트 루프에서 삭제를 책임지게 된다.

sendEvent()는 즉시 호출되어 제어가 되돌아 오므로 스택에 이벤트 객체를 생성하면 된다.

//Post 방식
QApplication::postEvent(mainWindow, new QKeyEvent(QEvent::KeyPress, Qt::Key_X, 'X', 0));
//Send 방식
QKeyEvent e(QEvent::KeyPress, Qt::Key_X, 'X', 0);
QApplication::sendEvent(mainWindow, &e);

 

개발자가 postEvent(), sendEvent()를 호출하는 경우는 그렇게 많지 않다.

왜냐하면 대부분의 경우 Qt 또는 윈도우 시스템에 의해 필요 시 자동으로 생성되기 때문이다.

 

사용자 정의 이벤트 타입(Custom Event Types)

 

Qt는 미리 정의되어 있는 이벤트 외에 개발자들이 직접 이벤트를 만들 수 있도록 하고 있다.

이 방법은 특히 쓰레드를 만들어 GUI 쓰레드와 통신하고자 할 때 유용하다.

물론 멀티쓰레드가 아니더라도 단일 쓰레드에서도 유용하게 사용될 수 있다.

 

일반 함수 호출도 있고 signal/slot 처리도 있는 데 굳이 사용자 처리 이벤트를 선택하는 건 동기(send)/비동기(post) 처리 모두에서 사용될 수 있기 때문이다.

일반 함수 호출 및 signal/slot에 의한 처리는 늘 동기적(synchronous)이다. 호출 즉시 처리되어 호출 지점으로 되돌아 오는 것이다.

 

또 다른 장점은 필요한 경우 이벤트 필터에 의해 필터링 되어 처리를 생략될 수 있다는 점이다.

자세한 건 다음에 보기로 하고 사용자 정의 이벤트 처리의 간단한 코드 샘플을 보자.

const QEvent::Type MyEvent = (QEvent::Type)1234;
...
QApplication::postEvent(obj, new QCustomEvent(MyEvent));

 

위의 예에서 이벤트는 QCustomEvent 이거나 이를 상속받은 클래스 이어야 한다.

이 이벤트를 처리하려면 obj 객체에 해당하는 클래스는 customEvent(QCustomEvent*)를 오버라이딩 하여야 한다.

void MyLineEdit::customEvent(QCustomEvent *e)
{
   if (e->type() == MyEvent) {
      myEvent();
   } else {
      QLineEdit::customEvent(e);
   }
}

 

QCustomEvent 클래스에는 범용적으로 사용될 수 있는 void* 형의 맴버가 준비되어 있다.

필요하면 추가정보를 void*에 설정해 customEvent()에서 캐스팅 해서 사용할 수 있다.

하지만 QCustomEvent를 상속받아 필요한 맴버 변수나 함수를 추가하여 처리하는 편이 타입안정성면에서 더 좋을 수 있다.

 

QCustomEvent는 Qt3에 해당하는 내용이며 Qt4에서는 Qt3 support 라이브러리를 이용해사용할 수 있는 듯 하다.Qt5 부터는 이를 사용할 수 없다.

 

이벤트 핸들링과 필터링

 

Qt에서 이벤트들은 5가지 방법으로 처리할 수 있다.

 

- 특정 이벤트 핸들러를 오버라이딩

   QObject와 QWidget에는 각기 다른 이벤트들을 처리하기 위한 많은 가상함수들이 준비되어 있다.

   예) QPaintEvent 를 처리하는 paintEvent()

 

- QObject::event()를 오버라이딩.

   이 함수는 오브젝트가 이벤트를 처리하는 진입점이다. QObject와 QWidget는 간단히 해당 이벤트들을 이벤트 핸들러에 전달하도록 기본 구현되어 있다.

 

- QObject 에 이벤트 필터 등록하기

   이벤트 필터를 등록하면 오브젝트가 처리하기 전에 이벤트 필터가 먼저 이벤트를 받을 수 있다.

 

- qApp에 이벤트 필터 등록하기

   특별히 qApp에 이벤트 필터를 등록하면 응용프로그램 내에서 오브젝트들에게 가는 모든 이벤트를 모니터링 할 수 있다.

 

- QApplication::notify() 오버라이딩 하기

   Qt 이벤트 루프와 sendEvent()는 이벤트를 디스패치하기 위해 이 함수를 호출한다.

   이 함수를 오버라이딩 하면 가장 앞서 이벤트를 받아 처리할 수 있는 우선순위를 가지게 된다.

 

어떤 이벤트들은 이벤트 처리 대상 객체가 처리를 하지 않을 경우(거부) 전파(Propergated)되는 특성을 가지는 데 이 때 Qt는 이를 처리할 객체를 찾을 때 까지 새로운 대상 오브젝트에서 QApplication::notify()를 호출하게 된다.

 

예를 들어 키 이벤트를 처리하고자 할 때 포커스를 가진 대상 오브젝트가 처리하지 않으면 최상위 오브젝트에 도달할 때 까지 계속 부모에게 전파하게 된다.

 

Accept or Ignore ?

 

전파될 수 있는 이벤트들은 accept()와 ignore() 맴버함수를 가진다.

만약 이벤트 핸들러안에서 accept()가 호출 된다면 이 이벤트는 더이상 전파되지 않을 것이다.

반대로 ignore()를 호출하면 처리할 수 있는 receiver를 찾을 때 까지 계속 시도할 것이다.

 

대부분의 Qt 개발자들 처럼 accept()와 ignore()를 사용하는 경우는 드물다.

 

왜냐하면 기본적으로 QEvent 는 "accept"를 기본 값으로 가지고 있고 QWidget 의 이벤트 핸들러들은 기본적으로 ignore()를 호출하기 때문이다.

 

아래 예를 보자.

void MyObject::keyPressEvent(QKeyEvent *e) // e는 기본적으로 "accept"
{
   if (e->key() == Qt::Key_Escape)
      doEscape();
   else
      QWidget::keyPressEvent(e); // ignore
}

 

위의 이벤트 핸들러에서 QWidget::keyPressEvent(e); 처럼 QWidget(또는 이를 상속받은 Qt의 위젯들)의 기본 이벤트 핸들러를 호출 하지 않는다면 이벤트 핸들러가 이벤트를 처리 했다고 판단하여 더이상 전파하지 않는다.

아래 코드를 보면 더 이해가 쉬울 것이다.

void QWidget::keyPressEvent(QKeyEvent *e)
{
   e->ignore();
}

 

이벤트 핸들러가 아닌 QWidget::event()를 오버라이딩 하여 구현하고 있다면 bool 값을 리턴하여 처리 완료할지 아니면 계속 전파를 할지 결정할 수 있다.

true는 "accept"를 의미하고 false는 "ignore"를 의미한다.

 

QWidget::event() 안에서 QEvent의 accept(), ignore()를 호출하는 것은 무의미 하다.

"accpet" 플래그는 특정 이벤트 핸들러와 event()사이에 처리여부를 판단하기 위한 용도이기 때문이다.

 

또 event()에서 리턴되는 bool값은 QApplication::notify() 내에서 처리여부를 판단하기 위한 용도로 쓰여진다.

 

아래 예를 보면서 event()의 기본 구현이 어떤식으로 되어 있는 살펴보자.

bool QWidget::event(QEvent *e)
{
   switch (e->type()) {
   case QEvent::KeyPress:
      keyPressEvent((QKeyEvent*)e);
      if (!e->isAccepted())
         return false;
   case QEvent::KeyRelease:
      keyReleaseEvent((QKeyEvent*)e);
      if (!e->isAccepted(e))
         return false;
   ...
   }
   return true;
}

 

예외적으로 close 이벤트의 경우 구현이 조금 다른데 QCloseEvent::ignore() 를 호출하면 응용프로그램 닫기를 취소하고 accept()를 하면 Qt 가 closing 처리를 계속 이어갈 수 있도록 한다.

 

이로인한 혼란을 피하기 위해서는 명시적으로 accept(), ignore()를 직접 호출하는 코드를 작성하면 된다.

void MainWindow::closeEvent(QCloseEvent *e)
{
   if (userReallyWantsToQuit)
      e->accept();
   else
      e->ignore();
}

 

번호 제목 글쓴이 날짜 조회 수
공지 Qt프로그래밍(QtQuick) Beginner를 위한 글 읽는 순서 운영자 2019.01.05 91474
20 Windows환경에서 mingw로 Qt 5.10 정적(static)빌드 file makersweb 2018.02.01 6513
19 다국어 지원 어플리케이션 개발 file makersweb 2018.01.27 3893
18 Qt 어플리에이션 전역에 폰트 설정 makersweb 2018.01.24 6811
17 Qt 3D Studio 시작하기 file makersweb 2018.01.11 4693
16 QPA 플러그인과 HTML5 Backend file makersweb 2017.12.27 1720
15 임의의 메모리 영역(QImage)에 QPainter를 이용하여 그리기 file makersweb 2017.12.19 4531
14 QML에서 undefined를 확인하는 방법 makersweb 2017.11.29 2372
13 QPA 플러그인과 EGLFS file makersweb 2017.11.21 5395
12 타임스탬프( timestamp) 유닉스 시간 makersweb 2017.10.19 3039
11 Qt Logging Rule, Qt 프레임워크 로그 출력 makersweb 2017.01.13 5437
10 QString 문자열에서 숫자만 추출해서 QString으로 반환 makersweb 2017.01.10 4747
» 멀티 스레드환경, 스레드에 안전한 이벤트처리 makersweb 2016.10.27 6050
8 Ubuntu Linux에서 Qt Creator 설치 file makersweb 2016.03.06 12190
7 QtConcurrent를 이용하여 쓰레드를 만드는 방법과 MapReduce file makersweb 2016.01.24 10287
6 Qt 프로그래밍의 시작 makersweb 2015.10.25 15926
5 Qt의 스레드간 시그널 슬롯의 커넥션타입 [1] makersweb 2015.10.24 11944
4 Qt의 시그널 슬롯 시스템 file makersweb 2015.10.20 25938
3 QQuickImageProvider 를 이용한 Qml 에서 이미지 표시 makersweb 2015.10.18 7034
2 Qml 사용자 ScrollBar 구현 file makersweb 2015.07.24 7562
1 z-order 를 컨트롤 하기위한 방법 makersweb 2015.05.13 8085