QT5 自定义窗口的详细设计方案

QT 同时被 2 个专栏收录
1 篇文章 0 订阅
1 篇文章 0 订阅

windows系统下,QT的Widget窗口,使用的是操作系统的窗口Style,很多老板不喜欢这个窗口的标题栏和系统按键,程序员自己无法更改这个窗口标题栏,而大伤脑筋。网上有很多介绍用QT实现无框架窗口的的案例都很好,但都是没有完整的案例可以使用。

今天,用ui拉控件的方式,实现了一个自定义窗口的框架,运行起来还算是稳定。分享出来让初学者学习研究。

一. 无边框窗口实现的难点

1、鼠标拖动标题栏,实现拖动窗口移动。

2、鼠标拖动窗口的边框、四角,实现窗口拖动改变窗口的大小。

二. 无边框窗口实现的技术要点

1、设置无边框

setWindowFlags(Qt::FramelessWindowHint);//无边框

2、调用 paintEvent 重绘窗口的边框

3、捕捉标题栏的鼠标拖动事件,实现拖动窗口移动。

4、过滤鼠标点击、移动、释放事件,拖动窗口边框,改变窗口大小。

三. 运行界面示例图

四. 运行界面组成

 

边框的设计结构如下图:

根据图的内容,很容易在UI中,通过 GridLayout 布局,对窗口的边框和窗口内容进行布局。

本例中,我使用了8个 Label 来作为窗口的边框,其中 4 个作为上下左右的边框,四个作为四个角的边框。 用了一个widget作为窗口内容的框架。总共9个元素形成一个窗口的整体框架,符合九宫布局格式,用 GridLayout进行布局。

上下边框Label设置固定高度 5pix, 左右边框label设置固定宽度5pix。 四个角设置固定5pix高,5pix宽。窗口内容widget根据布局自动拉伸宽高。

肯定有人问,我为什么用 Label作为边框,解释一下,

(1)节省资源:拉伸窗口时,必须捕捉鼠标的位置,如果整个窗口都开启鼠标跟踪,当鼠标在窗口上时,无论你是否想拉伸窗口,系统都在捕捉鼠标的位置,耗用资源太多。如果用label作为边框,只需要开启这九个Label的鼠标跟踪就可以了,只有鼠标在这9个Label上,系统才开始捕捉鼠标的位置,节省cpu资源。

(2)因为Label上可以用来显示图片,如果你不想画边框的话,在Label上贴图,可以做成好看的边框,比如圆角边框,或者带阴影的窗口。

  (3) 当然你可以不用Label, 一样可以完成这个功能,这不是本文讨论的内容。

五. 窗口布局设计方案

 1, 打开 QT Creator , 创建一个 widget 应用工程,选择 带上ui, 窗口的类型为 QMainWindow 或者 QDialog。工程的名字为 MainWindow, 当然你喜欢的话,工程名随你喜欢。

2, 打开 MainWindow ui,进行编辑。

3、删除MainWindow的菜单、工具栏、状态栏(根据需要是否保存状态栏)

4,拖进三个 Label 放在ui窗口的第一行,分别命名为:leftTopLabel, topLabel,rightTopLabel, 分别代表 左上角边框,顶部边框、右上角边框,清除 Label上的文字。

5、分别拖进去 一个 Label 一个 widget(frame也行)和一个 Label,放在第二行 命名为 :leftLabel, mainWidger, rightLabel.分别代表 左边框,主框架,右边框,清除Label上的文字。

6、拖进去三个Label,放在第三行,命名为:leftBottomLabel, bottomLabel,rightBottomLabel, 分别代表左下角边框,底部边框、右下角边框。清除Lael上的文字。

7、右键点击ui的mainWindow 空余地带,在菜单中选择布局,选择 Grid 布局, 9个元素平均分配大小。

8、 选择9个Label,在属性栏里, 开启鼠标跟踪。

9、选择顶部和底部两个Label, 在属性栏里,设置最大、最小高度为 5,选择鼠标样式为上下移动的鼠标样式。

10、 选择左右两个Label,在属性栏里,设置最大、最小宽度为5,选择鼠标样式为左右移动的鼠标样式。

11、选择左上、右下两个Label,在属性栏里,设置最大、最小宽度为5,最大最小高度为5, 设置鼠标样式为 左上右下的移动鼠标样式。

12、选择 右上、左下两个Label,在属性栏里,设置最大、最小宽度为5,最大最小高度为5,设置鼠标样式为右上左下的移动鼠标样式。

这样设置完后,整个窗口布局中, mainWidget就占据了基本全部的窗口区域,每个边框Label占据5pix的区域,基本上完成了整个窗口的布局设计。

13、 编辑 mainWindow.cpp , 在构造函数里,添加下面的代码:

// 设置无边框
  setWindowFlags(Qt::FramelessWindowHint);//无边框
 // 设置透明度
   setWindowOpacity(0.99);

14、运行程序,一个无边框的窗口就出现了,背景颜色是 QT默认的灰白色,设置透明度,可以设置窗口的透明效果。把鼠标分别放在窗口的上下左右边框上,看鼠标的样式是否是 左右、上下移动的鼠标样式,四个边角,是否是斜上、斜下移动的鼠标样式。

