首页 Qt 学习之路 2 Qt 学习之路 2(11):布局管理器

Qt 学习之路 2(11):布局管理器

85 7.7K

所谓 GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件定位的机制。Qt 提供了两种组件定位机制:绝对定位和布局定位。

顾名思义,绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值。这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。这也很自然,因为你并没有告诉 Qt,在窗口变化时,组件是否要更新自己以及如何更新。如果你需要让组件自动更新——这是很常见的需求,比如在最大化时,Word 总会把稿纸区放大,把工具栏拉长——就要自己编写相应的函数来响应这些变化。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。

针对这种变化的需求,Qt 提供了另外的一种机制——布局——来解决这个问题。你只要把组件放入某一种布局,布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候,Qt 使用对应的布局管理器进行调整。下面来看一个例子:

// !!! Qt 5

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget window;
    window.setWindowTitle("Enter your age");

    QSpinBox *spinBox = new QSpinBox(&window);
    QSlider *slider = new QSlider(Qt::Horizontal, &window);
    spinBox->setRange(0, 130);
    slider->setRange(0, 130);

    QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);
    void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);
    spinBox->setValue(35);

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(slider);
    window.setLayout(layout);

    window.show();

    return app.exec();
}

这段例子还是有些东西值得解释的。我们可以先来看看运行结果:

Qt Layout 原始大小示例

当我们拖动窗口时,可以看到组件自动有了变化:

Qt Layout 调整大小示例

我们在这段代码中引入了两个新的组件:QSpinBoxQSliderQSpinBox就是只能输入数字的输入框,并且带有上下箭头的步进按钮。QSlider则是带有滑块的滑竿。我们可以从上面的截图中清楚地辨别出这两个组件。当我们创建了这两个组件的实例之后,我们使用setRange()函数设置其范围。既然我们的窗口标题是“Enter your age(输入你的年龄)”,那么把 range(范围)设置为 0 到 130 应该足够了。

有趣的部分在下面的connect()函数。我们已经清楚connect()函数的使用,因此我们写出

QObject::connect(slider, &QSlider::valueChanged, spinBox, &QSpinBox::setValue);

将 slider 的valueChanged()信号同 spinBox 的setValue()函数相连。这是我们熟悉的。但是,当我们直接写

QObject::connect(spinBox, &QSpinBox::valueChanged, slider, &QSlider::setValue);

的时候,编译器却会报错:

no matching function for call to 'QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))'

这是怎么回事呢?从出错信息可以看出,编译器认为QSpinBox::valueChanged是一个 overloaded 的函数。我们看一下QSpinBox的文档发现,QSpinBox的确有两个信号:

  • void valueChanged(int)
  • void valueChanged(const QString &)

当我们使用&QSpinBox::valueChanged取函数指针时,编译器不知道应该取哪一个函数(记住前面我们介绍过的,经过 moc 预处理后,signal 也是一个普通的函数。)的地址,因此报错。解决的方法很简单,编译器不是不能确定哪一个函数吗?那么我们就显式指定一个函数。方法就是,我们创建一个函数指针,这个函数指针参数指定为 int:

void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;

然后我们将这个函数指针作为 signal,与 QSlider 的函数连接:

QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);

这样便避免了编译错误。

仔细观察这两个connect()的作用,它们实际完成了一个双向的数据绑定。当然,对于 Qt 自己的信号函数,我们可以比较放心地使用。但是,如果是我们自己的信号,应当注意避免发生无限循环!

下面的代码,我们创建了一个QHBoxLayout对象。显然,这就是一个布局管理器。然后将这两个组件都添加到这个布局管理器,并且把该布局管理器设置为窗口的布局管理器。这些代码看起来都是顺理成章的,应该很容易明白。并且,布局管理器很聪明地做出了正确的行为:保持QSpinBox宽度不变,自动拉伸QSlider的宽度。

