QLayout and discrete widget representation - how to? - c++

Lets, for example, we have QHBoxLayout inside QMainWindow.
Lets we have set of the widgets inside above layout.
But (!) few of widgets have discrete visual representations.
In another words they have few states dependent on available space.
For example:
if there are too much available space - it must look like big image + some text
if there available minimal space - it must look like little image
if there available few more than minimal space - it must look like button + label
etc...
So when the user change the main window size our "dynamic" widgets must show own representation dependent on available space.
How it could be achieved in Qt?
UPD: the closest behavior present in the Microsoft ribbon interface
UPD2: the QML closest behavior present in gif below (on part where window resized by user)
UPD3: more complex example - each panel in the menu panel change content elements view and count that depends from available space

There's no magic solution for this. You'll need to implement events/actions to take when the container is resized. It can be as simple or complex as you need. There are several different ways to go about actually implementing it, depending on complexity and scope involved (it is just a few items? the whole UI? etc...).
Here is a very basic (crude but effective) example which moves a toolbar to either be on the same line as another toolbar (for wider window) or to its own line (for narrower window). MdiChild in this case is a QWidget implementation which, obviously, contains other widgets and manages their layout.
void MdiChild::resizeEvent(QResizeEvent * event)
{
QWidget::resizeEvent(event);
if (size().width() > ui->topToolbarLayout->sizeHint().width() + ui->botToolbarLayout->sizeHint().width() + 30) {
ui->botToolbarLayout->removeWidget(modelsToolbar);
ui->topToolbarLayout->insertWidget(1, modelsToolbar);
}
else {
ui->topToolbarLayout->removeWidget(modelsToolbar);
ui->botToolbarLayout->insertWidget(0, modelsToolbar);
}
}
As you can see it has to take the container size into account, and the most direct way to do that is in the QWidget::resizeEvent(). You could do anything here you want... change text buttons to icons, hide/show/move stuff... whatever.
Instead of handling the visual changes in the container, you could of course hook up signals/slots to the contained widgets... eg. a custom signal emit availableSizeChanged(QSize size) from the resizeEvent() handler connect()ed to the contained widget(s).
You may find QStateMachine handy to maintain visual state in a formal manner (this is what QML uses for it's state[s] property and transition system).
You could also implement your own QLayout as others have suggested. Though the scope of the question itself suggests you may not need to. The question is quite general IMHO. The two examples provided have massive differences in terms of complexity.

For this answer, I will use Qt-equivalent terms, not the official MS Ribbon terminology.
An Overview
You are actually looking at a number of layouts, in a pattern like so:
QToolbar
| (Layout)
+--> QGroupBox/QToolButton
| | (Layout)
| +----->Button
| +----->Button
+--> QGroupBox/QToolButton
The Pattern
Let's start with just the QGroupBox that populates helps sort our buttons into groups.
Consider that our group box may hold both our dynamic QToolButton and regular widgets. When the available space shrinks, the layout:
Calculates the minimum space required for fixed-sized widgets and the minimumSizeHint() values of the widgets without fixed size policies.
Apportions the remaining space based on the growth policy of each widget.
However, our dynamic tool button may or may not change size depending on available space.
Now we have to start checking if the children of the layout are dynamic or not, using qobject_cast.
When we find a dynamic button, we have to determine if it can shrink or not. If it does, we cache this and its preferred, smaller size.
If it's going to shrink, we have to recalculate our minimum size.
And then we have to keep going, hoping that shrinking the buttons during resize doesn't cause any tricky tie-breakers.
Tie breakers: if two buttons can collapse to a smaller size, which one goess first? We have to account for that, too. Also, MS uses rows of three buttons. We can't collapse one from Qt::ToolButtonTextBesideIcon to Qt::ToolButtonIconOnly, we have to collapse a set of three.
Three horizontal buttons can collapse to three vertical buttons. This, too, needs to be calculated and cached.
There is hope.
We can simplify by making each container only able to hold dynamic tool buttons. Then we don't have to deal with tricky issues like fixed sized widgets and we only need to deal with one type of widget.
Microsoft has also helpfully implemented this behavior for us. You can learn a lot about the behavioral constraints of your layout and child widgets by empirical observation. When do groups collapse? If a group collapses, do other groups expand to take up the space? (The answer to that is no, by the way.) How do buttons group together as they collapse? Are some special in how they collapse and expand (constraints on their expansion and collapsing behavior)?
Qt has also implemented several layouts for us to study and get an idea of how they work and how they are different. QGridLayout is a promising basis, particularly if you do some math to change the row span of widgets on the fly (this is for grouping buttons as they collapse from vertical layout to sets of three horizontal buttons).
In Summary
Completely answering your question is far too broad to be on-topic on SO, but I hope this info guides you where you need to go.

Related

Is there a non-fragile way to enforce a maximum window size based on the size of a QScrollArea's target widget?

I've got a Qt app with a window that contains a QScrollArea (plus a few small fixed-size controls around the borders, but window is mainly taken up by the QScrollArea).
Inside the QScrollArea is a fixed-size view of various widgets that the user can scroll across (using the scroll bars) when the window has been sized too small to fit the entire view.
There are some "Show Foo" and "Hide Foo" type menu items that allow the user to customize (to some extent) what widgets are visible inside the fixed-sized view, and when the user selects one of these menu items, the size of the fixed-sized view changes as the corresponding widgets are hidden or shown.
This all works okay; however I'd like to have the maximum-size of the window always be set to just large enough to allow the user to see the entire fixed-sized view without scrolling; there's no point in allowing the user to resize the window any larger than that, since the extra space would just be empty and useless.
I currently implement this behavior by manually calculating the maximum-window-size based on the current size of the fixed-size view and calling setMaximumSize() on the window. However, this technique seems very fragile, since it depends not only on calculating the maximum size correctly (which is doable) but also calculating it at the correct time (i.e. calculate it too soon and the resulting size might not include all the new widgets yet, resulting in the maximum-window-size getting set too small).
That makes me wonder, is there some better way to achieve this result? This seems like it might be a fairly common use-case in Qt GUIs that use QScrollArea.

What makes a Qt widget and its layout behave properly (in regard to its size)?

I'm having all sorts of size problems with Qt. I am creating my own widgets and using different layouts (generally, I need my own to make them work properly without spending hours on the "powerful" default layouts... which don't lay things out as intended.)
Once I'm done with a widget and its layout though, it doesn't work right. The size is never getting set properly unless I call widget->resize(1, 1); which finally forces a "resize" and makes the widget look correct (i.e. recompute the geometry.) Even the updateGeometry() call has no effect.
This is a dreadful problem when the resize() needs to be called on the parent widget (yuck!) and from what I'm reading should not be necessary were the layouts properly programmed.
Is there a sample that works and is not several thousand of lines long, or does Qt require several thousand lines to make anything work perfectly, even the simplest widget?
What are the minimal functions to be called to make a widget & its layout work at once?
Thank you.
Alexis
P.S. I tried to implement the sizeHint(), minimumSize(), maximumSize(), others that I'm missing? I was hoping that would be enough. Obviously, I also implement the setGeometry() on the layout to resize the children appropriately.
--- addition 1
There is a sample image with a layout that clearly isn't available as is in Qt. The positioning, functions, and colors of the different keys is XML driven and works for any keyboard in the world.
(note, this sample doesn't show the Enter key displayed on two rows and wider below than at the top; more or less, not doable at all with the regular layouts; of course, it works with my version.)
--- clarification
I'm not too sure how to describe the problem better. I was thinking to write a test widget next to see how I can reproduce the problem and then post that and eventually fix it. 8-)
The default layout function that the internal Qt layouts make use of require a lot of coding. I would like to avoid having to copy/paste all of that because for maintenance, it makes it close to impossible.
--- today's findings
As I needed to tweak one of the widgets, I decided to add a VBoxLayout and make it work.
I actually found the problem... One of the widgets in my tree is a QScrollArea and that sizeHint() returns (-1, -1). Not exactly what I'd expect but... whatever you put inside that widget has better know how to compute its width and height or else... it fails.
Looking at the code closely, I could actually compute the width by using the widest width found. Once I used that, the widget would appear (and it actually resizes itself as things change in the list, kinda cool.)
This being said, my earlier comment about having a tree of widgets that auto-resize themselves stands. From the root up to the parents of the leaves in your tree, all of those widgets will need a valid layout. Once I added one in the top widget it resized itself and its children properly (well... in my case up to the QScrollArea, the rest required a bottom to top resizing. Funny how that works!)
--- ah! ha! moment (or: what you find reading the implementation code!)
Today I bumped in another problem which just needed the correct call... I just couldn't find anything worth it in the documentation.
All the objects have a layout now, but a certain parent would not resize properly. Plain simple.
I had a call to the parent as following:
// changes to the children are changing the geometry
parentWidget()->updateGeometry();
Yeah. The docs says that's what you have to do. Nothing happens at all with that call. No idea what it's supposed to do, I did not look at that function. It never did anything for me anyway.
So... I looked at the layout to try to understand how it would send the info up/down. I did not see much except for one interesting comment:
// will trigger resize
This is said of the SetFixedSize mode. To reach that function you need to make the layout for update. Ah! Yes... the layout, not the parent widget... let's try that instead:
parentWidget()->layout()->update();
And voila! It resizes correctly in all cases I have. Quite incredible that the widget updateGeometry() doesn't trigger the same effect...
Although it's possible to do what you want it sounds like the problems you are having are because you're using Qt in a way that it's not meant to be used. Why do you need separate widgets for each key represented on the keyboard?
I see two options, both of which are better in some way:
Use QGraphicsScene and QGraphicsView.
A single custom widget that uses custom drawing to display the keyboard (and likely uses hover for hints).
The first option is probably better. Your keys could then be represented by QGraphicsSimpleTextItem's or even a QGraphicsSvgItem. It also provides a number of standard layouts or you could choose to write your own layout. By default you can use the keyPressEvent or mouseReleaseEvent to respond to user interactions.
I'd highly recommend you take a look at the QGraphicsView examples to get an idea what you can do.
If you go the second route you'll need to record the different key locations so you can respond accordingly as the user moves the mouse around, clicks, etc.
This won't help you with your immediate issue but I wanted to show you a keyboard I made using standard layouts and buttons. It's not perfect and it still won't help you with an enter key that spans two rows but it's not bad. It's resizable too by resizing the window, although I'm not sure if that will be apparent from the images below as SO may be scaling them. (you can view the actual images by opening them in their own tab)
Anyway, this was done using only Qt Designer with no manual coding. It consists of a top level vertical layout with 5 horizontal layouts in it. The buttons are then inserted into one of the 5 horizontal layouts. The size of the keys can be controlled by setting the horizontal and vertical size policies to "ignored" for most of the buttons and then horizontal "minimum" for buttons that you want to be wider. Things can be tweaked by setting min and max size restrictions to buttons. When resized, the buttons will not maintain their relative proportions though, that would probably take some custom programming.
The styling in your example could be approximated pretty well using css style sheets and background images. Still not a minor effort but you should be able to get most of the way there without custom layouts and buttons.

Qt - Adding dynamic controls to a placeholder

I am looking for the best way to dynamically swap in and out controls within Qt on a predefined layout created from the Qt Designer.
I come from a ASP.NET background, where often I would use the idea of a "placeholder" for this type of task and add controls as children at runtime.
Does Qt support this type of functionality or something similar?
You can just add a layout control to any widget and later on dynamically add controls to the layout:
for(int i = 0; i < 10; i++)
{
QLabel *plbl = new QLabel(myform);
plbl->setText(QLabel::tr(L"My dynamic text box"));
mylayout->addWidget(plbl);
}
Edit: There are different Layout classes that support different "fill" styles (e.g. horizontal next to each other, grid layout, vertical layout, etc.). You won't need any placeholders or similar - just a widget (or layout) as parent to fill.
If your Qt Layout already uses layouts, then best idea would be to leave some place for runtime controls in layout tree, or perhaps even a empty layout just for those. Layouts have ability to dynamicaly add or remove widgets and sub-layouts, so that solves the problem. If you want to insert new widgets immediatley after removing old ones interface might flicker as qt will try to reuse temporarly freed space. It might slow application down if there are any slow rendering widgets.
Another soultion would be to insert empty widget - that is more similar to ASP.NET approach. It is a bit more crude method, but might be a good way to avoid flickering of interface. It would prevent layout from reusing space even if you don't display any widget and leave unused space - it might suggest to a user, that after some interaction something might appear here - if it's desired behavior i would suggest this way.
If you have several sets of controls apperaing always in the same groups you might consider using QStackedWidget, that allows you to create those widgets already at design stage and swich between groups at runtime.

Programming user interface advice?

In my project I going to generate a user interface through programming. Scalability of this UI is very important requirement.
So far I am using two dimensional graphics for generating the UI.
I think there may be different solutions but for the moment I know only two.
First one is supplying X,Y coordinates of each two dimensional graphic on my UI.(I do not prefer this solution because I do not want to calculate X,Y coordinates of each graphic. For the moment I don't have a logic for doing this easily)
Second one (which is currently I am using now) is using layouts which organizes its contents according to size of item. In this solution I don't have to calculate X,Y coordinates of each item. (Layout is doing this for me.) But this approach may have its own pitfalls.
I am very new to user interface programming. Can you give me advice about this issue?
The general rule I follow is that you should always use layout containers unless you have very specific reasons to use an absolute layout. The only real times I use absolute layouts is when I'm implementing a weird custom layout that doesn't fit easily with built in layout managers.
Layout managers will make your life much easier. Handling resizing windows or variably sized content is made significantly easier with layout managers.
I can't remember every having a problem with qt's built in layout stuff.
I am assuming you rail map only needs to be a schematic of the real track layout, you could create components that are all layed out on a grid, and for each grid cell you implement a simple layout algorithm, depending on your requirements you could just state that one grid cell can only have one actual control on it (and make the cells smaller) or for larger cells you have fixed spots for each type of control, or just arrange the controls within a cell from left to right/top to bottom, whatever works for you. You could also subdivide the cells themselves into subcells to constrain the controls. So that when you scale the whole, each cell can then tell the control what size it should be.
It might also help to implement things as layers on your display, for example make the track layer separate from the control layer.
You are working on a very specific "non-traditional" ui you will need some solutions that fit your problem.
I don't know if you are doing this already, but think if you can implement a data driven approach for the configuration of your UI. Don't hardcode the layout, separate the layout functionality from the actual operational parts and move them into a file that can easily be changed.
It sounds you have some kind of working solution but you said "I do not prefer this solution because I do not want to calculate X,Y coordinates of each graphic", if you have graphical controls that can be placed anywhere on the screen though code, it is probably not a big step to have an editor where a user can place these controls. It might not be as much effort as you think, especially if you are already using a configuration file for your UI instead of hardcoded values.
I have recently done a lot of work changing the normal look of qt elements through styles, but I don't think that the normal qt-gui parts will be enough for your endeavor, but the QGraphicsView subsytem would probably be a good fit it does scales well and handle interactions with large number of elements well, but it is hard to give you a more specific answer without knowing more details
There is Qt Designer (GUI Builder) which allows designing GUI like in Visual Studio.
Calculating X,Y by hand is a bad approach - it is absolutely inscalable.
Don't reinvent the wheel:
Inherit your objects from UI objects e.g. panels so you could use the UI techniques from those objects.
For the rest use containers or any other layout objects

Controls Are cut in MFC when changing display to 125% in windows 7

I have an MFC application. When running it on Windows 7 I realized that when changing the display percentage to meduium - 125%, I have a TextControl which is cut off (end of sentence doesn't appear.)
How can I fix this?
I could enlarge the size of the control on the dialog, but I'd rather do that via the code.
My application is localized and I woudn't want to change all the dialogs on every language.
In general, the issue is that absolute coordinates are being used for some sizing rather than relative. The framework will initially lay things out correctly in large DPI, it's then up to you to keep things straight through resizes.
You can use a layout framework like this one: Ultimate Toolbox Layout Manager, or you can roll your own.
Here's a common pattern I use:
Define a struct that captures ID, size, location, and layout behavior (anchor top|left|right|bottom) of a control
In the document constructor initialize an array of structs with your desired layout behavior
In OnInitDialog, capture the initial control positions, e.g. for controls set to anchor top left you need to grab the initial distance from the top and left of the parent.
In OnSize, reposition and resize each control according to its layout behavior.
I'm not exactly sure if it helps in your case but the ResizableLib works pretty well for me. You can also skip creating a library and just use the files in your project.
There is a separate article for CResizableDialog which explains in a few easy steps how to implement this for existing dialogs.