Home > Back-end >  QT Designer: Moving & Resizing widgets while respecting window rescaling PyQT5
QT Designer: Moving & Resizing widgets while respecting window rescaling PyQT5

Time:09-17

TLDR: QT Designer won't let me move and resize widgets while respecting window scaling. I would like to have the right most button move closer to the edge of the window when dragging it from image 1 to 2, aka keeping the proportions.

I am using PyQT5 with QT Designer. I am trying to manually organize (move & resize) my widgets, but both of my approaches (explained next) fail.

When I use a grid layout, I can scale the widgets by changing the window size by dragging its edges (as expected... great!). But when I use layouts, I cannot change the relative positions of widgets. When using layouts, I also can't drag the edges of an individual widget to resize it within the window (bummer, and kind of important for building my UI).

On the flip side, when I don't use any layout, I can move and resize individual widgets freely (yay!). But without using a layout, my widgets no longer scale to fit window size.

My question is, how can I both move and resize widgets in QT Designer? I would think I could do it in QT Designer itself and not rely on messing with PyQT5, since that would complicate even minor changes and isn't drag and drop what QT Designer is for?

Attached are a couple of screenshots to show what I mean pictorially. The first 2 photos show what goes on in the case with no layout. I made the window bigger but my widgets stayed in the same spot with the same size. The 3rd screenshot shows what happens when I employ a grid layout. I can't resize any widget, nor can I move them much.

In other words, is there a more effective approach to moving and resizing with QT Designer that solves both of these failures?

I would like to have the right most button move closer to the edge of the window when dragging it from image 1 to 2, aka keeping the proportions.

enter image description here enter image description here enter image description here

CodePudding user response:

If you want to implement the proportion then:

  • Use additional QWidgets (which are invisible by default but for my demo I will set background colors) or QSpacerItem,

  • Use stretch factors that meet the condition, and

  • Set the sizePolice of all widgets to Ignored.

The gridlayout will use that information to rescale the geometry.

For my demo window has a size of 200x200 and the button is in position 50x50 and size 40x40.

enter image description here

  • For the widget on the left side you must set the horizontal stretch factor to 40

  • For the central on the right side you must set the horizontal stretch factor to 50 and the horizontal stretch factor to 50.

  • For the widget on the right side you must set the horizontal stretch factor to 90(200 - 50 -40)

  • For the widget on the top side you must set the vertical stretch factor to 50.

  • For the widget on the bottom side you must set the vertical stretch factor to 90(200 - 50 -40).

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>593</width>
    <height>439</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout">
    <item row="0" column="1">
     <widget class="QWidget" name="top" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
        <horstretch>0</horstretch>
        <verstretch>50</verstretch>
       </sizepolicy>
      </property>
      <property name="styleSheet">
       <string notr="true">background-color: rgb(85, 255, 127);</string>
      </property>
     </widget>
    </item>
    <item row="1" column="0">
     <widget class="QWidget" name="left" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
        <horstretch>50</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="styleSheet">
       <string notr="true">background-color: rgb(255, 0, 0);</string>
      </property>
     </widget>
    </item>
    <item row="1" column="1">
     <widget class="QPushButton" name="pushButton">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
        <horstretch>40</horstretch>
        <verstretch>40</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="1" column="2">
     <widget class="QWidget" name="right" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
        <horstretch>110</horstretch>
        <verstretch>0</verstretch>
       </sizepolicy>
      </property>
      <property name="styleSheet">
       <string notr="true">background-color: rgb(255, 85, 255);</string>
      </property>
     </widget>
    </item>
    <item row="2" column="1">
     <widget class="QWidget" name="bottom" native="true">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Ignored" vsizetype="Ignored">
        <horstretch>0</horstretch>
        <verstretch>110</verstretch>
       </sizepolicy>
      </property>
      <property name="styleSheet">
       <string notr="true">background-color: rgb(85, 255, 255);</string>
      </property>
     </widget>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>593</width>
     <height>28</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

CodePudding user response:

While overcrowded UIs are usually not a good idea, having lots of empty space is rarely better. Layout managers consider this aspect, and try to optimize the available space by making the widgets use as much space as possible (assuming that they can be resized).
That said, there are obviously lots of situations for which a big window with lots of space is fine (for instance, a login access interface that is big enough to get the attention).

What is important to understand is that a layout manager, as the name says, manages the layout, and it does that by a lot of complex computations, based on the widget on which it is installed and all the size hints/policies/constraints of all its child items, recursively. If you have some requirements about how some widget should be able to resize, you must specify them.

