扁平窗口实现(三) 标题栏窗口

扁平窗口实现之标题栏窗口

开发步骤

  • 标题栏的实现
  • 带标题栏窗口的思路
  • 实现整个窗口可放大缩小
  • 实现鼠标点住后随标题栏移动

标题栏开发

在前面两篇文章中实现了可以拖拽和放大缩小的空白控件,但一般widget独立出现时都是带标题栏的,这篇文章就从标题栏开始,实现一个完整的扁平窗口。

先来看看标题栏的开发,标题栏一般包含就是三个部分,分别由logotitle一组按钮组成。因此,只要创建对应的控件,使用水平布局分别放置即可,标题栏控件可以继承QWidget。我这里直接继承我之前的自定义空白控件,代码如下:

//.h
namespace jq {
    class JQFlatTitleBar
            : public JQFlatBase {
    public:
        explicit JQFlatTitleBar(QWidget * _parent = nullptr);
        ~JQFlatTitleBar() override;

    private:
        QSize                               bar_size_;
        QMap<QString, QLabel*>              title_all_label_;
        QMap<QString, QPushButton*>         title_all_button_;
    };
} // jq

//.cpp
namespace jq {
    JQFlatTitleBar::~JQFlatTitleBar() {

    }

    JQFlatTitleBar::JQFlatTitleBar(QWidget *_parent)
        : JQFlatBase(_parent)
        , bar_size_(60, 30)
    {
        setMinimumSize(bar_size_);

        title_all_label_["logo"] = new QLabel("");
        title_all_label_["窗口标题"] = new QLabel("");

        title_all_button_["最大化"] = new QPushButton("最大化");
        title_all_button_["最小化"] = new QPushButton("最小化");
        title_all_button_["关闭"] = new QPushButton("关闭");

        auto main_layout = new QHBoxLayout();
        setLayout(main_layout);

        main_layout->addWidget(title_all_label_["logo"]);
        main_layout->addWidget(title_all_label_["软件名称"]);

        main_layout->addWidget(title_all_button_["最大化"]);
        main_layout->addWidget(title_all_button_["最小化"]);
        main_layout->addWidget(title_all_button_["关闭"]);


        title_all_label_["软件名称"]->setText(qAppName());
        QIcon icon((":/logo.jpg"));
        title_all_label_["logo"]->setPixmap(icon.pixmap(18, 18));
    }
}

上面这些代码就可以组合成一个简单标题栏控件,效果如下:
标题栏简单版

这里先记录一个题外话,如果不是使用VisualStudio进行开发,那么,在调试阶段需要复制一下plugins\imageformats\qjpegd.dll到当前.exe的目录下,且保持目录层级不变,这样才能加载出图片。

回到正题,虽然效果很拉跨但基本像是一个标题栏了,后边在继续优化和调整。对于标题栏来说,现代风格的扁平化标题栏其实包含很多的东西,例如有的可能是在标题栏的中间显示当前打开的文档名称、有的可能是在标题栏嵌入一个搜索栏还有的在标题栏中显示当前软件使用软件的用户头像和名称,总之这个东西是随心而变的,这里只是提供一个思路,需要根据具体需求进行控件和布局方面的调整。

扁平化窗口实现思路

有了简单的标题栏控件后可以开始实现一个扁平风格的widget控件了,先说一下思路,一般的网上的自定义窗体都是采用在一个大的widget上进行布局,将标题栏放在最上边,这里也不例外,采用这种方式,首先实现一个继承QWidget的窗体类,同样的这里继承了我之前开发的那个空白的自定义个窗口控件,代码如下:

// .h
namespace jq {

    class JQFlatTitleBar;

    class JQFlatWidget
            : public JQFlatBase {

    public:
        explicit JQFlatWidget(QWidget * _parent = nullptr);
        ~JQFlatWidget() override;

    private:
        void initUI();
        void initData();

    private:
        JQFlatBase*                         center_;
        JQFlatTitleBar*                     flat_title_bar_;
    };

} // jq

// .cpp
namespace jq {
    JQFlatWidget::~JQFlatWidget() {

    }

    JQFlatWidget::JQFlatWidget(QWidget *_parent)
        : JQFlatBase(_parent)
        , center_(nullptr)
        , flat_title_bar_(nullptr)
    {
        initUI();
        initData();
    }

    void JQFlatWidget::initUI() {
        center_ = new JQFlatBase();
        flat_title_bar_ = new JQFlatTitleBar();

        auto main_layout = new QVBoxLayout();
        main_layout->setContentsMargins(0,0,0,0);
        main_layout->setSpacing(0);
        setLayout(main_layout);

        main_layout->addWidget(flat_title_bar_);
        main_layout->addWidget(center_, 1);
    }

    void JQFlatWidget::initData() {
        flat_title_bar_->setBackgroundColor(QColor(199, 125, 187));
        center_->setBackgroundColor(QColor(35, 94, 197));
    }

} // jq

