Qt 프레임리스 창 구현: 드래그 및 리사이즈 기능 만들기

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

이 구현은 창 이동, 마우스滚轮으로 크기 조절, 커스텀 테두리 시각화를 모두 지원합니다.

태그: Qt C++ GUI FramelessWindow MouseEvent

7월 27일 20:42에 게시됨