I have an empty horizontal layout that I dynamically populate with three children. This is a card game so this layout gets dynamically populated with children that are QLabel which have an image set. When I click on a card to play it and move it at the center of the screen I'd like the positions of the other cards to stay the same. Instead the cards tend to "float" to the right. How can I force them to stay in their original position?
+-----------------+
| |
| [A] - [B] - [C] |
| |
+-----------------+
I tried putting an expandable horizontal spacer in between each card but it doesn't really work. I also tried with replacing the horizontal layout with a grid layout but this forces me to specify the initial coordinates of the cards which I'd rather not. I like the fact that I can append widgets with addWidget pretty much like I do in the HTML version.
In the HTML version I use some wrapper divs with a fixed size around the cards. How can I do something similar in Qt?
EDIT
A little clarification on how the layout should look when one card (B) is played.
+-----------------+
| |
| [A] - - [C] |
| |
+-----------------+
As I mentioned I managed to fill the layout with empty QWidget and put cards on top of them (not inside them). I actually got also rid of the horizontal spacers I had as they are not needed anymore. This seems to work as I expect, although I still didn't fully validate this approach.
Simple algorithm steps to do what you want :
User clicks
Select the label where the click happened
If there is an image, Remove the image from the label
Don't remove the label from the layout, and make sure that the layout don't resize the label
Add the image to a label in the center of the board game.
When the user clicks on a label which has nothing, just ignore the click.
For each player you know the maximum numbers of cards which can be shown in your layout. Moreover, I don't think the size of the card is variable. So each time you create a deck for one player, create empty labels, each one having a fixed size.
EDIT: SOME OTHER PROPOSITIONS
fix size of labels using QWidget::setFixedSize();
Don't hide your label to remove your pixmap . Don't set an empty pixmap. Rather, set
a pixmap which is transparent or filled with some neutral color...
Alternatively subclass QLabel such that you can indicate a minimum size hint. The layout must and will respect anything you set. Example:
class MyQLabel : public QLabel {
MyQLabel(QSize cardSize, //remaining qlabel args)
:QLabel(//label args)
{_cardsize = cardcardSize;}
//only method to re-implement
virtual QSize minimumSizeHint () const {return _cardsize;}
private:
final QSize _cardsize;
}
Each time your whole application need to be painted again, all widgets properties are checked. My two cents is that a size property is modified when you play a card,
and the layout adapt to this modification. Tell me if the size hint trick work (it should).
In Qt you can use wrapper QWidget with Fixed sizePolicy.
Related
I'm developing an app with a complex hierarchy of widgets and layouts, but in short it has a central widget with a formulary as upper widget and a QScrollArea as buttom widget (by means of a QVBoxLayout).
That QScrollArea represents a list (grid layout indeed) of QPushButtons which can contain a huge number of buttons (or not).
I want my app fits the following constraints:
Both (form and list) consume all available horizontal space, redistributing its contents to fill all horizontal space (nor SpaceItems neither contents margins).
Both must save as vertical space as possible, in order to make "lines" close to each other.
I've solve partially my problem making use of setSizeConstraint(QLayout::SetFixedSize) on the form, which shrinks it vertically, but also horizontally, causing that both, list and form, have different widths, wich doesn't look like very well.
How can I achieve that? I mean, how can specify something like grow horizontally to fill the widget but shrink vertically has much as possible?
Add a spacer as the last item to the layout:
gridLayout->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Expanding), lastrow, 0);
I think this is what you want:
If you know how many columns you will have (and it doesn't change), insertStretch() in the last column (although it might give you the same effect as using a spacer).
int columnCount = gridLayout()->columnCount();
gridLayout->insertStretch( columnCount(), 1 ); // Default stretch for other
Note that this will resize your buttons to the size Qt thinks they should be unless you are explicitly changing their widths.
I have a QScrollArea Widget, which starts empty;
It has a vertical layout, with a QGridLayout, and a vertical spacer to keep it at the top, and prevent it from stretching over the whole scroll area;
Elsewhere in the program, there is a QTextEdit, which when changed, has its contents scanned for "species" elements, and then they are added to the QGridLayout. Any species elements which have been removed are removed too. This bit works;
I have turned the vertical scrollbar on all the time, so that when it appears it does not sit on top of the other stuff in there. Note that the scroll bar is larger than the scroll box already though, despite not needing to be.
This is the problem. The scroll area seems to be preset, and i cannot change it. If i add more rows to the QGridLayout, the scroll area doesn't increase in size.
Instead, it stays the same size, and squeezes the QGridLayout, making it look ugly (at first);
And then after adding even more it becomes unusable;
Note that again, the scroll bar is still the same size as in previous images. The first two images are from Qt Designer, the subsequent 3 are from the program running.
If I resize the window so that the QScrollArea grows, then I see this:
Indicating that there's some layout inside the scroll area that is not resizing properly.
My question is; what do I need to do to make the scrollable area of the widget resize dynamically as I add and remove from the QGridLayout?
If you're coming here from Google and not having luck with the accepted answer, that's because you're missing the other secret invocation: QScrollArea::setWidget. You must create and explicitly identify a single widget which is to be scrolled. It's not enough to just add the item as a child! Adding multiple items directly to the ScrollArea will also not work.
This script demonstrates a simple working example of QScrollArea:
from PySide.QtGui import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
inner.layout().addWidget(b)
scroll.show()
app.exec_()
The documentation provide an answer :
widgetResizable : bool
This property holds whether the scroll area should resize the view widget.
If this property is set to false (the default), the scroll area honors the size of its widget.
Set it to true.
Why don't you use a QListView for your rows, it will manage all the issues for you? Just make sure that after you add it you click on the Class (top right window of designer) and assign a layout or it wont expand properly.
I use a QLIstWidget inside a QScrollArea to make a scrollable image list
Try this for adding other objects to the list, this is how I add an image to the list.
QImage& qim = myclass.getQTImage();
QImage iconImage = copyImageToSquareRegion(qim, ui->display_image->palette().color(QWidget::backgroundRole()));
QListWidgetItem* pItem = new QListWidgetItem(QIcon(QPixmap::fromImage(iconImage)), NULL);
pItem->setData(Qt::UserRole, "thumb" + QString::number(ui->ImageThumbList->count())); // probably not necessary for you
QString strTooltip = "a tooltip"
pItem->setToolTip(strTooltip);
ui->ImageThumbList->addItem(pItem);
Update on Artfunkel's answer:
Here's a PySide6 demo that uses a "Populate" button to run the for loop adding items to the scroll area. Each button will also delete itself when clicked.
from PySide6.QtWidgets import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
def on_remove_widget(button):
button.deleteLater()
def populate():
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
b.clicked.connect(b.deleteLater)
inner.layout().addWidget(b)
b = QPushButton(inner)
b.setText("Populate")
b.clicked.connect(populate)
inner.layout().addWidget(b)
scroll.show()
app.exec()
I am new to QT. I'm trying to understand the layout mechanism by trying to implement this small window seen below. It has the following elements under the QWidget that's the main window:
One big QWidget that stretches on all the client area.
Two QWidget containers on the top of the window. Both should have the same height, but the right one stretches horizontally, as the window grows/shrinks.
one button container widget on the top right, with fixed height and width
Large QWidget container filling the rest of the client area, that should resize as the window resizes.
The parent window itself is resizeable.
I'm looking for hints as to what layout I should use. How do I achieve this programatically? define what stretches automatically, what stays with a fix size? and how the proportions are kept where they need to be kept.
I'd appreciate any pointer you may have.
The easiest, and IMHO best, way to accomplish this is via the QHBoxLayout and QVBoxLayouts. You can do this via the designer in QtCreator, but I find it doesn't work perfectly if you need to adapt things over time. If it's a static set of widgets, I do suggest designing it using the QtCreator designer as it'll greatly simplify your life.
If you're going to do it programatically, the main window should be set to use a QVBoxLayout and then two sub-QVBoxLayout's after that, where the bottom one is configured to take any space it can get. Then in the top QVBoxLayout, add a QHBoxLayout with your two upper components.
to set a widget to fixed size in code you call setFixedSize( int h, int w ) on the widget. To do it in Designer click on the widget and look in the property editor in the QWidget section. open the sizePolicy thingy and set horizontal and/or vertical to fixed. Then open Geometry and set the width and Height.
To make them stretch at different ratios in code you use a separate argument when using a box layout. eg layout->addWidget( button1, 1 ); layout->addWidget (button2, 2); this would cause button2 to expand at twice the rate of button1. To do this in designer, open the sizePolicy property of the widgets and set the HorizontalStrech and/or VerticalSretch. Note that the size policy needs to not be Fixed in this case for the direction you want to set the stretch on. Also it will never let a widget shrink below its minimum size (it would rather mess up the ratio than shrink something too small).
I'm trying to have a 2x2 QGridLayout arranged as follows:
+-----+-----+
|(1) |(2) |
+-----+ |
|(3) | |
+-----+-----+
I want to be able to expand (1) programatically to occupy the whole first row, like as follows:
+-----------+
|(1) |
+-----+-----+
|(3) |(2) |
+-----+-----+
Actually, I want to expand and contract any widget to any direction.
I'm able to do this by just detecting what widget is in the cell I need to expand to. But the problem is that it is a pain to make this happen on any direction and any grid size, because I need to go back to the initial position of the just expanded widget (1), so I don't need to remember positions, except the one I'm expanding/contracting.
So, in the example above, I decided to just expand the widget to occupy the position 0,0 with column span of 2. This will result in overlapping widgets, which is ok for me. The problem is that widget (1) will be underneath (2), I guess because of order of insertion into the grid. Hence the question: how do I control a widget's overlap priority in a QGridLayout?
Thanks!
Perhaps you can make something like this work for you with some experimentation.
Both shots are of a QVBoxLayout with two QHBoxLayouts in them. You should be able to dynamically remove and add buttons from one QHBoxLayout to another. You can add or remove a spacer to determine if a button will span the whole width or not.
I realize that it is still adding and removing widgets but it might be a little easier than the grid layout.
I got the answer to my question. The solution is to call raise() on the widget I'm resizing. This "resizing" is actually a removeWidget from the layout and an addWidget to the layout (again), with the new cells and cell spans information. After this, I call raise() on the widget, and that's it.
I'm not sure that this will do what you want, but from what I understand you want to resize your three widgets in any way you want.
You could try to look into the QSplitter class. It allows you to change (manually or programmatically) the size of your widgets, a bit like the outlook separations.
It is really easy to use and may be flexible enough to give you what you want.
Hope this helps.
I've been running into this problem with Flex for nearly a year, and each time I work up a quick hack solution that works for the time being. I'd like to see if anyone has a better idea.
Here are the conditions of a problem:
|------Container ------------|
| explicitHeight: 400 (or whatever)
| |
| |-------- VBox -------| |
| | percentHeight: 100 | |
| | | |
| | |-Repeater------| | |
| | | Potentially | | |
| | | a lot of stuff. | |
|--|--|---------------|---|---|
The problem is that, contrary to what I would like to happen, the VBox will ALWAYS expand to accommodate the content inside it, instead of sticking to the explicit height of its parent and creating a scroll bar.
My solution has been to hard code in a reference to the parent (or however far up the display list we need to go to find an explicitly set value as opposed to a percentage).
I've even considered using this in a utility class:
public static function getFirstExplicitHeightInDisplayList(comp:UIComponent):Number{
if (!isNaN(comp.explicitHeight)) return comp.explicitHeight;
if (comp.parent is UIComponent) return
getFirstExplicitHeightInDisplayList(UIComponent(comp.parent));
else return 0;
}
Please tell me there's a better way.
setting minHeight = 0 is all you need to do.
This tells the VBox to ignore it's children's measurements when sizing itself, and calculate its height instead based on it's own/it's parents constraints. Set everything else as you normally would, scrolling and everything else will work perfectly.
Spent DAYS on this one a year ago- it's not intuitive, they could have probably named the property better. Hope this saves u guys some time...
You have to use the "autoLayout" parameter on the VBox as documentation say:
"By default, the size of the VBox container is big enough to hold the image at it original size. If you disable layout updates, and use the Zoom effect to enlarge the image, or use the Move effect to reposition the image, the image might extend past the boundaries of the VBox container.
You set the autoLayout property to false, so the VBox container does not resize as the image resizes. If the image grows to a size so that it extends beyond the boundaries of the VBox container, the container adds scroll bars and clips the image at its boundaries.
I hope that will help you.
AutoLayout=false seems to only prevent layout from being rerun when the childrens' size change. However if you add or remove children, layout will rerun anyway.
Setting minHeight=0 does indeed completely disconnect the (outer) size of the VBox from the size and number of the children, which is what I wanted.
Pawing through the Flex source code I didn't see the mechanism by which setting minHeight=0 made it work like I wanted, so I salute Yarin for discovering it. Thanks!
Set the properties of your Container:
clipContent = true;
verticalScrollPolicy = "off"
Then your VBox should automatically clip when it has percentHeight = 100;
Works for me in Flex 3.
If you need to get really fancy, you can set the scrollRect on objects:
scrollRect = new Rectangle(x, y, w, h);
depending on what you need it to do.
In fact, Yarin Kessler brought us the only right answer here
(unfortunately, i don't have the rights to comment its post, that's why i'm doing it here).
When your HBox sizing is based on a percentage value, you are hoping that only its container will influence its size. That's wrong, there is an other rule, a stronger one.
It's the fact that a container (which HBox is) has a minimal size, which is the addition of the default/explicit sizes of its own childs components.
So, if your percentage value result in a value smaller than the minimal size, the minimal size wins and applied to the HBox. Since the HBox is displaying all of its children, there is no need for scrollbars.
So using :
minHeight = 0;
minWidth = 0;
is like telling to the HBox that's its minimal size is 0 instead of its children default sizes. You are redefining it and that way the minimal size is smaller than the percentage value and lose the battle.
The only phrase i found in Adobe documentation explaining this is this one :
A percentage-based container size is advisory. Flex makes the container large enough to fit its children at their minimum sizes.
Hope i made myself clear,
(feel free to correct my incorrect english sentences...)