Qt formlayout not expanding qplaintextedit vertically - layout

I'm confused why a QPlainTextEdit widget will not resize vertically when added to a QFormLayout. In the code below the text field correctly scales up horizontally, but does not scale up vertically.
Can anyone explain this behavior and offer a solution? I've tried all the tricks I know to no avail.
from PyQt4 import QtGui
class Diag(QtGui.QDialog):
def __init__(self, parent, *args, **kwargs):
QtGui.QDialog.__init__(self, parent)
layout = QtGui.QFormLayout(self)
widg = QtGui.QPlainTextEdit(self)
layout.addRow('Entry', widg)
if __name__ == '__main__': #pragma: no cover
app = QtGui.QApplication([])
window = Diag(None)
window.show()
app.exec_()
Here is an example of the QPlainTextEdit widget not resizing vertically:
This is on Windows 7 using PyQt 4.5.2 and Python 32-bit 2.6.
Thanks.

It seems that, by default, a QFormLayout will only resize the height of its fields according to their sizeHint.
To change this behaviour, adjust the vertical stretch as appropriate:
policy = widg.sizePolicy()
policy.setVerticalStretch(1)
widg.setSizePolicy(policy)

You should set the object in the last row of formlayout (see QPlainTextEdit), its vertical Stretch factor should not be 0.

This works for me:
it is small at the time of calculating the initial size of the dialog widget and can grow with the dialog once it is already visible
class q2text(QTextEdit):
def showEvent(self, ev):
self.updateGeometry()
return super().showEvent(ev)
def sizeHint(self):
if self.isVisible():
return QSize(99999, 99999)
else:
return super().sizeHint()

Related

How do I add scroll function to main window in python pyqt5?

I'm trying to learn pyqt5 in python by creating a small application. For one of the windows, I need to add a vertical scroll bar to the window. Now, this window has a table made using QLabel and QLineEdit. Check the picture to get exactly how it looks like.
As you can see there are a lot of chemicals, which goes below the window screen. I have tried numerous approaches but somehow couldn't get the result. If I am able to get the scroll, all the elements get aligned one under another (QVBoxLayout) which is not the way I want the elements to be aligned.
Here's the code I'm using
class ChemicalWindow(QWidget):
def __init__(self,chemicals,data):
super().__init__()
self.layout = QVBoxLayout()
self.setWindowTitle("Chemicals")
self.setMinimumSize(QSize(600,600))
self.setStyleSheet("background-color:#eaf4f4;")
self.chemicals = chemicals
self.data = data
self.createBody()
self.createButtons()
def createBody(self):
headerLabel = QLabel('Chemicals',scroll_widget)
headerLabel.move(265,10)
headerLabel.resize(70,40)
headerLabel.setStyleSheet("color:#000;")
tcLabel = QLabel('Tc',scroll_widget)
tcLabel.move(200,50)
tcLabel.resize(60,30)
tcLabel.setStyleSheet("color:#000;")
pcLabel = QLabel('Pc',scroll_widget)
pcLabel.move(280,50)
pcLabel.resize(60,30)
pcLabel.setStyleSheet("color:#000;")
cpLabel = QLabel('Cp',scroll_widget)
cpLabel.move(360,50)
cpLabel.resize(60,30)
cpLabel.setStyleSheet("color:#000;")
self.chemical_names = self.chemicals.keys()
y_position = 90
# List for keeping chemical inputs variables in form of dict of list -> {A:[chemical_a_tc,chemical_a_pc,chemical_a_cp],
# B:[chemical_b_tc,chemical_b_pc,...],...}
self.chemical_inputs = dict()
# Creating labels for the chemical names
for name in self.chemical_names:
chemicalLabel = QLabel(name,scroll_widget)
chemicalLabel.move(70,y_position)
chemicalLabel.resize(75,30)
chemicalLabel.setStyleSheet("color:#000;")
chemicalLabel.setToolTip(name)
y_position += 40
current_chemical_inputs = dict()
for chemical_input in self.chemicals[name]:
current_chemical_inputs[chemical_input] = QLineEdit(scroll_widget)
self.chemical_inputs[name] = current_chemical_inputs
position_y = 90
for individual_chemical in self.chemical_inputs:
position_x = 160
for chemical_input in self.chemical_inputs[individual_chemical]:
self.chemical_inputs[individual_chemical][chemical_input].setText(str(self.data['chemicals'][individual_chemical][chemical_input]))
self.chemical_inputs[individual_chemical][chemical_input].move(position_x,position_y)
self.chemical_inputs[individual_chemical][chemical_input].resize(80,30)
self.chemical_inputs[individual_chemical][chemical_input].setStyleSheet("color:#000;background-color:#a9d6e5;padding:2px;")
position_x += 90
position_y += 40
def createButtons(self):
close_button = QPushButton('Close',self)
close_button.move(510,550)
close_button.resize(70,30)
close_button.setStyleSheet("background-color:#00509d;color:#fff;")
close_button.clicked.connect(self.closeButton)
def closeButton(self):
self.close()
What am I doing wrong?
Firstly, instead of using .move() to manually place your widgets, you should be using a QLayout (ex. QHBoxLayout or QVBoxLayout). This will automatically space your labels, and you can modify it by adjusting stretch and adding spacers (QSpacerItem). For more complex layouts, you can either nest multiple box layouts, or use a QGridLayout.
Now to address the scrolling:
First, you want to create your scroll area. Make this widget the central widget. Remember to set setWidgetResizable to True.
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
Next, create your container and add it to the scroll area. All your layout elements (labels, buttons, etc.) should be placed in this container.
self.container = QWidget()
scroller.setWidget(self.container)
Here's the full sample program I created:
import sys
from PyQt5.QtWidgets import QMainWindow, QWidget, QScrollArea, QVBoxLayout, QLabel, QApplication
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(1100, 800)
scroller = QScrollArea()
scroller.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.container = QWidget()
scroller.setWidget(self.container)
scroller.resize(self.width(),self.height())
scroller.setWidgetResizable(True)
self.setCentralWidget(scroller)
self.holderColumn=QVBoxLayout()
txtList=["apple","banana","orange","triangle","circle","square","moon","star","sun","delta"]
objs=list()
for i in txtList:
tempLabel=QLabel()
tempLabel.setText(i)
tempLabel.setFixedSize(300,300)
objs.append(tempLabel)
self.holderColumn.addWidget(tempLabel)
self.container.setLayout(self.holderColumn)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

