第6 章 布局管理及多窗口技术

6.1 控件布局技术

        所谓GUI界面,归根结底,就是一堆可视化控件的叠加。创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,控件的位置尤为重要。我们必须指定控件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就设计控件定位的机制,Qt提供了两种控件定位机制:绝对定位和布局定位。
        绝对定位是一种最原始的定位方法:给出这个控件的坐标和长宽值。这样,Qt就知道该把控件放在哪里以及如何设置控件的大小。这样就带来一个问题是,如果用户改变了窗口大小,例如单击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的空间是不会有任何影响的。这也很自然,因为你并没有告诉Qt,在窗口变化时,窗体控件是否要更新自己以及如何更新。如果希望控件自动更新(就如同微软公司的Word在最大化时总会把稿纸区变大,把工具栏拉长),就要自己编写相应的函数来响应窗户口变化。当然还有更简单地方法:禁止用户改变窗口大小。
        针对这种控件自适应窗口变化的需求,Qt提供了另外一种“布局机制”来解决这个问题。只要把控件放入某一种布局,布局由专门的布局管理器管理。当需要调整空间大小或者位置时,Qt使用相应的布局管理期自动进行管理。
        Qt通过一些类实现布局管理,包括QHBoxLayout、QVBoxLayout、QGridLayout和QFormLayout。这些类型继承自QLayout,但QLayout并非继承自QWidget,而是直接派生于QObject。他们负责窗体中控件的布局管理。上述4个类的作用如下:
        1 QHBoxLayout,配置widget控件成横向一行
        2 QVBoxLayout,配置widget控件成垂直一列
        3 QGridLayout,配置widget控件按照平面网格排列。
        4 QFormLayout,配置widget控件用于表单布局。
设计界面时,一个布局类的使用过程一般如下
(1)创建各个控件
(2)定义一个布局类对象
(3)将控件加入布局类对象
(4)在某个窗体上设置该布局

6.1.1 水平布局

        QHBoxLayout用于水平布局
【例6-1】QHBoxLayout的使用
第1步,建立一个基于QWidget类的应用程序。创建时取消UI界面文件
第2步,修改main.cpp

#include "widget.h"
#include <QApplication>
#include<QSpinBox>
#include<QSlider>
#include<QLayout>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    //添加两个空间
    QSpinBox *spinBox=new QSpinBox(&w);//创建数字按钮
    QSlider*slider=new QSlider(Qt::Horizontal,&w);//创建滑动条
    spinBox->setValue(35);
    slider->setValue(50);
    //创建布局对象,将空间加入其中
    QHBoxLayout *layout=new QHBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(slider);
    w.setLayout(layout);//窗体设置layout
    w.show();
    return a.exec();

    return a.exec();
}

编译运行,得到下图所示的效果。可以看出,SpinBox和Slider两个空间是水平排列的。当窗体拉大时,Slider控件被拉长。控件的高度没有变,SpinBox的宽度也没有变。这是Qt智能拉伸控件的结果,系统认为滑动条应优先拉伸。
        如果界面中只有一个SpinBox,那么当窗口拉大时,SpinBox也会横向拉长。同理,如果将例6-1中的SpinBox换成Button,当窗体拉大时仍然只有Slider被拉宽。但是当水平布局中有若干个按钮时,当窗体拉大,每个按钮都会同时被拉长。

6.1.2 垂直布局

QVBoxLayout用于水平布局
【例6-2】QVBoxLayout的使用
第一步,建立一个基于QWidget类的应用程序。创建时取消UI界面文件
第二步,修改main.cpp
 

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

int main(int argc, char *argv[])
{
    QApplication a(argc,argv);
    Widget w;
    //创建两个控件
    QLineEdit *LEdit=new QLineEdit("a line text",&w);
    QTextEdit*REdit=new QTextEdit(&w);
    //将控件放入layout对象
    QVBoxLayout *layout=new QVBoxLayout;
    layout->addWidget(LEdit);
    layout->addWidget(REdit);
    w.setLayout(layout);
    w.show();
    return a.exec();
}

从上图可以看出,当窗体被拉大后,多行文本框被智能纵向拉长,并且两个编辑框都很像扩展填满了窗体。

6.1.3 网格布局

QGridLayout用于实现网格布局。网格是m行n列的样式,但是在通常的情况下,每个格子的尺寸是不同的,每个格子的大小受空间自身大小的影响,还可以人为设定行与行点的高度比或者列与列的宽度比。

