한국어
Qt


강좌는 하단 링크의 글을 발로 번역한 글이며 원문의 작성 시점이 꽤 오래된 관계로 일부의 내용은 현재와 다른 부분이 존재할 수 있음을 알림.

하지만 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();
}


원문 출처 : http://doc.qt.digia.com/qq/qq11-events.html