我们在编写Qt应用是,经常会遇到需要获取某个窗口或控件下面的所有子控件,但是有时发现获取到的子控件总是不对,今天抽空对该函数进行分析一下:
这是创建的一个QApplication应用,UI文件未做任何修改。
接下来往centralwidget中放一些控件,看看是否能正确获取到子控件:
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame(ui->centralwidget);
- QFrame *frame_2 = new QFrame(ui->centralwidget);
- centerLayout->setObjectName("centerLayout");
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
- }
这是正常的写法,我们在创建时都为frame_1和frame_2都设置了父对象,然后再分别添加到layout中去,此时的结果:
可以看到children能正确访问到,并且按照设置父对象的顺序。注意:该函数会返回控件的布局,因为布局的父对象也是该控件,一般情况下,我们是不需要获取到布局的。而且这里的顺序并不是添加到layout中的顺序,接下来我们加以验证:
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame();
- QFrame *frame_2 = new QFrame();
- centerLayout->setObjectName("centerLayout");
- frame_2->setParent(ui->centralwidget);
- frame_1->setParent(ui->centralwidget);
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
- }
结果如下:
这样写的话从children中获取的顺序就和实际显示顺序不一致,因为我们是先给fram_2设置的父对象。
接下来我们再变化一下,将frame_1的父对象设置为frame_2,这样centralwidget还能通过children获取到吗?
结果如下:
可以看到结果与之前的方案并没有差别,这是因为,尽管我们设置了frame_1的parent为frame_2,但是当我们把frame_1添加到centralwidget的布局中时,会重新设置frame_1的父对象为centralwidget。相关逻辑Qt源码中可以查看:
进一步验证,我们在最后再重新设置frame_1的父窗口为frame_2,看看会发生什么:
结果:
可以看到此时frame_1是显示在frame_2中,因为它的父窗口是frame_2,并且此时centerwidget通过children访问不到frame_1。
那如果我们想要按照子控件在布局中的位置顺序获取子控件该怎么办呢?此时可以通过layout的属性去获取:
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame();
- QFrame *frame_2 = new QFrame();
- centerLayout->setObjectName("centerLayout");
- frame_2->setParent(ui->centralwidget);
- frame_1->setParent(ui->centralwidget);
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
-
- for (int i = 0; i < centerLayout->count(); ++i) {
- QLayoutItem *item = centerLayout->itemAt(i);
- QWidget *w = item->widget();
- qDebug()<<"centerLayout->item:"<
objectName(); - }
-
- }
结果如下:
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame();
- QFrame *frame_2 = new QFrame();
- centerLayout->setObjectName("centerLayout");
- frame_2->setParent(ui->centralwidget);
- frame_1->setParent(ui->centralwidget);
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- QFrame *frame_1_1 = new QFrame(frame_1);
- QFrame *frame_1_2 = new QFrame(frame_1);
- QVBoxLayout *frame_1_layout = new QVBoxLayout(frame_1);
- frame_1_1->setObjectName("frame_1_1");
- frame_1_2->setObjectName("frame_1_2");
- frame_1_layout->setObjectName("frame_1_layout");
- frame_1_1->setStyleSheet("background-color:blue;");
- frame_1_2->setStyleSheet("background-color:white;");
- frame_1_layout->addWidget(frame_1_1);
- frame_1_layout->addWidget(frame_1_2);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
-
- for (int i = 0; i < centerLayout->count(); ++i) {
- QLayoutItem *item = centerLayout->itemAt(i);
- QWidget *w = item->widget();
- qDebug()<<"centerLayout->item:"<
objectName(); - }
-
- }
结果如下:
可以看出上面的方法都不能访问到子控件中的子控件。
如果我们需要递归获取所有的子控件,可以使用findChildren:
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame();
- QFrame *frame_2 = new QFrame();
- centerLayout->setObjectName("centerLayout");
- frame_2->setParent(ui->centralwidget);
- frame_1->setParent(ui->centralwidget);
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- QFrame *frame_1_1 = new QFrame(frame_1);
- QFrame *frame_1_2 = new QFrame(frame_1);
- QVBoxLayout *frame_1_layout = new QVBoxLayout(frame_1);
- frame_1_1->setObjectName("frame_1_1");
- frame_1_2->setObjectName("frame_1_2");
- frame_1_layout->setObjectName("frame_1_layout");
- frame_1_1->setStyleSheet("background-color:blue;");
- frame_1_2->setStyleSheet("background-color:white;");
- frame_1_layout->addWidget(frame_1_1);
- frame_1_layout->addWidget(frame_1_2);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
-
- for (int i = 0; i < centerLayout->count(); ++i) {
- QLayoutItem *item = centerLayout->itemAt(i);
- QWidget *w = item->widget();
- qDebug()<<"centerLayout->item:"<
objectName(); - }
-
- for (QObject *o : ui->centralwidget->findChildren
()) { - qDebug()<<"ui->centralwidget->findChildren
:" <objectName(); - }
-
- }
结果如下:
注意findChildren获取的顺序是设置父窗口的顺序,并不是实际布局中的显示顺序。
如果必须要按照顺序获取,那只能我们自己实现一个递归算法:
- void showAllChildren(QWidget *widget, bool firstIn = false)
- {
- if (!firstIn) {
- qDebug()<<"*********children:"<
objectName(); - }
-
- if (widget->layout()) {
- for (int i = 0; i < widget->layout()->count(); ++i) {
- QLayoutItem *item = widget->layout()->itemAt(i);
- QWidget *w = item->widget();
- showAllChildren(w, false);
- }
- }
- }
- MainWindow::MainWindow(QWidget *parent)
- : QMainWindow(parent)
- , ui(new Ui::MainWindow)
- {
- ui->setupUi(this);
- ui->centralwidget->setObjectName("centralwidget");
- QVBoxLayout *centerLayout = new QVBoxLayout(ui->centralwidget);
- QFrame *frame_1 = new QFrame();
- QFrame *frame_2 = new QFrame();
- centerLayout->setObjectName("centerLayout");
- frame_2->setParent(ui->centralwidget);
- frame_1->setParent(ui->centralwidget);
- frame_1->setObjectName("frame_1");
- frame_2->setObjectName("frame_2");
- frame_1->setStyleSheet("background-color:red;");
- frame_2->setStyleSheet("background-color:yellow;");
- centerLayout->addWidget(frame_1);
- centerLayout->addWidget(frame_2);
-
- QFrame *frame_1_1 = new QFrame(frame_1);
- QFrame *frame_1_2 = new QFrame(frame_1);
- QPushButton * button_1_3 = new QPushButton(frame_1);
- QVBoxLayout *frame_1_layout = new QVBoxLayout(frame_1);
- frame_1_1->setObjectName("frame_1_1");
- frame_1_2->setObjectName("frame_1_2");
- button_1_3->setObjectName("button_1_3");
- frame_1_layout->setObjectName("frame_1_layout");
- frame_1_1->setStyleSheet("background-color:blue;");
- frame_1_2->setStyleSheet("background-color:white;");
-
- frame_1_layout->addWidget(frame_1_1);
- frame_1_layout->addWidget(frame_1_2);
- frame_1_layout->addWidget(button_1_3);
-
- for (QObject *o : ui->centralwidget->children()) {
- qDebug()<<"ui->centralwidget->children:"<
objectName(); - }
-
- for (int i = 0; i < centerLayout->count(); ++i) {
- QLayoutItem *item = centerLayout->itemAt(i);
- QWidget *w = item->widget();
- qDebug()<<"centerLayout->item:"<
objectName(); - }
-
- for (QObject *o : ui->centralwidget->findChildren
()) { - qDebug()<<"ui->centralwidget->findChildren
:" <objectName(); - }
-
- showAllChildren(ui->centralwidget, true);
-
- }
结果如下:
如果需要过滤掉带layout的子控件(本身作为容器去加载其它控件的控件),则递归函数改为:
- void showAllChildren(QWidget *widget)
- {
- if (widget->layout()) {
- for (int i = 0; i < widget->layout()->count(); ++i) {
- QLayoutItem *item = widget->layout()->itemAt(i);
- QWidget *w = item->widget();
- showAllChildren(w);
- }
- } else {
- qDebug()<<"*********children:"<
objectName(); - }
- }
结果如下:
frame_1作为其它控件的容器,这里过滤掉。该函数在实际项目中可根据需要添加条件进行筛选需要的控件。