【例6-3】QGridLayout的基本用法实例

第一步,建立一个基于QDialog的程序,取消UI文件的创建。
第二步,修改main.cpp:

#include "dialog.h"
#include <QApplication>
#include<QGridLayout>
#include<QLabel>
int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QString texts[]={"1","2","3","4","5","6","7","8","9"};
    QWidget*window=new QWidget;
    window->setWindowTitle("QGridLayout");
    window->resize(250,100);
    QGridLayout*gridLayout=new QGridLayout;
    gridLayout->setSpacing(2);//设置单元间隔
    gridLayout->setMargin(2);//设置队伍
    for(int i=0,k=0;i<3;i++,k+=3)
    {
        for(int j=0;j<3;j++)
        {
            QLabel *label=new QLabel(texts[k+j]);
            //设定label的显示方式,使其显示的更清楚
            label->setFrameStyle(QFrame::Panel+QFrame::Sunken);
            if(i<2)
                label->setMinimumSize(55,0);
            else
                label->setMinimumSize(55,50);
            label->setAlignment(Qt::AlignCenter);
            gridLayout->addWidget(label,i,j);//添加控件到网格
        }
    }
    //列宽比,第0列与第1列宽度只比为1:2
    gridLayout->setColumnStretch(0,1);
    gridLayout->setColumnStretch(1,2);
    window->setLayout(gridLayout);

    window->show();
    return app.exec();
    return app.exec();
}

编译运行如上图所示,由图可以看出,第2行的高度比较大,因为在程序中设置设置第2行的高度不小于50的结果,使用了如下语句label->setMinimumSize(50,50);当窗体拉大以后,由于每一行的高度都大于50,因此所有行的高度就均匀分布了

但是第0、1两列的宽度比一直是1:2,原因在于设定了宽度比例,语句为
gridLayout->setColumnStretch(0,1);//设定第0列为1
gridLayout->setColumnStretch(1,2);//设定第1列为2
类似的,还可以设定行与行的高度比,可使用下列函数
void QGridLayout::setRowStretch(int row,int stretch),该函数设定第row行的因子为stretch
网格布局中添加的控件在垂直或水平方向上可占据多个单元格,可使用下列函数添加控件:
void addWidget(QWidget*widget,int fromRow,int fromColumn,int rowSpan,int columSpan,Qt::Alignment alignment=0)
该函数将控件widget的左上角放在(fromRow,fromColumn),纵向占据rowSpan个单元,横向跨越columnSpan个单元,rowSpan和columSpan参数智能同时出现或同时忽略,若没有这两个参数,占据一个单元格。

【例6-4】用网格布局构造温度转换程序界面
假定要实现上图所示的界面(其中有7个空间。为了使网格布局实现这一界面,可以将界面划分成4行3列的网格,显然,第0行按钮占据1行3列。第1行左边的标签占据2列,右边的标签占一个单元,下面的摄氏温度标签以及调整温度的滑竿各站2行一列。液晶数字和转盘各占一个单元。于是可以按如下不走编写程序
第一步,建立一个基于QDialog的程序,取消UI文件的创建
第二步,修改dialog.cpp:
 

#include "dialog.h"
#include<QGridLayout>
#include<QLabel>
#include<QPushButton>
#include<QSlider>
#include<QLCDNumber>
#include<QDial>
Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    QPushButton *m_QuitButton=new QPushButton("Quit",this);
    QLabel*m_CenLabel=new QLabel("Centigrade",this);
    QLabel*m_FahLabel=new QLabel("Fahrenheit",this);
//    m_FahLabel->setAlignment(Qt::Alignment);
    QLabel*m_Label=new QLabel("0",this);
    QSlider*m_Slider=new QSlider(this);
    QLCDNumber*m_LCDNumber=new QLCDNumber(this);
    QDial*m_Dial=new QDial(this);
    QGridLayout*layout=new QGridLayout(this);
    layout->setSpacing(10);
    layout->setMargin(20);
    //Quit按钮,起始于(0,0),横跨3个单元格,即colSpan=3
    layout->addWidget(m_QuitButton,0,0,1,3);
    //Centigrade标签,起始于(1,0),横跨2个单元格,即colSpan=2
    layout->addWidget(m_CenLabel,1,0,1,2);
    //Fahrenheit标签,始于(1,2),占一个单元,不设rowSpan和colSpan
    layout->addWidget(m_FahLabel,1,2);
    //"0"标签,起始于(2,0),纵跨2个单元格,rowSpan=2
    layout->addWidget(m_Label,2,0,2,1);
    //滑竿,起始于(2,1),纵跨2个单元格,rowSpan=2
    layout->addWidget(m_Slider,2,1,2,1);
    //液晶数字,起始于(3,2),占用一个单元格
    layout->addWidget(m_LCDNumber,2,2);
    //转盘,起始于(3,2),占用一个单元格
    layout->addWidget(m_Dial,3,2);
    this->setLayout(layout);
}

