Qt 프레임리스 창 구현 개요
Qt GUI 애플리케이션에서 프레임리스 창을 구현하고 사용자가 창을 자유롭게 이동하고 크기 조절할 수 있게 만드는 것은 일반적인 요구사항입니다. 이 글에서는 Qt의 setWindowFlags() 함수를 사용하여 창 테두리를 제거하고, 마우스 이벤트를 처리하여 사용자 정의 드래그 기능을 구현하는 방법을 설명합니다. 또한 이벤트 필터를 활용하여 마우스滚轮事件로 창 크기를 조절하는 기능도 다룹니다.
1. 프레임리스 창 기초
1.1 창 테두리의 역할
창 테두리는 GUI의 핵심 구성요소로, 창의 시각적 경계를 명확히 하고 사용자가 창을辨识하고 조작할 수 있게 합니다. 최소화, 최대화, 닫기 버튼이 모두 테두리에 위치하며, 사용자는 이를 통해 창을 이동하거나 크기를 조절합니다.
1.2 setWindowFlags() 함수로 프레임리스 창 생성
Qt에서 프레임리스 창을 만들려면 Qt::FramelessWindowHint 플래그를 사용합니다.
#include <QApplication>
#include <QWidget>
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QWidget *mainWindow = new QWidget();
mainWindow->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
mainWindow->resize(300, 250);
mainWindow->show();
return app.exec();
}
위 코드는 기본적인 프레임리스 창을 생성합니다. setWindowFlags()에 Qt::FramelessWindowHint를 전달하면 기본 테두리와 타이틀 바가 제거됩니다.
2. 마우스 이벤트로 드래그 구현
2.1 마우스 이벤트 처리 개요
프레임리스 창에서는 기본 타이틀 바가 없으므로 사용자가 창을 드래그할 수 없습니다. 이 문제를 해결하려면 마우스 이벤트를 직접 처리해야 합니다. Qt에서 제공하는 마우스 이벤트 핸들러는 다음과 같습니다:
mousePressEvent(): 마우스 버튼이 눌릴 때 호출mouseMoveEvent(): 마우스가 창 위에서 이동할 때 호출mouseReleaseEvent(): 마우스 버튼이 놓일 때 호출
2.2 마우스 클릭 이벤트 구현
드래그 기능을 구현하려면 먼저 마우스 클릭 위치를 저장해야 합니다.
class DraggableWindow : public QWidget {
Q_OBJECT
private:
QPoint startPoint;
bool isDragging = false;
public:
DraggableWindow(QWidget *parent = nullptr) : QWidget(parent) {
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
}
protected:
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
startPoint = event->globalPos() - frameGeometry().topLeft();
isDragging = true;
event->accept();
}
}
};
이 코드에서 startPoint는 마우스가 클릭된 위치를 저장하고, isDragging 플래그는 현재 드래그 상태를 추적합니다.
2.3 마우스 이동 이벤트 구현
마우스 이동 시 창 위치를 업데이트합니다.
protected:
void mouseMoveEvent(QMouseEvent *event) override {
if (isDragging && (event->buttons() & Qt::LeftButton)) {
move(event->globalPos() - startPoint);
event->accept();
}
}
void mouseReleaseEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
isDragging = false;
event->accept();
}
}
마우스가 이동할 때마다 현재 마우스 위치에서 이전에 저장한 시작 위치를 빼서 창의 새 위치를 계산합니다.
3. 이벤트 필터로 리사이즈 구현
3.1 이벤트 필터 원리
이벤트 필터는 Qt에서 이벤트를 가로채고 처리할 수 있는 강력한 메커니즘입니다. 이벤트가 대상 객체에 전달되기 전에 Intercept하여 사용자 정의 처리가 가능합니다.
3.2 이벤트 필터 설정
이벤트 필터를 사용하려면 QObject::installEventFilter()를 호출하고 eventFilter() 메서드를 재정의합니다.
class ResizableWindow : public QWidget {
Q_OBJECT
public:
ResizableWindow(QWidget *parent = nullptr) : QWidget(parent) {
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
ResizeFilter *filter = new ResizeFilter(this);
this->installEventFilter(filter);
}
};
class ResizeFilter : public QObject {
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event) override {
if (obj->parent() == this->parent()) {
if (event->type() == QEvent::Wheel) {
QWheelEvent *wheelEvent = static_cast<QWheelEvent *>(event);
int delta = wheelEvent->angleDelta().y();
QWidget *target = qobject_cast<QWidget *>(obj);
int newWidth = target->width() + (delta > 0 ? -15 : 15);
int newHeight = target->height() + (delta > 0 ? -15 : 15);
target->resize(newWidth, newHeight);
return true;
}
}
return QObject::eventFilter(obj, event);
}
};
위 코드는 마우스滚轮事件를 감지하여 창 크기를 조절합니다.滚轮을 위로 굴리면 창이 작아지고, 아래로 굴리면 커집니다.
4. 커스텀 테두리로 리사이즈 구현
4.1 커스텀 테두리 그리기
창四周에 커스텀 테두리를 그리고 그 테두리를 통해 크기 조절을 할 수 있습니다.
class CustomBorderWidget : public QWidget {
Q_OBJECT
private:
QRect edgeRect;
int borderWidth = 8;
protected:
void paintEvent(QPaintEvent *event) override {
QPainter painter(this);
painter.setPen(Qt::darkGray);
painter.setBrush(Qt::NoBrush);
painter.drawRect(0, 0, width() - 1, height() - 1);
}
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::MouseMove) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
updateCursorShape(mouseEvent->pos());
}
return QWidget::eventFilter(obj, event);
}
void updateCursorShape(const QPoint &pos) {
bool onLeft = pos.x() < borderWidth;
bool onRight = pos.x() > width() - borderWidth;
bool onTop = pos.y() < borderWidth;
bool onBottom = pos.y() > height() - borderWidth;
if ((onLeft || onRight) && (onTop || onBottom)) {
setCursor(Qt::SizeFDiagCursor);
} else if (onLeft || onRight) {
setCursor(Qt::SizeHorCursor);
} else if (onTop || onBottom) {
setCursor(Qt::SizeVerCursor);
} else {
unsetCursor();
}
}
};
5. setWindowFlags() 상세 활용
5.1 주요 플래그 옵션
setWindowFlags()는 다양한 플래그를 조합하여 사용할 수 있습니다:
Qt::FramelessWindowHint: 테두리 없는 창Qt::WindowStaysOnTopHint: 항상 최상단 표시Qt::WindowMinimizeButtonHint: 최소화 버튼 표시Qt::WindowMaximizeButtonHint: 최대화 버튼 표시Qt::WindowCloseButtonHint: 닫기 버튼 표시
5.2 플래그 조합 예시
void setupWindow() {
setWindowFlags(Qt::FramelessWindowHint
| Qt::WindowMinimizeButtonHint
| Qt::WindowCloseButtonHint);
setFixedSize(400, 300);
show();
}
이 코드는 프레임리스 창을 만들면서 최소화 및 닫기 버튼만 표시하고 크기를 고정합니다.
5.3 플래그 충돌 피하기
플래그 설정 시 충돌을 피하려면 순서에 주의해야 합니다.
void safeSetFlags() {
Qt::WindowFlags flags = Qt::FramelessWindowHint;
flags |= Qt::Window;
setWindowFlags(flags);
}
위 방법처럼 비트 연산자를 사용하여 플래그를 점진적으로 추가하면 의도치 않은 충돌을 방지할 수 있습니다.
6. 통합 구현 예제
드래그와 리사이즈 기능을 모두 포함한 완전한 예제입니다.
#include <QApplication>
#include <QWidget>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QPainter>
class CustomWindow : public QWidget {
Q_OBJECT
private:
QPoint dragOffset;
bool dragging = false;
const int edgeMargin = 10;
public:
CustomWindow() {
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
setMinimumSize(200, 150);
resize(400, 300);
}
protected:
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
dragOffset = event->globalPos() - pos();
dragging = true;
}
}
void mouseMoveEvent(QMouseEvent *event) override {
if (dragging && event->buttons() & Qt::LeftButton) {
move(event->globalPos() - dragOffset);
}
updateCursor(event->pos());
}
void mouseReleaseEvent(QMouseEvent *event) override {
dragging = false;
}
void wheelEvent(QWheelEvent *event) override {
int delta = event->angleDelta().y();
int widthChange = delta > 0 ? -20 : 20;
int heightChange = delta > 0 ? -20 : 20;
resize(width() + widthChange, height() + heightChange);
}
void paintEvent(QPaintEvent *event) override {
QPainter p(this);
p.setPen(QColor(100, 100, 100));
p.drawRect(0, 0, width() - 1, height() - 1);
}
private:
void updateCursor(const QPoint &pos) {
bool left = pos.x() < edgeMargin;
bool right = pos.x() > width() - edgeMargin;
bool top = pos.y() < edgeMargin;
bool bottom = pos.y() > height() - edgeMargin;
if ((left || right) && (top || bottom)) {
setCursor(Qt::SizeFDiagCursor);
} else if (left || right) {
setCursor(Qt::SizeHorCursor);
} else if (top || bottom) {
setCursor(Qt::SizeVerCursor);
} else {
unsetCursor();
}
}
};
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
CustomWindow window;
window.show();
return app.exec();
}
이 구현은 창 이동, 마우스滚轮으로 크기 조절, 커스텀 테두리 시각화를 모두 지원합니다.