Qt 提供了几种布局管理器供我们选择:

  • QHBoxLayout:按照水平方向从左到右布局;
  • QVBoxLayout:按照竖直方向从上到下布局;
  • QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;
  • QFormLayout:按照表格布局,每一行前面是一段文本,文本后面跟随一个组件(通常是输入框),类似 HTML 的 form;
  • QStackedLayout:层叠的布局,允许我们将几个组件按照 Z 轴方向堆叠,可以形成向导那种一页一页的效果。

当然,我们也可以使用 Qt 4 来编译上面的代码,不过,正如大家应该想到的一样,我们必须把connect()函数修改一下:

// !!! Qt 4

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QWidget window;
    window.setWindowTitle("Enter your age");

    QSpinBox *spinBox = new QSpinBox(&window);
    QSlider *slider = new QSlider(Qt::Horizontal, &window);
    spinBox->setRange(0, 130);
    slider->setRange(0, 130);

    QObject::connect(slider,  SIGNAL(valueChanged(int)),
                     spinBox, SLOT(setValue(int)));
    QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                     slider,  SLOT(setValue(int)));
    spinBox->setValue(35);

    QHBoxLayout *layout = new QHBoxLayout;
    layout->addWidget(spinBox);
    layout->addWidget(slider);
    window.setLayout(layout);

    window.show();

    return app.exec();
}

这里我们强调一下,上面的代码在 Qt 5 中同样可以编译通过。不过,我们减少了使用函数指针指定信号的步骤。也就是说,在 Qt 5 中,如果你想使用 overloaded 的 signal,有两种方式可供选择:

  1. 使用 Qt 4 的SIGNALSLOT宏,因为这两个宏已经指定了参数信息,所以不存在这个问题;
  2. 使用函数指针显式指定使用哪一个信号。

有时候,使用 Qt 4 的语法更简洁。但是需要注意的是,Qt 4 的语法是没有编译期错误检查的。这也是同 Qt 5 的信号槽新语法不同之处之一。

85 评论

josephpei 2012年9月4日 - 18:01

老大,代码里的 ">","&"什么时候才能改过来

回复
DevBean 2012年9月4日 - 21:56

不好意思,wp-syntax 升级之后出现的问题,现在已经修改过了。

回复
lonewolf 2012年9月28日 - 11:46

QT4的代码里第二个connect搞反了,应该是spinBox触发valueChanged(int)时调用slider的setValue(int)

回复
DevBean 2012年9月28日 - 12:29

谢谢指出!已经改正过来

回复
smile 2013年1月10日 - 14:34

真好,通俗易懂。

回复
提问 2013年2月17日 - 03:11

豆子哥,我又碰到问题了,这个程序运行没问题,但关闭时就崩溃了。

int main(int argc,char *argv[])
{
QApplication App(argc,argv);
QWidget Window;
Window.resize(350,160);
Window.setWindowTitle("登陆界面");

QLabel usr("账号: ",&Window);
QLabel pwd("密码: ",&Window);
QLineEdit usrL(&Window);
QLineEdit pwdL(&Window);
QPushButton log("登陆",&Window);
QPushButton cls("清除",&Window);

// --------------------
QHBoxLayout HLyt;
HLyt.addWidget(&usr);
HLyt.addWidget(&usrL);
HLyt.addWidget(&log);

QHBoxLayout HLyt1;
HLyt1.addWidget(&pwd);
HLyt1.addWidget(&pwdL);
HLyt1.addWidget(&cls);

QVBoxLayout VLyt;
VLyt.addItem(&HLyt);
VLyt.addItem(&HLyt1);

Window.setLayout(&VLyt);
// --------------------

Window.show();
return App.exec();
}

这个程序运行正常,但我点x关闭程序时,程序就崩溃了,这里无法发图片,不然可以截图给你看。

回复
提问 2013年2月17日 - 03:18

还有就是,我用的Qt5,支持直接中文,所以不是中文什么的问题。
我试过把中文全换成英文,效果还是一样的,程序一关闭就崩溃了。

回复
提问 2013年2月17日 - 03:50

我了个去,找到解决办法了:

int main(int argc,char *argv[])
{
QApplication App(argc,argv);
QWidget *Window=new QWidget;
Window->resize(350,160);
Window->setWindowTitle("登陆界面");

QLabel usr("账号: ",Window);
QLabel pwd("密码: ",Window);
QLineEdit usrL(Window);
QLineEdit pwdL(Window);
QPushButton log("登陆",Window);
QPushButton cls("清除",Window);

QHBoxLayout HLyt;
HLyt.addWidget(&usr);
HLyt.addWidget(&usrL);
HLyt.addWidget(&log);

QHBoxLayout HLyt1;
HLyt1.addWidget(&pwd);
HLyt1.addWidget(&pwdL);
HLyt1.addWidget(&cls);

QVBoxLayout *VLyt=new QVBoxLayout;
VLyt->addItem(&HLyt);
VLyt->addItem(&HLyt1);

Window->setLayout(VLyt);

Window->show();
return App.exec();
delete VLyt;
delete Window;
}

貌似是栈空间不足的原因?我一般若是没写类的话,就不用new的,还要delete。
不知道Qt中是否需要用delete呢?似乎都没看见你们用过,我用了也没出现什么问题。

回复
豆子 2013年2月18日 - 10:18

编译你的上段代码,错误是 Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse),也就是编译器在试图 delete 一个栈上分配的对象。由于你的所有的组件都是栈上分配的,所以在析构时会有错误,这个与栈空间之类没有关系。

Qt 中有 new 的肯定要 delete,只不过这个 delete 有时候不需要你自己去写。比如,如果组件的 parent 属性不为空,在析构 parent 时,子组件会自动被析构。此时如果你再 delete 子组件就会发生错误。至于你的例子,鉴于一般程序都会在单独的类中添加组件,一般不会有这个问题。我们的这种写法仅适合于简单的演示。所以,如果你的程序已经足够复杂(就像你的代码),最好放到单独的类中来避免这一系列问题。

回复
提问 2013年2月18日 - 20:17

嗯,非常感谢。不过不明白,所有组件在栈上分配的,貌似没问题吧?难道一定要用new?
这个例子因为还没什么功能,所以我就写在一个cpp文件里面了,也没使用类,所以就没用new,懒得delete。
不过我写类的时候,的确没出现过这种问题。一般写类都是继承于某个对象QWidget、QObject、QDialog之类的,若是继承的类的构造函数里面为空的话,还是需要自己delete的吧?
最开始我还不知道,他们写继承的构造函数里面都写上QWidget *parent是做什么的,现在明白了,谢谢豆子哥!

豆子 2013年2月20日 - 15:14

的确应该没有什么问题。我也没有遇到过,怀疑是因为层次结构过于复杂,Qt 在有 parent 的地方调用 delete 了,所以出错。如果 parent 为空的话,也是要自己 delete 的。

hailong 2013年5月9日 - 13:02

栈上变量为毛还要delete呢,肯定要crush

豆子 2013年5月9日 - 15:35

栈上变量肯定不能 delete。这里给出的代码也没有 delete,但是 Qt 内部调用了 delete 所以出现了问题。

Kiwee 2013年4月16日 - 12:54

。。。。。楼主,我又来了。。。这儿你用的还是在堆上创建对象,是不是这种也不是推荐的写法啊楼主。。。

回复
豆子 2013年4月16日 - 17:11

虽然里面的 QSpinBox 是在堆上分配的,但是最外层的 window 却是在栈上。注意 QSpinBox 的 parent 参数都设置为 window,也就是说,当 window 析构时,其子元素也会被析构,因此不存在什么问题。

回复
piaodonkey 2015年4月30日 - 09:10

请问豆子哥那照这样说的话是不是要改成这样
//QHBoxLayout *layout = new QHBoxLayout;
QHBoxLayout *layout = new QHBoxLayout(&windows);
还是window.setLayout(layout);已经把parent 参数传递进来了

回复
Kiwee 2013年4月16日 - 14:07

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

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.setWindowTitle("Test Demo");
QSpinBox spinbox;
spinbox.setRange(0,130);
QSlider slider;
slider.setRange(1,130);