Dialog::~Dialog()
{

}

6.1.4 表单布局

QFormLayout称为表单布局。这里的表单由两个列组成:第一格列用于显示信息,给与用户同时,一般称为label域;第二个是需要用户输入的,一般称为field域。表单就是由很多label域-field域两项(两列)内容组成的行的布局。label与field是关联的。
        表单布局完全可以使用网格布局实现,是一种多行多列的表格,但表单布局提供了比较完善的策略,其主要有如下优点
(1)自适应不同操作系统,有不同的外观
例如在MacOS X的Aqua截面和Linux的KDE界面中,这种两列结构中的标签应该是右对齐的,而在windows和GNOME界面中这种标签通常是左对齐的。
(2)支持自动换行
如果输入的filed域设定的比较长,则filed域换一行显示,还可以设定filed域总是换一行显示,当然默认的情形时一行显示两个域。
(3)函数接口简单,label和field空间可以直接一对对的插入,使用下面的函数可以在表单布局中一次插入两个控件,即标签和文本框:void addRow(QWidget*label,QWidget*field)
addRow函数在插入一个label和field后,自动将两者设置为伙伴(buddy)关系。而伙伴关系的好处就是,加入label的快捷键是ALT+W,按下快捷键是,输入焦点自动跳到label的伙伴上(即filed上),下面的语句将创建下面的界面

QLineEdit*name=new QLineEdit;
QLineEdit*email=new QLineEdit;
QLineEdit*address=new QLineEdit;
下面的函数将自动创建一个文字为labelText的标签,再将标签、文本框一起插入到表单布局中:
void addRow(const QString &labelText,QWidget*field)
QFormLayout*formLayout=new QFormLayout;
formLayout->addRow(tr("&Name"),name);
formLayout->addRow(tr("&Email"),email);
formLayout->addRow(tr("&Address"),address);
setLayout(formLayout);
这里的"&Name:"中的&符号指明了快捷方式,表明按下ALT+N键就跳转到Name后的编辑框
如果在上述FormLayout的实现代码中添加下列语句:
formLayout->setLabelAlignment(Qt::AlignRight);
则标签文字右对齐。如果在上述FormLayout的实现代码中添加如下语句:
formLayout->setRowWrapPolicy(QFormLayout::WrapAllRows);
如果使用QGridLayout,为了实现上述的截面,需要以下代码:
 

QLineEdit*name=new QLineEdit;
QLineEdit*emial=new QLineEdit;
QLineEdit*address=new QLineEdit;
QLabel*nameLabel=new QLabel(tr("&Name:"));
nameLabel->setBuddy(name);
QLabel*emailLabel=new QLabel(tr("&email:"));
emailLabel->setBuddy(emial);
QLabel*addLabel=new QLabel(tr("&Adress:"));
addLabel->setBuddy(address);
QGridLayout *gridLayout=new QGridLayout=new QGridLayout;
gridLayout->addWidget(nameLabel,0,1);
gridLayout->addWidget(name,0,1);
gridLayout->addWidget(emailLabel,1,0);
gridLayout->addWidget(name,1,1);
gridLayout->addWidget(emailLabel,2,0);
gridLayout->addWidget(name,2,1);
setLayout(gridLayout);

6.1.5 综合布局实例

在做界面设计的时候,就是利用上面几种布局管理器对部件进行组合和排列,同时水平布局、垂直布局和网格布局可以互相嵌套,从而构造比较复杂的截面。

【例6-5】用水平布局和垂直布局构造温度转换程序界面
这里用水平布局和垂直布局相互嵌套的方式实现例6-4中的温度转换器的界面。对于水平布局和垂直布局,都可以利用下列函数将一个将一个布局添加到另一个布局中:
void QBoxLayout::addLayout(QLayout *layout,int stetch=0)
这里参数layout就是被加入的布局。对于网格布局也有类似函数,本例没有使用网格布局,香炉奥杰具体细节可以参照Qt文档
        

