subclassed QGraphicsItem not getting mouse events when used as module [closed] - pyqt

This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I'm using Pyside to create an interactive subclassed QGraphicsView - containing some subclassed QGraphicsItems.
It works well in its own module and receives mouse events.
But when used as a module from another file, and incorporated into another Layout - my mouse events are not being triggered.
However .itemChange is working.
Everything except the mouse events.
I am not using tracking events such as hover. I am using mousePressEvent and mouseReleaseEvent.
I have seen c++ responses about setting "setMouseTracking" but this is for widgets and my QGraphicsItems have been added as items not widgets. So when I call this funcion it tells me it doesn't exist for items. Also that seems to be for hover type of events - which I am not needing.
I believe I am subclassing properly and I pass on the events to the parent class. As I said at the start - my code works fine in a standalone file.
Any ideas what I've forgotten to do ?
Here is the standalone - working program:
Save as test_subclass_module.py
import sys
import weakref
import math
from PySide import QtCore, QtGui
###
class Edge(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 2
def __init__(self, sourceNode, destNode):
QtGui.QGraphicsItem.__init__(self)
#
self.sourcePoint = QtCore.QPointF()
self.destPoint = QtCore.QPointF()
self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
self.source = weakref.ref(sourceNode)
self.dest = weakref.ref(destNode)
self.source().addEdge(self)
self.dest().addEdge(self)
self.set_index()
self.adjust()
def type(self):
return Edge.Type
def sourceNode(self):
return self.source()
def setSourceNode(self, node):
self.source = weakref.ref(node)
self.adjust()
def destNode(self):
return self.dest()
def setDestNode(self, node):
self.dest = weakref.ref(node)
self.adjust()
def set_index(self):
self.setToolTip(self.source().label)
def adjust(self):
# do we have a line to draw ?
if self.source() and self.dest():
line = QtCore.QLineF(self.mapFromItem(self.source(), 0, 0), self.mapFromItem(self.dest(), 0, 0))
length = line.length()
if length > 20:
edgeOffset = QtCore.QPointF((line.dx() * 10) / length, (line.dy() * 10) / length)
self.prepareGeometryChange()
self.sourcePoint = line.p1() + edgeOffset
self.destPoint = line.p2() - edgeOffset
else: # want to make sure line not drawn
self.prepareGeometryChange()
self.sourcePoint = self.destPoint
def boundingRect(self):
# do we have a line to draw ?
if not self.source() or not self.dest():
return QtCore.QRectF()
else:
extra = 1
return QtCore.QRectF(self.sourcePoint,
QtCore.QSizeF(self.destPoint.x() - self.sourcePoint.x(),
self.destPoint.y() - self.sourcePoint.y())).normalized().adjusted(-extra, -extra, extra, extra)
def paint(self, painter, option, widget):
if self.source() and self.dest():
# Draw the line itself.
line = QtCore.QLineF(self.sourcePoint, self.destPoint)
if line.length() > 0.0:
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
painter.drawLine(line)
###
class Node(QtGui.QGraphicsItem):
Type = QtGui.QGraphicsItem.UserType + 1
def __init__(self, graphWidget, time, temp, pos):
QtGui.QGraphicsItem.__init__(self)
self.graph = weakref.ref(graphWidget)
self.edgeList = []
self.set_index(pos)
self.newPos = QtCore.QPointF()
self.setFlag(QtGui.QGraphicsItem.ItemIsMovable)
self.setFlag(QtGui.QGraphicsItem.ItemSendsGeometryChanges)
self.setCacheMode(self.DeviceCoordinateCache)
self.setZValue(-1)
#
self.temp = temp
self.time = time
x,y = self.map_temptime_to_pos()
self.setPos(x,y)
self.marker = False
def type(self):
return Node.Type
def addEdge(self, edge):
self.edgeList.append(weakref.ref(edge))
def set_index(self, index):
self.index = index
self.label = "Step %d" % index
self.setToolTip(self.label)
def get_prev_edge(self):
index = 1000
edge = False
for e in self.edgeList:
sn = e().source().index
dn = e().dest().index
if sn < index:
index = sn
edge = e
if dn < index:
index = dn
edge = e
return edge
def get_next_edge(self):
index = -1
edge = False
for e in self.edgeList:
sn = e().source().index
dn = e().dest().index
if sn > index:
index = sn
edge = e
if dn > index:
index = dn
edge = e
return edge
def map_temptime_to_pos(self):
x = self.time * self.graph().graph_width_ratio
y = self.graph().size[3] - self.temp * self.graph().graph_height_ratio
return (x,y)
def boundingRect(self):
adjust = 2.0
return QtCore.QRectF(-10 - adjust, -10 - adjust,
22 + adjust, 23 + adjust)
def paint(self, painter, option, widget):
painter.drawLine(QtCore.QLineF(6,-40,6,-2))
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(QtCore.Qt.lightGray)
painter.drawEllipse(-10, -10, 20, 20)
gradient = QtGui.QRadialGradient(0, 0, 22)
if option.state & QtGui.QStyle.State_Sunken: # selected
gradient.setColorAt(0, QtGui.QColor(QtCore.Qt.darkGreen).lighter(120))
else:
gradient.setColorAt(1, QtCore.Qt.blue)
painter.setBrush(QtGui.QBrush(gradient))
painter.setPen(QtGui.QPen(QtCore.Qt.black, 0))
painter.drawEllipse(-6, -6, 12, 12)
def itemChange(self, change, value):
if change == QtGui.QGraphicsItem.ItemPositionChange:
for edge in self.edgeList:
edge().adjust()
return QtGui.QGraphicsItem.itemChange(self, change, value)
def mousePressEvent(self, event):
if not self.graph().inhibit_edit:
self.update()
print "Node pressed"
QtGui.QGraphicsItem.mousePressEvent(self, event)
def mouseReleaseEvent(self, event):
if not self.graph().inhibit_edit:
self.update()
print "Node released"
#
QtGui.QGraphicsItem.mouseReleaseEvent(self, event)
###
class GraphWidget(QtGui.QGraphicsView):
def __init__(self):
QtGui.QGraphicsView.__init__(self)
self.size = (-30, 30, 600, 400)
#
scene = QtGui.QGraphicsScene(self)
scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
scene.setSceneRect(self.size[0],self.size[1],self.size[2],self.size[3])
self.setScene(scene)
self.setCacheMode(QtGui.QGraphicsView.CacheBackground)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
#
self.maxtemp = 300
self.maxtime = 160
self.nodecount = 0
self.calc_upper_limits()
#
self.scale(0.8, 0.8)
self.setMinimumSize(600, 400)
self.setWindowTitle(self.tr("Elastic Nodes"))
self.inhibit_edit = False
def calc_upper_limits(self):
self.toptemp = (self.maxtemp / 100 + 1) * 100
self.toptime = (int(self.maxtime) / 30 + 1) * 30
self.graph_width_ratio = float(self.size[2]) /self.toptime
self.graph_height_ratio = float(self.size[3]) / self.toptemp
def add_node(self, time, temp, marker=False, pos=-1):
self.nodecount += 1
scene = self.scene()
# Insert Node into scene
node = Node(self, time, temp, self.nodecount)
scene.addItem(node)
# Insert new edges
nodes = self.get_ordered_nodes()
if len(nodes) > 1:
e = Edge(nodes[-2], node)
scene.addItem(e)
# cleanup edge tooltips
for n in self.get_ordered_nodes():
edges = n.edgeList
for e in edges:
e().set_index()
def get_ordered_nodes(self):
nodes = [item for item in self.scene().items() if isinstance(item, Node)]
nodes.sort(key=lambda n: n.index)
return nodes
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Plus:
self.scaleView(1.2)
elif key == QtCore.Qt.Key_Minus:
self.scaleView(1 / 1.2)
else:
QtGui.QGraphicsView.keyPressEvent(self, event)
def mousePressEvent(self, event):
print "GraphWidget mouse"
QtGui.QGraphicsView.mousePressEvent(self, event)
def wheelEvent(self, event):
self.scaleView(math.pow(2.0, -event.delta() / 240.0))
def scaleView(self, scaleFactor):
factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
if factor < 0.07 or factor > 100:
return
self.scale(scaleFactor, scaleFactor)
def drawBackground(self, painter, rect):
sceneRect = self.sceneRect()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
widget = GraphWidget()
widget.add_node(0, 25)
widget.add_node(30, 100)
widget.add_node(60, 200)
widget.show()
sys.exit(app.exec_())
Here is the parent program - which does not get the mouse events:
Called test_toplevel.py
# import user interface etc
from PySide import QtCore, QtGui
from test_tabs_ui import Ui_Form
from test_subclass_module import *
import sys
Programs = {"Gen13": {"steps": [[0, 0, 0], [0, 30, 30], [0, 60, 60], [0, 77, 77]]
}}
###-----------------------------------------------------------
### The dialog
class Nusku_tab_Add_kiln(QtGui.QWidget):
""" Create dialog to add/delete kilns from controlled kilns """
def __init__(self, parent=None):
# Get the UI loaded
super(Nusku_tab_Add_kiln, self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.current = Programs['Gen13']
# draw program in graphicsView
# swap out the standin
self.ui.graphLayout.removeWidget(self.ui.graphicsView)
self.ui.graphicsView.setParent(None)
self.ui.graphicsView.deleteLater()
self.graph = GraphWidget()
self.ui.graphLayout.addWidget(self.graph)
self.draw_graph()
def choose_program(self):
pass
def draw_graph(self):
graph = self.graph
graph.inhibit_edit = True
steps = self.current['steps']
for s in steps:
print s
graph.add_node(s[1],s[2])
###------------------------------------------------
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
prog = Nusku_tab_Add_kiln()
prog.show()
sys.exit(app.exec_())
For sake of completeness. Here is the ui file it imports:
test_tabs_ui.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test_tabs.ui'
#
# Created: Wed Dec 05 15:20:02 2012
# by: pyside-uic 0.2.14 running on PySide 1.1.1
#
# WARNING! All changes made in this file will be lost!
from PySide import QtCore, QtGui
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(595, 540)
self.verticalLayout = QtGui.QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.tabWidget = QtGui.QTabWidget(Form)
self.tabWidget.setObjectName("tabWidget")
self.Tab_Program = QtGui.QWidget()
self.Tab_Program.setObjectName("Tab_Program")
self.verticalLayout_2 = QtGui.QVBoxLayout(self.Tab_Program)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.frame_graph_status = QtGui.QFrame(self.Tab_Program)
self.frame_graph_status.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_graph_status.setFrameShadow(QtGui.QFrame.Raised)
self.frame_graph_status.setObjectName("frame_graph_status")
self.horizontalLayout_7 = QtGui.QHBoxLayout(self.frame_graph_status)
self.horizontalLayout_7.setSpacing(0)
self.horizontalLayout_7.setContentsMargins(0, 0, 0, 0)
self.horizontalLayout_7.setObjectName("horizontalLayout_7")
self.frame_program = QtGui.QFrame(self.frame_graph_status)
self.frame_program.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_program.setFrameShadow(QtGui.QFrame.Raised)
self.frame_program.setObjectName("frame_program")
self.graphLayout = QtGui.QVBoxLayout(self.frame_program)
self.graphLayout.setContentsMargins(-1, 0, -1, 0)
self.graphLayout.setObjectName("graphLayout")
self.graphicsView = QtGui.QGraphicsView(self.frame_program)
self.graphicsView.setObjectName("graphicsView")
self.graphLayout.addWidget(self.graphicsView)
self.horizontalLayout_7.addWidget(self.frame_program)
self.verticalLayout_2.addWidget(self.frame_graph_status)
self.widget_prog = QtGui.QWidget(self.Tab_Program)
self.widget_prog.setObjectName("widget_prog")
self.prog_layout = QtGui.QGridLayout(self.widget_prog)
self.prog_layout.setContentsMargins(4, 4, 4, 4)
self.prog_layout.setSpacing(0)
self.prog_layout.setContentsMargins(0, 0, 0, 0)
self.prog_layout.setObjectName("prog_layout")
self.verticalLayout_2.addWidget(self.widget_prog)
self.tabWidget.addTab(self.Tab_Program, "")
self.Tab_alarms = QtGui.QWidget()
self.Tab_alarms.setObjectName("Tab_alarms")
self.alarms_tab_layout = QtGui.QVBoxLayout(self.Tab_alarms)
self.alarms_tab_layout.setObjectName("alarms_tab_layout")
self.tabWidget.addTab(self.Tab_alarms, "")
self.Tab_settings = QtGui.QWidget()
self.Tab_settings.setObjectName("Tab_settings")
self.settings_tab_layout = QtGui.QVBoxLayout(self.Tab_settings)
self.settings_tab_layout.setObjectName("settings_tab_layout")
self.tabWidget.addTab(self.Tab_settings, "")
self.Tab_pid = QtGui.QWidget()
self.Tab_pid.setObjectName("Tab_pid")
self.verticalLayout_8 = QtGui.QVBoxLayout(self.Tab_pid)
self.verticalLayout_8.setSpacing(0)
self.verticalLayout_8.setContentsMargins(0, 0, 0, 0)
self.verticalLayout_8.setObjectName("verticalLayout_8")
self.scrollArea_2 = QtGui.QScrollArea(self.Tab_pid)
self.scrollArea_2.setWidgetResizable(True)
self.scrollArea_2.setObjectName("scrollArea_2")
self.scrollAreaWidgetContents_2 = QtGui.QWidget()
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 569, 494))
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
self.PID_tab_layout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents_2)
self.PID_tab_layout.setSpacing(0)
self.PID_tab_layout.setContentsMargins(0, 0, 0, 0)
self.PID_tab_layout.setObjectName("PID_tab_layout")
self.scrollArea_2.setWidget(self.scrollAreaWidgetContents_2)
self.verticalLayout_8.addWidget(self.scrollArea_2)
self.tabWidget.addTab(self.Tab_pid, "")
self.verticalLayout.addWidget(self.tabWidget)
self.retranslateUi(Form)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(Form)
Form.setTabOrder(self.tabWidget, self.graphicsView)
Form.setTabOrder(self.graphicsView, self.scrollArea_2)
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsView.setToolTip(QtGui.QApplication.translate("Form", "decimal point", None, QtGui.QApplication.UnicodeUTF8))
self.graphicsView.setStatusTip(QtGui.QApplication.translate("Form", "Accuracy can be increased at lower temperatures", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_Program), QtGui.QApplication.translate("Form", "Program", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_alarms), QtGui.QApplication.translate("Form", "Alarms", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_settings), QtGui.QApplication.translate("Form", "Settings", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Tab_pid), QtGui.QApplication.translate("Form", "PID", None, QtGui.QApplication.UnicodeUTF8))

