Moving on from my last question, I'm stuck once again. I'm trying to update content of parent widget from child widget. The code seems to work first time but after closing and re-opening the form widget it does not update the parent widget.
Following is the code.
from PyQt4 import QtGui, QtCore
from functools import partial
import sys
class MainWidget(QtGui.QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.main_widget()
def main_widget(self):
self.form = Form()
self.simple = Simple()
grid = QtGui.QGridLayout()
self.last_input_label = QtGui.QLabel("")
grid.addWidget(self.last_input_label, 1, 0, 3, 1)
show_form_button = QtGui.QPushButton("Show Form")
show_form_button.clicked.connect(partial(self.form.show_form, self.last_input_label))
grid.addWidget(show_form_button, 0, 0)
show_simple_button = QtGui.QPushButton("Show Simple")
show_simple_button.clicked.connect(self.simple.show_simple)
grid.addWidget(show_simple_button, 0, 1)
another_button = QtGui.QPushButton("Print Hello")
another_button.clicked.connect(partial(print, "Hello"))
grid.addWidget(another_button, 0, 2)
self.setLayout(grid)
self.setWindowTitle("Main Widget")
self.show()
def closeEvent(self, QCloseEvent):
QtGui.QApplication.closeAllWindows()
class Form(QtGui.QWidget):
def __init__(self):
print("form initialized")
super(Form, self).__init__()
def show_form(self, last_input_label):
print("form called")
grid = QtGui.QGridLayout()
self.last_input_label = last_input_label
label = QtGui.QLabel("Name")
grid.addWidget(label, 0, 0)
self.line_edit = QtGui.QLineEdit()
grid.addWidget(self.line_edit, 0, 1)
self.submit_button = QtGui.QPushButton("Submit")
self.submit_button.clicked.connect(self.print_form_data)
grid.addWidget(self.submit_button, 1, 1)
self.setLayout(grid)
self.setGeometry(250, 300, 250, 150)
self.setWindowTitle("Form Widget")
self.show()
def get_form_data(self):
form_data = {
"name": self.line_edit.text()
}
return form_data
def print_form_data(self):
self.x = self.get_form_data()
for item in self.x:
print(item + ": " + self.x[item])
self.last_input_label.setText(self.x[item])
return
class Simple(QtGui.QDialog):
def __init__(self):
print("simple initialized")
super(Simple, self).__init__()
def show_simple(self):
print("simple called")
self.setGeometry(300, 250, 250, 150)
self.setWindowTitle("Simple Widget")
self.show()
def main():
app = QtGui.QApplication(sys.argv)
main_widget = MainWidget()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Please Help!
You're calling the initialization code each time you show the widget. Move all that into the __init__ method where it belongs and it all works.
Move everything beside this into the init method. I can't say exactly why running the init code more would break the connection. But somehow it does. Maybe someone else can fill in that detail.
def show_form(self, last_input_label):
print("form called")
self.last_input_label = last_input_label
self.show()
Related
There is a QWidget, which is called with the button "Start" in the main widget.
It shows some text, which should be updated every minute during the work of the QWidget.
How can I make this infinity updating within this code?
class ExampleWidget(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle('Example Widget ScrollArea')
self.initUi()
def initUi(self):
area = QScrollArea(self)
area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QLabel(some_text, self)
area.setWidget(self.scrollAreaWidgetContents)
button = QPushButton("Close")
button.clicked.connect(self.goMainWindow)
layoutV = QVBoxLayout()
layoutV.addWidget(area)
layoutV.addWidget(button)
self.setLayout(layoutV)
def goMainWindow(self):
self.hide()
def sizeHint(self):
return QSize(400, 200)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
start_main_button = QPushButton('Start', self)
start_main_button.move(40, 40)
start_main_button.clicked.connect(self.start)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Test')
def start(self):
global some_text
some_text = 'some text'
self.result_widget = ExampleWidget()
self.result_widget.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
One of my tries:
def start(self):
global some_text
some_text = 'some text'
self.result_widget = ExampleWidget()
self.result_widget.show()
i = 0
while True:
i+=1
some_text+=str(i)
self.result_widget = ExampleWidget()
self.result_widget.show()
Forget the global variables because they are considered bad practice, on the other hand in a GUI you should avoid having loops that consume a lot of time, and in your case a while True blocks the GUI. In Qt if you want to do periodic tasks you must use a QTimer:
from PyQt5 import QtCore, QtWidgets
class ExampleWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle('Example Widget ScrollArea')
self.initUi()
def initUi(self):
area = QtWidgets.QScrollArea()
area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QtWidgets.QLabel("some_text")
area.setWidget(self.scrollAreaWidgetContents)
button = QtWidgets.QPushButton("Close")
button.clicked.connect(self.hide)
layoutV = QtWidgets.QVBoxLayout(self)
layoutV.addWidget(area)
layoutV.addWidget(button)
def update_text(self, text):
# update the text
self.scrollAreaWidgetContents.setText(text)
def sizeHint(self):
return QtCore.QSize(400, 200)
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
start_main_button = QtWidgets.QPushButton('Start', self)
start_main_button.move(40, 40)
start_main_button.clicked.connect(self.start)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Test')
self.result_widget = ExampleWidget()
self.timer = QtCore.QTimer(self, interval=60*1000)
self.timer.timeout.connect(self.on_timeout)
self.counter = 0
self.initial_text = "some_text"
def on_timeout(self):
# this method will be called every 60 * 1000 ms
self.initial_text += str(self.counter)
self.result_widget.update_text(self.initial_text)
self.counter += 1
#QtCore.pyqtSlot()
def start(self):
self.result_widget.show()
# start timer
self.timer.start()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
I tried to make some simple local chatting app for practice. I make a button to send a message every time i push the button the message displayed but the scrollarea become more narrow and not expanded the scrollarea. Am I missing something or added something i shouldn't in my code? how can i fix this?
Here is my code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Bubble(QLabel):
def __init__(self,text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5,5,5,5)
def paintEvent(self, e):
p = QPainter(self)
p.setRenderHint(QPainter.Antialiasing,True)
p.drawRoundedRect(0,0,self.width()-1,self.height()-1,5,5)
super(Bubble,self).paintEvent(e)
class MyWidget(QWidget):
def __init__(self,text,left=True):
super(MyWidget,self).__init__()
hbox = QHBoxLayout()
label = Bubble(text)
if not left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.addWidget(label)
if left:
hbox.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Preferred))
hbox.setContentsMargins(0,0,0,0)
self.setLayout(hbox)
self.setContentsMargins(0,0,0,0)
class Chatting(QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
self.vbox = QVBoxLayout()
for _ in range(20):
self.vbox.addWidget(MyWidget("Left side"))
widget = QWidget()
widget.setLayout(self.vbox)
scroll = QScrollArea()
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setWidgetResizable(False)
scroll.setWidget(widget)
#Scroll Area Layer add
vLayout = QVBoxLayout(self)
vLayout.addWidget(scroll)
send = QPushButton('')
send.setIcon(QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QSize(84,20))
send.clicked.connect(self.send_messages)
vLayout.addWidget(send)
self.setLayout(vLayout)
def send_messages(self):
self.vbox.addWidget(MyWidget('testing'))
The problem is simple you have to enable the widgetResizable property to True since that property allows the QScrollArea to calculate the size of the content.
scroll.setWidgetResizable(True)
On the other hand I have taken the time to improve your code and I show it below:
from PyQt4 import QtGui, QtCore
class Bubble(QtGui.QLabel):
def __init__(self, text):
super(Bubble,self).__init__(text)
self.setContentsMargins(5, 5, 5, 5)
def paintEvent(self, e):
p = QtGui.QPainter(self)
p.setRenderHint(QtGui.QPainter.Antialiasing, True)
p.drawRoundedRect(self.rect().adjusted(0, 0, -1, -1), 5, 5)
super(Bubble, self).paintEvent(e)
class MyWidget(QtGui.QWidget):
def __init__(self, text, left=True):
super(MyWidget,self).__init__()
lay = QtGui.QVBoxLayout(self)
lay.setContentsMargins(0, 0, 0, 0)
self.setContentsMargins(0, 0, 0, 0)
label = Bubble(text)
lay.addWidget(label, alignment= QtCore.Qt.AlignLeft if left else QtCore.Qt.AlignRight)
class Chatting(QtGui.QWidget):
def __init__(self, parent=None):
super(Chatting, self).__init__(parent)
widget = QtGui.QWidget()
self.vbox = QtGui.QVBoxLayout(widget)
self.vbox.addStretch()
self.scroll = QtGui.QScrollArea(widgetResizable=True)
self.scroll.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.scroll.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.scroll.setWidget(widget)
#Scroll Area Layer add
send = QtGui.QPushButton(icon= QtGui.QIcon('images/send.png'))
send.setStyleSheet("background-color:#d00001; width: 44px")
send.setIconSize(QtCore.QSize(84,20))
send.clicked.connect(self.on_clicked)
vLayout = QtGui.QVBoxLayout(self)
vLayout.addWidget(self.scroll)
vLayout.addWidget(send)
def send_message(self, text, direction=True):
widget = MyWidget(text, direction)
self.vbox.insertWidget(self.vbox.count()-1, widget)
scroll_bar = self.scroll.verticalScrollBar()
# move to end
QtCore.QTimer.singleShot(10, lambda: scroll_bar.setValue(scroll_bar.maximum()))
#QtCore.pyqtSlot()
def on_clicked(self):
self.send_message("testing")
if __name__ == '__main__':
import sys
import random
app = QtGui.QApplication(sys.argv)
w = Chatting()
def test():
for _ in range(8):
w.send_message("testing", random.choice((True, False)))
QtCore.QTimer.singleShot(1000, test)
w.resize(240, 480)
w.show()
sys.exit(app.exec_())
I'm making a Solitaire card game to practice OOP and PyQt5, and I'm having trouble adding a card, which inherits QAbstractButton, to a layout (QGridLayout, QHBoxLayout, or QVBoxLayout). Here is part of the Card object:
class Card(QAbstractButton):
def __init__(self, rank=None, suit=None, parent=None):
super().__init__(parent)
self.rank = rank
self.suit = suit
self.visibility = False
def paintEvent(self, e):
painter = QPainter()
painter.begin(self)
if self.visibility == True:
self.draw_card_front(painter)
else:
self.draw_card_back(painter)
painter.end()
def draw_card_back(self, painter):
painter.setPen(COLOR_OUTLINE)
painter.setBrush(COLOR_BACK)
painter.drawRoundedRect(0, 0, CARD_WIDTH-1, CARD_HEIGHT-1, 10, 10)
def draw_card_front(self, painter):
painter.setPen(COLOR_OUTLINE)
painter.setBrush(COLOR_FRONT)
painter.drawRoundedRect(0, 0, CARD_WIDTH-1, CARD_HEIGHT-1, 10, 10)
self.draw_rank(painter)
self.draw_suit(painter)
...
And here is the game's class:
class Solitaire(QWidget):
def __init__(self):
super().__init__()
self.score = 0
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
self.card1 = Card(rank=1, suit=2, parent=self)
self.card2 = Card(rank=1, suit=2, parent=self)
grid.addWidget(self.card1, 0, 0)
grid.addWidget(self.card2, 1, 0)
self.setWindowTitle('Yay')
self.setGeometry(300, 300, 400, 400)
self.show()
...
if __name__ == '__main__':
app = QApplication(sys.argv)
game = Solitaire()
app.exec_()
When I run the program, the Card does not show up. But if I don't use a layout, the Card shows up normally. And if I try adding a QPushButton to a layout it works fine, too. I feel like I'm missing something with the parent property, or perhaps I'm not overloading a function from QAbstractButton in the Card class. Can anyone advise?
According to the docs:
To subclass QAbstractButton, you must reimplement at least
paintEvent() to draw the button's outline and its text or pixmap. It
is generally advisable to reimplement sizeHint() as well, and
sometimes hitButton() (to determine whether a button press is within
the button). For buttons with more than two states (like tri-state
buttons), you will also have to reimplement checkStateSet() and
nextCheckState().
From the above we conclude that you must implement the paintEvent() method that is responsible for drawing the button, this depends on what you want to draw, and the sizeHint() method which is the size used by the layouts.
For example:
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
class Card(QAbstractButton):
def __init__(self, rank=None, suit=None, parent=None):
super().__init__(parent)
self.rank = rank
self.suit = suit
self.visibility = False
def sizeHint(self):
return QSize(100, 100)
def paintEvent(self, e):
painter = QPainter(self)
if self.visibility:
self.draw_card_front(painter)
else:
self.draw_card_back(painter)
...
class Solitaire(QWidget):
def __init__(self):
super().__init__()
self.score = 0
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
self.card1 = Card(rank=1, suit=2, parent=self)
self.card2 = Card(rank=1, suit=2, parent=self)
grid.addWidget(self.card1, 0, 0)
grid.addWidget(self.card2, 1, 0)
self.setWindowTitle('Yay')
self.setGeometry(300, 300, 400, 400)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
game = Solitaire()
app.exec_()
import sys, os
import PyQt4
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class Tab1Widget1(QWidget):
def __init__(self, parent=None):
super().__init__()
self.Tab1Widget1initUI()
self.bridge = Tab1Widget2()
def Tab1Widget1initUI(self):
self.setLayout(QGridLayout())
self.T1W1_checkbox = QCheckBox('checkbox1', self)
self.layout().addWidget(self.T1W1_checkbox, 1, 0)
def test(self):
print ('123')
def run(self):
if self.T1W1_checkbox.isChecked() == True:
self.test()
if self.bridge.T1W2_checkbox.isChecked() == True:
print (self.bridge.T1W2_le.text())
class Tab1Widget2(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.T1W2_checkbox = QCheckBox('checkbox2', self)
self.layout().addWidget(self.T1W2_checkbox, 0, 0)
self.T1W2_le = QLineEdit()
self.layout().addWidget(self.T1W2_le, 0, 1)
class Tab1Layout(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.group1 = Tab1Widget1(self)
scroll = QScrollArea(self)
scroll.setWidget(self.group1)
scroll.setWidgetResizable(True)
self.layout().addWidget(scroll, 0, 0)
self.group2 = Tab1Widget2(self)
self.layout().addWidget(self.group2, 1, 0)
self.btnRun = QPushButton('Run', self)
self.layout().addWidget(self.btnRun, 3, 0)
self.btnRun.clicked.connect(self.group1.run)
class Page1(QTabWidget):
def __init__(self, parent=None):
super().__init__()
self.tab1 = Tab1Layout()
self.addTab(self.tab1, "Tab1")
self.tab2 = QWidget()
self.tab3 = QWidget()
self.addTab(self.tab2, "Tab2")
self.addTab(self.tab3, "Tab3")
self.tab2_initUI()
self.tab3_initUI()
def tab2_initUI(self):
grid = QGridLayout()
self.tab2.setLayout(grid)
def tab3_initUI(self):
grid = QGridLayout()
self.tab3.setLayout(grid)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__()
self.setGeometry(450, 250, 800, 550)
self.startPage1()
def startPage1(self):
x = Page1(self)
self.setWindowTitle("Auto Benchmark")
self.setCentralWidget(x)
self.show()
def main():
app = QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If checkbox1 is checked and I press the run button, it will print 123. However, by pressing run button, I want checkbox2 to also print some texts entered in lineedit if the checkbox1 are also checked (i.e. it should print 123 first and then print 456).
I've looked up some similar types of questions, but none of that provides a proper answer. If anyone knows how to solve it, pls let me know thanks!!
The problem is that you are creating several Tab1Widget2, the first one you created in Tab1Layout, and that is the one you see, then you have created another one in Tab1Widget1, but not the time because you have not passed a parent, if you pass it to ** self ** as parent you will observe the following:
self.bridge = Tab1Widget2(self)
which is not what you want, so instead of creating a new you must pass the one that already exists, an option is to pass it through the constructor:
class Tab1Widget1(QWidget):
def __init__(self, bridge, parent=None): # Modify here
super().__init__(parent)
self.Tab1Widget1initUI()
self.bridge = bridge #Modify here
# ...
def test(self): print ('123')
def run(self):
if self.T1W1_checkbox.isChecked():
self.test()
if self.bridge.T1W2_checkbox.isChecked():
print (self.bridge.T1W2_le.text())
class Tab1Widget2(QWidget):
# ...
class Tab1Layout(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setLayout(QGridLayout())
self.group2 = Tab1Widget2(self) # Modify here
self.group1 = Tab1Widget1(self.group2, self) # Modify here
scroll = QScrollArea(self)
scroll.setWidget(self.group1)
scroll.setWidgetResizable(True)
self.layout().addWidget(scroll, 0, 0)
# self.group2 = Tab1Widget2(self) # Modify here
self.layout().addWidget(self.group2, 1, 0)
# ...
I'm trying to make a simple widget with three buttons where for each button when clicked will show the corresponding other widgets.
Following is the code I'm trying to run but cant figure out why the new widgets are not showing.
from PyQt4 import QtGui, QtCore
from functools import partial
import sys
class MainWidget(QtGui.QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.main_widget()
def main_widget(self):
another = Another()
simple = Simple()
grid = QtGui.QGridLayout()
show_another_button = QtGui.QPushButton("Show Another")
show_another_button.clicked.connect(another.show_another)
grid.addWidget(show_another_button, 0, 0)
show_simple_button = QtGui.QPushButton("Show Simple")
show_simple_button.clicked.connect(simple.show_simple)
grid.addWidget(show_simple_button, 0, 1)
print_button = QtGui.QPushButton("Print Hello")
print_button.clicked.connect(partial(print, "Hello"))
grid.addWidget(another_button, 0, 2)
self.setLayout(grid)
self.show()
class Another(QtGui.QWidget):
def __init__(self):
print("another initialized")
super(Another, self).__init__()
def show_another(self):
print("another called")
grid = QtGui.QGridLayout()
self.setLayout(grid)
self.show()
class Simple(QtGui.QDialog):
def __init__(self):
print("simple initialized")
super(Simple, self).__init__()
def show_simple(self):
print("simple called")
self.show()
def main():
app = QtGui.QApplication(sys.argv)
main_widget = MainWidget()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Please Help!
Modified your code slightly. The below works on my system.
from PyQt4 import QtGui
from functools import partial
import sys
class MainWidget(QtGui.QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.another = Another()
self.simple = Simple()
self.grid = QtGui.QGridLayout()
self.main_widget()
def main_widget(self):
show_another_button = QtGui.QPushButton("Show Another")
show_another_button.clicked.connect(self.another.show_form)
self.grid.addWidget(show_another_button, 0, 0)
show_simple_button = QtGui.QPushButton("Show Simple")
show_simple_button.clicked.connect(self.simple.show_simple)
self.grid.addWidget(show_simple_button, 0, 1)
another_button = QtGui.QPushButton("Print Hello")
another_button.clicked.connect(partial(print, "Hello"))
self.grid.addWidget(another_button, 0, 2)
self.setLayout(self.grid)
class Another(QtGui.QWidget):
def __init__(self):
super(Another, self).__init__()
print("another initialized")
def show_form(self):
print("another called")
grid = QtGui.QGridLayout()
self.setLayout(grid)
self.show()
class Simple(QtGui.QDialog):
def __init__(self):
super(Simple, self).__init__()
print("simple initialized")
def show_simple(self):
print("simple called")
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = MainWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Please note: If you try to launch Another() when the Widget is still open you will get an error:
QWidget::setLayout: Attempting to set QLayout "" on Another "", which already has a layout