考察上图所示截面,共有七个部件(1个PushButton,3个Label,1个Slider,1个LCDNumber和一个Dial),可用下面的方法构建程序界面布局。
        首先,将界面拆分成4个部分,如右图所示
        第一部分是第一行,只有一个PushButton,记作区域1
        第二部分是第二行,是两个水平排列的Label,可以用水平布局管理器将其放在一起,记作区域2.
        第三部分是下方深色区域水平排列的Label和Slider,用于显示和调整温度,也可用水平布局管理器将其放在一起,记作区域3.
        第四部分是垂直排列的LCDNumber和Dial,可以使用垂直布局管理器将其放在一起,记作区域4.
        此后,整个窗体的布局变成自上到下三部分:区域1、区域2、区域A,而且这3个部分是垂直排列的,所以可再用垂直布局管理器江浙3个大部件再次组合。
        至此,利用水平和垂直布局管理器不断嵌套组合的方法,就顺利完成了整个应用程序界面的布局。
        程序实现步骤如下:
第一步,建立一个基于QDialog的程序,取消UI文件的创建
第二步,修改dialog.cpp

#include "dialog.h"
#include<QHBoxLayout>
#include<QVBoxLayout>
#include<QLabel>
#include<QPushButton>
#include<QSlider>
#include<QLCDNumber>
#include<QDial>
Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
{
    QPushButton *m_QuitButton=new QPushButton("Quit",this);
    QLabel*m_CenLabel=new QLabel("Centigrade",this);
    QLabel*m_FahLabel=new QLabel("Fahrenheit",this);
    m_FahLabel->setAlignment(Qt::AlignCenter);
    QLabel*m_Label=new QLabel("0",this);
    QSlider*m_Slider=new QSlider(this);
    QLCDNumber*m_LCDNumber=new QLCDNumber(this);
    QDial *m_Dial=new QDial(this);
    //将两个Label放到水平布局管理器(区域2中)
    QHBoxLayout *layout2=new QHBoxLayout;
    layout2->addWidget(m_CenLabel);
    layout2->addWidget(m_FahLabel);
    //将Label和QSlider放到水平布局管理器(区域3)
    QHBoxLayout *layout3=new QHBoxLayout;
    layout3->addWidget(m_Label);
    layout3->addWidget(m_Slider);
    //将LCDNumber和Dial放到垂直布局管理器(区域4)
    QVBoxLayout *layout4=new QVBoxLayout;
    layout4->addWidget(m_LCDNumber);
    layout4->addWidget(m_Dial);
    //将区域3和区域4放到水平布局管理器(区域A)
    QHBoxLayout *layoutA=new QHBoxLayout;

    layoutA->addLayout(layout3);
    layoutA->addLayout(layout4);
    //将区域1,2,A放到主布局管理器
    QVBoxLayout*layout=new QVBoxLayout;
    layout->addWidget(m_QuitButton);

    layout->addLayout(layout2);
     layout->addLayout(layoutA);
    layout->setSpacing(10);
    layout->setMargin(20);
    this->setLayout(layout);
}

Dialog::~Dialog()
{

}


 

编译运行,程序界面如图所示,可以发现与前面的图很相似,但是任然稍有差别。从前面的讲解中可以知道,利用水平和垂直布局管理器也可以得到比较复杂的截面。但是当界面比较复杂是,需要利用比较多的布局其才能达到最终的效果。布局管理器相互嵌套,如同递归函数一样,作者增加了复杂性。
        与使用水平布局管理器和垂直布局管理器的组合方式相比,使用网格布局管理器只需要消耗一个布局管理器即可完成整个界面的布局。但是这种那个方式的最大缺点是需要实现精确设计好每个部件的位置和占用尺寸,再不见数量比较大的情况下,仅仅网格布局管理器也显得力不重心了。
        所以在做界面布局的时候,可以使用网格布局管理器做整体框架设计,然后在其中填充一些水平或垂直布局管理器或者他们的组合,以达到更好的效果。

6.2 窗口的切分与停靠

6.2.1 使用QSpliter实现分割窗口

        分割窗口在应用程序中经常用到,他可以灵活设计窗口布局,经常用语类似文件资源管理器的窗口设计中。QSpliter控件就是一个可以包含一些其他窗口部件的部件。在QSpliter中的这些窗口部件会通过切分条(Spliter handle)分隔开。用户可以通过拖动这些分隔条改变QSpliter控件子窗口的大小。QSpliter中的子窗口部件将会自动按照创建时的书顺序一个一个的放在一起,并且以窗口分隔条来分割相邻的窗口。