15、重写 paintEvent , 给没边框的窗口画一个边框,给窗口设定一个背景色。

void MainWindow::paintEvent(QPaintEvent* event)
 {
    // 绘图处理
    // 重绘窗口的背景颜色
    QPalette palette = this->palette();
    qDebug() << "palette color: " << palette.color(QPalette::Window);
    QColor color(100,100,100);   
    palette.setBrush(QPalette::Window, QBrush(color));
    this->setPalette(palette);
    // 重绘窗口的边框大小
    QPainter painter(this);
     QRect rect = this->rect();
     QPen pen;
     pen.setWidth(5);
     pen.setColor(Qt::black);
     painter.setPen(pen);
     painter.drawRect(rect);

    QMainWindow::paintEvent(event);
 }

 

重新运行程序,一个崭新的窗口就出来了。

六. 拖动窗口的边框,改变窗口的大小的实现。

1,  在mainWindow.cpp 的 MainWindow 构造函数中,给上面做好的边框Label添加事件过滤器,

//设置过滤器,8个Label作为边框,过滤的鼠标事件
    ui->leftTopLabel->installEventFilter(this);
    ui->topLabel->installEventFilter(this);
    ui->rightTopLabel->installEventFilter(this);
    ui->leftLabel->installEventFilter(this);
    ui->rightLabel->installEventFilter(this);
    ui->leftBottomLabel->installEventFilter(this);
    ui->bottomLabel->installEventFilter(this);
    ui->rightBottomLabel->installEventFilter(this);

 //设置拉伸边框改变大小的参数
    mWp = unKnown; //设置拉伸边框初始标志为 unKnow, 这是一个枚举型变量
    m_bPressed = false;  // 设置鼠标按下的状态,如果鼠标在边框Label上拖动,则改参数为false.
    mouseEvent = nullptr;  //设置鼠标事件的初始值,默认为null

2. 在 mainWindow.h 头文件中, 定义 mWp, m_bPressed,, mouseEvent ,pLast参数。

enum LabelBorderStatus {
       unKnown = 0,
       toTop = 1,
       toBottom = 2,
       toLeft = 3,
       toRight = 4,
       toLeftTop = 5,
       toRightTop = 6,
       toLeftBottom = 7,
       toRightBottom = 8
    };

 LabelBorderStatus mWp;
    QMouseEvent* mouseEvent;
    bool m_bPressed;
    QPoint pLast; 

(1)定义了一个枚举类型, 用来确定是哪一条边框被拖动,后者哪一个边角被拖动。默认为 unKnow。系统中的变量名为 mWp.

(2)定义了一个布尔型 m_bPressed. 当鼠标在边框Label上被按压时,设定标志位 true, 如果有鼠标被释放,则 false

(3)定义了一个位置 pLast, 每次拖动后,保存拖动后的坐标点,再次触发拖动后,新的位置与该变量的差值,就是窗口应该增加或减少的数量。

 

3、 实现拖动,重写事件过滤器。

// 过滤 8 个 Label 边框的点击、移动、改变窗口大小
 bool MainWindow::eventFilter(QObject *target, QEvent *event)
 {
    // 如果过滤事件是鼠标按下,设置改变窗口大小的标志为鼠标按下状态。
     if(event->type() == QEvent::MouseButtonPress)
     {
         //检测是哪条边框按下
         mWp = testLabelBorder(target->objectName());
        if(mWp == LabelBorderStatus::unKnown)
        {
           m_bPressed = false;
        }
        else {
             m_bPressed = true;
             mouseEvent = nullptr;
             mouseEvent = static_cast<QMouseEvent*>(event);
             if(mouseEvent )
             {
                 // 获取鼠标按下的定位。
                 pLast = mouseEvent->globalPos();
                 event->ignore();
             }
        }
     }
     // 如果是鼠标释放事件,设置拖动标志位初始状态。
     else if(event->type() == QEvent::MouseButtonRelease)
     {
         m_bPressed = false;
         mWp = LabelBorderStatus::unKnown;
     }
     // 如果过滤事件是鼠标移动事件,处理是否拖动边框改变窗口大小
     else if(event->type() == QEvent::MouseMove)
     {
         //检测鼠标是否按下,并且拖动的边框类型
         if(m_bPressed && mWp != LabelBorderStatus::unKnown)
          {
             mouseEvent = nullptr;
             mouseEvent = static_cast<QMouseEvent*>(event);
             if(mouseEvent)
             {
                 //获取鼠标拖动的位置,
                 QPoint ptemp = mouseEvent->globalPos();
                 // 计算鼠标按下并拖动的的位移
                 ptemp = ptemp - pLast ;
                 //保存当前鼠标拖动的位置,用于下一次计算拖动位移
                 pLast = mouseEvent->globalPos();
                 event->ignore();
                 // 拉伸窗口函数
                 moveAndResizeWindow(ptemp);
             }
         }
     }
     return QMainWindow::eventFilter(target,event);
 }