QObject::connect(&slider, &QSlider::valueChanged, &spinbox, &QSpinBox::setValue);
void (QSpinBox:: *spinboxsignal)(int)=&QSpinBox::valueChanged;
QObject::connect(&spinbox,spinboxsignal,&slider,&QSlider::setValue);
spinbox.setValue(35);

QHBoxLayout layout;
layout.addWidget(&spinbox);
layout.addWidget(&slider);
w.setLayout(&layout);
w.show();

return a.exec();
}
楼主,我把你的代码改成都在栈上创建对象,这个是运行不出结果的,我后创建的QHBoxLayout,spinbox跟slider都是后添加进去的,QHBoxLayout先被析构的时候会不会对spinbox跟slider造成影响。。。麻烦您了,求解答。是不是推荐的写法就是创建这些组件都放在单独的类里面。

回复
Kiwee 2013年4月16日 - 14:28

我查了一下,是不是因为我在栈里面分配了大量局部变量造成的啊,楼主。。。。

回复
Kiwee 2013年4月16日 - 16:52

楼主。。。要不您建个群吧。。。我有好多小问题想咨询一下。。。。

回复
豆子 2013年4月16日 - 17:13

已经有群的,就在页面上方的关于页面中

回复
在河之洲 2013年9月7日 - 18:38

我觉得Kiwee的这个问题 在于两点 第一 spinbox和slider在创建的时候 没有定父对象为window
第二 window的类型应该是 widget;我修改了一下发现是可以运行的;

 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    w.setWindowTitle("test demo");
    QSpinBox spinbox(&w);
    spinbox.setRange(0,130);
    QSlider slider(Qt::Horizontal,&w);
    slider.setRange(1,130);

    QObject::connect(&slider, &QSlider::valueChanged, &spinbox, &QSpinBox::setValue);
    void (QSpinBox:: *spinboxsignal)(int)=&QSpinBox::valueChanged;
    QObject::connect(&spinbox,spinboxsignal,&slider,&QSlider::setValue);
    spinbox.setValue(35);

    QHBoxLayout layout;
    layout.addWidget(&spinbox);
    layout.addWidget(&slider);
    w.setLayout(&layout);
    w.show();

    return a.exec();
}
回复
tttttbbb 2013年6月10日 - 21:01

楼主,我用qt5.02,ubuntu32位。
用楼主的代码,启动软件后,东西全部挤叠在在一起了。
还有一个警告:QWidget::setLayout: Attempting to set QLayout "" on MainWindow "", which already has a layout
请问是怎么回事啊?是我环镜没配置好吗?

回复
tang 2014年3月23日 - 17:16

注意代码中是QWidget window;
你大概是用成了QMainWindow window;吧?
具体的原因在下面的评论中博主有解释。

回复
tom 2013年8月12日 - 00:03

LZ,我用KDE 4.8桌面,安装QTC装了QT5,但是我在项目构建管理的QT Versions里只有4.8,这是怎么回事?

回复
豆子 2013年8月12日 - 14:44

不知道你是怎样安装的 Qt5,如果是使用安装文件,应该在 QtCreator 的配置中选择 Qt5 的 qmake 配置一下就可以了。

回复
tomisacat 2013年8月12日 - 09:49

LZ,我把Qwidget换成QMainwindow,然后spinbox和slider就重叠了,如果我想用QMainwindow的话,需要修改什么呢?谢谢!

回复
豆子 2013年8月12日 - 14:51

QMainWindow 有自己的布局管理器,所以一般是把你的布局放在一个 QWidget 上,然后将这个 QWidget 设置为 centralWidget

回复
在河之洲 2013年9月7日 - 19:15

博主 我想知道 为什么 把
QWidget window;
window.setWindowTitle("Enter your age");
的window的类型改成 QMainWindow之后添加上去的两个部件重叠在一起了 QMainWindow不是QWidget的派生类么?

回复
豆子 2013年9月9日 - 09:18

QMainWindow 的确是继承自 QWidget,但是它同时添加了自己的 layout,正因为有 layout 的存在,所以你需要使用 setCentralWidget() 函数把一个 QWidget 设置为中心的组件,而不是把一个个的子组件用自己的 layout 添加进去(因为 QMainWindow 已经有了默认的 layout,你自己的 layout 才会不起作用)。