【例6-6】QSpliter使用示例之一
第一步,建立一个基于QMainWindow的程序,取消UI文件的创建
第2步,修改mainwindow.cpp中包含的头文件和构造函数

#include "mainwindow.h"
#include<QSplitter>
#include<QTextEdit>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //创建主分割窗口,设置为水平分割窗口(左右分割)
   QSplitter *mainSplitter=new QSplitter(Qt::Horizontal);
   //创建一个QTextEdit空间,设置其父控件为mainSplitter
   QTextEdit*leftEdit=new QTextEdit(QObject::tr("左窗口"),mainSplitter);
   //设置QTextEdit中文字的对其方式为居中显示
   leftEdit->setAlignment(Qt::AlignCenter);
   //创建右侧垂直分割(上下分割),设置其父控件为mainSpliter
   QSplitter*rightSplitter=new QSplitter(Qt::Vertical,mainSplitter);
   //设置拖动分隔条时,只显示灰线。拖动到位后在显示分隔条
   rightSplitter->setOpaqueResize(false);
   //设置右侧分割的上下两个窗口内容
   QTextEdit*upEdit=new QTextEdit(QObject::tr("上窗口"),rightSplitter);
   upEdit->setAlignment(Qt::AlignCenter);
   QTextEdit*bottomEdit=new QTextEdit(QObject::tr("下窗口"),rightSplitter);
   bottomEdit->setAlignment(Qt::AlignCenter);
   //设置右窗口为可伸缩控件
   mainSplitter->setStretchFactor(1,1);
   mainSplitter->setWindowTitle(QObject::tr("分割窗口"));
   //将主分割设为中央部件
   setCentralWidget(mainSplitter);
   mainSplitter->show();
}

MainWindow::~MainWindow()
{

}

分隔条可以拖动

在上个例子中,使用了QSplitter构造函数,原型如下:
QSplitter::QSplitter(Qt::Orientation orientation,QWidget*parent=0);
第一个参数通过Qt::Horizontal和Qt::Vertical来设定为水平分割和垂直分割。第二个参数是父窗口指针,0代表无父窗口。因此语句
QSplitter*rightSplitter=new QSplitter(Qt::Vertical,mainSplitter);
就设定了rightSplitter的父组件为mainSplitter,即右侧分割包含在主分割内部。
        函数setOpaqueResize的父组件为mainSplitter。即右侧分割包含在主分割内部。
        函数setOpaqueResize设置拖动时是否实时更新:
rightSplitter->setOpaqueResize(false);
就是在设定拖动分割时只显示一条灰色的线条,当把其中的参数改为true时,则分割将实时更新。
        函数setStretchFactor设置可伸缩控件,下面是程序中该语句的用法:
mainSplitter->setStretchFactor(1,1);
其中第一个参数指定控件序号,该空间序号将插入的先后顺序从0开始变好,第二个参数大于0表示此控件为伸缩空间。此例中设置右侧窗口为伸缩空间。当把窗口向右拉伸后,左边的宽度不变
        QSplitter每次向一个分割区添加一个控件,因此如果有多个控件布置在某个分区,可以将它们先放到一个Widget窗体中,然后将次窗体放入分区。

【例6-7】QSplitter使用示例之二

第一步,建立一个基于QDialog的程序,取消UI文件的创建
第二步,修改main.cpp
 

#include "dialog.h"
#include <QApplication>
#include<QSplitter>
#include<QTextEdit>
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    //创建3个编辑框
    QTextEdit*editor1=new QTextEdit;
    QTextEdit*editor2=new QTextEdit;
    QTextEdit*editor3=new QTextEdit;
    QSplitter splitter(Qt::Horizontal);//定义一个切分窗口
    splitter.addWidget(editor1);//将文本空间加入到切分狂
    splitter.addWidget(editor2);
    splitter.addWidget(editor3);
    editor1->setPlainText("One\nTwo\nThree");
    editor2->setPlainText("1\n2\n3");
    editor3->setPlainText("A\nB\nC");
    splitter.setWindowTitle(QObject::tr("Splitter"));



splitter.show();
return a.exec();

    return a.exec();
}

这个例子说明,QSplitter控件并不一定是将主窗口一分为二,而是根据加入的子窗口的数量自动分割,这里加入了3个空间,于是就产生了3个子窗口。

