0

Here is my problem, I display Buttons and Labels manually with setGeometry() method.

MyClass which inherits from QWidget

Here is my code :

void MyClass::function() {
QLabel *imgP;
  QLabel *name;
  QPushButton *newConv;
  QPixmap *profilPic;

  std::stringstream ss;
  int j = 0;
  int i = 0;
  for (int tmp = 0; tmp < 15; tmp++)
    {
      if (i % 7 == 0 && i != 0)
        {
          i = 0;
          j++;                                                                        
        }
      ss << "Username " << tmp;
      name = new QLabel(tr(ss.str().c_str()), this);
      name->setAlignment(Qt::AlignCenter);
      name->setGeometry((30 * (i + 1) + 240 * i), (30 + 390 * j),
                        240, 60);
      profilPic = new QPixmap("./gui/img/avatar1.png");
      imgP = new QLabel(this);
      imgP->setPixmap(profilPic->scaled(240, 240));
      imgP->setGeometry((30 * (i + 1) + 240 * i), (90 + 390 * j),
                        240, 240);
      newConv = new QPushButton(tr("Chat"), this);
      newConv->setGeometry((30 * (i + 1) + 240 * i), (330 + 390 * j),
                           240, 60);
      newConv->setFocusPolicy(Qt::NoFocus);
      connect(newConv, SIGNAL(released()), this, SLOT(addTab()));

      ss.str("");
      ss.clear();
      i++;
    }
}

It may be a bit more complicated than it should, but it works just the way I wanted ..

it looks like this :

The result

As you can see, the result is good, I have my contacts displayed, but the 15th element is hidden because the window is too small. So my question is :

How can I make a ScrollView when this happens ? I already know QScrollArea, but I would have to work with QBoxLayout, and I don't think this will do the job.

Thanks for your help !

EDIT

This is my MainWidget class which displays the window :

class QTabBar;

MainWidget::MainWidget(QWidget *parent) : QWidget(parent)
{                                                                                                                                                                           
  setFixedSize(1920, 1200);
  setWindowTitle(tr("Babel"));

  QVBoxLayout *mainLayout = new QVBoxLayout;
  QTabBar *tb;

  UiContact *contact = new UiContact(this);
  QScrollArea *contactScrollArea = new QScrollArea();
  contactScrollArea->setWidget(contact);

  _tabWidget = new QTabWidget;
  tb = _tabWidget->tabBar();

  _tabWidget->addTab(new Home(), tr("Home"));
  _tabWidget->addTab(contactScrollArea, tr("Contact"));

  std::ostringstream oss;

  _tabWidget->setTabsClosable(true);
  connect(_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));

  tb->tabButton(0, QTabBar::RightSide)->hide();
  tb->tabButton(1, QTabBar::RightSide)->hide();

  _tabWidget->setFocusPolicy(Qt::NoFocus);

  mainLayout->addWidget(_tabWidget);
  setLayout(mainLayout);
}
Nicolas Charvoz
  • 1,509
  • 15
  • 41
  • 1
    In this situation, I'd make a custom layout (maybe using the flow layout as a starting point: http://doc.qt.io/qt-4.8/qt-layouts-flowlayout-example.html). – Nicolas Holthaus Nov 04 '15 at 18:59
  • 1
    On a side note, you have a memory leak. `profilPic` is never deleted. You should create it on the stack so it will be destroyed automatically. – thuga Nov 05 '15 at 06:53

1 Answers1

1

QScrollArea is certainly the way to go. It is not necessary to use a layout, it's only necessary to force the widget to be the size it needs to be. A QScrollArea can handle any QWidget derived class, and it will display its scrollbars as needed.

Practically: if you can calculate how much space you need (which I think you can do), and set the size constraints of the containing widget accordingly, the QScrollArea will show the scrollbars automatically. You can use QWidget::setMinimumSize for that purpose, a simple setMinimumSize(itemWidth*7,itemHeight*(count%7)) should suffice.

This method does allow the widget to grow as large as to fill the QScrollArea. Also, it's possible to disable the frame around the QScrollArea if preferred.

edit:

You probably have something like this:

MyClass *myClass = new MyClass();

...

tabs->insertTab(1,myClass,"Contact");

An example implementation in that situation would be:

MyClass *myClass = new MyClass();

...

QScrollArea* contactScrollArea = new QScrollArea();
contactScrollArea->setWidget(myClass);
tabs->insertTab(1,contactScrollArea,"Contact");

It will place a scroll area inside the tab widget and put the instance of MyClass inside it

A QScrollArea is used as a relatively high container class. Think of a QScrollArea as a widget which can embed another widget inside it. By default it creates an empty widget, which can get a layout. But by using setWidget, you can literally place anything you want in it. A possible "combination" is a QLabel with a large licence text or any other widget that can potentially grow too large (like your widget).

Now, just some extra information:

When you draw stuff yourself (not creating several widgets and code your own layout stuff), you may use QAbstractScrollArea. It would give you full control. It gives scrollbars and a separate middle widget to which you can draw during paintEvent. But I think that's beyond the scope of this.