In python3 tkinter, the wigdet frame doesn't show in interface

I use the same format of frame but it doesn't show in the interface, hope someone could tell me the solution, thanks.
class Interface(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.master.title("measurement")
self.grid()
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = Frame(self,relief=GROOVE,bg='white')
self.master.Frame1.grid(column=1,row=9)
self.can =Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = Canvas(self.master, width=150, height=120, background='snow')
ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = Tk()
window.resizable(False, False)
Interface(window).mainloop()
I can't figure out why you have 2 Canvas's, but the problem is that you aren't placing them on their respective parents. I cut out a lot of the code that seemed unnecessary and restructured your code to make it more logical:
class Interface(Frame):
def __init__(self, parent):
self.parent = parent
super().__init__(self.parent)
self.Frame1 = Frame(self, relief=GROOVE)
self.Frame1.grid()
self.canvas = Canvas(self.Frame1, bg="ivory", width=200, height=150)
self.canvas.grid()
self.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
root = Tk()
# Tk configurations are not relevant to
# the Interface and should be done out here
root.title('Measurement')
root.geometry('700x400+100+50')
root.resizable(False, False)
Interface(root).pack()
root.mainloop()
i think I don't really understand your problem, you don't see your frame because you don't have any widget in it, that's all
import tkinter as tk
class Interface(tk.Frame):
def __init__(self,parent=None):
tk.Frame.__init__(self,parent)
self.master.title("measurement")
self.grid(row=0, column=0)
# fix the size and parameters of widget
self.master.geometry("700x400+100+50")
self.master.Frame1 = tk.Frame(self,relief='groove',bg='white')
self.master.Frame1.grid(column=1,row=9)
labelExemple =tk.Label(self.master.Frame1, text="Exemple")
labelExemple.grid(row=0,column=0)
self.can = tk.Canvas(self, bg="ivory", width =200, height =150)
self.master.canvas = tk.Canvas(self.master, width=150, height=120, background='snow')
self.ligne1=self.master.canvas.create_line(75, 0, 75, 120)
if __name__ == "__main__":
window = tk.Tk()
window.resizable(False, False)
Interface(window).mainloop()
PS : use import tkinter as tk instead of from tkinter import *
There are several problems with those few lines of code, almost all having to do with the way you're using grid:
you aren't using the sticky option, so widgets won't expand to fill the space they are given
you aren't setting the weight for any rows or columns, so tkinter doesn't know how to allocate unused space
you aren't using grid or pack to put the canvases inside of frames, so the frames stay their default size of 1x1
The biggest problem is that you're trying to solve all of those problems at once. Layout problems are usually pretty simple to solve as long as you're only trying to solve one problem at a time.
Start by removing all of the widgets from Interface. Then, give that frame a distinctive background color and then try to make it fill the window (assuming that's ultimately what you want it to do). Also, remove the root.resizable(False, False). It's rarely something a user would want (they like to be able to control their windows), plus it makes your job of debugging layout problems harder.
Once you get your instance of Interface to appear, add a single widget and make sure it appears too. Then add the next, and the next, adding one widget at a time and observing how it behaves.

Pyqt4: How to correct QGraphicsItem position?

I'm trying to draw a path on a QGraphicsView. However, the position seems not right. The first point(red) is (0,0), which is supposed to be at the top-left corner. How do I move the drawing to the right position?
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import QPointF as qpf
import sys
from PyQt4.QtGui import QPainterPath
data= [qpf(0,0),qpf(40,30),qpf(30,60),qpf(70,90),qpf(20,120),qpf(60,150)]
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.view = View(self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.view)
class View(QtGui.QGraphicsView):
def __init__(self, parent):
QtGui.QGraphicsView.__init__(self, parent)
self.setScene(QtGui.QGraphicsScene(self))
item = QtGui.QGraphicsRectItem(data[0].x()-2,data[0].y()-2,4,4)
item.setBrush(QtCore.Qt.red)
self.scene().addItem(item)
self.path = path = QPainterPath(data[0])
for d in data[1:]:
path.lineTo(d)
item = QtGui.QGraphicsPathItem(path)
self.scene().addItem(item)
def mouseReleaseEvent(self, event):
pos = event.pos()
rect = QtCore.QRectF(pos.x()-2, pos.y()-2,4,4)
item = QtGui.QGraphicsRectItem(rect)
self.scene().addItem(item)
if self.path.intersects(rect):
print 'on line'
else:
print 'Not belong to line (%d, %d)' % (pos.x(), pos.y())
QtGui.QGraphicsView.mouseReleaseEvent(self, event)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(400, 400)
window.show()
sys.exit(app.exec_())
This behavior is because you draw using QGraphicsScene and QGraphicsView. This is because view and scene have some automatism, which are very convenient, normally. The scene coordinated can be completely different from the view (which is in pixels).
From the docs: "After you call show(), the view will by default scroll to the center of the scene and display any items that are visible at this point." show() is called implicitly in your case, I believe after you add an item to the scene.
I can think of two possibilities to get what you want:
1) change the view onto your scene, so that the scene-coordinate (0, 0) is in the upper left corner of your view.
2) Do NOT use QGraphicsScene and QGraphicsView, but just draw on a widget from its paint event as shown for example here. This means your dimensions are all in pixels, i.e. the coordinates of your points are pixels. And no automatism that might confuse you is done.
You would want to use self.setAlignment(QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) in your view class constructor, that will make 0,0 of the scene coordinates the same as 0,0 in view coordinates.AlignmentFlag
I would also suggest pos = self.mapToScene(event.pos()) in the mouseReleaseEvent. that way if you zoom in or scroll the scene, the click will happen in the right place in the view. QGraphicsView.mapFromScene