6.2.2 可停靠窗口QDockWidget

在许多程序中,有些窗口可以被拖动到另一个长体重,并合为一体,可以停留在主窗口的上下或左右两侧,还可以浮动在主窗口至上

【例6-8】停靠窗口示例

第一步,建立一个基于QMainWindow的应用,取消UI设计设计界面度选矿的选中状态
第二步,在源文件mainwindow.cpp中编写如下:
 

class MainWindow:public QMainWindow
{
    Q_OBJECT
    //添加组件、函数的定义
    QTextEdit *textEdit;
    QMenu*viewMenu;
    QToolBar*viewToolBar;
public:
    MainWindow(QWidget*parent=0);
    ~MainWindow();

}

第3步,修改mainwindow.cpp文件中的构造函数
 

MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
    //创建一个QTextEditor控件,作为主窗口
        textEdit=new QTextEdit;
        this->setCentralWidget(textEdit);
        viewMenu=menuBar()->addMenu(tr("视图"));//添加菜单
        viewToolBar=this->addToolBar(tr(" "));//添加工具条
        createDockwidget();//创建停靠窗体
        this->setWindowTitle(tr("停靠窗口"));
    
    }

第4步,修改mainwindow.cpp文件,添加createDockWidget函数的实现:

#include "mainwindow.h"
#include<QTextEdit>
#include<QMenuBar>
#include<QToolBar>
#include<QDockWidget>//包含停靠窗口
#include<QCalendarWidget>
    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent)
    {
    //创建一个QTextEditor控件,作为主窗口
        textEdit=new QTextEdit;
        this->setCentralWidget(textEdit);
        viewMenu=menuBar()->addMenu(tr("视图"));//添加菜单
        viewToolBar=this->addToolBar(tr(" "));//添加工具条
        createDockwidget();//创建停靠窗体
        this->setWindowTitle(tr("停靠窗口"));

    }

    void MainWindow::createDockwidget()
    {

        //设置主窗体的第一个QDockWidget
        QDockWidget*dock=new QDockWidget(this);
        //设置dock窗口的名称
        dock->setWindowTitle(tr("日期"));
        //设置dock的可停靠区域,全部可停靠
        dock->setAllowedAreas(Qt::AllDockWidgetAreas);
        //设置dock内的控件
        QCalendarWidget*calendar=new QCalendarWidget;
        //将日历控件设置为dock的主空间
        dock->setWidget(calendar);
        //向主窗体中添加dock的第一个参数,表示初始化
        //第二个参数是要添加的QDock控件
        this->addDockWidget(Qt::RightDockWidgetArea,dock);
        //向菜单和工具栏中添加显示和隐藏dock窗口的动作
        viewMenu->addAction(dock->toggleViewAction());
        viewToolBar->addAction(dock->toggleViewAction());
    }

编译运行如上,这里停靠窗口可以移动到左右上下各个位置,并且停靠窗口还可以脱离主窗口(处于浮动状态)
        停靠窗口QDockWidget的函数setAllowedAreas用于设定窗体的停靠区域,使得参数可以取以下的值
Qt::LeftDockWidgetArea,停靠在左侧
Qt::RightDockWidgetArea,停靠在右侧
Qt::TopDockWidgetArea,停靠在顶部
Qt::BottonDockWidgetArea,停靠在底部
Qt::AllDockWidgetAreas,可停靠在任何位置

6.3 多文档界面应用程序

使用Qt编写多文档界面(MDI)的应用主要会用到QMdiArea和QMidSubWindow两个类。

1 QMdiArea

这个类相当于一个MDI窗口管理器,用来管理添加到这个区域中的多个子窗口。在应用中新建的所有子窗口需要通过addSubWindow的中央部件,但是,也可以将它添加到任意的不居中。下面的代码就是将其添加到中央部件:
QMainWindow *mainWindow=new QMianWindow;
mainWindow->SetCentralWidget(mdiArea);

2 QMdiSubWindow

这个类继承自QWidget,主要用来创建MDI子窗体实例。然后,可以通过调用QMdiArea的addSubWindow方法将新建的子窗体实例添加到多文档界面区域。也可以不用QMdiSubWindow类来创建子窗体,而直接使用继承自QWidget的类,例如下面的代码
void MainWindow::actNewWindow()
{
QLabel*label=new QLabel;
m_mdiArea->addSubWindow(label);
}