由于我们设置了8个边框Label的事件过滤器, 那么鼠标在这八个边框Label上发生的鼠标事件,都被时间过滤器进行监控。

(1)当过滤器被触发,首先我们要判定被触发的事件类型, 目前我们感兴趣的时 鼠标按压事件,鼠标移动事件,鼠标释放事件,除了这三种事件需要我们处理外,其他的事件交给父类去处理。 因此我们过滤出鼠标的三种事件。

(2)首先,如果我们过滤到鼠标被按压的事件,就必须先检测鼠标按压到哪个边框上,如果不是边框Label, 则就不是我们要的事件。这里用一个函数testLabelBorder 检测,如果事件的目标是8个边框Label之一,则返回边框的枚举值,否则返回 unKnow。 如果是是边框,设定 m_bPreesed 为true, 否则设定为 false。 后面都不再处理鼠标事件。

(3) 处理鼠标释放事件。 如果鼠标被释放, 设定标志为 m_bPressed,  不用处理鼠标的事件。

 (4)鼠标移动事件过滤处理:如果鼠标移动触发的事件,首先我们要确认鼠标是否被按下,并且鼠标移动的目标是我们定义的8个边框Label, 我们才处理鼠标移动事件。

     拖动边框计算鼠标移动的偏移量设计思想:

    当鼠标移动事件被触发,此时鼠标的位置ptmp 与 上次保存的 pLast 位置进行相减,计算出鼠标每一次移动的偏移量。

testLabelBorder 检测边框函数的如下:

// 根据边框Label的objectName, 确定拉伸的是哪个边框
 MainWindow::LabelBorderStatus MainWindow::testLabelBorder(QString _objectName)
 {
     if(_objectName == "leftTopLabel") return LabelBorderStatus::toLeftTop;
    if(_objectName == "topLabel") return LabelBorderStatus::toTop;
     if(_objectName == "rightTopLabel") return LabelBorderStatus::toRightTop;
     if(_objectName == "leftLabel") return LabelBorderStatus::toLeft;
     if(_objectName == "rightLabel") return LabelBorderStatus::toRight;
    if(_objectName == "leftBottomLabel") return LabelBorderStatus::toLeftBottom;
     if(_objectName == "bottomLabel") return LabelBorderStatus::toBottom;
     if(_objectName == "rightBottomLabel") return LabelBorderStatus::toRightBottom;
     return LabelBorderStatus::unKnown;
 }

(4) 通过鼠标过滤器,我们计算出来鼠标每次移动的偏移量和那一条边框上鼠标发生位移, 则我们就可以对窗口的大小进行拖动处理。

拖动处理的函数如下:

// 移动并改变窗口大小
 void MainWindow::moveAndResizeWindow(QPoint& pos)
 {
     if(this->isMaximized()) return;
     int x=0,y=0,w=0,h=0;
    // 根据拖动的那一条边框,确定拉伸还是缩小窗口。
     switch (mWp) {
     // 左边框被拉伸
     case LabelBorderStatus::toLeft:
         x = this->pos().x() + pos.x();
         y = this->pos().y();
         w = this->size().width() - pos.x();
         h = this->size().height();
        break;
      // 右边框被拉伸
     case LabelBorderStatus::toRight:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() + pos.x();
         h = this->size().height();
         break;
         // 上边框被拉伸
     case LabelBorderStatus::toTop:
         x = this->pos().x();
         y = this->pos().y() + pos.y();
         w = this->size().width() ;
         h = this->size().height() - pos.y();
         break;
         // 下边框被拉伸
     case LabelBorderStatus::toBottom:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() ;
         h = this->size().height() + pos.y();
         break;
         //右上角被拉伸
     case LabelBorderStatus::toRightTop:
         x = this->pos().x();
         y = this->pos().y() + pos.y();
         w = this->size().width() + pos.x() ;
         h = this->size().height() - pos.y();
         break;
         //左上角被拉伸
     case LabelBorderStatus::toLeftTop:
         x = this->pos().x() + pos.x();
         y = this->pos().y() + pos.y();
         w = this->size().width() - pos.x() ;
         h = this->size().height() - pos.y();
         break;
         // 右下角被拉伸
     case LabelBorderStatus::toRightBottom:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() + pos.x() ;
         h = this->size().height() + pos.y();
         break;
         // 左下角被拉伸
     case LabelBorderStatus::toLeftBottom:
         x = this->pos().x() + pos.x();
         y = this->pos().y();
         w = this->size().width() - pos.x() ;
         h = this->size().height() + pos.y();
         break;
     default:
         return;
     }
    // 改变窗口的大小和位置。
     if(w < this->minimumWidth())
     {
         x = this->pos().x();
         w = this->size().width();
     }
     if(h < this->minimumHeight())
     {
         y = this->pos().y();
         h = this->size().height();
     }
     this->setGeometry(x,y,w,h);
 }

再次运行程序,把鼠标放到新窗口的四条边、四个角上,拖动窗口的变、角,就可以改变窗口的大小了。一个完整的可拖动改变窗口大小的程序就完成了。