initUI()函数中就是布局的思路,为了看出效果特意在基类里实现了一个设置背景颜色的接口setBackgroundColor()思路就是重写paintEvent()函数,这个不是本文重点暂且不表,如果是直接继承QWidget的话可以自行百度,或者不设置也可以这步只是为了看效果,整体效果如下:

初始扁平化窗口

目前看是象那么回事,但这个还是不行鼠标点击标题栏开始移动时,就会发现三个控件放是放到一起了但是都是各自为战的,如下所示:

各自为战的窗口
接下来就是改造这个控件,让这三位可以在一起干活。

整个窗体放大缩小

由于我是使用的自己开发的控件因此为了解决问题,我需要给这个控件做两个接口就是不能改变大小和不能拖拽,如果是直接继承QWidget的话就可以省去此步。设置状态的思路就是设置变量在移动和改变大小的关键位置进行屏蔽,核心代码如下:

// .h
namespace jq {

    class JQFlatBase
            : public QWidget {
        Q_OBJECT
    public:
        explicit JQFlatBase(QWidget* _parent = nullptr);
        ~JQFlatBase() override;

    protected:
        /* 设置禁用全部方向的拖拽 */
        void dragSizeEnable(bool _is_enable);
        /* 设置禁用控件移动 */
        void dragMoveEnable(bool _is_enable);

    private:
        bool                            enable_drag_size_;
        bool                            enable_drag_move_;
    };

} // jq

// .cpp
namespace jq {

	void JQFlatBase::dragSizeEnable(bool _is_enable) {
        enable_drag_size_ = _is_enable;
    }

    void JQFlatBase::dragMoveEnable(bool _is_enable) {
        enable_drag_move_ = _is_enable;
    }
    
    void JQFlatBase::updateWidget(const QPoint & _update_pos) {
        // ......
        if (enable_drag_size_) {
            setGeometry(changed_rect);
        }

    }
    
    void JQFlatBase::changedMouseShape() {
        if (!enable_drag_size_) {
            return;
        }
        // ......
    }
    
    void JQFlatBase::mouseMoveEvent(QMouseEvent * event) {
		// ......
        if (is_move_ && is_mouse_press_ && enable_drag_move_) {
            move(event->pos() - last_mouse_point_ + pos());
        } 
        // ......
    }

} // jq

这样一来标题栏和中心区域控件都不会有鼠标事件响应了,当然这只是第一步,真正要解决问题从下面开始。

首先来解决如何让窗体放大缩小,由于我用两个控件覆盖在一个控件上,因此,覆盖住的才是主要窗体,它需要响应鼠标事件,但由于被覆盖导致鼠标的事件也没有传递到最下层控件。

经过仔细观察windows的窗口发现,并不是当鼠标进入到控件的边上时才会触发拖拽,而是在快进入到控件周围时就已经改变了。这个和我之前实现扁平控件的原理不太一样,而且鼠标要获取控件之外的鼠标移动事件,这可能会涉及到一些底层API,这会不会牵扯出跨平台问题。总之,我选择比较邪教的方法,O(∩_∩)O,核心代码如下:

void JQFlatWidget::initData() {
    dragSizeEnable(true);
    dragMoveEnable(true);

    setBackgroundColor(QColor(255, 255, 255, 1));
    flat_title_bar_->setBackgroundColor(QColor(199, 125, 187));
    center_->setBackgroundColor(QColor(35, 94, 197));

    layout()->setContentsMargins(dragSizeWidth(),dragSizeWidth(),
                                 dragSizeWidth(),dragSizeWidth());
}

原理就是让在布局对象使用setContentsMargins(),不设置成四个0,而是根据可退拽改变窗体事件的响应宽度进行设置(PS:最开始不设置间距的话就没这个问题了-.-!!),同时,将底层控件的背景色的透明度改为1,使其基本透明,绝对不能完全透明全透的话也响应不了事件,具体效果如下:

解决整窗体改变大小问题

整个窗体移动

接下来解决整个窗体的移动功能,其原理也是一样的就是让上层的标题栏控件不阻止事件的传递,只不过放大缩小可以通过缩小核心区域可控制区域露出来,这个整体移动就没有办法了。

因此只能在JQFlatTitleBar这个类中重写mouseMoveEvent()mousePressEvent()mouseReleaseEvent()三个函数,并且在其中调用QMouseEvent::ignore()这个函数,这个函数的意思我个人理解是这个事件我不要,给其控件吧,核心代码如下:

void JQFlatTitleBar::mouseMoveEvent(QMouseEvent *event) {
    JQFlatBase::mouseMoveEvent(event);
    event->ignore();
}

void JQFlatTitleBar::mousePressEvent(QMouseEvent *event) {
    JQFlatBase::mousePressEvent(event);
    event->ignore();
}

void JQFlatTitleBar::mouseReleaseEvent(QMouseEvent *event) {
    JQFlatBase::mouseReleaseEvent(event);
    event->ignore();
}

至此,整个控件的点住标题栏并整体移动功能就完成了,效果如下:

完整扁平化窗口

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值