不过,如果使用QMdiSubWindow类,就可以使用其提供的一些便捷的成员函数,下面给出一段实例代码
void MainWindow::actNewWindow()
{
QLabel*label=new QLabel;
QMdiSubWindow*subWin=new QMdiSubWindow;
subWin->setWidget(label);
subWin->setAttribute(Qt::WA_DeleteOnClose);
m_mdiArea->addSubWindow(suWin);
subWin->show();
}

这里设置的Qt::WA_DeleletOnClose的作用是,当关闭子窗口时,不是隐藏窗口,而是测地关闭窗口,回收其占用的资源。

【例6-9】多文档应用示例

第一步,使用Qt向导创建一个QMainWindow应用,并使用UI设计界面
第二步,在UI设计界面中添加文件菜单,并在其下添加一个New菜单栏,用来每次创建一个子窗体,并显示MDI区域
第三步,在mainwindow.h中添加头文件#include<QMdiArea>

同时在mainwindow.h中添加下面的变量
QMdiArea *m_mdiArea;

再添加槽函数的定义void actNewWindow();
第四步,修改mainwindow.cpp中的构造函数

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //创建MDI区域
    this->setCentralWidget(m_mdiArea);
    connect(ui->actionNew,SIGNAL(triggered()),this,SLOT(actNewWindow()));
}

第5步,在mainwindow.cpp中添加新建子窗体的槽函数
 