回复
tang 2014年3月23日 - 17:06

真受用!

回复
鞠家兴 2016年8月28日 - 18:27

赞赞赞!解答很详细!

回复
zshyou@163.com 2013年11月18日 - 23:04

建议博主在前面加上头文件,不然想我等新手又捉鸡了。

回复
豆子 2013年11月18日 - 23:18

入门学习的时候直接添加 #include <QtGui> (Qt5 的话还需要 #include <QtWidgets>)就可以了。在 cpp 文件中引入大的头文件问题不是很大,实际开发中也未尝不可。

回复
zshyou@163.com 2013年11月18日 - 23:21

嗯,问题是你不说我也不知道啊。

回复
lauthor 2013年12月13日 - 09:56

很有用,初学中,感谢啊!

回复
welliam 2014年3月28日 - 10:57

请问一个使用 heightForWidth的问题。我继承一个QWidget,现在想让这个widget在缩放时保持宽高比例。我的相关代码如下
*************************widget**********************
#include "myitemwidget.h"
#include

MyItemWidget::MyItemWidget(QWidget *parent)
:QWidget(parent)
{
m_labShow = new MyShowLabel(this);
m_edtArtist = new QLineEdit(this);
m_edtName = new QLineEdit(this);

QSizePolicy p(sizePolicy());
p.setHeightForWidth(true);
this->setSizePolicy(p);

init();
setMyLayout();
}

int MyItemWidget::heightForWidth(int w) const
{
return w;
}

QSize MyItemWidget::sizeHint() const
{
return QSize(100,120);
}
void MyItemWidget::init()
{
m_edtArtist->setDisabled(true);
m_edtName->setDisabled(true);
}

void MyItemWidget::setMyLayout()
{
m_edtArtist->setGeometry(0, 100, 50, 10);
m_edtName->setGeometry(0,110,50,10);

m_labShow->setGeometry(0, 0, 100, 100);

QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(m_labShow);
layout->addWidget(m_edtArtist);
layout->addWidget(m_edtName);

this->setLayout(layout);

this->resize(100, 120);
}
***********************main****************************
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyItemWidget w;
w.show();
return a.exec();
}
但是没有效果。
谷歌到几个 http://stackoverflow.com/questions/452333/how-to-maintain-widgets-aspect-ratio-in-qt/1160476#1160476 、http://stackoverflow.com/questions/19076544/qwidgetheightforwidth-and-qmainwindow 、http://stackoverflow.com/questions/17708449/qwidgetheightforwidth-is-not-called 。能看明白,但是我自己代码上怎么修改就是没效果。

回复
豆子 2014年3月29日 - 22:43

这几种方法也没有测试通过,目前还没有解决方法。

回复
朦朦胧胧 2014年4月8日 - 22:45

你好,请问如果不使用QT的布局管理器,若在qwidget里面安插十来个控件,并重叠放置时,如何管理这些控件的先后呈现(绘图)顺序,像bringToFront之类的功能。类似如gvf那样有个setZValue(int)函数。

回复
呵呵 2014年4月11日 - 07:33

这个评论正在审核两天了……再在这问下吧……请教下博主,有什么办法能让每次新建工程时,都在pro里自动添加QMAKE__CXXFLAGS = -std=c++11吗,要改模板么……能说下具体怎么做么,多谢了!

回复
呵呵 2014年4月11日 - 07:34

是 += 少打了+

回复
豆子 2014年4月11日 - 10:17

不好意思,最近有点忙。你的问题需要自己修改 QtCreator 的源代码并重新编译才可以解决。因为 Qt Widgets Application 这些类模板是直接写在源代码里面的。更合理的方法是,自己创建一个自己的项目模板,也就是自己写一个 QtCreator 的插件。可以参考 http://qt-project.org/doc/qtcreator-3.0/creator-project-wizards.html 的说明。

回复
silenceper 2014年5月22日 - 19:28

