Custom Qt widget with discrete allowed widths - c++

I would like to create a custom widget in QT that is, essentially, a grid. I would like to allow this widget to be resized, but only so long as all boxes in the grid are of the same width and height. For example, if the grid is 30x20, then the widget need only be able to have widths that divide by 30 and heights that divide by 20.
I did not find anything in the size policy that seems like what I'm trying to do.
I know that X11 does support such size policies. Then again, it also supports defining aspect ratio ranges for top level windows, so that does not, necessarily, mean that Qt supports it too :-)
I know I can catch the resize signal, and then round down the size given. I think this might have unpleasant effects if used inside a broader layout, though. For example, placing two of those one next to the other might cause an infinite loop.
Thanks,
Shachar

Related

Qt resizeEvent(), setGeometry() and update() - what order are they called and what calls what, when?

I can't seem to find a guide on Qt's widget / layout system that explains how it all fits together. Only examples of what to do in typical use-cases.
Slightly more specifically:
When my widget (which has a QGridLayout of child widgets) is resized, I want to:
layout the children, so obtain their sizes,
do some stuff before everything gets repainted
have the children painted, presumably by calling update()
How do I slot number 2 between 1 and 3? If the answer is, "Your design is bad, don't do this", then this is my specific problem:
I have a PlotFrame class that shows Cartesian graphs. It houses a PlotPane (the main area of the graph) and a dynamic number of axes. The PlotPane and Axis classes are QWidgets laid out with a QGridLayout and I'm using explicit double buffering so I can display transient mouse-driven features on top of the base buffered image.
When the PlotFrame is resized, the number 2 in the list above is this:
Create the buffered image of the axes whose ticks and labels dynamically depend on the length of the axis on the screen, this also calculates positions of grid lines for display in the main PlotPane.
Get the grid line positions just calculated and pass them to the PlotPane.
Create the buffered image of the PlotPane.
Once this is done, I'm happy to have Qt do its asynchronous stuff, and to deal with any transient graphical features in each of the widgets' paintEvent() functions.
Thanks in advance. I'm want to get a better understanding of how this all works but don't really want to be digging in the source code if I can help it. I find that Qt's own docs lack depth / are confusing to navigate. There are some great guides I've found (eg. flylib.com) but they are well out of date, therefore misleading. I'm currently using Qt6.
EDIT: After further experimentation, it seems that the parent widget (PlotFrame) resizeEvent() gets called after its children have been laid out and before they are painted. Is this so? Can I rely on this?

QLayout and discrete widget representation - how to?

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.

Are sizers a good way to deal with a form?

I am moving an application of mine from an old GUI (where I used fixed sizes) to wxWidgets (where I am trying to learn the basis of sizers).
Inside the application I have several forms with quite complex structure. Nature of data and workflow suggest me to keep all these data together in a single screen.
But this screen has to be properly designed be usable.
Here's the old version of the form ...
... and here's more or less what I could come up with using sizers ...
Please note that I could not simply use a wxGridSizer, since some of the widgets span throughout the row, some other get out of the fixed grid etc.
Obviously I could keep working on it to make it less ugly, but I found myself thinking maybe sizers are not the correct tool to do it. To achieve what I want I would need something similar to wxGridSizer with some sort of colspan/rowspan mechanism, and maybe that wouldn't be enough.
So I started thinking to build a "Form Sizer" by myself that could go something like
int w,h;
GetClientSize(&w,&h);
MyFormSizer *formSizer(w,h);
formSizer->SetDimension( widget1, 100, 20 );
formSizer->SetDimension( widget2, 500, 20 );
formSizer->newRow();
formSizer->SetDimension( widget3, 100, 20 );
formSizer->SetDimension( widget4, 250, 20 );
formSizer->SetDimension( widget5, 250, 20 );
formSizer->Layout();
The user would set the relative sizes of every widget, to block the geometry of the form completely. To make this geometry fit the wxPanel when resizing Layout() would calculate an appropriate scaling factor f and then would set all sizes and positions doing something like
widget1->SetSize(CalculatedX, CalculatedY, 100/f, 20/f);
CalculatedX+=100/f;
widget2->SetSize(CalculatedX, CalculatedY, 500/f, 20/f);
CalculatedX+=0;
CalculatedY+=20/f;
and so on (this is just a scratch, take it as the rough idea; there should be mechanisms to avoid making everything too small to fit the text in the widgets and/or to scale the font size accordingly and such stuff).
Does it seem reasonable? How would you face the topic? This choice is going to affect quite a bit of my future developing, so any answer, criticism and comment will be very welcome
wxGridSizer might not fulfil your needs but more flexible classes such as wxGridBagSizer should do what you want. This class allows you to set items to span multiple rows and columns by setting wxGBSpan when calling wxGridBagSizer::Add.
As mentioned in #sjdowling's answer, you could use wxGridBagSizer. However I don't recommend using this class as it suffers from some problems of the absolute positioning, notably it makes it very difficult to move elements around it or to add them except at the very end.
It's true that wxWidgets sizers lack the ability to align elements across different sizes, which is what you'd need here if you used normal box or grid sizers and this is annoying. However you can work around it by fixing the sizes of the columns explicitly, either by just hard-coding them (please at least use dialog units and not pixels though to avoid broken layouts, especially in high DPI situations), or by iterating over all the labels and calling GetTextExtent() on all of them and choosing the longest one (this is much more reliable, especially when using translations which can drastically change the width of the string in pixels). Then simply assign this width to the labels you create: as the initial width is also the minimal width, this will ensure that all of them have this width and so are aligned, even if they are in different sizers.
As I wrote, this is somewhat annoying but really not that bad if you're creating the layout in the code. If you do it in a GUI designer, it's even uglier, but you can still call wxSizer::SetItemMinSize() from the code. Of course, the only real solution would be to add support for inter-sizer alignment, but for now this is still preferable to using wxGridBagSizer IMO.

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.

Supporting different monitor resolutions

I have a MFC application with some bitmaps, dialog boxes and menus. Currently it supports only one monitor resolution (1280x1024). I am planning to add different monitor resolution support to it. To do that I guess I have to load different resolution bitmaps, change the font size etc. My question is, how these are handled in a typical windows application? Do they use a single bitmap and stretch/shrink it according to monitor resolution or actually have different set of bitmaps in the resource and load them dynamically depending on the resolution? Is there any standard solution to this?
In the past I have used one large image and scaled accordingly.
Making sure that the menus and dialogs resize is tricky but there are helper codes on CodeProject that could help.
I would say to use multiple resources with different resolutions. While you can scale the icons dynamically, they'll probably look better if you resize them in a proper image-editing program.
Menu and toolbar icons are normally displayed with the same number of pixels regardless of screen resolution. Thus menus and toolbars take up a smaller proportion of the screen as the resolution increases.
I don't think there's any standard way of handling different resolutions where bitmaps are concerned.
I would also make sure your application works with Windows DPI scaling. That might be a better alternative when running on higher resolution displays rather than having to redesign the application to meet a specific resolution.
Scaling bitmaps will look bad (making them bigger will always look bad, making them smaller kind of depends on the source). If possible, see if you can compose your big bitmap of several smaller bitmaps that can scale nicely. Lots of times it is fairly easy to make a bitmap that can scale in one direction nicely. For example, if you want to make a frame around something, instead of using one bitmap, cut it up like a tic-tac-toe board into 9 pieces. the four corner pieces stay their original size, the top and bottom pieces stretch horizontally, the left and right vertically, and the center goes both directions (if it is used at all).