Now, for sake of clean coding. I would actually suggest you create a separate class ContactItem. It would consist of that label, image and pushbutton, held together with a QVBoxLayout (which makes them neatly packed above eachother). This "item" can be put inside a QGridLayout. This way, you don't need to concern yourself with arranging the items. If you set a minimum size on the picture, it will make sure the items are your preferred width/height. The label size constraints make sure that font differences don't affect the presentation (same goes for the buttons). Last but not least, your list is suddenly resizable. (By using setRowStretch and setColumnStretch you can make sure your list is centered, or top aligned, in case the window is larger than your list takes up.

Functional interpretation

Here I have a possible implementation (it isn't my best code) from what I got from the screenshot and given code.

Preview of interface

It consists of a 'Widget' which is responsible for the tab layout:

mainwidget.h

#include <QWidget>
#include "uicontact.h"

class QTabWidget;

class MainWidget : public QWidget
{
  Q_OBJECT

  QTabWidget *_tabWidget;

public:
  MainWidget(QWidget *parent = 0);
  ~MainWidget();

public slots:
  void addChatTab();
};

mainwidget.cpp

#include "mainwidget.h"

#include <QScrollArea>

#include <QTabWidget>
#include <QTabBar>
#include <QVBoxLayout>

#include "home.h"

MainWidget::MainWidget(QWidget *parent)
  : QWidget(parent)
{
  setFixedSize(1920,1200);
  setWindowTitle(tr("Babel"));

  QVBoxLayout *mainLayout = new QVBoxLayout;
  QTabBar *tb;

  UiContact *contact = new UiContact(this);
  QScrollArea *contactScrollArea = new QScrollArea();
  contactScrollArea->setWidget(contact);

  _tabWidget = new QTabWidget;
  tb = _tabWidget->tabBar();

  _tabWidget->addTab(new Home(), tr("Home"));
  _tabWidget->addTab(contactScrollArea, tr("Contact"));

  _tabWidget->setTabsClosable(true);
  connect(_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));

  tb->tabButton(0, QTabBar::RightSide)->hide();
  tb->tabButton(1, QTabBar::RightSide)->hide();

  _tabWidget->setFocusPolicy(Qt::NoFocus);

  mainLayout->addWidget(_tabWidget);
  setLayout(mainLayout);
}

MainWidget::~MainWidget()
{

}

void MainWidget::addChatTab()
{
  _tabWidget->addTab(new QWidget, tr("Number %1").arg(_tabWidget->count()-2));
}

The class MyClass is responsible for creating the 'contact' tab:

uicontact.cpp

#include "uicontact.h"

#include "mainwidget.h"

#include <QLabel>
#include <QPushButton>

UiContact::UiContact(MainWidget *owner,QWidget *parent) : QWidget(parent)
{
  QLabel *imgP;
  QLabel *name;
  QPushButton *newConv;
  QPixmap *profilPic;

  int j = 0;
  int i = 0;
  for (int tmp = 0; tmp < 15; tmp++)
    {
      if (i % 7 == 0 && i != 0)
        {
          i = 0;
          j++;
        }

      name = new QLabel(tr("Username %1").arg(tmp), this);
      name->setAlignment(Qt::AlignCenter);
      name->setGeometry((30 * (i + 1) + 240 * i), (30 + 390 * j),
                        240, 60);
      profilPic = new QPixmap("./gui/img/avatar1.png");
      imgP = new QLabel(this);
      imgP->setPixmap(profilPic->scaled(240, 240));
      imgP->setGeometry((30 * (i + 1) + 240 * i), (90 + 390 * j),
                        240, 240);
      newConv = new QPushButton(tr("Chat"), this);
      newConv->setGeometry((30 * (i + 1) + 240 * i), (330 + 390 * j),
                           240, 60);
      newConv->setFocusPolicy(Qt::NoFocus);
      connect(newConv, SIGNAL(clicked(bool)), owner, SLOT(addChatTab()));

      i++;
    }

  setMinimumSize(270*7,420*(15/7+1));
}

The uicontact.h file is very trivial so omitted.

A few things to note: MyClass receives an 'owner' pointer, this allows it to talk directly to the top level widget responsible for adding tabs. Probably you want to look at QSignalMapper to be able to map the individual QPushButtons to a more known value. With QSignalMapper, you can map the button to an integer, string, QWidget* or QObject*.

Also note the tr("Contact %1").arg(tmp) which is the correct way to make your program locale aware.

Daniël Sonck
  • 869
  • 7
  • 9
  • Can you be more precise ? If I understand right, I need to set the minimum size of my current widget (which is my class) and add it to QScrollArea, so I would do : `QScrollArea->setWidget(this);` ? – Nicolas Charvoz Nov 05 '15 at 08:25
  • I've updated the answer to include an example usage and provide some alternatives. – Daniël Sonck Nov 05 '15 at 14:28
  • Wow your answer seems great, I'm at work now, but I will try this after work and give you a feedback ! – Nicolas Charvoz Nov 05 '15 at 14:31
  • It doesn't seem to work .. Maybe I'm missing something – Nicolas Charvoz Nov 05 '15 at 20:04
  • 1
    I will check the code later myself and create a full implementation so you can check what's missing and to demonstrate some Qt ways – Daniël Sonck Nov 06 '15 at 17:10
  • Trying on my own too :) Thanks for your help ! – Nicolas Charvoz Nov 06 '15 at 21:53
  • I've updated the post to exactly create the thing you are trying to achieve ;-) – Daniël Sonck Nov 07 '15 at 00:24
  • Your code seems to work properly but my class Widget can't be derivated from QTabWidget since it's my MainWidget .. See my edit – Nicolas Charvoz Nov 07 '15 at 09:27
  • Alright, I've reflected the new details in the answer. If you only need a tabwidget as main window, consider subclassing it. Any widget can be a main widget. But if you intend to add more widgets to the layout, yes, you may want to do it like this. If you wish to see an implementation using QGridLayout, I can provide an alternative. – Daniël Sonck Nov 07 '15 at 13:05
  • Wooow .. I was missing a line .. setMinimumSize ! Thank you so much for helping me ! YOu"re a beast ! – Nicolas Charvoz Nov 07 '15 at 13:33