because there is an answer - which reasonably asks me to delete my answer - I have to answer the question with my own answer to close it. After waiting 3 days... hopefully I can delete it - but probably not as it has an answer..

I downloaded and tried your application and saw it more or less working
(although the cursor position gets wrong in test_toplevel.py).
After removing graph.inhibit_edit = False also the cursor stays right.
So is the problem already solved? Then it would be very helpful to remove your
question or mak it as resolved. Or please clarify the question.

Related

Turtle screen gets closed when .onclick() or onscreenclick() is used

I have a global variable, 'is_game_on' set to 'False' to start with. I have a turtle which responds to .ondrag() function. My program works perfectly fine if I change the 'is_game_on' variable to 'True' (The main program runs in a while loop when 'is_game_on' is 'True').
In the same Screen I have created turtle (a text- 'Click to start') in the top right of the screen, which I want to return 'is_game_on' = 'True' when the mouse is clicked on it so that the rest of my program starts working there after. However, my screen gets closed when I click the mouse. I think this is because of the command screen.exitonclick() at the end. Appreciate any suggestions how to avoid this problem.
Below is my code. I want to start with 'is_game_on == False' and with the output a static display. Then when I click the mouse on 'Click to Start', a mechanism to trigger 'is_game_on" as True and then the ball starts bouncing up and down.
from turtle import Screen, Turtle
import time
# is_game_on = False
is_game_on = True
def click(i, j):
global is_game_on
if i >= 250 and j >= 300:
is_game_on = True
print(is_game_on)
return is_game_on
class Ball(Turtle):
def __init__(self):
super().__init__()
self.shape('circle')
self.color('black')
self.shapesize(stretch_wid=2, stretch_len=2)
self.penup()
self.speed(6)
self.goto(0, -355)
self.x_move = 0
self.y_move = 1
self.move_speed = 10
def move(self):
xcor_new = self.xcor() + self.x_move
ycor_new = self.ycor() + self.y_move
self.goto(xcor_new, ycor_new)
def bounce_y(self):
self.y_move *= -1
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.shape('square')
self.penup()
self.goto(0, -380)
self.color('blue')
self.shapesize(stretch_wid=.5, stretch_len=10)
def move(self,i, j):
self.goto(i, -380)
class Start(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.goto(250, 300)
self.color('blue')
self.shapesize(stretch_wid=4, stretch_len=10)
self.hideturtle()
self.write('Click to Start', font=('Arial', 35, 'bold'))
screen = Screen()
screen.colormode(255)
screen.bgcolor('white')
screen.setup(1200, 800)
screen.tracer(0)
paddle = Paddle()
ball = Ball()
screen.listen()
paddle.ondrag(paddle.move)
screen.onclick(click)
start = Start()
while is_game_on:
time.sleep(0)
screen.update()
ball.move()
if ball.ycor() >= 375:
ball.bounce_y()
if (abs(ball.xcor() - paddle.xcor()) < 120) and ball.ycor() == -355:
ball.bounce_y()
screen.update()
screen.exitonclick()
After lot of trial and errors and lot of web searches, I found the solution.
The screen closing problem can be simply avoided by importing turtle and adding turtle.done() command just before screen.exitonclick() command. The complete code will be.
from turtle import Screen, Turtle
import time
import turtle
is_game_on = False
# is_game_on = True
def click(i, j):
global is_game_on
if i >= 250 and j >= 300:
is_game_on = True
print(is_game_on)
return is_game_on
class Ball(Turtle):
def __init__(self):
super().__init__()
self.shape('circle')
self.color('black')
self.shapesize(stretch_wid=2, stretch_len=2)
self.penup()
self.speed(6)
self.goto(0, -355)
self.x_move = 0
self.y_move = 1
self.move_speed = 10
def move(self):
xcor_new = self.xcor() + self.x_move
ycor_new = self.ycor() + self.y_move
self.goto(xcor_new, ycor_new)
def bounce_y(self):
self.y_move *= -1
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.shape('square')
self.penup()
self.goto(0, -380)
self.color('blue')
self.shapesize(stretch_wid=.5, stretch_len=10)
def move(self,i, j):
self.goto(i, -380)
class Start(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.goto(250, 300)
self.color('blue')
self.shapesize(stretch_wid=4, stretch_len=10)
self.hideturtle()
self.write('Click to Start', font=('Arial', 35, 'bold'))
screen = Screen()
screen.colormode(255)
screen.bgcolor('white')
screen.setup(1200, 800)
screen.tracer(0)
paddle = Paddle()
ball = Ball()
screen.listen()
paddle.ondrag(paddle.move)
screen.onclick(click)
start = Start()
while is_game_on:
time.sleep(0)
screen.update()
ball.move()
if ball.ycor() >= 375:
ball.bounce_y()
if (abs(ball.xcor() - paddle.xcor()) < 120) and ball.ycor() == -355:
ball.bounce_y()
screen.update()
turtle.done()
screen.exitonclick()
The animation can work by moving the while loop into the click() function. The code will be as follows.
from turtle import Screen, Turtle
import time
import turtle
is_game_on = False
# is_game_on = True
def click(i, j):
global is_game_on
if i >= 250 and j >= 300:
is_game_on = True
print(is_game_on)
while is_game_on:
time.sleep(0)
screen.update()
ball.move()
if ball.ycor() >= 375:
ball.bounce_y()
if (abs(ball.xcor() - paddle.xcor()) < 120) and ball.ycor() == -355:
ball.bounce_y()
return is_game_on
class Ball(Turtle):
def __init__(self):
super().__init__()
self.shape('circle')
self.color('black')
self.shapesize(stretch_wid=2, stretch_len=2)
self.penup()
self.speed(6)
self.goto(0, -355)
self.x_move = 0
self.y_move = 1
self.move_speed = 10
def move(self):
xcor_new = self.xcor() + self.x_move
ycor_new = self.ycor() + self.y_move
self.goto(xcor_new, ycor_new)
def bounce_y(self):
self.y_move *= -1
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.shape('square')
self.penup()
self.goto(0, -380)
self.color('blue')
self.shapesize(stretch_wid=.5, stretch_len=10)
def move(self,i, j):
self.goto(i, -380)
class Start(Turtle):
def __init__(self):
super().__init__()
self.penup()
self.goto(250, 300)
self.color('blue')
self.shapesize(stretch_wid=4, stretch_len=10)
self.hideturtle()
self.write('Click to Start', font=('Arial', 35, 'bold'))
screen = Screen()
screen.colormode(255)
screen.bgcolor('white')
screen.setup(1200, 800)
screen.tracer(0)
paddle = Paddle()
ball = Ball()
screen.listen()
paddle.ondrag(paddle.move)
screen.onclick(click)
start = Start()
screen.update()
turtle.done()
screen.exitonclick()

QHeaderView going out of viewport

I'm having couple of issues while trying to make a QHeaderView for my QTableView.
I want QHeaderView to be resizable by the user (Qt.ResizeMode.Interactive) while being able to stretch its sections proportionately when the window or QTableView is being resized. I found this problem online, and managed to mostly solve it but there is still some stuttering when the resizing begins and I think there should be a better solution than mine. Currently it's done by using QTimer to stop sections from going out of the viewport. Timer is being updated every millisecond. If the update interval is bigger, sections would go out of viewport and magically teleport back when the timer is updated, so once per millisecond in my case. There's still some stuttering visible if the user is dragging sections out of the viewport by dragging their mouse faster, not so visible when the mouse is slower, but visible none the less.
Every section should be resizable and movable, besides the first two. The first two sections should be immovable and fixed. I managed to make them fixed and they don't seem to have an effect on resizing of the sections, but I have no idea how to make them immovable while all the other sections are movable.
Sections should have text eliding, which I managed to make an item delegate for, but setting it on QHeaderView seems to do absolutely nothing (paint() method doesn't even get called). It's probably because item delegate isn't affecting sections, if so, how can I make a delegate that does affect them?
Here's my current code (it's a bit of a mess, but hopefully you'll get the idea):
import sys
import weakref
from typing import Any, Optional
from PyQt6 import QtWidgets, QtCore, QtGui
from PyQt6.QtCore import pyqtSlot, Qt
from PyQt6.QtGui import QFontMetrics
from PyQt6.QtWidgets import QHeaderView, QStyledItemDelegate, QStyleOptionViewItem
class MyItemDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super().__init__(parent)
def paint(self, painter: QtGui.QPainter, option: QStyleOptionViewItem, index: QtCore.QModelIndex) -> None:
text = index.data(Qt.ItemDataRole.DisplayRole)
# print(text)
if text:
elided_text = QFontMetrics(option.font).elidedText(str(text), Qt.TextElideMode.ElideRight, option.rect.width())
painter.drawText(option.rect, Qt.AlignmentFlag.AlignLeft, elided_text)
class HeaderView(QtWidgets.QHeaderView):
def __init__(self,
orientation: QtCore.Qt.Orientation = Qt.Orientation.Horizontal,
parent: Optional[QtWidgets.QWidget] = None):
super(HeaderView, self).__init__(orientation, parent)
item_delegate = MyItemDelegate(self)
self.setItemDelegate(item_delegate)
self.setMinimumSectionSize(5)
self.setStretchLastSection(True)
self.setCascadingSectionResizes(True)
self.setSectionsMovable(True)
self.fixed_section_indexes = (0, 1)
timer = QtCore.QTimer(self)
timer.setSingleShot(True)
timer.setTimerType(Qt.TimerType.PreciseTimer)
timer.timeout.connect(self._update_sizes)
resize_mode_timer = QtCore.QTimer(self)
resize_mode_timer.setTimerType(Qt.TimerType.PreciseTimer)
resize_mode_timer.setSingleShot(True)
resize_mode_timer.timeout.connect(lambda: self.setSectionResizeMode(QHeaderView.ResizeMode.Interactive))
self._resize_mode_timer = weakref.proxy(resize_mode_timer)
self._timer = weakref.proxy(timer)
self.sectionResized.connect(self._handle_resize)
self.setTextElideMode(Qt.TextElideMode.ElideLeft)
self.setDefaultAlignment(Qt.AlignmentFlag.AlignLeft)
self.proportions = []
self.mouse_pressed = False
def mouseReleaseEvent(self, e: QtGui.QMouseEvent) -> None:
self.mouse_pressed = False
super().mouseReleaseEvent(e)
self.proportions = [self.sectionSize(i) / self.width() for i in range(self.count())]
# print(self.mouse_pressed)
def init_sizes(self):
each = self.width() // self.count()
for i in range(self.count()):
self.resizeSection(self.logicalIndex(i), each)
#pyqtSlot(int, int, int)
def _handle_resize(self, logicalIndex: int, oldSize: int, newSize: int):
self._timer.start(1)
def resizeEvent(self, event: QtGui.QResizeEvent):
super().resizeEvent(event)
width = self.width()
# sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
for i in range(self.count()):
if not self.proportions:
break
if i not in self.fixed_section_indexes:
self.resizeSection(i, int(self.proportions[i] * width_without_fixed))
self._timer.start(1)
#pyqtSlot()
def _update_sizes(self):
width = self.width()
sizes = [self.sectionSize(self.logicalIndex(i)) for i in range(self.count())]
# width_without_fixed = width - sum([self.sectionSize(i) for i in self.fixed_section_indexes])
index = len(sizes) - 1
i = 0
while index >= 0 and sum(sizes) > width:
i += 1
if i > 100:
break
if sizes[index] > 5 and index not in self.fixed_section_indexes: # minimum width (5)
new_width = width - (sum(sizes) - sizes[index])
if new_width < 5:
new_width = 5
sizes[index] = new_width
index -= 1
for j, value in enumerate(sizes):
self.resizeSection(self.logicalIndex(j), value)
if not self.proportions:
self.proportions = [self.sectionSize(i) / width for i in range(self.count())]
class Model(QtCore.QAbstractTableModel):
def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super(Model, self).__init__(parent)
self.__headers = ["Column A", "Column B", "Column C", "Column D", "Column E", "Column F", "Column G"]
self.__data = []
for i in range(10):
row = [0, 1, 2, 3, 42222222222, 5, 6, 74444444]
self.__data.append(row)
def rowCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__data)
def columnCount(self, index: Optional[QtCore.QModelIndex] = None) -> int:
return len(self.__headers)
def headerData(self, section: int, orientation: QtCore.Qt.Orientation,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role == Qt.ItemDataRole.DisplayRole:
if orientation == Qt.Orientation.Horizontal:
return self.__headers[section]
return f"{section}"
return None
def data(self, index: QtCore.QModelIndex,
role: QtCore.Qt.ItemDataRole = Qt.ItemDataRole.DisplayRole) -> Any:
if role in [Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole]:
return self.__data[index.row()][index.column()]
return None
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
view = QtWidgets.QTableView()
view.resize(600, 600)
header = HeaderView()
view.setHorizontalHeader(header)
model = Model()
view.setModel(model)
header.init_sizes()
view.horizontalHeader().resizeSection(0, 30)
view.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Fixed)
view.horizontalHeader().resizeSection(1, 30)
view.horizontalHeader().setSectionResizeMode(1, QHeaderView.ResizeMode.Fixed)
view.show()
app.exec()

PyQt5 - Custom QWidget not showing up on parent widget

I am working on a game in which I have completed the engine and the networking layers. I am now working on some GUI for the game using PyQt5 (as the drawing isn't complex). I made a custom QWidget for drawing the game board which is just a widget that positions custom QPushButtons inside itself. Here's the code for that:
class BoardButton(QtWidgets.QPushButton):
tilePressed = pyqtSignal(Tile)
def __init__(self, tile: Tile = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setAutoFillBackground(True)
self.setCheckable(False)
self.setDefault(False)
self.setFlat(False)
self.raise_()
self.tile = tile
self.default_background(tile)
self.highlight = False
self.is_clicked = False
def __change_bgd(self, colour):
p = self.palette()
p.setColor(self.backgroundRole(), colour)
self.setPalette(p)
def default_background(self, tile: Tile):
if tile == self.tile:
self.__change_bgd(QtGui.QColor(153, 76, 0))
def highlight_background(self, tile: Tile):
if tile == self.tile:
self.__change_bgd(QtCore.Qt.green)
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
self.tilePressed.emit(self.tile)
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
if self.highlight:
self.highlight_background(self.tile)
else:
self.default_background(self.tile)
painter = QtGui.QPainter(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
painter.setPen(QtGui.QPen(QtCore.Qt.black, QtCore.Qt.SolidLine))
painter.drawRect(0, 0, self.width(), self.height())
if self.tile:
if self.tile.is_special:
first_line = self.width() / 3
second_line = first_line * 2
painter.drawLine(first_line, 0, first_line, self.height())
painter.drawLine(second_line, 0, second_line, self.height())
painter.drawLine(0, first_line, self.width(), first_line)
painter.drawLine(0, second_line, self.width(), second_line)
if self.tile.is_exit:
painter.setBrush(QtCore.Qt.NoBrush)
painter.drawEllipse(self.width() / 4, self.height() / 4, self.width() / 2, self.height() / 2)
if self.tile.piece is not None:
if self.tile.piece.is_white:
colour = QtCore.Qt.white
else:
colour = QtCore.Qt.black
painter.setBrush(QtGui.QBrush(colour, QtCore.Qt.SolidPattern))
painter.drawEllipse(0, 0, self.width(), self.height())
if self.tile.piece.is_king:
painter.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern))
painter.drawEllipse(self.width() / 4, self.height() / 4, self.width() / 2, self.height() / 2)
class GameBoard(QtWidgets.QWidget):
tilePressed = pyqtSignal(Tile)
boardUpdate = pyqtSignal()
def __init__(self, game: Game = None, is_white: bool = None, playable=True, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setGeometry(QtCore.QRect(20, 20, 650, 650))
self.setObjectName("boardWidget")
self.game = game
self.is_white = is_white
self.playable = playable
self.buttons = []
self.__selected = None
self.tilePressed.connect(self.__tile_pressed)
def init_buttons(self):
def pos_to_name(x, y):
return f'btn_{x}{y}'
self.buttons = []
board = self.game.board
board_size = board.width
if board.width > board.height:
board_size = board.width
elif board.width < board.height:
board_size = board.height
btn_size = self.width() / board_size
start_x = self.width() / 2 - btn_size * (board.width / 2)
start_y = self.height() / 2 - btn_size * (board.height / 2)
for y in range(board.height):
row = []
for x in range(board.width):
button = BoardButton(board[y][x], parent=self)
button.setGeometry(QtCore.QRect(start_x + x * btn_size, start_y + y * btn_size, btn_size, btn_size))
button.setObjectName(pos_to_name(x, y))
button.tilePressed.connect(self.tilePressed.emit)
row.append(button)
self.buttons.append(row)
def __get_button(self, tile: Tile) -> BoardButton:
return self.buttons[tile.y][tile.x]
def update(self) -> None:
self.boardUpdate.emit()
for y, row in enumerate(self.buttons):
for x, button in enumerate(row):
button.tile = self.game.board[y][x]
button.update()
super().update()
def remove_highlights(self):
self.__selected = None
for row in self.buttons:
for button in row:
button.highlight = False
button.is_clicked = False
def highlight_buttons(self, source_tile: Tile):
self.__selected = source_tile
for tile in self.game.board.valid_moves(source_tile):
self.buttons[tile.y][tile.x].highlight = True
def move_piece(self, target_tile: Tile):
if not self.__selected:
raise ValueError('Cannot move piece because no piece is selected.')
self.game.move(self.__selected, target_tile)
def is_highlight(self, tile: Tile) -> bool:
return self.__get_button(tile).highlight
def is_clicked(self, tile: Tile) -> bool:
return self.__get_button(tile).is_clicked
def set_clicked(self, tile: Tile, value: bool):
self.__get_button(tile).is_clicked = value
def __tile_pressed(self, tile: Tile):
if not tile or self.game.game_over or not self.playable:
return
if self.is_white is not None and not is_turn(self.is_white, self.game):
return
if not tile.piece and self.is_highlight(tile):
try:
self.move_piece(tile)
except (BoardGameException, Win):
pass
self.remove_highlights()
elif not tile.piece and not self.is_highlight(tile):
self.remove_highlights()
elif tile.piece:
if self.is_clicked(tile):
self.remove_highlights()
elif self.game.is_turn(tile):
self.remove_highlights()
self.highlight_buttons(tile)
self.set_clicked(tile, True)
self.update()
This code works perfectly with one of my windows:
class LocalGameWindow(Ui_FrmLocalGame):
def __init__(self, game, playable=True):
super().__init__(game=game, playable=playable)
self.init_gameboard()
self.gameboard.update()
if self.gameboard.playable:
self.btnUndo.clicked.connect(self.btn_undo_clicked)
else:
self.setWindowTitle(...)
self.btnUndo.setText('Play')
self.lblTurn.setVisible(False)
self.lblBlackPieces.setVisible(False)
self.lblWhitePieces.setVisible(False)
self.btnExit.clicked.connect(self.close)
def btn_undo_clicked(self):
self.gameboard.game.undo()
self.gameboard.update()
Which inherits from the following (Ui_FrmLocalGame was generated with Qt Designer):
class Ui_FrmLocalGame(_GameBoardWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
def setupUi(self, FrmLocalGame):
...
Which in turn inherits from:
class _GameBoardWindow(GameWidget):
def __init__(self, client: Client = None, *args, **kwargs):
super().__init__()
is_white = None
if client:
is_white = client.is_white
self.gameboard = GameBoard(is_white=is_white, *args, **kwargs, parent=self)
self.client = client
self.gameboard.boardUpdate.connect(self.update_labels)
def init_gameboard(self, game: Game = None):
if game:
self.gameboard.game = game
self.gameboard.init_buttons()
def update_labels(self):
game = self.gameboard.game
if not game:
return
if not game.game_over:
text = f"{'Black' if game.black.is_turn else 'White'}'s Turn"
else:
text = f"{'Black' if game.black.won else 'White'} Won!"
self.lblTurn.setText(text)
self.lblBlackPieces.setText(f'Black: {game.board.num_black}/{game.board.num_start_black}')
self.lblWhitePieces.setText(f'White: {game.board.num_white}/{game.board.num_start_white}')
This gives me the result I expect, it draws the gameboard widget on the window widget:
However, when I try to do seemingly the same thing with a different window widget (the online window), it just doesn't appear on the window as it does with the local game window. Here's the code for the window:
class Ui_FrmOnlineGame(_GameBoardWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
def setupUi(self, FrmOnlineGame):
...
class OnlineGameWindow(Ui_FrmOnlineGame):
def __init__(self, client: Client):
super().__init__(client=client)
self.btnSend.clicked.connect(self.btn_send_clicked)
self.txtChat.returnPressed.connect(self.btn_send_clicked)
self.btnExit.clicked.connect(self.close)
self.__timer = QtCore.QTimer(self)
self.__timer.setInterval(100)
self.__timer.timeout.connect(self.__game_loop)
self.__timer.start()
def __game_loop(self):
try:
msg = self.client.recv_msg()
except BlockingIOError:
return
...
elif msg.startswith(Message.GameUpdate.value):
game = Game.from_serial(msg[1:])
self.gameboard.game = game
if not len(self.gameboard.buttons):
self.init_gameboard()
self.gameboard.update() # This should draw the gameboard widget like it does on the other window
...
def display_chat(self, text: str):
...
def display_colour(self):
show_dialog(f"You are {'white' if self.client.is_white else 'black'}.", self, 'Game Start', modal=False)
def btn_send_clicked(self):
...
def close(self) -> bool:
self.__timer.stop()
self.client.exit()
return super().close()
I've tried many things such as checking the custom widget's geometry, raising it, and making sure that it does have a parent (which it does). What have I done wrong? Any help would be appreciated, and sorry about all the code, I just feel it's necessary to find the issue.

Pyqt5 inheritance

full code: link to file
I'm trying to build a GUI using PyQT5, and I have two classes:
In the main class "Window", I have a method to close/exit the GUI, and when I use the method within the class, everything is working
class Window(QMainWindow):
choice = QMessageBox.question(self, ' WARNING!!!!', 'Are you sure to {}'.format(message),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No)
if choice == QMessageBox.Yes:
print('Quiting Application')
return sys.exit()
else:
pass
But the problem starts here, with the second class, when I try to quit using other class : Q_button.clicked.connect(Window.close_app)
class NewGrid(QWidget):
def __init__(self, parent=None):
super(NewGrid, self).__init__(parent)
grid = QGridLayout()
grid.addWidget(self.createExampleGroup(), 0, 0)
grid.addWidget(self.check_box_vBBU(), 21, 21)
grid.addWidget(self.button_test(), 0, 1)
grid.addWidget(self.quit_button(), 2, 2)
self.setLayout(grid)
def quit_button(self):
groupBox = QGroupBox("Quit_placeholder")
Q_button = QPushButton('Quit', self)
box = QHBoxLayout()
box.addWidget(Q_button)
super()
**Q_button.clicked.connect(Window.close_app)**
groupBox.setLayout(box)
return groupBox
any solution?
edit:
Here is the full code
import sys
from ctypes import windll
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
SCREEN_WIDTH = windll.user32.GetSystemMetrics(0) # 1920
SCREEN_HEIGHT = windll.user32.GetSystemMetrics(1) # 1080
class CustomDialog(QDialog):
def __init__(self, *args, **kwargs):
super(CustomDialog, self).__init__(parent=None)
Qbtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel
self.buttonBox.accepted(self.accept)
self.buttonBox.rejected(self.reject)
self.layout = QVBoxLayout()
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(SCREEN_WIDTH / 3, SCREEN_HEIGHT / 3, SCREEN_WIDTH / 3,
SCREEN_HEIGHT / 2) # (start_point_X,start_point_Y,DIMensionX,DimensionY)
self.setWindowIcon(QIcon('dru_icon.png'))
self.setWindowTitle('----------------------DRU GUI-----------------')
self.statusBar()
self.file_menu()
self.view_menu()
self.toolbar_menu()
self.home()
def home(self):
# btn = QPushButton('quit', self)
# btn.clicked.connect(self.close_app)
# make the buttons near the bottom right: 1920-50=450, 1080-50=250
# btn.move(450, 250)
# btn.resize(50, 50)
# btn.move(SCREEN_WIDTH / 3 - 50, SCREEN_HEIGHT / 2 - 50)
label = QLabel("Holla")
label.setAlignment(Qt.AlignBottom)
self.setCentralWidget(label)
widget = NewGrid()
self.setCentralWidget(widget)
self.show()
def toolbar_menu(self):
toolbar = QToolBar("My Main Toolbar")
toolbar.setIconSize(QSize(32, 32)) # manual size
self.addToolBar(toolbar) # showing toolbar
icon1 = self.set_toolbar_icon('new_icon', icon_image='truck--plus')
icon1.triggered.connect(self.close_app)
icon2 = self.set_toolbar_icon('iconNum2')
# icon2.triggered.connect(self.notification_button)
icon3 = self.set_toolbar_icon('iconNum3', icon_image='application-monitor')
def set_toolbar_icon(self, icon_name="NONE", icon_image='animal-monkey.png', Width=32, Length=32):
toolbar = QToolBar("My Main Toolbar")
toolbar.setIconSize(QSize(Width, Length)) # manual size
self.addToolBar(toolbar) # showing toolbar
icon_name = QAction(QIcon(icon_image), icon_name, self)
# icon_name.triggered.connect(
# self.notification_button) # add different command later, for now its quitting,
self.toolBar = self.addToolBar('RUN IT')
# self.toolBar.addAction(icon1) #will be the default icon size from windows
toolbar.addAction(icon_name)
return icon_name
# ------main menu dialog --------
def file_menu(self):
# creating a toolbar with menu
mainMenu = self.menuBar()
fileMenu = mainMenu.addMenu('File')
quit_tooltip = QAction('&Quit', self)
quit_tooltip.setShortcut('Ctrl+Q')
quit_tooltip.setToolTip('close the app')
# quit_tooltip.triggered.connect(self.close_app)
quit_tooltip.triggered.connect(self.close_app)
fileMenu.addAction(quit_tooltip)
def view_menu(self):
mainMenu = self.menuBar()
viewMenu = mainMenu.addMenu('View')
# ------main menu dialog --------
def notification_button(self):
return self.areYouSure_toolbar(message='NO ACTION DEFINED')
def close_app(self): # defined our own method of closing
# choice = QMessageBox.question(self, ' WARNING!!!!', 'Are you sure to quit?',
# QMessageBox.Yes | QMessageBox.No,
# QMessageBox.No) # the last QMessageBox.No is to highliht the option implicitly
choice = self.areYouSure_toolbar(message='Quit')
if choice == QMessageBox.Yes:
print('Quiting Application')
return sys.exit()
else:
pass
def areYouSure_toolbar(self, message='____'):
choice = QMessageBox.question(self, ' WARNING!!!!', 'Are you sure to {}'.format(message),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No) # the last QMessageBox.No is to highliht the option implicitly
return choice
class NewGrid(QWidget):
def __init__(self, parent=None):
super(NewGrid, self).__init__(parent)
grid = QGridLayout()
grid.addWidget(self.createExampleGroup(), 0, 0)
grid.addWidget(self.check_box_vBBU(), 0, 1)
grid.addWidget(self.button_test(), 1, 0)
grid.addWidget(self.check_box_LPR(), 1, 1)
grid.addWidget(self.quit_button(), 3, 3)
# grid.addWidget(self.createExampleGroup(), 1, 2)
self.setLayout(grid)
# temp = Window()
# self.setWindowTitle("PyQt5 Group Box")
# self.resize(400, 300)
def check_box_vBBU(self):
groupBox = QGroupBox("Input (From vBBU) ")
checkbox1 = QCheckBox('Port {}'.format(1), self)
checkbox2 = QCheckBox('Port {}'.format(2), self)
check_box = QHBoxLayout()
check_box.addWidget(checkbox1)
check_box.addWidget(checkbox2)
check_box.addStretch(1)
groupBox.setLayout(check_box)
return groupBox
def check_box_LPR(self):
groupBox = QGroupBox("Output (from LPR) ")
layout = QHBoxLayout()
for n in range(20):
btn = QCheckBox('LPR' + str(n))
# btn.pressed.connect(self.close_app_Newgrid) #where to connect
layout.addWidget(btn)
# btn.setChecked(True)
num_of_buttons = n
# widget = QWidget()
selectRandom_btn = QPushButton('Random ports')
layout.addWidget(selectRandom_btn)
selectAll_btn = QPushButton('All')
selectAll_btn.pressed.connect(lambda : self.select_buttons(num_of_buttons, btn))
layout.addWidget(selectAll_btn)
groupBox.setLayout(layout)
return groupBox
def select_buttons(self, num_of_buttons,btn):
for x in range(1,num_of_buttons-1):
btn.setC
# btn[1].setChecked(True)
def quit_button(self):
groupBox = QGroupBox("Quit_placeholder")
Q_button = QPushButton('Quit', self)
box = QHBoxLayout()
box.addWidget(Q_button)
super()
# Q_button.clicked.connect(self.close_app_Newgrid)
# Q_button.clicked.connect(Window.close_app)
groupBox.setLayout(box)
return groupBox
def button_test(self):
groupBox = QGroupBox("test buttons")
btn1 = QPushButton("Push me", self)
# btn1.clicked.connect(self.close_app)# need to connect the button somewhere..
vbox = QHBoxLayout()
vbox.addWidget(btn1)
# vbox.addStretch(1)
groupBox.setLayout(vbox)
return groupBox
def createExampleGroup(self):
groupBox = QGroupBox("Data type")
radio1 = QRadioButton("Binary")
radio2 = QRadioButton("Decimal")
radio1.setChecked(True)
vbox = QVBoxLayout()
vbox.addWidget(radio1)
vbox.addWidget(radio2)
vbox.addStretch(1)
groupBox.setLayout(vbox)
return groupBox
def close_app_Newgrid(self): # defined our own method of closing
choice = QMessageBox.question(self, ' WARNING!!!!', 'Are you sure to quit?',
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No) # the last QMessageBox.No is to highliht the option implicitly
if choice == QMessageBox.Yes:
# print('Quiting Application')
return sys.exit()
else:
return
def run():
app = QApplication(sys.argv)
Gui = Window()
sys.exit(app.exec_())
run()
update :
I tried to simplify the solution below:
for w in QApplication.topLevelWidgets():
if isinstance(w,Window):
w = True
windows = w
The connection is between the signal of one object and the slot of another, not between classes, so the instruction is incorrect.
So in order to perform this task we must get some way to the Window object, a possible solution is to take advantage of Window is the window, so it is a top-Level for it we use the topLevelWidgets() method and we filter through isinstance():
def quit_button(self):
groupBox = QGroupBox("Quit_placeholder")
Q_button = QPushButton('Quit', self)
box = QHBoxLayout()
box.addWidget(Q_button)
#Q_button.clicked.connect(self.close_app_Newgrid)
windows = [w for w in QApplication.topLevelWidgets() if isinstance(w, Window)]
if windows:
Q_button.clicked.connect(windows[0].close_app)
groupBox.setLayout(box)
return groupBox
Note: the problem is not due to inheritance.

PyQt4 video player crashes when moving window

I've written a simple PyQt4 GUI that plays an OpenCV VideoCapture. This requires converting frames from numpy arrays to QImages. I'm using OpenCV so that I can detect circles using my findCircles method.
However, when I pass my frames to findCircles, the program crashes when the window is moved. This problem does not occur when I don't search for circles. I don't understand why this is happening, as I'm under the impression that the work is being done on a different thread than the GUI since I call findCircles from the run method of a QThread.
Note that I don't receive a normal error message in the console; Python crashes like such:
Here is the video file I've been using to test my player. I'm running Python 2.7.6 on Windows 8.1.
import sys
import cv2.cv as cv, cv2
from PyQt4.Qt import *
import time
def numpyArrayToQImage(array):
if array != None:
height, width, bytesPerComponent = array.shape
bytesPerLine = bytesPerComponent * width;
cv2.cvtColor(array, cv.CV_BGR2RGB, array)
return QImage(array.data, width, height, bytesPerLine, QImage.Format_RGB888)
return None
def findCircles(frame):
grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurredFrame = cv2.medianBlur(grayFrame, 3)
circles = cv2.HoughCircles(blurredFrame, cv.CV_HOUGH_GRADIENT, 1, 30, param1=50, param2=30, minRadius=30, maxRadius=35)
if circles is not None:
for i in circles[0]:
cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center
class VideoThread(QThread):
frameProcessed = pyqtSignal(QImage)
def __init__(self, video, videoLabel):
QThread.__init__(self)
self.video = video
self.fps = self.video.get(cv.CV_CAP_PROP_FPS)
self.frameCount = self.video.get(cv.CV_CAP_PROP_FRAME_COUNT)
self.startingSecond = 0
self.videoLabel = videoLabel
def run(self):
clockAtStart = time.clock()
while True:
runtime = self.startingSecond + (time.clock() - clockAtStart)
currentFrame = int(runtime * self.fps)
if currentFrame < self.frameCount - 1:
self.video.set(cv.CV_CAP_PROP_POS_FRAMES, currentFrame)
frame = self.video.read()[1]
findCircles(frame) # Removing this line removes the issue
self.frameProcessed.emit(numpyArrayToQImage(frame))
time.sleep(.02)
else:
break
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.initUI()
#pyqtSlot(QImage)
def updateVideoLabel (self, image):
self.videoLabel.setPixmap(QPixmap.fromImage(image))
self.videoLabel.update()
def initUI(self):
self.setGeometry(300, 300, 500, 375)
self.setMinimumHeight(250)
self.createWidgets()
self.addWidgets()
def startNewVideo(self):
self.video = cv2.VideoCapture(unicode(QFileDialog.getOpenFileName(self, "Open video").toUtf8(), encoding="UTF-8"))
self.videoThread = VideoThread(self.video, self.videoLabel)
self.videoThread.frameProcessed.connect(self.updateVideoLabel)
self.playVideoFrom(0)
def playVideoFrom(self, frame):
self.videoThread.startingSecond = frame / self.videoThread.fps
self.videoThread.start()
def createWidgets(self):
self.populateMenuBar()
self.videoLabel = QLabel()
self.videoLabel.setStyleSheet('background-color : black;');
def populateMenuBar(self):
self.menuBar = self.menuBar()
fileMenu = QMenu('File', self)
openAction = QAction('Open video...', self)
openAction.triggered.connect(self.startNewVideo)
fileMenu.addAction(openAction)
self.menuBar.addMenu(fileMenu)
def addWidgets(self):
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.videoLabel, 1)
centralWidget = QWidget()
self.setCentralWidget(centralWidget)
centralWidget.setLayout(mainLayout)
if __name__ == '__main__':
app = QApplication(sys.argv)
player = MainWindow()
player.show()
sys.exit(app.exec_())
I've tested your program, and it crashes when it finds no circles as indicated in the error message:
Traceback (most recent call last):
File "test_opencv_tkinter.py", line 53, in run
findCircles(frame) # Removing this line removes the issue
File "test_opencv_tkinter.py", line 26, in findCircles
if len(circles) > 0:
TypeError: object of type 'NoneType' has no len()
I've made some changes in the findCircles(frame) function, as follows, and it runs without error, even when I move the window around on the screen.
def findCircles(frame):
grayFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurredFrame = cv2.medianBlur(grayFrame, 3)
circles = cv2.HoughCircles(grayFrame,cv.CV_HOUGH_GRADIENT,1,20,
param1=50,param2=30,minRadius=0,maxRadius=0)
if circles == None:
print "no circles found"
return
if len(circles) > 0:
print "found circles ", len(circles[0])
for i in circles[0]:
cv2.circle(frame, (i[0], i[1]), i[2], (255, 0, 0), 1) # Perimeter
cv2.circle(frame, (i[0], i[1]), 3, (0, 255, 0), -1) # Center

Resources