PyQt5 - add AbstractButton to layout - python

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_()

Related

Click on button will launch a function that blocks the entire interface [duplicate]

I have a pyqt widget that is shown over all windows when launched. I need it to keep unclosed until user decides to. Is that possible to catch mouse release event every time mouseclick performed no matter where it was done: inside QtWidget window or outside of it?
Here is a sample I use:
class Release_check(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.canvas = iface.mapCanvas()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.grid = QGridLayout()
self.grid.setSpacing(10)
self.setGeometry(500, 500, 400, 100)
self.text_out = QTextEdit()
self.setLayout(self.grid)
self.grid.addWidget(self.text_out, 0, 1, 1, 2)
self.show()
def mouseReleaseEvent(self, e):
screen_coordinate = f"x:{e.x()}, y:{e.y()}"
self.text_out.setText(screen_coordinate)
super(Release_check, self).mouseReleaseEvent(e)
app = Release_check()
Qt only detects the click inside the widget, if you want to detect outside the widgets then you must use another library that uses OS resources to monitor OS events such as pyinput:
import sys
from pynput import mouse
from PyQt5 import QtCore, QtGui, QtWidgets
class ButtonReleaseManager(QtCore.QObject):
released = QtCore.pyqtSignal(int, int)
def __init__(self, parent=None):
super().__init__(parent)
self._listener = mouse.Listener(on_click=self._handle_click)
self._listener.start()
def _handle_click(self, x, y, button, pressed):
if not pressed:
self.released.emit(x, y)
class Release_check(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.manager = ButtonReleaseManager()
self.manager.released.connect(self.show_position)
def initUI(self):
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setGeometry(500, 500, 400, 100)
self.text_out = QtWidgets.QTextEdit()
grid = QtWidgets.QGridLayout(self)
grid.setSpacing(10)
grid.addWidget(self.text_out, 0, 1, 1, 2)
#QtCore.pyqtSlot(int, int)
def show_position(self, x, y):
screen_coordinate = f"x:{x}, y:{x}"
self.text_out.setText(screen_coordinate)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Release_check()
w.show()
sys.exit(app.exec_())

When one checkbox is checked, by pressing a button to print some texts from a LineEdit if another checkbox is also checked

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)
# ...

Why is my QGraphicsItem not selectable?

I copied some code snippets and made my own version of it. The initial snippet (which I don't have anymore) allowed to move and also select a QgraphicsItem. My modified version allows movement, but not selecting. What am I doing wrong?
#!d:/python27/python -u
import sys
from PyQt4 import QtGui, QtCore
class GraphicsItem(QtGui.QGraphicsItem):
#
# QtGui.QGraphicsItem always needs to override its two public abstract methods
# paint, boundingRect
#
def __init__(self, rect=None, parent=None):
super(GraphicsItem, self).__init__(parent)
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.pen = QtGui.QPen(QtCore.Qt.SolidLine)
self.pen.setColor(QtCore.Qt.blue)
self.pen.setWidth(8)
self.brush = QtGui.QBrush(QtCore.Qt.red)
self.rect = QtCore.QRectF(rect[0], rect[1], rect[2], rect[3])
def mouseMoveEvent(self, event):
# move object
QtGui.QGraphicsItem.mouseMoveEvent(self, event)
def boundingRect(self):
return self.rect
def paint(self, painter, option, widget):
painter.setBrush(self.brush)
painter.setPen(self.pen)
painter.drawEllipse(self.rect)
class MyMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyMainWindow, self).__init__(parent)
width = 1000
height = 800
scene = QtGui.QGraphicsScene(-width/2, -height/2, width, height)
graphicsItem = GraphicsItem((-100, -100, 200, 200))
scene.addItem(graphicsItem)
view = QtGui.QGraphicsView()
# set QGraphicsView attributes
view.setRenderHints(QtGui.QPainter.Antialiasing |
QtGui.QPainter.HighQualityAntialiasing)
view.setViewportUpdateMode(QtGui.QGraphicsView.FullViewportUpdate)
view.setScene(scene)
self.setCentralWidget(view)
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Escape:
sys.exit(QtGui.qApp.quit())
else:
super(GraphicsView, self).keyPressEvent(event)
def main():
app = QtGui.QApplication(sys.argv)
form = MyMainWindow()
form.setGeometry(700, 100, 1050, 850)
form.show()
app.exec_()
if __name__ == '__main__':
main()
You miss this method in class GraphicsItem:
def mousePressEvent(self, event):
# select object
QtGui.QGraphicsItem.mousePressEvent(self, event)
print (self) # show the selected item

Updating pyqt widget content from another widget

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()

adding QProgress bar within QLineEdit in PyQt or PySide

Hi I want to add the QProgressBar behind the QLIneEdit, just like it is in Safari Browser or IE, So here is my starting point how can I hook the ProgressBar and MyLineEdit together so that when user is done entering the path the progress bar should show the progress while the path is opened !!!
from PyQt4 import QtGui, QtCore
import sys
class ProgressBar(QtGui.QProgressBar):
""" docstring for ProgressBar
"""
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent)
self.timer = QtCore.QBasicTimer()
self.step = 0
self.doAction()
def timerEvent(self, e):
if self.step >= 100:
self.timer.stop()
return
self.step = self.step + 15
self.setValue(self.step)
def doAction(self):
if self.timer.isActive():
self.timer.stop()
else:
self.timer.start(100, self)
class MyLineEdit(QtGui.QLineEdit):
""" docstring for MyLineEdit
"""
def __init__(self, parent=None):
super(MyLineEdit, self).__init__(parent)
# I want to hook this bar at the backgroind of MyLineEdit
pbar = ProgressBar()
class Example(QtGui.QWidget):
def __init__(self, parent=None):
super(Example, self).__init__(parent)
self.pbar = ProgressBar(self)
self.editbx = MyLineEdit(self.pbar)
newPalette = QtGui.QPalette()
newPalette.setColor(self.editbx.backgroundRole(), QtCore.Qt.transparent)
self.editbx.setPalette(newPalette)
self.editbx.setText("Defaukt text set")
self.editbx.setStyleSheet("QLineEdit { border:none;}")
self.pbar.setStyleSheet("QProgressBar {border:none;}")
self.initUI()
def initUI(self):
# self.pbar.setGeometry(30, 40, 200, 25)
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('QtGui.QProgressBar')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
win = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I also looking forward to add a QCombobox in place of text entered so it can list the other existing folders, not the way QCompleter uses though becuase it doesnt has look of QCombobox, and I do not want to allow user to enter anything that doesnt exists.
Any help would be greatly appreciated.
I've attached an example of a QLineEdit with a progress bar behind it. It was heavily influenced by this post: http://www.qtcentre.org/threads/54758-Progress-bar-form-QLineEdit-issue
Basically you have to manage painting yourself. Unfortunately it didn't seem to work when I tried to do the same thing with a QComboBox. I would suggest posting a new question specifically about painting a progress bar on a QComboBox once you get up to it!
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class MyLineEdit(QLineEdit):
def __init__(self, parent=None):
QLineEdit.__init__(self, parent)
self.timer = QBasicTimer()
self.step = 0
self.doAction()
def timerEvent(self, e):
if self.step >= 100:
self.timer.stop()
return
self.step = self.step + 10
self.repaint()
def doAction(self):
if self.timer.isActive():
self.timer.stop()
else:
self.timer.start(1000, self)
def generateGradient(self, color):
gradient = QLinearGradient(0, 0, 0, self.height());
m_defaultBaseColor = self.palette().color(QPalette.Base)
gradient.setColorAt(0, m_defaultBaseColor)
gradient.setColorAt(0.15, color.lighter(120))
gradient.setColorAt(0.5, color)
gradient.setColorAt(0.85, color.lighter(120))
gradient.setColorAt(1, m_defaultBaseColor)
return gradient
def paintEvent(self, event):
p = QPainter(self)
panel = QStyleOptionFrameV2()
self.initStyleOption(panel)
self.style().drawPrimitive(QStyle.PE_PanelLineEdit, panel, p, self)
# an alternative to painting the QLineEdit is to do it only when the widget has focus and the progress bar is finished
#if self.hasFocus() or self.step >= 100: QLineEdit.paintEvent(self, event)
# however I've chosen to paint it always
QLineEdit.paintEvent(self, event)
painter = QPainter(self)
lenap = QStyleOptionFrameV2()
self.initStyleOption(lenap)
backgroundRect = self.style().subElementRect(QStyle.SE_LineEditContents, lenap, self)
# some alternative if statements you might like to use instead...
#
# if not self.hasFocus() and self.step < 100:
# if self.step < 100:
if True:
loadingColor = QColor(116,192,250)
painter.setBrush(self.generateGradient(loadingColor))
painter.setPen(Qt.transparent)
mid = int(backgroundRect.width()/100.0*self.step)
progressRect = QRect(backgroundRect.x(), backgroundRect.y(), mid, backgroundRect.height())
painter.drawRect(progressRect)
painter.setPen(Qt.SolidLine)
painter.drawText(backgroundRect, Qt.AlignLeft|Qt.AlignVCenter, " " + self.text())
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self._control = QWidget()
self.setCentralWidget(self._control)
l = QVBoxLayout(self._control)
e = MyLineEdit()
l.addWidget(e)
b = QPushButton('a')
l.addWidget(b)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Window()
sys.exit(app.exec_())

Categories