voidMainWindow:: actNewWindow()
    {

        //这里没格子窗体都是一个QLabel部件
        QLabel *label=new QLabel(tr("MDI SubWindow"));
        QMdiSubWindow*subWin=new QMdiSubWindow();
       subWin->setWidget(label);
       subWin->setAttribute(Qt::WA_DeleteOnClose);
       subWin->resize(180,100);
       m_mdiArea->addSubWindow(subWin);
       subWin->show();
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/136329.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JDK1.8 新特性(一)【默认方法、静态方法和Lambda表达式】

前言 今天学习Java8 新特性&#xff0c;主要是之前在学习 Scala、JavaFX 中遇到一些 Lambda 表达式&#xff0c;感觉 lambda 表达式确实很简洁&#xff0c;很有必要学一学。 目录 前言 1、接口的默认方法与静态方法 编写接口 编写接口的实现类 测试 2、Lambda表达式&am…

xsschallenge通关攻略详解

xsschallenge通过攻略 文章目录 xsschallenge通过攻略第一关第二关第三关第四关第五关第六关第七关第八关第九关第十关第十一关第十二关第十三关 简述 xsschallenge挑战攻略 ps: 终极测试代码 <sCr<ScRiPt>IPT>OonN"\/(hrHRefEF)</sCr</ScRiPt>IPT&g…

【Go入门】面向对象

【Go入门】面向对象 前面两章我们介绍了函数和struct&#xff0c;那你是否想过函数当作struct的字段一样来处理呢&#xff1f;今天我们就讲解一下函数的另一种形态&#xff0c;带有接收者的函数&#xff0c;我们称为method method 现在假设有这么一个场景&#xff0c;你定义…

2.4 - 网络协议 - TCP协议工作原理,报文格式,抓包实战,UDP报文,UDP检错原理

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 TCP协议 1、TCP协议工作原理2、TCP协议报文格式3、…

Accelerate 0.24.0文档 一:极速入门

文章目录 一、概述1.1 PyTorch DDP1.2 Accelerate 分布式训练简介1.2.1 实例化Accelerator类1.2.2 将所有训练相关 PyTorch 对象传递给 prepare()方法1.2.3 启用 accelerator.backward(loss) 1.3 Accelerate 分布式评估1.4 accelerate launch 二、Accelerate 进阶2.1 notebook_…

Home Assistant使用ios主题更换背景

Home Assistant使用ios主题、更换背景 lovelace-ios-dark-mode-theme 默认前置情况&#xff0c;1、已安转HACS插件2、搜索安装 IOS Dark Mode Theme1&#xff09;第一、二步应该很容易实现&#xff0c;configuration.yaml文件很容易被找到2&#xff09;而本人在进行第三步操作时…

时间序列预测实战(十四)Transformer模型实现长期预测并可视化结果(附代码+数据集+原理介绍)

论文地址->Transformer官方论文地址 官方代码地址->暂时还没有找到有官方的Transformer用于时间序列预测的代码地址 个人修改地址-> Transformer模型下载地址CSDN免费 一、本文介绍 这篇文章给大家带来是Transformer在时间序列预测上的应用&#xff0c;这种模型最…

【数据结构】拓扑序列求法

概念不多说了&#xff0c;有疑问的搜一下&#xff0c;这里直接放求法&#xff1a; 找到入度为0的节点输出并删除该节点&#xff0c;并删除与该点链接的边重复第一步 例子 输出a&#xff0c;删除a输出b&#xff0c;删除b输出c&#xff0c;删除c 最终结果为abcdef 注意 拓扑排…

【python海洋专题四十五】海洋研究区域示意图

【python海洋专题四十五】海洋研究区域示意图 【python海洋专题四十五】海洋研究区域示意图 结果展示&#xff1a; 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【python海洋专…

【蓝桥杯选拔赛真题66】Scratch画图机器人 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch画图机器人 一、题目要求 编程实现 二、案例分析 1、角色分析

OpenGL_Learn09(摄像机)

1. 摄像机环绕观察 texture两个文件以及shader就是之前的版本 #include <glad/glad.h> #include <GLFW/glfw3.h>#include <iostream> #include "stb_image.h" #include <cmath> #include "shader.h"#include <glm/glm.hpp>…

【见缝插针】射击类游戏-微信小程序项目开发流程详解

还记得小时候玩过的见缝插针游戏吗&#xff0c;比一比看谁插得针比较多&#xff0c;可有趣了&#xff0c;当然了&#xff0c;通过它可以训练自己的手速反应&#xff0c;以及射击水平&#xff0c;把握时机&#xff0c;得分越高就越有成就感&#xff0c;相信小朋友们会喜欢它的&a…

高效简洁的文档翻译网站

一款简单而强大的文档翻译网站 一款文字/文件翻译的网站,支持多个领域的翻译&#xff0c;支持常见的语言翻译(韩/日/法/英/俄/德…),最大百分比的保持原文排版(及个别除外基本100%还原)。 新用户注册就有100页的免费额度&#xff0c;每月系统还会随机赠送翻译额度&#xff0c;…

C#多线程入门概念及技巧

C#多线程入门概念及技巧 一、什么是线程1.1线程的概念1.2为什么要多线程1.3线程池1.4线程安全1.4.1同步机制1.4.2原子操作 1.5线程安全示例1.5.1示例一1.5.2示例二 1.6C#一些自带的方法实现并行1.6.1 Parallel——For、ForEach、Invoke1.6.1 PLINQ——AsParallel、AsSequential…

关于DataLoader是否shuffle在VOC2007语义分割数据集上引发的问题

问题描述&#xff1a; 在训练过程中&#xff0c;训练集和验证集实时得到的F1分数相差很大&#xff0c;如下图&#xff1a; 这个问题之前从未遇到过&#xff0c;后来经过不断的排查&#xff0c;发现是因为验证集的数据加载器中shuffle设置的为False&#xff0c;而训练集设置的为…

python工具网康下一代防火墙RCE

python漏洞利用​ 构造payload POST /directdata/direct/router HTTP/1.1{"action":"SSLVPN_Resource","method":"deleteImage","data":[{"data":["/var/www/html/d.txt;cat /etc/passwd >/var/www/htm…

450. 删除二叉搜索树中的节点

题目描述 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除节点可分为两个步骤&#x…

antlr4踩坑记录

一. syntax error: ‘<’ came as a complete surprise to me while matching alternative 参考这个issue&#xff0c;antlr版本必须得是4.6 下载链接&#xff1a;http://www.antlr.org/download/antlr-4.6-complete.jar 二.org.antlr.v4.analysis.LeftRecursiveRuleTrans…

Ubuntu诞生已经19年了

导读2004 年 10 月 20 日&#xff0c;Ubuntu 4.10 正式发布&#xff0c;代号‘Warty Warthog’。 2004 年 10 月 20 日&#xff0c;Ubuntu 4.10 正式发布&#xff0c;代号‘Warty Warthog’。 ▲ Ubuntu 4.10 与最新版 Ubuntu 23.10 的对比 作为 Ubuntu 第一个版本&#xff0…

什么是微服务自动化测试?

什么是微服务&#xff1f; 微服务 - 也称为微服务架构 - 是一种构建方式&#xff0c;它将应用程序构建为松散耦合服务的集合&#xff0c;具有完整的业务功能。微服务架构允许连续交付/部署大型复杂应用程序。本文将概述自动微服务测试工具和最佳实践。 它还使组织能够发展其技…
最新文章