按照之前的对象树的概念,为什么不是传入new QHBoxLayout(&window)呢?而是使用空参数的折构方法呢?

回复
豆子 2014年5月23日 - 15:06

这是因为调用 setLayout() 函数的时候,如果这个 layout 不属于调用 setLayout() 函数的组件,则会自动设置为该组件。因此,你可以在 new 的时候就添加 parent 指针参数,也可以通过 setLayout() 设置。需要注意的是,如果构造函数添加了 parent 指针,后来又调用了 setLayout() 函数,如果二者不相同,则 setLayout() 函数设置的 parent 指针会覆盖构造函数传入的。

回复
silenceper 2014年5月23日 - 18:32

回复邮件怎么收不到? 你回复了我也不知道。

回复
fxbszj 2014年7月18日 - 16:15

window,spinBox,slider这几个,什么时候该用指针,什么时候不该用呢?还有layout。。。

回复
豆子 2014年7月20日 - 16:04

所有 QObject 的子类,因为 Qt 可以利用 parent 指针进行半自动化管理,因此建议使用指针。因为由于QObject 的拷贝构造函数等都是私有的,只能以指针的形式保存到容器中,因而指针更为方便。容器之类一般都是使用栈。不过这一点并不是绝对的,还是具体情况具体分析最好。

回复
xzp21st 2014年7月21日 - 13:42

请问豆子:我在layout之后,如何才能让窗口居中呢?
我的程序是点个按钮然后隐藏的部分窗体控件才弹出,然后重新layout,但是layout之后整个窗口变大,就不居中了 , 我在layout后直接move,发现窗口本身的width和height还是窗口控件隐藏前的大小,所以没法用move来使窗口居中,不知道该怎么办了,望回复

回复
good 2014年9月1日 - 23:11

怎么把这篇和添加动作那篇结合到一起,我把spinbox和slider放到mainwindow里用布局管理器好像没有作用

回复
豆子 2014年9月2日 - 09:37

因为QMainWindow自己有一个 layout,所以你需要将这些组件放在一个带有 layout 的QWidget上,然后把这个QWidget设置为 code>QMainWindow的中心组件。

回复
AceXIE 2014年12月27日 - 22:54

是这个样子的么?

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

setWindowTitle(tr("Main Window"));
openAction = new QAction(QIcon(":/images/doc-open"),tr("&Open"),this);
openAction->setShortcuts(QKeySequence::Open);//这个QAction的快捷键
openAction->setStatusTip(tr("Open an exiting file"));//当用户鼠标滑过这个 action 时,会在主窗口下方的状态栏显示相应的提示
connect(openAction,&QAction::triggered,this,&MainWindow::open);//将这个QAction的triggered()信号与MainWindow类的open()函数连接起来

QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);//向菜单栏添加了一个 File 菜单,并且把这个QAction对象添加到这个菜单
QToolBar *toolBar=addToolBar(tr("&File"));
toolBar->addAction(openAction);

statusBar();

QWidget *window=new QWidget(this);
QSpinBox *spinBox = new QSpinBox(window);
QSlider *slider=new QSlider(Qt::Horizontal,window);
spinBox->setRange(1,130);
slider->setRange(0,130);
QObject::connect(slider,&QSlider::valueChanged,spinBox,&QSpinBox::setValue);
void (QSpinBox::*spinBoxSignal)(int)=&QSpinBox::valueChanged;//创建一个函数指针,这个函数指针参数指定为 int
QObject::connect(spinBox,spinBoxSignal,slider,&QSlider::setValue);
spinBox->setValue(35);

QHBoxLayout *layout=new QHBoxLayout(this);
layout->addWidget(spinBox);
layout->addWidget(slider);
window->setLayout(layout);
setCentralWidget(window);
// w->setWidget(window);
}

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

void MainWindow::open()
{
QMessageBox::information(this,tr("Information"),tr("Open"));
}

回复
mseyj 2015年1月18日 - 13:05