七. 制作可拖动的标题栏 和 最大化最小化系统按钮

前面已经做好了无边框可拖动改变大小的窗口,还没有标题栏, 按照我的思路,由于我们使用了事件过滤器,同样可以过滤标题栏的鼠标事件,来拖动窗口标题,实现移动窗口的位置功能。考虑到事件过滤器代码不能太大,和模块化设计思想,我们把标题栏单独作为一个组件进行封装开发。

1、创建一个标题功能栏UI

  (1) 新建一个 ui类, 命名为 TitleFrame, 父类为 widget ,当然也可以选择 Frame。

 (2)水平方向,拖一个 Label, 设定固定大小 为35,作为标题栏的 logo, 清除文字,增加 icon 图片,作为logo图片,命名为 logoLabel。

 (3) 水平方向,拖一个Label,设定文本为“窗口标题”,设定最小高度值为35.

 (4) 水平方向 拉三个 PushButton, 分别命名为 :pb_close, pb_mid_max, pb_mix 分别代表 关闭、最大恢复、最小化按钮。设置固定大小为35,分别给三个按钮设定图标图片。我的示例中,添加了6个PushButton, 分别用来表示 个人信息、设置、帮助按钮,你可以根据需要添加功能按钮。

(5) 右键UI窗口的空白处,选择布局 -》水平布局。一个标题栏就做好了。

2、 给标题栏添加系统按钮添加事件

 (1)右键最大化、最小化、恢复按钮,添加 click 槽。

(2)定义信号,把功能按钮的信号,交给 主窗口 mainWindow来处理。根据设计思想,最大化最小化应该由主窗口来实现具体功能。

signals:
    void quitEvent();
    void mid_maxEvent();
    void minEvent();
   void moveWindow(QPoint& pos); // 通知主窗口移动的信号

(3)实现 最大化最小化按钮的槽函数。


void TitleFrame::on_pb_close_clicked()
{
    emit quitEvent();
}

void TitleFrame::on_pb_mid_max_clicked()
{
    emit mid_maxEvent();
}

void TitleFrame::on_pb_min_clicked()
{
    emit minEvent();
}

(4) 在主窗口 卖弄Window中,实现最大化最小化的窗口功能。在 mainWindow 的构造函数中,链接槽和信号。

 // titleFrame 的信号处理
    connect(ui->titleFrame,SIGNAL(quitEvent()),this,SLOT(quitEvent()));
    connect(ui->titleFrame,SIGNAL(mid_maxEvent()),this,SLOT(mid_maxEvent()));
    connect(ui->titleFrame,SIGNAL(minEvent()),this,SLOT(minEvent()));

(5)在主窗口 中实现槽函数的最小化,最大化,恢复复窗口的功能。

// 退出按钮处理事件
void MainWindow::quitEvent()
{
    qApp->quit();
}
// 最大化回复窗口按钮事件
void MainWindow::mid_maxEvent()
{
    if(this->isMaximized())
    {
          showNormal();
         ui->titleFrame->setMaxIcon(false);
    }
    else {
        showMaximized();
        ui->titleFrame->setMaxIcon(true);
    }
}
// 最小化窗口事件
void MainWindow::minEvent()
{

     showMinimized();
}

(6)把标题栏框架放到主窗口中去。

    打开 mainWindow ui进行编辑, 在mainWidget中,命名为 titleFrame 拖一个widget,提升为 TitleFrame. 移动到mainWidget的顶部,修改titleFrame固定高度 36。

(7) 运行程序, 点击最大化,最小化、恢复 按钮,实现窗口的最大化和最小化窗口功能能。

3、实现拖拽窗口标题,拖动窗口移动

拖拽窗口标题移动窗口,使用了与主窗口的事件过滤器不同的方法,本例中使用 重写 鼠标事件的方式来实现。

(1) 当捕捉到鼠标按下的事件时, 首先要判断是否是 titleLabe , 如果是 则处理移动,否则交给父类处理,在标题框架 titleFrame.cpp 代码实现了 mouse Press Event。代码原理和上面介绍的拖动窗口的处理思想完全相同。


void TitleFrame::mousePressEvent(QMouseEvent *event)
{
    this->setFocus();
    if(Qt::LeftButton == event->button())
    {
        QPoint pos = event->pos();
       if(pos.x() > ui->windowTitle->pos().x() && pos.x() < ui->windowTitle->pos().x() + ui->windowTitle->rect().width())
        {
            if(pos.y() > ui->windowTitle->pos().y() && pos.y() < ui->windowTitle->pos().y() + ui->windowTitle->rect().height())
            {
                pLast=event->globalPos();
               // event->ignore();
                m_bPressed = true;
            }
        }
    }
}

(2) 当捕捉到鼠标释放的事件,处理代码和改变窗口大小完全一致。

void TitleFrame::mouseReleaseEvent(QMouseEvent * event)
 {
    m_bPressed = false;
 }

(3) 当捕捉到鼠标移动时, 计算鼠标移动的位移,发送鼠标移动的信号,交给主窗口来处理窗口移动。


