Project

General

Profile

Actions

Bug #3602

closed

Overlapping text when using nested layout managers

Added by Pierluigi Vicinanza about 10 years ago. Updated about 10 years ago.

Status:
Closed
Priority:
High
Assignee:
Target version:
Start date:
09/23/2014
Due date:
% Done:

0%

Estimated time:

Description

I'm trying to create a "log page" with a title and a scrolling area displaying several messages (test code and screenshot attached);

the scrolling area should resize to fit the rest of browser window.

Every time I shrink my web browser window, text lines in scrolling area overlap with each other.

I would expect labels to keep their size and scroll bars to adjust accordingly.

Not completely sure this is a bug. Am I missing something obvious?

Tested using latest Wt sources and default wt_config.xml, under Google Chrome v37.

Relevant code is in test_app.cpp:

/*------------------------------------------------------------------------------
  LogPage
------------------------------------------------------------------------------*/

class LogPage : public Wt::WContainerWidget
{
public:
    LogPage(Wt::WContainerWidget * parent = NULL)
        : WContainerWidget(parent)
    {
        WVBoxLayout * textLayout = new WVBoxLayout();

        WContainerWidget * textWidget = new WContainerWidget();
        textWidget->setLayout(textLayout);
        textWidget->setOverflow(WContainerWidget::OverflowAuto);

        WVBoxLayout * mainLayout = new WVBoxLayout();
        mainLayout->addWidget(textWidget);

        setLayout(mainLayout);

        for (int i = 0; i < 50; ++i)
        {
            WLabel * line = new WLabel("[22 Sep 2014 16:16:07 GMT Daylight Time]"
                                       "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
                                       "Pellentesque porta placerat massa, vitae laoreet justo vulputate nec.");
            textLayout->addWidget(line);
        }
    }
};

/*------------------------------------------------------------------------------
  MainWidget
------------------------------------------------------------------------------*/

class MainWidget : public WVBoxLayout
{
public:
    MainWidget::MainWidget(WWidget * parent = NULL)
        : WVBoxLayout(parent)
    {
        WLabel * title = new WLabel("Log");
        addWidget(title, 0, AlignCenter);

        LogPage * logPage = new LogPage();
        addWidget(logPage, 1);
    }
};

/*------------------------------------------------------------------------------
  TestApplication
------------------------------------------------------------------------------*/

void TestApplication::initialize()
{
    MainWidget * mainWidget = new MainWidget();
    root()->setLayout(mainWidget);
}

Files

wt_test.zip (28 KB) wt_test.zip Pierluigi Vicinanza, 09/23/2014 03:48 PM
Actions #1

Updated by Wim Dumon about 10 years ago

  • Status changed from New to Resolved

The typical way to do this is:

<code class="cpp">
class LogPage: public Wt::WContainerWidget
{
public:
  LogPage(Wt::WContainerWidget * parent = NULL)
    : WContainerWidget(parent)
  {
    WContainerWidget * textWidget = new WContainerWidget();
    textWidget->setOverflow(WContainerWidget::OverflowAuto);

    WVBoxLayout * mainLayout = new WVBoxLayout();
    mainLayout->addWidget(textWidget);

    setLayout(mainLayout);

    for (int i = 0; i < 50; ++i)
    {
      WText * line = new WText(WString("{1} [22 Sep 2014 16:16:07 GMT Daylight Time]"
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
        "Pellentesque porta placerat massa, vitae laoreet justo vulputate nec.").arg(i),
        textWidget);
      line->setInline(false);
    }
  }
};
</code>

WText is the more expected widget than WLabel, but WLabel should work as well. The lines are turned in non-inline (thus block) elements, which causes them to be displayed below each other. Through mainLayout, textWidget takes the height of its parent container. It will display scroll bars when its contents do not fit inside that area. In textWidget we put all the content lines, one below the other one, so textwidget will grow. When the lines don't fit in the area assigned by the layout manager, scroll bars will appear. For the horizontal spacing, I believe you want to add line->setWordWrap(false);, which will keep the log line on one line.

Note that the code still contains one unnecessary level of layout managers, it can further be simplified to:

<code class="cpp">
class LogPage: public Wt::WContainerWidget
{
public:
  LogPage(Wt::WContainerWidget * parent = NULL)
    : WContainerWidget(parent)
  {
    setOverflow(WContainerWidget::OverflowAuto);
    for (int i = 0; i < 50; ++i)
    {
      WText * line = new WText(WString("{1} [22 Sep 2014 16:16:07 GMT Daylight Time]"
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
        "Pellentesque porta placerat massa, vitae laoreet justo vulputate nec.").arg(i),
        this);
      line->setInline(false);
    }
  }
};
</code>