Is there a way to put the text of a QCheckBox above the icon?

I have a gridlayout that holds a bunch of check boxes. I wanted to add an image to the check boxes as well as some text. The problem I am having is that the layout of a check box is left to right (check box, icon, text).
Is there a way to put the text above the icon? Not sure if using a style sheet would work for this or not or even how that would look.
Thank you.
Answer : In PyQt4. No, your can't do it.
Why ? I read source code of QCheckBox Qt4 (C++) here and here. I saw it use default QStyleOptionButton to show check box, text and icon. It's use drawControl to draw all element in QStyleOptionButton by specified config in QStyleOptionButton. Also it have LayoutDirection. And layout direction in QStyleOptionButton. I don't know in Qt4 C++ and inheritance it and swap direction icon. But in PyQt4, It's impossible to do it.
Another way ? : Yes, It have another way to solve but not directly. Your just create your own widget just like QCheckBox and disable icon in QCheckBox and make your own QLabel ot show your icon and set it with same QLayout.
Example;
import sys
from PyQt4 import QtGui, QtCore
class QCustomCheckBox (QtGui.QWidget):
stateChanged = QtCore.pyqtSignal(int)
def __init__ (self, text, parentQWidget = None):
super(QCustomCheckBox, self).__init__(parentQWidget)
self.customQCheckBox = QtGui.QCheckBox(text)
self.iconQLabel = QtGui.QLabel()
allQHBoxLayout = QtGui.QHBoxLayout()
allQHBoxLayout.addWidget(self.customQCheckBox)
allQHBoxLayout.addWidget(self.iconQLabel)
allQHBoxLayout.addStretch(1)
self.setLayout(allQHBoxLayout)
self.customQCheckBox.stateChanged.connect(self.stateChanged.emit)
def setPixmap (self, newQPixmap, width = 48, height = 48):
self.iconQLabel.setPixmap(newQPixmap.scaled(width, height, QtCore.Qt.KeepAspectRatio))
def pixmap (self):
return self.iconQLabel.pixmap()
class QCustomWidget (QtGui.QWidget):
def __init__ (self, parent = None):
super(QCustomWidget, self).__init__(parent)
allQVBoxLayout = QtGui.QVBoxLayout()
firstQCustomCheckBox = QCustomCheckBox('First Check Box')
firstQCustomCheckBox.setPixmap(QtGui.QPixmap('1.jpg'))
allQVBoxLayout.addWidget(firstQCustomCheckBox)
secondQCustomCheckBox = QCustomCheckBox('Second Check Box')
secondQCustomCheckBox.setPixmap(QtGui.QPixmap('2.jpg'))
allQVBoxLayout.addWidget(secondQCustomCheckBox)
self.setLayout(allQVBoxLayout)
if __name__ == '__main__':
myQApplication = QtGui.QApplication(sys.argv)
myQCustomWidget = QCustomWidget()
myQCustomWidget.show()
sys.exit(myQApplication.exec_())

