PyQT custom widget fixed as square
I'm developing a custom widget (inheriting from QWidget) to use as a control. How can I fix the aspect-ratio of the widget to be square, but still allow it to be resized by the layout manager when both vertical and horizontal space allows?
I know that I can set the viewport of the QPainter so that it only draws in a central square area, but that still allows the user to click either side of the drawn area.
Apparently, my old approach had many flaws and some of the things in it are meaningless:
- QSizePolicy.setHeightForWidth and QSizePolicy.setWidthForHeight are exclusive... and setWidthForHeight doesn't even work in most cases...
- There is no such thing as QWidget.widthForHeight, so defining it doesn't really override anything.
It seems like there is no universal way to keep a widget square under all circumstances. You must choose one:
- Make its height depend on its width:
class MyWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) policy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) policy.setHeightForWidth(True) self.setSizePolicy(policy) ... def heightForWidth(self, width): return width ...
- Make its minimal width depend on its height:
class MyWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) ... def resizeEvent(self, e): setMinimumWidth(height()) ...
Such a widget will be kept square as long as there is such a possibility.
For other cases you should indeed consider changing the viewport, as you mentioned. Mouse events shouldn't be that much of a problem, just find the center of the widget (divide dimensions by 2), find min(width, height) and go from there. You should be able to validate the mouse events by coordinate. It is nice to call QMouseEvent.accept, only if the event passed the validation and you used the event.
I'd go with BlaXpirit's method, but here's an alternative that I've used before.
If you subclass the custom widget's resiseEvent() you can adjust the requested size to make it a square and then set the widget's size manually.
import sys from PyQt4 import QtCore, QtGui class CustomWidget(QtGui.QFrame): def __init__(self, parent=None): QtGui.QFrame.__init__(self, parent) # Give the frame a border so that we can see it. self.setFrameStyle(1) layout = QtGui.QVBoxLayout() self.label = QtGui.QLabel('Test') layout.addWidget(self.label) self.setLayout(layout) def resizeEvent(self, event): # Create a square base size of 10x10 and scale it to the new size # maintaining aspect ratio. new_size = QtCore.QSize(10, 10) new_size.scale(event.size(), QtCore.Qt.KeepAspectRatio) self.resize(new_size) class MainWidget(QtGui.QWidget): def __init__(self, parent=None): QtGui.QWidget.__init__(self, parent) layout = QtGui.QVBoxLayout() self.custom_widget = CustomWidget() layout.addWidget(self.custom_widget) self.setLayout(layout) app = QtGui.QApplication(sys.argv) window = MainWidget() window.show() sys.exit(app.exec_())