void TitleFrame::mouseMoveEvent(QMouseEvent* event)
{
    if( m_bPressed)
    {
        QPoint ptemp = event->globalPos();
        ptemp = ptemp - pLast ;
        pLast = event->globalPos();
        emit moveWindow(ptemp);
    }
}

(4) 主窗口处理窗口移动的代码


// 拖动窗口标题,移动窗口
void MainWindow::moveWindowEvent(QPoint& pos)
{
    if(this->isMaximized()) return;
    QPoint tmp = this->pos() + pos;
    this->move(tmp);
}

(5) 处理双击标题栏的事件处理, 双击标题栏, 判断最大化和标准状态,是窗口最大化和最小化。代码如下。

void TitleFrame::mouseDoubleClickEvent(QMouseEvent *event)
 {
     if(Qt::LeftButton == event->button())
     {
         QPoint pos = event->pos();
        if(pos.x() > ui->windowTitle->pos().x() && pos.x() < ui->windowTitle->pos().x() + ui->windowTitle->rect().width())
         {
             if(pos.y() > ui->windowTitle->pos().y() && pos.y() < ui->windowTitle->pos().y() + ui->windowTitle->rect().height())
             {
                  emit mid_maxEvent();
             }
         }
     }
 }

(6) 运行程序,拖动标题栏,可以任意移动窗口。

八. 程序完成代码。

1. mainwindow.cpp

/****************************************************************************
**
** Copyright (C) 2019 The Qt Project.
** 版权声明/
**
** 作者: 乔玉东.
** 电子邮件: newnbo@hotmail.com  whatwhy@foxmail.com
** 本程序代码为开放代码,你可以使用本代码中的所有内容。
** 当您使用本框架时,请保留版权声明
**
** *******************************************************************************/

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //设置拉伸边框改变大小的参数
    mWp = unKnown; //设置拉伸变宽初始标志为 unKnow
    m_bPressed = false;  // 设置鼠标按下的状态
    mouseEvent = nullptr;  //设置鼠标事件的初始值

    // 设置无边框
    setWindowFlags(Qt::FramelessWindowHint);//无边框
    // 设置透明度
    setWindowOpacity(0.99);
    // 设置窗口的标题
    ui->titleFrame->setWindowTitle("设置窗口标题,拖动移动窗口");

    //设置过滤器,8个Label作为边框,过滤的鼠标事件
    ui->leftTopLabel->installEventFilter(this);
    ui->topLabel->installEventFilter(this);
    ui->rightTopLabel->installEventFilter(this);
    ui->leftLabel->installEventFilter(this);
    ui->rightLabel->installEventFilter(this);
    ui->leftBottomLabel->installEventFilter(this);
    ui->bottomLabel->installEventFilter(this);
    ui->rightBottomLabel->installEventFilter(this);


    // titleFrame 的信号处理
    connect(ui->titleFrame,SIGNAL(quitEvent()),this,SLOT(quitEvent()));
    connect(ui->titleFrame,SIGNAL(mid_maxEvent()),this,SLOT(mid_maxEvent()));
    connect(ui->titleFrame,SIGNAL(minEvent()),this,SLOT(minEvent()));
    connect(ui->titleFrame,SIGNAL(aboutEvent()),this,SLOT(aboutEvent()));
    connect(ui->titleFrame,SIGNAL(configEvent()),this,SLOT(configEvent()));
    connect(ui->titleFrame,SIGNAL(personEvent()),this,SLOT(personEvent()));
    connect(ui->titleFrame,SIGNAL(moveWindow(QPoint&)),this,SLOT(moveWindowEvent(QPoint&)));

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent* event)
 {
    // 绘图处理
    // 重绘窗口的背景颜色
    QPalette palette = this->palette();
    qDebug() << "palette color: " << palette.color(QPalette::Window);
    QColor color(100,100,100);   
    palette.setBrush(QPalette::Window, QBrush(color));
    this->setPalette(palette);
    // 重绘窗口的边框大小
    QPainter painter(this);
     QRect rect = this->rect();
     QPen pen;
     pen.setWidth(5);
     pen.setColor(Qt::black);
     painter.setPen(pen);
     painter.drawRect(rect);

    QMainWindow::paintEvent(event);
 }
// 退出按钮处理事件
void MainWindow::quitEvent()
{
    qApp->quit();
}
// 最大化最小化窗口按钮事件
void MainWindow::mid_maxEvent()
{
    if(this->isMaximized())
    {
          showNormal();
         ui->titleFrame->setMaxIcon(false);
    }
    else {
        showMaximized();
        ui->titleFrame->setMaxIcon(true);
    }
}
// 最小化窗口事件
void MainWindow::minEvent()
{

     showMinimized();
}
// 关于按钮处理事件
void MainWindow::aboutEvent()
{

}
// 配置事件
void MainWindow::configEvent()
{

}
// 个人信息事件
void MainWindow::personEvent()
{

}

// 拖动窗口标题,移动窗口
void MainWindow::moveWindowEvent(QPoint& pos)
{
    if(this->isMaximized()) return;
    QPoint tmp = this->pos() + pos;
    this->move(tmp);
}

