I'm working on a very simple app in PyQt5 for displaying and sorting images. I'm new to Python and Qt in particular, and I've been having a few problems. I'm trying to display QPixmap images within QLabel objects, and they display properly. However, they're lined up in a row as default and I can't figure out how to move them.
(I know the code is terrible but I'm trying to get it working before I make it clean.)
Here is my code:
class ImageClassifier(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
hbox = QtWidgets.QHBoxLayout(self)
mainImg = QPixmap("img.png")
xmplImg0 = QPixmap("img0.png")
xmplImg1 = QPixmap("img1.png")
xmplImg2 = QPixmap("img2.png")
xmplImg3 = QPixmap("img3.png")
lbl = QtWidgets.QLabel(self)
lbl0 = QtWidgets.QLabel(self)
lbl1 = QtWidgets.QLabel(self)
lbl2 = QtWidgets.QLabel(self)
lbl3 = QtWidgets.QLabel(self)
lbl.setPixmap(mainImg)
lbl0.setPixmap(xmplImg0)
lbl1.setPixmap(xmplImg1)
lbl2.setPixmap(xmplImg2)
lbl3.setPixmap(xmplImg3)
hbox.addWidget(lbl)
hbox.addWidget(lbl0)
hbox.addWidget(lbl1)
hbox.addWidget(lbl2)
hbox.addWidget(lbl3)
lbl0.setGeometry(30, 30, 30, 30)
self.setLayout(hbox)
self.move(300, 200)
self.setWindowTitle('Fruit Classifier')
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ic = ImageClassifier()
sys.exit(app.exec_())
The lbl0.setGeometry() function isn't doing anything at all and I don't have a clue why. Does anyone know of a way to fix this, or a better way to set the position of a QLabel object?
Follow the following bullets.
When you add something to a QHBoxLayout/QVBoxLayout... you also have to set the geometry of your BoxLayout as well, not just of one element inside it.
Everything you throw inside a BoxLayout it will adjust to it's parent(BoxLayout).
Try setting the geometry of all your elements.
Try setting the geometry of your BoxLayout
If still doesn't work try setting ".setFixedSize(_width,_height)" also for all elements & BoxLayout, as you need.
[Update]
Look what I'm trying to say. Better with a example:
If you use a BoxLayout the position of your objects won't obey what you want. It will follow the BoxLayout policy. You also passed (self) for the QLabels what makes the QWidget its parent. That's a mess.
Check it out the following code.
I have no BoxLayout and I just said who is my(Qlabel)'s parent. So I'll behave as my "daddy" says. So now you can move it along.
import sys
from PyQt5 import QtWidgets
class ImageClassifier(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.init_ui()
def init_ui(self):
self.setFixedSize(500,500)
lbl = QtWidgets.QLabel(self)
lbl.setFixedSize(50,50)
lbl0 = QtWidgets.QLabel(self)
lbl0.setFixedSize(50, 50)
lbl1 = QtWidgets.QLabel(self)
lbl1.setFixedSize(50, 50)
lbl2 = QtWidgets.QLabel(self)
lbl2.setFixedSize(50, 50)
lbl3 = QtWidgets.QLabel(self)
lbl3.setFixedSize(50, 50)
lbl.setStyleSheet("""background-color: red;""")
lbl0.setStyleSheet("""background-color: green;""")
lbl1.setStyleSheet("""background-color: blue;""")
lbl2.setStyleSheet("""background-color: yellow;""")
lbl3.setStyleSheet("""background-color: black;""")
lbl0.move(50,50)
lbl1.move(100, 100)
lbl2.move(150, 150)
lbl3.move(150, 150)
self.move(300, 200)
self.setWindowTitle('Fruit Classifier')
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ic = ImageClassifier()
sys.exit(app.exec_())
That's just a simple way to do it. You better create your own Label that inherit QLabel. And personalize as you wish.
Related
In the list_widget I have added a add button I also want to add a remove button which asks which item you wants to remove and remove the chosen item. I was trying it to do but I didn't had any idea to do so .Also, please explain the solution I am a beginner with pyqt5 or I'd like to say absolute beginner.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QApplication,QMainWindow,
QListWidget, QListWidgetItem
import sys
class MyWindow(QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
self.x = 200
self.y = 200
self.width = 500
self.length = 500
self.setGeometry(self.x, self.y, self.width,
self.length)
self.setWindowTitle("Stock managment")
self.iniTUI()
def iniTUI(self):
# buttons
self.b1 = QtWidgets.QPushButton(self)
self.b1.setText("+")
self.b1.move(450, 100)
self.b1.resize(50, 25)
self.b1.clicked.connect(self.take_inputs)
# This is the button I want to define.
self.btn_minus = QtWidgets.QPushButton(self)
self.btn_minus.setText("-")
self.btn_minus.move(0, 100)
self.btn_minus.resize(50, 25)
# list
self.list_widget = QListWidget(self)
self.list_widget.setGeometry(120, 100, 250, 300)
self.item1 = QListWidgetItem("A")
self.item2 = QListWidgetItem("B")
self.item3 = QListWidgetItem("C")
self.list_widget.addItem(self.item1)
self.list_widget.addItem(self.item2)
self.list_widget.addItem(self.item3)
self.list_widget.setCurrentItem(self.item2)
def take_inputs(self):
self.name, self.done1 =
QtWidgets.QInputDialog.getText(
self, 'Add Item to List', 'Enter The Item you want
in
the list:')
self.roll, self.done2 = QtWidgets.QInputDialog.getInt(
self, f'Quantity of {str(self.name)}', f'Enter
Quantity of {str(self.name)}:')
if self.done1 and self.done2:
self.item4 = QListWidgetItem(f"{str(self.name)}
Quantity{self.roll}")
self.list_widget.addItem(self.item4)
self.list_widget.setCurrentItem(self.item4)
def clicked(self):
self.label.setText("You clicked the button")
self.update()
def update(self):
self.label.adjustSize()
def clicked():
print("meow")
def window():
apk = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(apk.exec_())
window()
The core issue here is the lack of separation of the view and the data. This makes it very hard to reason about how to work with graphical elements. You will almost certainly want to follow the Model View Controller design paradigm https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
which offers a systematic way to handle this separation.
Once you do so, it immediately becomes very straight forward how to proceed with the question: You essentially just have a list, and you either want to add a thing to this list, or remove one based on a selection.
I include an example here which happens to use the built-in classes QStringListModel and QListView in Qt5, but it is simple to write your own more specialized widgets and models. They all just use a simple signal to emit to the view that it needs to refresh the active information.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
class StuffViewer(QMainWindow):
def __init__(self, model):
super().__init__()
self.setWindowTitle("Stock managment")
# 1: Use layouts.
hbox = QHBoxLayout()
widget = QWidget()
widget.setLayout(hbox)
self.setCentralWidget(widget)
# 2: Don't needlessly store things in "self"
vbox = QVBoxLayout()
add = QPushButton("+")
add.clicked.connect(self.add_new_stuff)
vbox.addWidget(add)
sub = QPushButton("-")
sub.clicked.connect(self.remove_selected_stuff)
vbox.addWidget(sub)
vbox.addStretch(1)
hbox.addLayout(vbox)
# 3: Separate the view of the data from the data itself. Use Model-View-Controller design to achieve this.
self.model = model
self.stuffview = QListView()
self.stuffview.setModel(self.model)
hbox.addWidget(self.stuffview)
def add_new_stuff(self):
new_stuff, success = QInputDialog.getText(self, 'Add stuff', 'Enter new stuff you want')
if success:
self.stuff.setStringList(self.stuff.stringList() + [new_stuff])
def remove_selected_stuff(self):
index = self.stuffview.currentIndex()
all_stuff = self.stuff.stringList()
del all_stuff[index.column()]
self.stuff.setStringList(all_stuff)
def window():
apk = QApplication(sys.argv)
# Data is clearly separated:
# 4: Never enumerate variables! Use lists!
stuff = QStringListModel(["Foo", "Bar", "Baz"])
# The graphical components is just how you interface with the data with the user!
win = StuffViewer(stuff)
win.show()
sys.exit(apk.exec_())
window()
I am trying the whole morning already to fix that.
So I have a PyQt Main Window where I want to display two widgets.
In the first widget there are articles listed (which works so far).
When I click on them until now a QMessageBox is opening, but I want that
a second widget is opening where I can read the RSS Feed.
But this is not working. See Code below:
class ArticleWidgets(QWidget):
def __init__(self, *args):
super().__init__(*args)
self.setGeometry(610, 610, 600, 600)
self.initUi()
def initUi(self):
self.box = QHBoxLayout(self)
def show(self, feed=None):
self.title = QLabel()
self.summary = QLabel()
self.link = QLabel()
if feed:
self.title.setText(feed[0])
self.summary.setText(feed[1])
self.link.setText(feed[2])
self.box.addWidget(self.title)
self.box.addWidget(self.summary)
self.box.addWidget(self.link)
self.setLayout(self.box)
class TitleWidgets(QWidget):
def __init__(self, *args):
super().__init__(*args)
self.setGeometry(10, 10, 600, 600)
self.initUi()
def initUi(self):
vbox = QHBoxLayout(self)
self.titleList = QListWidget()
self.titleList.itemDoubleClicked.connect(self.onClicked)
self.titleList.setGeometry(0, 0, 400, 400)
self.news = ANFFeed()
for item in self.news.all_feeds:
self.titleList.addItem(item[0])
vbox.addWidget(self.titleList)
def onClicked(self, item):
feeds = self.news.all_feeds
id = 0
for elem in range(len(feeds)):
if feeds[elem][0] == item.text():
id = elem
summary = feeds[id][1] + '\n\n'
link = feeds[id][2]
if feeds and id:
#ANFApp(self).show_articles(feeds[id])
show = ANFApp()
show.show_articles(feed=feeds[id])
QMessageBox.information(self, 'Details', summary + link)
class ANFApp(QMainWindow):
def __init__(self, *args):
super().__init__(*args)
self.setWindowState(Qt.WindowMaximized)
self.setWindowIcon(QIcon('anf.png'))
self.setAutoFillBackground(True)
self.anfInit()
self.show()
def anfInit(self):
self.setWindowTitle('ANF RSS Reader')
TitleWidgets(self)
#article_box = ArticleWidgets(self)
exitBtn = QPushButton(self)
exitBtn.setGeometry(600, 600, 100, 50)
exitBtn.setText('Exit')
exitBtn.setStyleSheet("background-color: red")
exitBtn.clicked.connect(self.exit)
def show_articles(self, feed=None):
present = ArticleWidgets()
present.show(feed)
def exit(self):
QCoreApplication.instance().quit()
Solution using Pyqtgraph's Docks and QTextBrowser
Here is a code trying to reproduce your sketch. I used the Pyqtgraph module (Documentation here: Pyqtgraph's Documentation and Pyqtgraph's Web Page) because its Dock widget is easier to use and implement from my perspective.
You must install the pyqtgraph module before trying this code:
import sys
from PyQt5 import QtGui, QtCore
from pyqtgraph.dockarea import *
class DockArea(DockArea):
## This is to prevent the Dock from being resized to te point of disappear
def makeContainer(self, typ):
new = super(DockArea, self).makeContainer(typ)
new.setChildrenCollapsible(False)
return new
class MyApp(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
central_widget = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
central_widget.setLayout(layout)
self.setCentralWidget(central_widget)
label = QtGui.QLabel('This is a label, The widgets will be below')
label.setMaximumHeight(15)
## The DockArea as its name says, is the are where we place the Docks
dock_area = DockArea(self)
## Create the Docks and change some esthetic of them
self.dock1 = Dock('Widget 1', size=(300, 500))
self.dock2 = Dock('Widget 2', size=(400, 500))
self.dock1.hideTitleBar()
self.dock2.hideTitleBar()
self.dock1.nStyle = """
Dock > QWidget {
border: 0px solid #000;
border-radius: 0px;
}"""
self.dock2.nStyle = """
Dock > QWidget {
border: 0px solid #000;
border-radius: 0px;
}"""
self.button = QtGui.QPushButton('Exit')
self.widget_one = WidgetOne()
self.widget_two = WidgetTwo()
## Place the Docks inside the DockArea
dock_area.addDock(self.dock1)
dock_area.addDock(self.dock2, 'right', self.dock1)
## The statment above means that dock2 will be placed at the right of dock 1
layout.addWidget(label)
layout.addWidget(dock_area)
layout.addWidget(self.button)
## Add the Widgets inside each dock
self.dock1.addWidget(self.widget_one)
self.dock2.addWidget(self.widget_two)
## This is for set the initial size and posotion of the main window
self.setGeometry(100, 100, 600, 400)
## Connect the actions to functions, there is a default function called close()
self.widget_one.TitleClicked.connect(self.dob_click)
self.button.clicked.connect(self.close)
def dob_click(self, feed):
self.widget_two.text_box.clear()
## May look messy but wat i am doing is somethin like this:
## 'Title : ' + feed[0] + '\n\n' + 'Summary : ' + feed[1]
self.widget_two.text_box.setText(
'Title : ' + feed[0]\
+ '\n\n' +\
'Summary : ' + feed[1]
)
class WidgetOne(QtGui.QWidget):
## This signal is created to pass a "list" when it (the signal) is emited
TitleClicked = QtCore.pyqtSignal([list])
def __init__(self):
QtGui.QWidget.__init__(self)
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
self.titleList = QtGui.QListWidget()
self.label = QtGui.QLabel('Here is my list:')
self.layout.addWidget(self.label)
self.layout.addWidget(self.titleList)
self.titleList.addItem(QtGui.QListWidgetItem('Title 1'))
self.titleList.addItem(QtGui.QListWidgetItem('Title 2'))
self.titleList.itemDoubleClicked.connect(self.onClicked)
def onClicked(self, item):
## Just test values
title = item.text()
summary = "Here you will put the summary of {}. ".format(title)*50
## Pass the values as a list in the signal. You can pass as much values
## as you want, remember that all of them have to be inside one list
self.TitleClicked.emit([title, summary])
class WidgetTwo(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
self.label2 = QtGui.QLabel('Here we show results?:')
self.text_box = QtGui.QTextBrowser()
self.layout.addWidget(self.label2)
self.layout.addWidget(self.text_box)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())
Again, there are comments inside the code to help you understand what I did.
Here is how it looks:
If you pass the mouse between the two widgets you will see the mouse icon will change, with that you can readjust on the run the size of both widgets.
Final Words
This is another approach, more "interactive" and more esthetic than my previous answer. As you said, using a QSplitter works too.
Problems
The way you are building your GUI is, in my opinion, messy and it may lead to errors. I suggest the use of Layouts for a more organized GUI.
The other problem is that each widget is an independent class so if you want to connect an action in one widget to do something in the other widget through the Main Window, you must use Signals.
Edit : Another suggestion, use other name for the close function instead of exit and try using self.close() instead of QCoreApplication.instance().quit()
Solution
Trying to emulate what you want to do I made this GUI:
import sys
from PyQt5 import QtGui, QtCore
class MyWindow(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
## Generate the structure parts of the MainWindow
self.central_widget = QtGui.QWidget() # A QWidget to work as Central Widget
self.layout1 = QtGui.QVBoxLayout() # Vertical Layout
self.layout2 = QtGui.QHBoxLayout() # Horizontal Layout
self.widget_one = WidgetOne()
self.widget_two = WidgetTwo()
self.exitBtn = QtGui.QPushButton('Exit')
## Build the structure
# Insert a QWidget as a central widget for the MainWindow
self.setCentralWidget(self.central_widget)
# Add a principal layout for the widgets/layouts you want to add
self.central_widget.setLayout(self.layout1)
# Add widgets/layuts, as many as you want, remember they are in a Vertical
# layout: they will be added one below of the other
self.layout1.addLayout(self.layout2)
self.layout1.addWidget(self.exitBtn)
# Here we add the widgets to the horizontal layout: one next to the other
self.layout2.addWidget(self.widget_one)
self.layout2.addWidget(self.widget_two)
## Connect the signal
self.widget_one.TitleClicked.connect(self.dob_click)
def dob_click(self, feed):
## Change the properties of the elements in the second widget
self.widget_two.title.setText('Title : '+feed[0])
self.widget_two.summary.setText('Summary : '+feed[1])
## Build your widgets same as the Main Window, with the excepton that here you don't
## need a central widget, because it is already a widget.
class WidgetOne(QtGui.QWidget):
TitleClicked = QtCore.pyqtSignal([list]) # Signal Created
def __init__(self):
QtGui.QWidget.__init__(self)
##
self.layout = QtGui.QVBoxLayout() # Vertical Layout
self.setLayout(self.layout)
self.titleList = QtGui.QListWidget()
self.label = QtGui.QLabel('Here is my list:')
self.layout.addWidget(self.label)
self.layout.addWidget(self.titleList)
self.titleList.addItem(QtGui.QListWidgetItem('Title 1'))
self.titleList.addItem(QtGui.QListWidgetItem('Title 2'))
self.titleList.itemDoubleClicked.connect(self.onClicked)
def onClicked(self, item):
## Just test parameters and signal emited
self.TitleClicked.emit([item.text(), item.text()+item.text()])
class WidgetTwo(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.layout = QtGui.QVBoxLayout()
self.setLayout(self.layout)
self.title = QtGui.QLabel('Title : ---')
self.summary = QtGui.QLabel('Summary : ---')
self.link = QtGui.QLabel('Link : ---')
self.layout.addWidget(self.title)
self.layout.addWidget(self.summary)
self.layout.addWidget(self.link)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Inside the code, there are comments to help you understand why I did to build an organized GUI. There is also an example of a Signal being used to connect the action of itemDoubleClicked from the first widget to the second one. Here is how the MainWindow looks:
It is not very clear how the layouts work just from seeing the result, so I did a little paint over to a better understanding:
The blue box is the vertical layout (QVBoxLayout) and the red one is the horizontal layout (QHBoxLayout). Inside the blue layout, are located the red layout (above) and the exit button (below); and inside the red layout, are located the widget_1 (left) and the widget_2 (right).
Other Solution
An "easier" solution will be building the widgets inside the MainWindow instead of creating separate classes. With this you will avoid the use of signals, but the code will become a little more confusing because all the code will be cramped in one class.
I am trying to find a way to have one of my widgets maintain a width based on its height. I have other widgets working fine reimplementing the heightForWidth method. That was easy because that method is standard. I know there is no included widthForHeight method so I have tried many options the internet has suggested but have not gotten anything to work all the way. What I currently have almost gets me there.
I first reimplement my widget's sizeHint to get its width to be a ratio of its parent's (QHBoxLayout) height.
The sizeHint makes MyCustomLabel show with the right size at first show but did not update during times the user resized the window. I don't know if this the best way but to fix that I am reimplementing resizeEvent and calling adjustSize to force the sizeHint recalculation.
With those two reimplemented methods MyCustomLabel shows with the right size. I placed this custom widget in a QHBoxLayout with a few other standard widgets. The problem is the other widgets in the layout don't respect the new size of my MyCustomLabel when the user resizes the window. What I end up with is the other widgets in the layout either overlapping or being placed too far from MyCustomLabel. I kind of get it, I am brute forcing my widget to a size and not letting the layout do the work. However I thought updating the sizeHint would inform the layout of MyCustomLabel's new size and adjust everything accordingly. How do I fix this layout problem or am I going about this widthForHeight problem all the wrong way?
Edit:
I tried #AlexanderVX suggestion of setting the SizePolicy to Minimum and while it does prevent the other widgets from overlapping it also locked MyCustomLabel to a fixed size. I need the widget to expand and shrink with the layout. I also tried Preferred, Expanding, MinimumExpanding policies just to see if they would do anything but with no luck.
from __future__ import division
from PySide import QtCore
from PySide import QtGui
import sys
class MyCustomLabel(QtGui.QLabel):
clicked = QtCore.Signal(int)
dblClicked = QtCore.Signal(int)
def __init__(self, leadSide='height', parent=None):
super(MyCustomLabel, self).__init__()
self.leadSide = leadSide
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
# QtGui.QSizePolicy.Preferred)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,
# QtGui.QSizePolicy.Expanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding,
# QtGui.QSizePolicy.MinimumExpanding)
# sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum,
# QtGui.QSizePolicy.Minimum)
# self.setSizePolicy(sizePolicy)
def resizeEvent(self, event):
super(MyCustomLabel, self).resizeEvent(event)
self.adjustSize()
def sizeHint(self):
super(MyCustomLabel, self).sizeHint()
parentSize = self.parent().size().toTuple()
if self.leadSide.lower() == 'height':
new_size = QtCore.QSize(parentSize[1] * (16 / 9), parentSize[1]) * .9
if self.leadSide.lower() == 'width':
new_size = QtCore.QSize(parentSize[0], parentSize[0] / (16 / 9)) * .9
return new_size
class __Test__(QtGui.QWidget):
def __init__(self):
super(__Test__, self).__init__()
self.initUI()
def initUI(self):
customLabel = MyCustomLabel(leadSide='height')
# customLabel.setScaledContents(True)
customLabel.setStyleSheet('background-color: blue;'
'border:2px solid red;')
btn01 = QtGui.QPushButton('button')
btn01.setFixedHeight(80)
textEdit = QtGui.QTextEdit()
textEdit.setFixedSize(150, 150)
layout01 = QtGui.QHBoxLayout()
layout01.setContentsMargins(0,0,0,0)
layout01.setSpacing(0)
layout01.addWidget(customLabel)
layout01.addWidget(btn01)
layout01.addWidget(textEdit)
self.setLayout(layout01)
self.setGeometry(300, 300, 600, 300)
self.setWindowTitle('Testing')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = __Test__()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I never tried creating a GUI with a language other than Java(kinda left it aside not long ago)
and started using Python.
made a simple program that calculates Pi to a certain digit as the user wishes.
Now, I created a window with PyQt4, made a button and got everything in place.
How can I add a input box so that the user could enter a number into it, make the button "Enter" the information and at the end of all that output it to the window instead of the terminal?
That's what I've got for now:
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from decimal import *
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 800, 600)
self.setWindowTitle("Pi's Nth Digit")
self.setWindowIcon(QtGui.QIcon('icon.jpg'))
self.buttons()
def buttons(self):
btn = QtGui.QPushButton("Quit",self)
btn1 = QtGui.QPushButton("Get Pi",self)
btn.clicked.connect(QtCore.QCoreApplication.instance().quit)
btn1.clicked.connect(self.getpi())
btn1.resize(btn1.sizeHint())
btn.resize(btn.sizeHint())
btn1.move(350,500)
btn.move(450,500)
self.show()
def start():
app = QtGui.QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
start()
don't mind the getpi function.
Thanks! :)
You would want to use a QLineEdit or a QSpinBox for a number. If you want multiple things in a widget you would use a layout. A QMainWindow typically has one central widget and toolbars and dock widgets.
class Window(QtGui.QMainWindow):
def __init__(self):
super().__init__()
self.container = QtGui.QWidget()
self.setCentralWidget(self.container)
self.container_lay = QtGui.QVBoxLayout()
self.container.setLayout(self.container_lay)
# Input
self.le = QtGui.QLineEdit()
self.container_lay.addWidget(self.le)
# enter button
self.enter_btn = QtGui.QPushButton("Enter")
self.container_lay.addWidget(self.enter_btn)
self.enter_btn.clicked.connect(self.run) # No '()' on run you want to reference the method.
# display
self.container_lay.addWidget(QtGui.QLabel("Answer:"))
self.ans = QtGui.QLabel()
self.container_lay.addWidget(self.ans)
def run(self):
precision = self.le.text()
pi = str(round(math.pi, precision)) # probably different formatting
self.ans.setText(pi)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
You have almost everything, just add a QLineEdit to get the input and a QLabel where to show the result (with QLabel.setText).
I have this sample of code:
import sys
from PyQt4.QtGui import (QApplication, QHBoxLayout, QVBoxLayout, QDialog,
QFrame, QPushButton, QComboBox)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
moreButton = QPushButton('moreButton')
moreButton.setCheckable(True)
resizeButton = QPushButton('Resize')
combo = QComboBox()
combo.addItems(['item1', 'item2'])
layout1 = QHBoxLayout()
layout1.addWidget(moreButton)
layout1.addWidget(resizeButton)
layout2 = QHBoxLayout()
layout2.addWidget(combo)
self.frame = QFrame()
self.frame.setLayout(layout2)
self.frame.hide()
layout3 = QVBoxLayout()
layout3.addLayout(layout1)
layout3.addWidget(self.frame)
moreButton.toggled.connect(self.frame.setVisible)
moreButton.clicked.connect(self.method)
resizeButton.clicked.connect(self.method)
self.setLayout(layout3)
self.resize(630, 50)
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 250)
app = QApplication(sys.argv)
form = Form()
form.show()
app.exec_()
I run it and when moreButton clicked the ComboBox appears or disappears. Dialog's size also changes. But if I change the method to:
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 50)
(in order to set the initial size when combo is hidden) the resizing does not work. However, if I click resizeButton -which is connected to the same method- the resizing works properly.
I know that there are other ways to achieve such a result (eg. layout.setSizeConstraint(QLayout.SetFixedSize)), but I want to declare size explicitly.
What am I doing wrong?
My guess is that you are trying to resize the QDialog before it has time to re-adjust its size after you hide stuff. So at the time resize is called it has a minimumSize that makes sure the buttons and the combobox visible. When you call it after some time, it now has proper miminumSize and responds properly.
A quick fix is manually overriding minimumSize before resizing:
def method(self):
if self.frame.isVisible():
# uncomment below, if you like symmetry :)
# self.setMinimumSize(630, 150)
self.resize(630, 150)
else:
self.setMinimumSize(630, 50)
self.resize(630, 50)
But, if I were to tackle this, I'd just leave managing resizing to the layout and use sizeConstraint. That's what these layouts for anyways.
This question and the answers were helpful in my situation: Automatic sizing of a QDialog with QLayout/QVBoxLayout containing a QLabel of variable size content/message to the user while also avoiding the double arrow cursor at the border of the overall QDialog container. The sizePolicy of the dialog itself was set to Fixed and yet the arrows would appear even though it cannot be resized (wouldn't budge). And even though the inner layout does automatically/magically resize, using SetFixedSize on the layout (surprise, surprise) made those annoying double arrows of the overall QDialog go away.
QDialog: QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
QLayout: setSizeConstraint(QLayout.SetFixedSize)
QLabel: QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
...and now the dialog sizes itself appropriately to the volume contained in the label yet the dialog itself is not (seemingly) user-resizable, which is good for info and error messages.
Seemed counterintuitive to me so I thought worth adding here for others.
A bit more detail...
self.label = QtGui.QLabel(self)
self.label.sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.label.setSizePolicy(self.label.sizePolicy)
self.label.setMinimumSize(QtCore.QSize(450, 40))
self.label.setWordWrap(True)
self.verticalLayout = QtGui.QVBoxLayout(self)
# get rid of overall dialog resize arrows
self.verticalLayout.setSizeConstraint(QtGui.QLayout.SetFixedSize) # no resize arrows
self.verticalLayout.addWidget(self.label)
This problem appears to be caused by the order in which events are processed.
Here's a simple fix:
def method(self):
app.processEvents()
if self.frame.isVisible():
self.resize(630, 150)
else:
self.resize(630, 50)
did you try this ? unless i misunderstand, this is what you wanna do.
def method(self):
if self.frame.isVisible():
self.resize(630, 150)
self.frame.setVisible(False)
else:
self.resize(630, 50)
edit : the final answer is layout3.setSizeConstraint(QLayout.SetNoConstraint)