用Qt实现一个桌面弹幕程序(四)-- -- 桌面客户端实现①



  • 光实现弹幕还不够哦~桌面客户端也要做好看点才比较好用~

    接下来就跟随 杰洛君 一起实现一个QMainwindow 程序吧~

    拖控件与代码实现界面

    QMainWindow程序界面的编写在Qt中主要有三种方式:

    • 1.拖动控件
    • 2.代码实现
    • 3.以上二者混合

    拖动控件进行界面编写

    双击项目中的 mainwindow.ui 就会打开设计模块,左边看到的Layouts,Button等就是控件,你可以拖动进中间的窗体。

    右边有一颗控件树,由此我们可以知道,控件以树状结构进行管理。

    以上就是拖控件来进行界面编写啦~

    控件都在UI 命名空间中,所以你可以看到我们Mainwindow的实现代码总有这么一句 ui->setupUi(this); 访问控件都是用ui进行访问。

    不过呢,杰洛君的这个应用还是非常传统地用代码实现了,没有用到控件,主要是杰洛君比较笨,没有学会用好这个设计软件TAT
    (p.s. 为了都试着用代码写,所以mainwidnow.ui中的菜单栏,状态栏我全都右键删除了~)

    这两种方法各有优势,没有孰优孰劣之说,总之你喜欢就好~

    那么,接下来。。。

    桌面程序

    具体的就以这个为示例,说下如何做一个这样子的程序。

    先来一段分析

    上面图中的界面如何实现?

    观察可以知道,这个应用有很明显分为三个部分:

    • 标题栏(黑色部分)
    • 图片栏(蓝色部分)
    • 控制台(白色部分)

    我们简化它 , 发现它的结构如下图般简单~

    这里写图片描述

    那就开始一部分一部分地实现吧~

    首先是标题栏~

    你可以看到杰洛君并没有采用系统的标题栏,所以你在不同系统中看到的标题栏效果是一致的。

    小思考
    提问:自定义标题栏的优点和缺点各有那些呢?

    回答:
    自定义标题栏优点:
    1. 可以让界面画风一致,因为有时你的程序风格与系统原生界面是不一致的,自定义标题栏比较好看,防止画风突变(^__^)~
    2.不同平台下标题栏风格是一致的~

    自定义标题栏缺点:
    1.放弃了系统原生的标题栏意味着,放弃了系统原生实现好的那些拖放功能,最大化最小化,拉伸的功能等。也就是说这些功能都是需要自己实现的。

    既然放弃了很多系统原生功能,为了简化,那就那把应用的大小设置为固定的吧~这样可以专注学习布局,不用去实现拉伸,大大降低难度~

    实现一个自定义标题栏吧

    就如上面所说的,先固定窗体大小,剩下的就好办了。

    设置窗体固定大小很简单 在mainwindow的构造函数中

    this->setFixedSize(900,600);
    

    固定为宽为900,高为600的一个窗体。

    然后杰洛君创建一个新的类,TitleWidget 它集成自QWidget,并且重载它的paintEvent 函数。

    把这个类作为我们的标题栏~

    于是它至少应该是这个样子:

    class TitleWidget : public QWidget
    {
        Q_OBJECT
    public:
        explicit TitleWidget(QWidget *parent = 0);
    
    signals:
    
    public slots:
    
    protected:
        void paintEvent(QPaintEvent *);
    
    };
    

    为了让窗体的背景有一个渐变的效果,这里我们重载paintEvent

    void TitleWidget::paintEvent(QPaintEvent *){
        // 设置画笔
        QPen objPen(Qt::NoBrush,1);
        QPainter Painter(this);
        Painter.setPen(objPen);
        //设置渐变画刷
        QLinearGradient objLinear(rect().topLeft(),rect().bottomLeft());
        //顶部颜色和透明度
        objLinear.setColorAt(0,QColor(50,50,50,240));
        //中间颜色和透明度
        objLinear.setColorAt(0.8,QColor(34,34,34,255));
        //底部颜色和透明度
        objLinear.setColorAt(1,QColor(7,7,7,255));
        QBrush objBrush(objLinear);
        Painter.setBrush(objBrush);
        //画圆角矩形
        //Painter.drawRoundedRect(rect(),5,5);
        Painter.drawRect(rect());
    }
    

    QLinearGradient 用于创建一个渐变填充效果,赋值给画刷就可以绘制出渐变效果了~

    这个有什么用?确实很多应用追求扁平化会放弃无意义的渐变,不过像酷狗的桌面歌词还是可以看到这种渐变的使用。

    于是一个标题栏窗体就绘制好了,接下来如何放入杰洛君的mainwindow程序中呢。

    在MainWindow类中加入 头文件 “TitleWidget.h"以及 QVBoxLayout 头文件

    杰洛君在MainWindow类中加入一个私有变量 TitleWidget类型的titleWidget。

    同时为了方便管理代码,再在MainWindow类中添加一个函数声明

    void initUI();
    

    以后杰洛君的界面代码就写在这个函数中,并在构造函数中调用它,这样就不会造成构造函数长长一大串了。

    接下来就是在initUI函数中写入耿直的代码了:

        titleWidget = new TitleWidget(this);
        titleWidget->setFixedHeight(50);
        titleWidget->setFixedWidth(900);
    
        QVBoxLayout * mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(titleWidget);
    

    上面的代码中 杰洛君用到了一个布局类 QVBoxLayout 这个是让Widget 垂直排布的一个布局 ,说到V -> vertical。

    举一反三,Qt 中有一个QHBoxLayout 布局类 ,说到H 自然是horizon 与水平有关 (杰洛君才,才没有想歪呢,真,真的哦,我还只是个孩子,H什么滴真的不懂呀 (>\\\\<) )

    自定义标题栏按钮

    一个标题栏,一般左边是程序的Logo 右边有最大化最小化与关闭。这里我们做了简化,只有最小化与关闭按钮。

    按钮的话 自然是用Qt 的QPushButton啦~

    来来来在MainWindow中添加几个QPushButton类型的私有变量吧~

    • miniBut 最小化按钮
    • closeBut 关闭按钮
    • logoBut Logo按钮

    然后实现它们。

    
        miniBut = new QPushButton(this);
        miniBut->setFixedSize(29,32);
        
        //连接最小化事件        
        QObject::connect(miniBut,SIGNAL(clicked()),this,SLOT(showMinimized()));
    
        closeBut = new QPushButton(this);
        closeBut->setFixedSize(38,32);  
    //连接关闭槽函数,这个槽函数是自己定义的,里面用动画使窗体渐变退出
      QObject::connect(closeBut,SIGNAL(clicked()),this,SLOT(closeWidget()));
    
        logoBut = new QPushButton(this);
        logoBut->setFixedSize(376, 46);
    

    上面提到的closeWidget()是一个槽函数。

    在Mainwindow类中添加

    private slots:
    bool closeWidget();
    

    并且实现它:

    bool MainWindow::closeWidget()      //关闭按钮关联的槽函数
    {
        //界面动画,改变透明度的方式消失1 - 0渐变
        QPropertyAnimation *animation = new QPropertyAnimation(this, "windowOpacity");
        animation->setDuration(1500);
        animation->setStartValue(1);
        animation->setEndValue(0);
        animation->start();
        connect(animation, SIGNAL(finished()), this, SLOT(close()));
    
        return true;
    }
    

    于是关闭的时候你会看到个很有趣的效果。

    杰洛君看到这些按钮是水平布局的,所以就用QHBoxLayout 进行布局

    QHBoxLayout * titleLayout = new QHBoxLayout();      
    //新建了一个QHBoxLayout水平布局,这个水平布局是用来放Logo按钮  最小化按钮 还有关闭按钮的~
        titleLayout->addSpacing(5);     //加个间隔,不让logo按钮太贴边
        titleLayout->addWidget(logoBut);        //加入logo按钮
        titleLayout->addStretch(0);       //加个QSpacerItem
    
        titleLayout->addWidget(miniBut);    //加最小化
        titleLayout->addWidget(closeBut);   //加关闭按钮
        titleLayout->setSpacing(0);      //设置水平布局默认间隔为0  默认不为0会有点难看
        titleLayout->setMargin(2);      //设置控件间隔
        titleWidget->setLayout(titleLayout);        //把这个水平布局放入titleWidget中
    
    

    现在你会看到你的标题栏虽然已经初具形态,可惜仍然丑得可以。然我们先忽视丑丑的它,我们来看看如何实现一个标题栏该有的一个功能-- -- 拖动~

    实现窗体拖动

    窗体的拖动是很容易实现的,杰洛君是如何理解这个窗体的拖动呢?

    • 重载鼠标按下事件,鼠标单击并没有释放时,设置按下(一个bool类型变量)为真,记录鼠标坐标与窗体初始坐标。
    • 如果鼠标没有释放并进行移动,记录移动位置的坐标。
    • 重载鼠标移动事件,每次都移动窗体位置为当前窗体位置+鼠标移动位置-鼠标按下位置。
    • 重载鼠标释放事件,设置按下为false。

    为此我们在MainWindow类中添加了几个私有变量:

    • QPoint startPos; //窗体初始位置
    • QPoint clickPos; //鼠标单击位置
    • bool isLeftPressDown; //判断是否左键按下

    重载窗体的事件函数,在Mainwindow中添加:

    protected:
    //三个鼠标事件 按下 移动 释放
        void mousePressEvent(QMouseEvent *event);
        void mouseMoveEvent(QMouseEvent *event);
        void mouseReleaseEvent(QMouseEvent *event);
    

    并实现它们:

    void MainWindow::mousePressEvent(QMouseEvent *event)        //Qt的鼠标时间函数重载,下面三个函数共同实现了一个功能,那就是点击窗口任意位置可以拖动窗口移动
    {
        if(event->button()&Qt::LeftButton){
            startPos = this->pos();
            clickPos = mapToGlobal(event->pos());
            isLeftPressDown = true;
            this->setFocus();
        }
    }
    
    void MainWindow::mouseMoveEvent(QMouseEvent *event)
    {
        if (this->isMaximized())
            return;
        if(isLeftPressDown)
            this->move(startPos+event->globalPos()-clickPos);
        event->accept();
    }
    
    void MainWindow::mouseReleaseEvent(QMouseEvent *event)
    {
        //鼠标释放事件
        //自身重绘
        repaint();
        //如果是左键
        if(event->button() == Qt::LeftButton) {
            //左键按下参数为空
            isLeftPressDown = false;
        }
    }
    

    鼠标事件中的参数有鼠标的坐标 event->pos(); 但是这个坐标是相对于这个事件函数所属窗体的。

    全局的鼠标坐标获取需要用 event->globalPos()

    为了得到显示器坐标,用mapToGlobal()进行转换。

    更加详细的建议按下F1 查看帮助文档进行进一步学习哦~

    你会看到你已经可以通过拖动窗体进行移动了,不需要系统原生标题栏了~

    快快愉快滴去掉它!

    在MainWindow构造函数中添加这样一行代码

    setWindowFlags( Qt::FramelessWindowHint|Qt::WindowSystemMenuHint| Qt::WindowMinimizeButtonHint);
    

    好这样就去掉了系统标题栏了~

    可惜标题栏还是丑成狗狗呀!

    小C:我不要丑丑的界面T_T

    杰洛君:没有美工技能滴骚年伤不起(虎摸

    不急,接下来我们来美化它~

    美化自定义标题栏

    真正丑的其实是那三个按钮。

    杰洛君看到很多软件用到滴按钮都是三态按钮:

    • 普通状态
    • 鼠标在上面悬停会有高亮(外发光之类的)
    • 鼠标按下会有凹陷(内发光之类的)

    比如老版迅雷看看:
    关闭1-- --普通状态
    关闭2-- --悬停状态
    关闭3-- --按下状态

    这个怎么做呢?

    重载按钮?有点小烦 -- -- T_T 今晚已经写很多代码了,不想写了啦!

    有没有简单点的方法

    有!

    就用QSS来实现一个简单的三态按钮吧~

    QSS是什么?
    文档有写到定义,这里就不介绍了
    看到这个名字QSS,相信会一点网络编程的同学都会想到CSS。
    没错Qt也可以用类似CSS的代码进行美化。

    举个例子:

        closeBut->setStyleSheet(
        "QPushButton{border-image: url(:/project/close1);}"
        "QPushButton:hover{border-image: url(:/project/close2);}"
        "QPushButton:pressed{border-image: url(:/project/close3);}"
        );
    

    这么写看上去有点奇怪,但是这几行字符串会合并为一条长字符串,这可是利用C++的特性呀~忘记的同学快去补补C++

    如果不了解CSS,可以跟着杰洛君看这几个点:

    第一行 类名{图片:url(图片路径)}

    第二行 类名:悬停{图片:url(图片路径)}

    第三行 类名:按下{图片:url(图片路径)}

    就是这样把按钮三个状态对应的不同图片给设置好了。

    接下来你需要做的就是 ,找资源图片,把按钮设置得和图片一样大(否则会拉伸很难看)利用QSS 实现三态,美化自己的标题栏~

    后续

    今天和大家一起实现了一个自定义标题栏,呼,有点小累~

    QSS是个利器,大家可以好好看看~

    后续会继续窗体的实现~

    今天就到这里吧~O(∩_∩)O哈哈~

    题外话:

    我才不会告诉你最近没来社区玩是因为去画妹子了,逃(/▽\=)

    0_1457622863851_pic7.jpg



  • @杰洛飞 我是来看妹纸的


  • 网站研运

    @杰洛飞 说:

    举一反三,Qt 中有一个QHBoxLayout 布局类 ,说到H 自然是horizon 与水平有关 (杰洛君才,才没有想歪呢,真,真的哦,我还只是个孩子,H什么滴真的不懂呀 (>\\<) )

    嘿嘿,看我发现了什么~~


  • 网站研运

    @杰洛飞 说:

    我才不会告诉你最近没来社区玩是因为去画妹子了,逃(/▽\=)

    妹子真好看哪。看来我也要努力地绘画咯。



  • @taadis 你也是非常的耿直呀(/▽\=)



  • @jcy 我不听,我不听,我什么都不知道!

    0_1457786237428_39.png                       (GJ)


  • 网站研运

    @杰洛飞 H其实是好(hen)孩(tai)子的意思。



  • @jcy 加油!!期待你的大作~0_1457786613392_08.png


  • 网站研运

    @杰洛飞 我的大作在这里:http://qtdream.com/topic/281



  • @jcy 这个解释我喜欢~0_1457786768201_43.png


  • 网站研运

    @杰洛飞 对了,你的那个截图
    alt text
    用了很多的字体好像,这是不是要求别人的计算机安装这些字体呢?



  • @jcy 字体装入资源文件就可以用了,不需要电脑装字体~


Log in to reply
 

走马观花

最近的回复

  • C

    Qt for MCU需要商业授权的

    read more
  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more

关注我们

微博
QQ群