// 过滤 8 个 Label 边框的点击、移动、改变窗口大小
 bool MainWindow::eventFilter(QObject *target, QEvent *event)
 {
    // 如果过滤事件是鼠标按下,设置改变窗口大小的标志为鼠标按下状态。
     if(event->type() == QEvent::MouseButtonPress)
     {
         //检测是哪条边框按下
         mWp = testLabelBorder(target->objectName());
        if(mWp == LabelBorderStatus::unKnown)
        {
           m_bPressed = false;
        }
        else {
             m_bPressed = true;
             mouseEvent = nullptr;
             mouseEvent = static_cast<QMouseEvent*>(event);
             if(mouseEvent )
             {
                 // 获取鼠标按下的定位。
                 pLast = mouseEvent->globalPos();
                 event->ignore();
             }
        }
     }
     // 如果是鼠标释放事件,设置拖动标志位初始状态。
     else if(event->type() == QEvent::MouseButtonRelease)
     {
         m_bPressed = false;
         mWp = LabelBorderStatus::unKnown;
     }
     // 如果过滤事件是鼠标移动事件,处理是否拖动边框改变窗口大小
     else if(event->type() == QEvent::MouseMove)
     {
         //检测鼠标是否按下,并且拖动的边框类型
         if(m_bPressed && mWp != LabelBorderStatus::unKnown)
          {
             mouseEvent = nullptr;
             mouseEvent = static_cast<QMouseEvent*>(event);
             if(mouseEvent)
             {
                 //获取鼠标拖动的位置,
                 QPoint ptemp = mouseEvent->globalPos();
                 // 计算鼠标按下并拖动的的位移
                 ptemp = ptemp - pLast ;
                 //保存当前鼠标拖动的位置,用于下一次计算拖动位移
                 pLast = mouseEvent->globalPos();
                 event->ignore();
                 // 拉伸窗口函数
                 moveAndResizeWindow(ptemp);
             }
         }
     }
     return QMainWindow::eventFilter(target,event);
 }
// 移动并改变窗口大小
 void MainWindow::moveAndResizeWindow(QPoint& pos)
 {
     if(this->isMaximized()) return;
     int x=0,y=0,w=0,h=0;
    // 根据拖动的那一条边框,确定拉伸还是缩小窗口。
     switch (mWp) {
     // 左边框被拉伸
     case LabelBorderStatus::toLeft:
         x = this->pos().x() + pos.x();
         y = this->pos().y();
         w = this->size().width() - pos.x();
         h = this->size().height();
        break;
      // 右边框被拉伸
     case LabelBorderStatus::toRight:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() + pos.x();
         h = this->size().height();
         break;
         // 上边框被拉伸
     case LabelBorderStatus::toTop:
         x = this->pos().x();
         y = this->pos().y() + pos.y();
         w = this->size().width() ;
         h = this->size().height() - pos.y();
         break;
         // 下边框被拉伸
     case LabelBorderStatus::toBottom:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() ;
         h = this->size().height() + pos.y();
         break;
         //右上角被拉伸
     case LabelBorderStatus::toRightTop:
         x = this->pos().x();
         y = this->pos().y() + pos.y();
         w = this->size().width() + pos.x() ;
         h = this->size().height() - pos.y();
         break;
         //左上角被拉伸
     case LabelBorderStatus::toLeftTop:
         x = this->pos().x() + pos.x();
         y = this->pos().y() + pos.y();
         w = this->size().width() - pos.x() ;
         h = this->size().height() - pos.y();
         break;
         // 右下角被拉伸
     case LabelBorderStatus::toRightBottom:
         x = this->pos().x();
         y = this->pos().y();
         w = this->size().width() + pos.x() ;
         h = this->size().height() + pos.y();
         break;
         // 左下角被拉伸
     case LabelBorderStatus::toLeftBottom:
         x = this->pos().x() + pos.x();
         y = this->pos().y();
         w = this->size().width() - pos.x() ;
         h = this->size().height() + pos.y();
         break;
     default:
         return;
     }
// 资源中,缺少这一段判断代码,拖动左上、左、左下边框到最小窗口时,窗口会跟着移动,添加判断则可以解决。
    if(w < this->minimumWidth())
     {
         x = this->pos().x();
         w = this->size().width();
     }
     if(h < this->minimumHeight())
     {
         y = this->pos().y();
         h = this->size().height();
     }
    // 改变窗口的大小和位置。
     this->setGeometry(x,y,w,h);
 }
// 根据边框Label的objectName, 确定拉伸的是哪个边框
 MainWindow::LabelBorderStatus MainWindow::testLabelBorder(QString _objectName)
 {
     if(_objectName == "leftTopLabel") return LabelBorderStatus::toLeftTop;
    if(_objectName == "topLabel") return LabelBorderStatus::toTop;
     if(_objectName == "rightTopLabel") return LabelBorderStatus::toRightTop;
     if(_objectName == "leftLabel") return LabelBorderStatus::toLeft;
     if(_objectName == "rightLabel") return LabelBorderStatus::toRight;
    if(_objectName == "leftBottomLabel") return LabelBorderStatus::toLeftBottom;
     if(_objectName == "bottomLabel") return LabelBorderStatus::toBottom;
     if(_objectName == "rightBottomLabel") return LabelBorderStatus::toRightBottom;
     return LabelBorderStatus::unKnown;
 }