The problem with the original approach is that the nesting of WVBoxLayouts causes the height of the labels to be adjusted so that they fill the available vertical space. If the space is small, they are squeezed beyond the size of the font, until their minimum size, which was not set. This is explained in the documentation of WBoxLayout:

The space is divided so that each widget is given its preferred size, and remaining space is divided according to stretch factors among widgets. If not all widgets can be given their preferred size (there is not enough room), then widgets are given a smaller size (down to their minimum size). If necessary, the container (or parent layout) of this layout is resized to meet minimum size requirements.

The preferred width or height of a widget is based on its natural size, where it presents its contents without overflowing. WWidget::resize() or (CSS width, height properties) can be used to adjust the preferred size of a widget.

The minimum width or height of a widget is based on the minimum dimensions of the widget or the nested layout. The default minimum height or width for a widget is 0. It can be specified using WWidget::setMinimumSize() or using CSS min-width or min-height properties.

Actions #2

Updated by Pierluigi Vicinanza about 10 years ago

WText is the more expected widget than WLabel

I tend to use WLabel for the sake of better code readability (so that I don't have to specify "line->setWordWrap(false)" and "line->setFormat(Wt::PlainText)" every time I add some text). Could you please elaborate more on why WText is the preferred way? I've tested your code using a WLabel as well and it seems to work as good as with WText.

>The problem with the original approach is that the nesting of WVBoxLayouts causes the height of the labels to be adjusted [...]

Hmmm...I understand now. I think I got confused between preferred and minimum size. I was expecting WLabels to be resized up to "preferred size" regardless (as it normally happens with Qt/QLabel/desktop UIs, but I understand web UIs are a different beast...).

The reason why I was using a nested layout was:

  1. I could use WBoxLayout::itemAt(...), WBoxLayout::insertWidget(...) methods to perform widget manipulations based on index number
  2. I thought it was a better practice to insert every widget in a layout
  3. code readability (a developer sees exactly what layout I'm trying to achieve, instead of having to infer it from parent-child relationships)

So, to recap, whenever I want to enforce a vertical layout AND I want the area to be resized to fit the preferred size, I'll have to avoid a (vertical) layout manager and rely on the fact that widgets are implicitly layed-out at the end when added to a WContainerWidget. Is that correct?

Say I'd like to address the same problem but with an horizontal layout (e.g. adding several text fields on a line and displaying horizontal scollbars), how should I proceed? Shall I use a WHBoxLayout in that case? Should I rely on the fact that by default WText is inline?

Thank you very much for your help!

Actions #3

Updated by Wim Dumon about 10 years ago

With 'expected', I mean that when I see WLabel, I expect it to be used in combination with a budy widget (radiobutton, checkbox). The fact that you use it for storing text is not problematic, and has indeed no influence on the layout issue.

Wrt (1): The same functionality is available in a WContainerWidget: insertWidget, widget, count, indexOf, ...

Wrt (2): In general we recommend to use the web way for layouting when possible, and layouts when necessary.

Wrt (3): It's a habit? ;-)

So, to recap, whenever I want to enforce a vertical layout AND I want the area to be resized to fit the preferred size, I'll have to avoid a (vertical) layout manager and rely on the fact that widgets are implicitly layed-out at the end when added to a WContainerWidget. Is that correct?

You can do this with a layout manager, if the parent widget is not in a layout manager. If the parent widget is inside a layout or has a size configured, the layout manager will adjust the contents of the container to the size of the container, and not the other way around like you seem to suggest. If the parent widget has no size set (so no resize(), and not within a layout manager, and no CSS sizes), the layout manager will give its parent a size based on the preferred sizes of its children. I adapted the documentation of WBoxLayout to make this more explicit.

Say I'd like to address the same problem but with an horizontal layout (e.g. adding several text fields on a line and displaying horizontal scollbars), how should I proceed? Shall I use a WHBoxLayout in that case? Should I rely on the fact that by default WText is inline?

I see two possibilities: either use a WHBoxLayout in a widget with no defined width (attention: CSS will assign a width to a WContainerWidget by default), or place inline widgets one next to the other and set nowrap on the parent WContainerWidget.

Wim.

Actions #4

Updated by Pierluigi Vicinanza about 10 years ago

In general we recommend to use the web way for layouting when possible, and layouts when necessary

Good to know that!

I've done some further experimentations based on your suggestions; your example/notes proved to be extremely helpful: I've now managed to fix nearly all my layout issues. I'll try to iron last few issues out via layout redesign + CSS.

Thanks a lot Wim!

Pierluigi

Actions #5

Updated by Koen Deforche about 10 years ago

  • Assignee set to Wim Dumon
  • Target version set to 3.3.4
Actions #6

Updated by Koen Deforche about 10 years ago

  • Status changed from Resolved to Closed
Actions

Also available in: Atom PDF