楼主,可不可以控件之类的都在UI里面放,然后在cpp里用代码写布局管理器管理所有控件的布局,或者直接在UI里用布局管理器也行,不过这样总感觉好难用。一般看到的例子都是一些比较简单的布局,楼主应该再来一篇难度加大一点的提升一下的。谢谢楼主!

回复
豆子 2015年1月21日 - 14:51

如果你的控件在 UI 中,在 CPP 使用代码写布局,与全部使用 CPP 编写界面就没有区别了。复杂的布局就是一层层 QWidget 嵌套完成的

回复
yuanhui wang 2015年2月28日 - 05:31

纯外行,这段代码前面是不是要加#include 之类的

回复
豆子 2015年3月9日 - 14:12

是的,C++ 源代码需要 include 头文件

回复
梦觉 2015年3月1日 - 17:05

void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;
QObject::connect(spinBox, spinBoxSignal, slider, &QSlider::setValue);
这个代码段可以用lambda吧
QObject::connect(spinBox, [](int i){QSpinBox::valueChanged(i); } slider, &QSlider::setValue);

回复
梦觉 2015年3月1日 - 17:06

只是无聊,错误之处莫见怪

回复
豆子 2015年3月9日 - 14:27

signal 部分貌似不能使用 lambda 表达式

回复
三石 2015年5月28日 - 12:20

QHBoxLayout对象也是new出来的,并且没有父对象,这种情况还需要delete吗?我自己的代码里在类里面定义了QHBoxLayout*成员对象,在类的构造函数里new出来,没有指定父对象,需要在类的析构函数中delete,并置空吗?

回复
豆子 2015年6月3日 - 09:42

layout 这类对象,在调用 QWidget::setLayout() 函数时,会将这个 layout 的 parent 自动设置为该 widget(查阅文档,有 reparent 一段描述)。因此,如果你的 parent 能够一直追溯到一个可管理的组件(也就是能够被正确 delete 的对象),就不需要自己 delete 这个 layout,否则应该自己管理。

回复
三石 2015年6月3日 - 09:46

多谢解答,豁然开朗。

Depicture 2015年7月15日 - 22:45

请问一下...
如果在类的构造函数中用new声明对象这个对象的父类是否就是这个类

回复
豆子 2015年7月20日 - 09:01

对象的实际类型是由实际创建的对象决定的,因此使用 new 运算符创建一个父对象,其实际类型就是父对象。

回复
王月 2016年4月1日 - 22:23

关于这个:
仔细观察这两个connect()的作用,它们实际完成了一个双向的数据绑定。当然,对于 Qt 自己的信号函数,我们可以比较放心地使用。但是,如果是我们自己的信号,应当注意避免发生无限循环!
很好奇是内部怎么做到避免无限循环的。我们自己的信号,出现无限循环,怎么处理。有可能出现a->b,b->c,c->a这种绑定。

回复
learn 2016年4月17日 - 10:03

我照这个代码复制进去,头文件添加了,运行后,拖动滑竿,可以拖动,但窗口大小不会发生变化

回复
ben 2016年7月24日 - 21:41

void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;

这个函数指针如果用以下2句代码替换:
void (*spinBoxSignal)(int);
spinBoxSignal = &QSpinBox::valueChanged;
运行时就会指示:
error: no matches converting function 'valueChanged' to type 'void (*)(int)'spinBoxSignal = &QSpinBox::valueChanged;

为什么会这样呢?valueChanged的原型不就是 void valueChanged(int)吗?

回复
豆子 2016年7月24日 - 23:01

成员函数指针需要类实例去调用,因此成员函数指针和全局函数指针是不兼容的。你修改之后的代码:void (*spinBoxSignal)(int); 定义了一个全局函数指针,是不能用成员函数 QSpinBox::valueChanged 进行赋值的,这也就是为什么编译器会报无法转换的错误。

回复
ben 2016年7月25日 - 17:28

明白了,感谢。

回复
leavan 2016年8月23日 - 20:53

写得太好了,深入浅出,已收藏并推荐给同学!

回复
jswolf 2017年5月11日 - 11:01

博主,在window.show(); 前面再加一个 window.setLayout(layout); 布局就完整。不然在QT5.4.2中显示输入框和滑块就重叠再一起了