How do I size QLayout based on proportionality?

I'm creating a simple dialog box for my PySide application. Within this dialog, there are going to be multiple inputs that the user will have to fill out. Associated with those inputs are labels that go alongside the left of the labels. Right now I create the label, input pair using a separate class:
class inputLayout(PyGui.QHBoxLayout):
def __init__(self, Label, parent):
super(inputLayout, self).__init__()
label = PyGui.QLabel()
label.setText(Label)
self.addWidget(label)
self.__input = PyGui.QTextEdit()
self.addWidget(self.__input)
parent.addLayout(self)
and then add it to the master layout like so:
layout = PyGui.QVBoxLayout()
self.amp = inputLayout('Amplitude', layout)
self.test = inputLayout('test', layout)
self.test2 = inputLayout('test2', layout)
The problem is that when PySide does its automagic, it get something like the following:
Like my image suggests, I'd rather have the Label take up 1/3 (or some other proportional rate of my choice) to make it look more unified. How do I size the layout using this proportionality, or ratio?
I am aware of this question, however I'm not looking to statically set the size of the label, but rather do it dynamically using a ratio.
Here's a small example that should solve your problem. The key is in two parts:
Use a QGridLayout, which sets the column width to the width of the widest widget in its column (unless defined otherwise). This ensures that everything is aligned nicely along the vertical axis.
Set a stretch factor. This determines how an element should resize when their parent is resized. By default it's 0, so you don't have to set it, I just added it for illustration purposes. By setting the text_edit's column to 1, it will start stretching. By playing with the factors, you can make one column grow faster than the other.
from PySide import QtGui
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.input_widget = InputWidget(self)
self.layout = QtGui.QVBoxLayout()
self.layout.addWidget(self.input_widget)
self.setLayout(self.layout)
self.setCentralWidget(self.input_widget)
class InputWidget(QtGui.QWidget):
def __init__(self, parent):
super(InputWidget, self).__init__(parent)
self.grid_layout = QtGui.QGridLayout()
self.labels = ["amp", "more text", "blabla"]
self.text_edits = []
self.qlabels = []
for row, label in enumerate(self.labels):
label = QtGui.QLabel(label)
self.qlabels.append(label)
self.grid_layout.addWidget(label, row, 0)
text_edit = QtGui.QTextEdit()
self.text_edits.append(text_edit)
self.grid_layout.addWidget(text_edit, row, 1)
self.grid_layout.setColumnStretch(0, 0)
self.grid_layout.setColumnStretch(1, 2)
self.setLayout(self.grid_layout)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
If you want to access the text from the text_edit, you could search for the label's index in self.labels and then use that label in self.text_edits to retrieve the corresponding text_edit. Alternatively, once you close the dialog, you could loop through both self.labels and self.text_edits and create a dictionary that maps the label to the text from the text_edit.
results = {}
for label, text_edit in zip(self.labels, self.text_edits):
results[label] = text_edit.text()

Resources