Whenever any interface is going to be created, and especially if UX matters are being considered (as they should) and there's also a lot of empty space that should be proportionally used, there are at least two important steps:

  1. exactly understand how widgets should be placed (position, size and proportions) in "normal" conditions on a layout;
  2. think about those sizes and position should change when the container is resized;

The first point seems quite explanatory, but to even create a basic interface that considers empty areas using layouts is not that simple.
Depending on the situation, a grid layout might suffice, but in certain conditions you might need to use nested layouts.

In your specific case, a grid could be enough, but to ensure that proper spacings and proportion are going to be maintained, you need to use spacer items.

I've prepared a basic UI example for your case: the proportions might not be completely correct, but it's a start.

Here's how it looks at a normal size:

Normal size window

And that's how it looks when it's bigger:

Bigger size window

As you can see, I added spacers: there's one for each side near the edges, which is required to ensure that a proper "border margin" is maintained, then another two for the spacing between the two buttons; since the grid becomes 5x5, the "cell" position of the two central spacers might be in any place, as long as the vertical is in the central row and the horizontal in the central column.

Now, the proportions of widgets and spacings are achieved through the following:

  • both buttons have a Preferred size policy (both vertical and horizontal); this ensures that the layout knows that it can make them bigger if there's enough space for them, but without making them occupy all the available space;
  • the size policy of the top left button has a stretch of 1 (again, both vertical and horizontal), while it's 2 for the bottom right one; in this way, they second button will always try to be twice the width and height of the first;
  • the layout (accessible by clicking on the central widget) has also a column stretch of 2 set for the third column (the one in the middle); considering the above, the result will be that the layout will try to make the space in the middle as much wide as the bottom right button;

Note that the row or column stretch of the buttons can (and sometimes should) be directly set on the layout when using a grid.
Also consider that the spacers at the edges are basic QSpacerItems, which have very limited features (the minimum width/height and size policy), and if you want a bigger and proportional margin, you have to set it in the stretch. Another possibility is to use empty QWidgets instead of spacers.

Based on the above, a better choice is to actually set the stretches of the grid layout:

vertical (rows):      1, 2, 1, 4, 1
horizontal (columns): 1, 2, 4, 4, 1

Remember that the stretch values are relative proportions, if you want more precise sizes, use greater values.

Obviously, you could also set maximum sizes for any of the widgets, in case you don't want to exceed a certain size.

I'll attach the original file (with the stretch set on the widgets only) so I'll leave to you to alter those values and see what happens.

Finally, remember that if you add another widget that has one of the size policies that is not Preferred or Ignored, the layout will react accordingly. Try to add a button to an empty grid in the same row or column of an existing button, and you'll see the results.

Here's the basic UI:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>806</width>
    <height>507</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,2,0,0">
    <item row="1" column="2">
     <spacer name="horizontalSpacer_2">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>40</width>
        <height>20</height>
       </size>
      </property>
     </spacer>
    </item>
    <item row="2" column="2">
     <spacer name="verticalSpacer_2">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
       </size>
      </property>
     </spacer>
    </item>
    <item row="4" column="3">
     <spacer name="verticalSpacer_3">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
       </size>
      </property>
     </spacer>
    </item>
    <item row="3" column="4">
     <spacer name="horizontalSpacer_3">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>40</width>
        <height>20</height>
       </size>
      </property>
     </spacer>
    </item>
    <item row="1" column="1">
     <widget class="QPushButton" name="pushButton">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
        <horstretch>1</horstretch>
        <verstretch>1</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>PushButton</string>
      </property>
     </widget>
    </item>
    <item row="3" column="3">
     <widget class="QPushButton" name="pushButton_2">
      <property name="sizePolicy">
       <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
        <horstretch>2</horstretch>
        <verstretch>2</verstretch>
       </sizepolicy>
      </property>
      <property name="text">
       <string>...</string>
      </property>
     </widget>
    </item>
    <item row="1" column="0">
     <spacer name="horizontalSpacer">
      <property name="orientation">
       <enum>Qt::Horizontal</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>40</width>
        <height>20</height>
       </size>
      </property>
     </spacer>
    </item>
    <item row="0" column="1">
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
      </property>
      <property name="sizeHint" stdset="0">
       <size>
        <width>20</width>
        <height>40</height>
       </size>
      </property>
     </spacer>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>806</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>

While there are fundamentally just three basic layout managers (QFormLayout and QStackedLayout are a bit different), and they are very simple, learning how to use them requires a lot of time and experience. Also, knowing how the size hints and policies work is very important to better understand their behavior. I strongly suggest you to do a lot of experiments both in Designer and also by creating UI from code.
For learning purposes, you can create the UI in Designer, export it using pyuic and study its contents. But, remember, do not ever, never edit those files for production code. Read more about their usage on the official guidelines about using Designer.

  • Related