回复
wx 2018年1月2日 - 20:57

QObject::connect(slider, SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int)));

回复
xmc 2018年6月13日 - 16:58

谢谢博主

回复
cc 2019年6月3日 - 13:25

豆神,我用的qt createor 4.9.0 是基于qt 5.12.2的,提示 unknown type name QHBoxLayout选项。为啥呀~

回复
assange0 2019年9月14日 - 10:12

豆子,博客的图片这两天好像挂了

回复
豆子 2019年9月19日 - 15:29

图片托管在又拍云上面,之前对接有点问题,现在应该可以了

回复
Huang Zhen 2019年11月10日 - 12:12

我有两个建议:
一、关于QSpinBox的valueChanged这个信号函数官方文档有范例,可以使用 QOverload::of(QSpinBox::valueChanged) 做信号,可避免创建函数指针,代码也比较直观。我另外创建了一个QLabel调用另一个重载QOverload::of(&QSpinBox::valueChanged),可以正常显示。
二、关于两个connect可能无限循环的表述,不要说一句Qt官方的可以信任就完了,实际上这两个connect不会无限循环是因为setValue的前后值不一致才会触发valueChanged,这样读者就知道如何避免了。举例说明:当前spinBox的值是2,我将它改成3,那么会触发valueChanged将调用slider的setValue,slider的setValue函数发现之前的值是2,就会改为3,然后触发valueChanged调用spinBox的setValue,这时spinBox的setValue发现之前的值是3,要设置的值也是3,它就什么都不做了,也不会触发valueChanged,循环就停止了。

回复
Huang Zhen 2019年11月10日 - 12:18

呃,Qverload后面的中括号类型被截掉了,我试试用Html能不能显示:
QOverload&ltint&gt::of(&ampQSpinbox::valueChanged)
QOverload&ltconst QString &amp&gt::of(&ampQSpinbox::valueChanged)

回复
豆子 2019年11月14日 - 09:05

感谢您的建议。关于第一个问题,当时写这篇文章时,还没有 QOverload 这个类,看了一下文档,这个类好像是 Qt 5.7 才加入的,使用这个类的确会方便许多。关于第二个问题,的确按照您的建议比较合适一些。感谢提醒!

回复
Amoy 2020年2月14日 - 10:14

豆子哥,你好!关于这一句void (QSpinBox:: *spinBoxSignal)(int) = &QSpinBox::valueChanged;的前因后果你已在文中详述了,我也已知晓,但是就语法层面来讲我对这一句不是很理解,这是新建一个什么变量?然后赋值?再强制转吗?麻烦你给详细指点一下,谢谢!

回复
bingo 2020年7月1日 - 17:43

豆子哥,QSpinBox的void valueChanged(const QString &)信号从5.14之后废弃了(https://doc.qt.io/qt-5/qspinbox-obsolete.html),改成了textChanged(QString)

回复
yf_nian 2021年1月24日 - 20:31

豆神,运行后显示的界面,觉得很漂亮,是安装了什么插件嘛?

回复
豆子 2021年1月26日 - 09:54

截图是 KDE 桌面环境,不是 Windows 的,大概是说这个?

回复
Roy Huang 2021年2月7日 - 14:05

很赞的教程。
疑似文中overloaded应为"overridden"?

回复
豆子 2021年2月23日 - 17:27

overload 是同名不同参,override 是同名同参,所以这里应该是 overload

回复
Nephren 2021年6月19日 - 11:59

深入浅出,很受用。

回复
Nic 2021年8月4日 - 17:13

```
void textChanged(const QString &text)
void valueChanged(int i)
```
不知道是不是QT更新后信号名变了,QSpinBox的两个信号是上面这两个。

回复

回复 豆子 取消回复

关于我

devbean

devbean

豆子,生于山东,定居南京。毕业于山东大学软件工程专业。软件工程师,主要关注于 Qt、Angular 等界面技术。

主题 Salodad 由 PenciDesign 提供 | 静态文件存储由又拍云存储提供 | 苏ICP备13027999号-2