2.mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPainter>
#include <QPalette>
//#include <QGraphicsOpacityEffect>


namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:

    enum LabelBorderStatus {
       unKnown = 0,
       toTop = 1,
       toBottom = 2,
       toLeft = 3,
       toRight = 4,
       toLeftTop = 5,
       toRightTop = 6,
       toLeftBottom = 7,
       toRightBottom = 8
    };

    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    LabelBorderStatus mWp;
    QMouseEvent* mouseEvent;
    bool m_bPressed;
    QPoint pLast;

    LabelBorderStatus testLabelBorder(QString _objectName);
    void moveAndResizeWindow(QPoint& pos);

private slots:
    void quitEvent();
    void mid_maxEvent();
    void minEvent();
    void aboutEvent();
    void configEvent();
    void personEvent();
    void moveWindowEvent(QPoint& pos);

private:
    Ui::MainWindow *ui;

    void paintEvent(QPaintEvent* event);    
    bool eventFilter(QObject *target, QEvent *event);


};

#endif // MAINWINDOW_H

3.titleframe.h

#ifndef TITLEFRAME_H
#define TITLEFRAME_H

#include <QWidget>
#include <QDebug>
#include <QMouseEvent>

namespace Ui {
class TitleFrame;
}

class TitleFrame : public QWidget
{
    Q_OBJECT

public:
    explicit TitleFrame(QWidget *parent = nullptr);
    ~TitleFrame();

    void setMaxIcon(bool arg);
    void setWindowTitle(QString _title);

    bool m_bPressed;
    QPoint pLast;
   // QRect titleRect;

signals:
    void quitEvent();
    void mid_maxEvent();
    void minEvent();
    void aboutEvent();
    void configEvent();
    void personEvent();
    void moveWindow(QPoint& pos);



private slots:
    void on_pb_close_clicked();
    void on_pb_mid_max_clicked();
    void on_pb_min_clicked();
    void on_pb_about_clicked();
    void on_pb_config_clicked();
    void on_pb_person_clicked();

private:
    Ui::TitleFrame *ui;

    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent * event);
    void mouseReleaseEvent(QMouseEvent * event);
    void  mouseDoubleClickEvent(QMouseEvent *event);
};

#endif // TITLEFRAME_H

4.titleframe.cpp

/****************************************************************************
**
** Copyright (C) 2019 The Qt Project.
** 版权声明/
**
** 作者: 乔玉东.
** 电子邮件: newnbo@hotmail.com  whatwhy@foxmail.com
** 本程序代码为开放代码,你可以使用本代码中的所有内容。
** 当您使用本框架时,请保留版权声明
**
** *******************************************************************************/

#include "titleframe.h"
#include "ui_titleframe.h"

TitleFrame::TitleFrame(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::TitleFrame)
{
    m_bPressed = false;
    ui->setupUi(this);  

}

TitleFrame::~TitleFrame()
{
    delete ui;
}

void TitleFrame::on_pb_close_clicked()
{
    emit quitEvent();
}

void TitleFrame::on_pb_mid_max_clicked()
{
    emit mid_maxEvent();
}

void TitleFrame::on_pb_min_clicked()
{
    emit minEvent();
}

void TitleFrame::on_pb_about_clicked()
{
    emit aboutEvent();
}

void TitleFrame::on_pb_config_clicked()
{
    emit configEvent();
}

void TitleFrame::on_pb_person_clicked()
{
    emit personEvent();
}

void TitleFrame::setMaxIcon(bool arg)
{
    QIcon icon;
    if(arg)
    {
        icon.addFile(":/images/btn_mid.png");
    }
    else {
       icon.addFile(":/images/btn_max.png");
    }
    ui->pb_mid_max->setIcon(icon);
}

void TitleFrame::setWindowTitle(QString _title)
{
    ui->windowTitle->setText(_title);
}


void TitleFrame::mousePressEvent(QMouseEvent *event)
{
    this->setFocus();
    if(Qt::LeftButton == event->button())
    {
        QPoint pos = event->pos();
       if(pos.x() > ui->windowTitle->pos().x() && pos.x() < ui->windowTitle->pos().x() + ui->windowTitle->rect().width())
        {
            if(pos.y() > ui->windowTitle->pos().y() && pos.y() < ui->windowTitle->pos().y() + ui->windowTitle->rect().height())
            {
                pLast=event->globalPos();
               // event->ignore();
                m_bPressed = true;
            }
        }
    }
}

void TitleFrame::mouseMoveEvent(QMouseEvent* event)
{
    if( m_bPressed)
    {
        QPoint ptemp = event->globalPos();
        ptemp = ptemp - pLast ;
        pLast = event->globalPos();
        emit moveWindow(ptemp);
    }
}

 void TitleFrame::mouseReleaseEvent(QMouseEvent * event)
 {
    m_bPressed = false;
 }

 void TitleFrame::mouseDoubleClickEvent(QMouseEvent *event)
 {
     if(Qt::LeftButton == event->button())
     {
         QPoint pos = event->pos();
        if(pos.x() > ui->windowTitle->pos().x() && pos.x() < ui->windowTitle->pos().x() + ui->windowTitle->rect().width())
         {
             if(pos.y() > ui->windowTitle->pos().y() && pos.y() < ui->windowTitle->pos().y() + ui->windowTitle->rect().height())
             {
                  emit mid_maxEvent();
             }
         }
     }
 }

5. main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

6.mainwindow.pro

#-------------------------------------------------
#
# Project created by QtCreator 2019-03-29T14:46:38
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = MainWindow
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

CONFIG += c++11

SOURCES += \
        main.cpp \
        mainwindow.cpp \
    titleframe.cpp

HEADERS += \
        mainwindow.h \
    titleframe.h \
    compnent/minibutton.h

FORMS += \
        mainwindow.ui \
    titleframe.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

RESOURCES += \
    qrc.qrc

 

整个自定义窗口的代码完成了。写了整个下午的文档,很累。 如果你觉得本文章对你有帮助,扫描下面二维码,微信打赏一下。1块不嫌少,100块不嫌多。

本项目资源可以下载, 下载地址:QT实现的自定义窗口框架

  • 11
    点赞
  • 1
    评论
  • 31
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

<p> 本课程详细、全面地介绍了 Qt 开发中的各个技术细节,并且额外赠送在嵌入式端编写Qt程序的技巧。整个课程涵盖知识点非常多,知识模块囊括 Qt-Core 组件、QWidgets、多媒体、网络、绘图、数据库,超过200个 C++ 类的分析和使用,学完之后将拥有 Qt 图形界面开发的非常坚实的功底。  </p> <p> <br /></p> <p> 每个知识点不仅仅会通过视频讲解清楚,并且会配以精心安排的实验和作业,用来保证学习过程中切实掌握核心技术和概念,通过实验来巩固,通过实验来检验,实验与作业的目的是发现问题,发现技术盲点,通过答疑和沟通夯实技术技能。 </p> <p> <br /></p> <p> <span style="color:#E53333;"><strong>注意</strong></span>:本套视频教程来源于线下的实体班级,因此视频中有少量场景对话和学生问答,对此比较介意的亲们谨慎购买。 </p> <p> <img src="https://img-bss.csdnimg.cn/202006111209386420.jpg" alt="" /></p> <p> <br /></p> <p> <span style="color:#E53333;"><strong>注意</strong></span>:本套视频教程包含大量课堂源码,包含对应每个知识点的精心编排的作业。由于CSDN官方规定在课程介绍中不能出现作者的联系方式,因此在这里法直接给出QQ答疑号,视频中的源码、资料和作业文档链接统一在购买后从CSDN平台跟我沟通,我会及时回复跟进。 </p> <p> <br /></p> <p> <strong><span style="color:#E53333;">注意</span></strong>:本套视频教程包含全套10套作业题,覆盖所有视频知识点,循序渐进,各个击破,作业总纲如下: </p> <p> <img src="https://img-bss.csdnimg.cn/202006111337059806.jpg" alt="" /></p> <p> <br /></p> <p> <br /></p> <p> </p><p style="font-size:16px;"> <span style="color:#3A4151;">下面是部分作业题目展示,每道题都有知识点说明,是检验学习效果的一大利器:</span> </p> <p style="font-size:16px;"> <span style="color:#3A4151;"><span style="font-size:16px;">(部分作业展示,为了防止盗图盗题对题干做了模糊处理)</span></span> </p> <p style="font-size:16px;"> <span style="color:#3A4151;"><img src="https://img-bss.csdnimg.cn/202006121455228969.jpg" alt="" /><br /></span> </p> <p style="font-size:16px;"> <span style="color:#3A4151;"><br /></span> </p> <p style="font-size:16px;"> (部分作业展示,为了防止盗图盗题对题干做了模糊处理) </p> <p> <img src="https://img-bss.csdnimg.cn/202006121448525610.jpg" alt="" /></p> <p> <br /></p> <p> <span style="font-size:16px;">(部分作业展示,为了防止盗图盗题对题干做了模糊处理)</span> </p> <p> <img src="https://img-bss.csdnimg.cn/202006121450392202.jpg" alt="" /></p> <p> <br /></p> <p> <span style="font-size:16px;">(部分作业展示,为了防止盗图盗题对题干做了模糊处理)</span> </p> <p> <img src="https://img-bss.csdnimg.cn/202006121450547079.jpg" alt="" /></p> <p> <br /></p> <p> <span style="font-size:16px;">(部分作业展示,为了防止盗图盗题对题干做了模糊处理)</span> </p> <p> <img src="https://img-bss.csdnimg.cn/202006121455494156.jpg" alt="" /></p> <p> …… …… </p>
©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值