PyQt6 get the parent window of QTreeWidgetItem? - python

I am using PyQt6 6.1.0 on Python 3.9.6 and I would like to get the parent QMainWindow of the parent QTreeWidget of parents QTreeWidgetItems of a QTreeWidgetItem, because I need to modify the window from within the QTreeWidgetItem.
The full code is very long, over 1800 lines and over 60 KiB in size, when I put it in one file it works without problem, but when in one file its readability is very low, and when split into files the global variables can be hard to access from within classes.
Basically my idea is to call .parent() repetitively until the object's class name is 'MainWindow'.
def getWindow(obj):
window = obj.parent()
while True:
if type(window).__name__ == 'MainWindow':
break
window = window.parent()
return window
I didn't use the class directly because the class is defined in the main file.
Here are two classes defined in a file named groupA.py:
class TreeNode(QTreeWidgetItem):
def __init__(self, texts):
super().__init__()
self.isSongNode = False
self.setFlags(
FLAG.ItemIsAutoTristate
| FLAG.ItemIsEnabled
| FLAG.ItemIsSelectable
| FLAG.ItemIsUserCheckable
)
self.setCheckState(0, Qt.CheckState.Unchecked)
self.data = dict(zip(fields, texts))
self.song = None
for i in ("title", "album", "artist"):
if i in self.data:
if i == "title":
self.setTextAlignment(0, Qt.AlignmentFlag.AlignLeft)
self.setText(0, self.data["title"])
self.setToolTip(0, self.data["title"])
self.setText(1, self.data["duration"])
self.setText(2, self.data["instrumental"])
self.setText(3, self.data["downloaded"])
self.setText(4, ",".join(self.data["language"]))
self.setText(5, self.data["artistgender"])
for j in range(1, 6):
self.setTextAlignment(j, Qt.AlignmentFlag.AlignCenter)
self.song = song(*[self.data[i] for i in cells])
self.isSongNode = True
else:
self.setText(0, self.data[i])
self.setToolTip(0, self.data[i])
break
def detail(self):
print(self)
print(self.parent())
print(self.parent().parent())
print(self.parent().parent().parent())
window = getWindow(self)
if self.childCount() != 0:
for i in range(self.childCount()):
self.child(i).detail()
else:
if self.song not in detailed_items and self.isSongNode:
widget = SongPage(self.data)
window.detailArea.scrollArea.Layout.addWidget(widget)
widget.autoResize()
detailed_items.append(self.song)
class Tree(QTreeWidget):
def __init__(self):
super().__init__()
self.init()
def init(self):
self.setColumnCount(len(HEADERS))
self.setHeaderLabels(HEADERS)
font = Font(9)
self.setFont(font)
self.header().setFont(font)
self.setColumnWidth(0, 900)
fontRuler = QFontMetrics(font)
for i in range(1, len(HEADERS)):
Width = fontRuler.size(0, HEADERS[i]).width() + 16
self.setColumnWidth(i, Width)
self.setAutoScroll(True)
self.setIndentation(32)
self.setAlternatingRowColors(True)
self.setUniformRowHeights(True)
self.itemClicked.connect(self.onItemClicked)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
#pyqtSlot(QTreeWidgetItem)
def onItemClicked(self, item):
window = getWindow(self)
nodes = self.findItems(
"", Qt.MatchFlag.MatchContains | Qt.MatchFlag.MatchRecursive
)
for node in nodes:
if (
node.checkState(0) == Qt.CheckState.Unchecked
and node.song in detailed_items
):
i = detailed_items.index(node.song)
detailed_items.remove(node.song)
layoutitem = window.detailArea.scrollArea.Layout.itemAt(i)
widget = layoutitem.widget()
window.detailArea.scrollArea.Layout.removeItem(layoutitem)
window.detailArea.scrollArea.Layout.removeWidget(widget)
elif (
node.childCount() == 0
and node.checkState(0) == Qt.CheckState.Checked
and node.isSongNode
):
node.detail()
if item.childCount() == 0 and item.isSongNode:
if item.song not in detailed_items:
item.detail()
index = detailed_items.index(item.song)
dim_others(window, index)
widget = window.detailArea.scrollArea.Layout.itemAt(index).widget()
QTimer.singleShot(
25, lambda: window.detailArea.scrollArea.ensureWidgetVisible(
widget)
)
widget.highlight()
setButtonsStatus(window)
The tree is filled by this:
def add_nodes(tree, cls, data):
indexes.clear()
for artist, albums in data.items():
artist_node = cls([artist])
indexes.add([artist])
for album, songs in albums.items():
album_node = cls([artist, album])
indexes.add([artist, album])
for song in songs:
song_node = cls([artist, album, *song])
indexes.add([artist, album, song[0]])
album_node.addChild(song_node)
artist_node.addChild(album_node)
tree.addTopLevelItem(artist_node)
And this is the start of code execution:
if __name__ == '__main__':
mysql80 = psutil.win_service_get("MySQL80").as_dict()
if mysql80["status"] != "running":
os.system("net start MySQL80")
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(u'Asceteria')
songs = fetchdata()
entries = treefy(songs)
app = QApplication(sys.argv)
app.setStyle("Fusion")
tree = Tree()
add_nodes(tree, TreeNode, entries)
filterArea = FilterArea()
Window = MainWindow()
print(tree)
print(tree.parent())
print(tree.parent().parent())
Window.showMaximized()
app.setWindowIcon(QIcon(ICON))
app.exec()
When I check any QTreeWidgetItem, the program crashes, because the top level QTreeWidgetItems don't have parents:
<groupA.Tree object at 0x0000022ABCFE9430>
<PyQt6.QtWidgets.QWidget object at 0x0000022AC10DBAF0>
<__main__.MainWindow object at 0x0000022AC10DBA60>
<groupA.TreeNode object at 0x0000022ABE087EE0>
<groupA.TreeNode object at 0x0000022ABE087E50>
<groupA.TreeNode object at 0x0000022ABE087DC0>
None
Traceback (most recent call last):
File "D:\Asceteria\groupA.py", line 148, in onItemClicked
node.detail()
File "D:\Asceteria\groupA.py", line 90, in detail
window = getWindow(self)
File "D:\Asceteria\functions.py", line 112, in getWindow
window = window.parent()
AttributeError: 'NoneType' object has no attribute 'parent'
How can I fix this?

It is not necessary to implement any method to obtain the window associated with a widget since Qt provides the window() method for it:
QWidget *QWidget::window() const Returns the window for this widget,
i.e. the next ancestor widget that has (or could have) a window-system
frame.
If the widget is a window, the widget itself is returned.
Typical usage is changing the window title:
aWidget->window()->setWindowTitle("New Window Title");
In your case use:
window = self.treeWidget().window()
Note: If you want to verify that an object is an instance of a class or its ancestor then you must use isinstace:
isinstance(window, MainWindow)
Note: The relationship between QTreeWidgetItem is different from that of QWidgets.

Related

Crashes when moving cellWidgets around in a TableWidget

I am writing a tool that allows me to track some tasks along a path of predifined stages, from something on a backlog, to ToDo, through WIP, Review and finally to done.
I created a custom widget, that will eventually be yellow, not unlike a postit note and perhaps with a bit of formatting it to give it a nice frame, etc... but stopped before getting far enough to make it look right because of this issue.
The idea is that each of these yellow Task widgets will have a stage they are at, and that I can select them in a Table Widget, and move them onto the next or previous stage, which will update taht objects stage, then refresh the TableWidget, read all the widget and where thay should be and set them in their new place.
So I have it kind of working to some degree (below), where I can move the tasks forward and they update location, but I noticed when I click the cells that the widget was previously in, print statement still says that the cell still has a widget there (which kind of makes sense, as code below isn't removing the previous one, but I'd expect to visually still see it). And I can move them forward and backwards, and the information on the tasks does update correctly, but the table won't refresh unless the task moves to a cell that never had a cellWidget in it. Test this by moving it backwards. It works, movnig forward visually does nothing, but moving again, does show up.
I tried clearing the TableWidget and rebuilding from scratch and that crashes. The main issue I am having is that with all these crashes, which is an issue in itself as it makes debugging very tough... When I try and clear the TableWidget (with .clear()) before repopulating, I get this.
Process finished with exit code -1073741819 (0xC0000005)
Same error code if I try removing the old cells by setting the Table Widget to 0 rows before adding the correct number of rows.
A known issue that is less important is when I select a cell without a widget and try and move it, gies me this, but don't worry too much about that fix, as it's known issue.
Process finished with exit code -1073740791 (0xC0000409)
Also tried cleaning up by iterating every cell and if it has a cell widget, remove cell widget before re-setting them to correct place and it still crashes. I'm out of ideas.
Task Widget
import sys
from PyQt5.QtWidgets import (QApplication, QTableWidget, QWidget, QFrame, QHBoxLayout, QLabel,
QPushButton,QVBoxLayout)
class Task(QWidget):
def __init__(self, ID, name, est):
super(Task, self).__init__()
# Creates a small widget that will be added to a table widget
self.ID = ID
self.name = name
self.est = est
# These cell widgets represent tasks. So each task has a particular 'stage' it is at
self.stage = 'ToDo'
self.stages = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
self.objects_labels = {}
self.initUI()
def initUI(self):
# adds a bunch of labels to the widget
layout = QVBoxLayout()
frame = QFrame()
frame.setFrameShape(QFrame.StyledPanel)
frame.setStyleSheet('background-color: red')
frame.setLineWidth(2)
layout.addWidget(frame)
info = [self.ID, self.name, self.est]
for section in info:
self.objects_labels[section] = QLabel(str(section))
layout.addWidget(self.objects_labels[section])
self.setLayout(layout)
self.setStyleSheet('background-color: yellow')
def task_move(self, forward = True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
print(self.stages)
print(index)
if forward:
print('--->')
if self.stage == self.stages[-1]:
print('Already at the end of process')
return
self.stage = self.stages[index + 1]
else:
print('<---')
if self.stage == self.stages[0]:
print('Already at the start of process')
return
self.stage = self.stages[index - 1]
MainWidget
class MainWidget(QWidget):
def __init__(self):
super().__init__()
self.tasks = self.make_tasks()
self.init_ui()
self.update_tw()
def make_tasks(self):
# Create a few tasks
a = Task(0, 'Name_A', 44)
b = Task(0, 'Name_B', 22)
c = Task(0, 'Name_C', 66)
d = Task(0, 'Name_D', 90)
return [a, b, c, d]
def init_ui(self):
layout_main = QVBoxLayout()
self.tw = QTableWidget()
self.tw.cellClicked.connect(self.cell_clicked)
self.tw.horizontalHeader().setDefaultSectionSize(120)
self.tw.verticalHeader().setDefaultSectionSize(120)
layout_main.addWidget(self.tw)
layout_bottom_button_bar = QHBoxLayout()
self.btn_task_backward = QPushButton('<--- Task')
self.btn_task_backward.clicked.connect(lambda: self.move_task(forward=False))
self.btn_task_forward = QPushButton('Task --->')
self.btn_task_forward.clicked.connect(lambda: self.move_task())
for widget in [self.btn_task_backward, self.btn_task_forward]:
layout_bottom_button_bar.addWidget(widget)
layout_main.addLayout(layout_bottom_button_bar)
self.setLayout(layout_main)
self.setGeometry(300, 300, 800, 600)
self.setWindowTitle('MainWidget')
self.show()
#property
def tw_header(self):
return {'Backlog': 0, 'ToDo': 1, 'WIP': 2, 'Review': 3, 'Done': 4}
#property
def selected_indices(self):
return [(x.row(), x.column()) for x in self.tw.selectedIndexes()]
#property
def selected_widgets(self):
selected_widgets = [self.tw.cellWidget(x[0], x[1]) for x in self.selected_indices]
print(selected_widgets)
return selected_widgets
def move_task(self, forward=True):
# Crashes if you select a non-widget cell, but thats a known issue
# Moves the task forward or backward and then prompts to update the TableWidget
for object in self.selected_widgets:
object.task_move(forward=forward)
self.tw.clearSelection()
self.update_tw()
def cell_clicked(self, row, column):
if self.tw.cellWidget(row, column):
print(self.selected_indices)
print(self.selected_widgets)
else:
print('No Cell Widget here')
def update_tw(self):
#I wanted to clear the Table widget and rebuild, but this crashes
# self.tw.clear()
self.tw.setHorizontalHeaderLabels(self.tw_header.keys())
rows = len(self.tasks)
columns = len(self.tw_header)
self.tw.setRowCount(rows)
self.tw.setColumnCount(columns)
# Looks through each task, and then gets it's stage, and then adds the widget to the correct column
for index, object in enumerate(self.tasks):
column = self.tw_header[object.stage]
print('Setting stage {} for {}\n...to r={}, c={}\n***'.format(object.stage, object, index, column))
self.tw.setCellWidget(index, column, object)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWidget()
sys.exit(app.exec_())
From my previous experience, I always found using setCellWidget clunky, underperforming and buggy.
Most of the times my Widgets were lost or misplaced, while refreshing the table similarly to the way you are doing it.
In addition, I guess you would want to use this "Task Mover" on a larger scale, and from what I could see, setting separate Widgets inside QWidgetItems becomes quite slow when done on loads of items.
My suggestion would be to use style delegates, so that you can customize the look of your items to your liking, without having to deal with the setCellWidget stuff which is giving you problem.
Once you have your own delegate, and paint the items the way you want, you can just keep updating that item data and moving the items around the table by using "take" and "set".
I am not sure if this would be the best way of executing this specific task, but moving towards this direction would probably give you greater flexibility and customisation power in the long run.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class TaskProperty():
properties = ["ID", "name", "est", "stage"]
count = 4
ID, Name, Est, Stage = [Qt.UserRole + x for x in range(count)]
STAGES = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
class MainWidget(QWidget):
def __init__(self):
super(MainWidget, self).__init__()
self.tasks = self.make_tasks()
self.init_ui()
self.update_tw()
def make_tasks(self):
# Create a few tasks
a = Task(0, 'Name_A', 44)
b = Task(0, 'Name_B', 22)
c = Task(0, 'Name_C', 66)
d = Task(0, 'Name_D', 90)
return [a, b, c, d]
def init_ui(self):
layout_main = QVBoxLayout()
self.tw = QTableWidget()
# create and set the delegate to the TableWidget
self.delegate = TaskDelegate(self.tw )
self.tw.setItemDelegate(self.delegate)
self.tw.cellClicked.connect(self.cell_clicked)
self.tw.horizontalHeader().setDefaultSectionSize(120)
self.tw.verticalHeader().setDefaultSectionSize(120)
layout_main.addWidget(self.tw)
layout_bottom_button_bar = QHBoxLayout()
self.btn_task_backward = QPushButton('<--- Task')
self.btn_task_backward.clicked.connect(lambda: self.move_task(forward=False))
self.btn_task_forward = QPushButton('Task --->')
self.btn_task_forward.clicked.connect(lambda: self.move_task())
for widget in [self.btn_task_backward, self.btn_task_forward]:
layout_bottom_button_bar.addWidget(widget)
layout_main.addLayout(layout_bottom_button_bar)
self.setLayout(layout_main)
self.setGeometry(300, 300, 800, 600)
self.setWindowTitle('MainWidget')
self.show()
#property
def tw_header(self):
return {'Backlog': 0, 'ToDo': 1, 'WIP': 2, 'Review': 3, 'Done': 4}
#property
def selected_indices(self):
return [(x.row(), x.column()) for x in self.tw.selectedIndexes()]
def move_task(self, forward=True):
'''
To move the task to the next step, we iterate all the items selected.
If the task can be moved, we take the corresponding item from its current cell and move it to the destination.
:param forward:
:return:
'''
selected =self.tw.selectedItems()
for item in selected:
item.setSelected(False)
result = item.task_move(forward=forward)
if result:
next = 1 if forward else -1
row = item.row()
column = item.column()
moveItem = self.tw.takeItem(row, column)
self.tw.setItem(row, column + next, moveItem)
moveItem.setSelected(True)
def cell_clicked(self, row, column):
item = self.tw.item(row, column)
if not isinstance(item, TaskItem):
print "No Task Item Here"
def update_tw(self):
# I wanted to clear the Table widget and rebuild, but this crashes
# self.tw.clear()
self.tw.clear()
self.tw.setHorizontalHeaderLabels(self.tw_header.keys())
rows = len(self.tasks)
columns = len(self.tw_header)
self.tw.setRowCount(rows)
self.tw.setColumnCount(columns)
# Looks through each task, and then gets it's stage, and then adds the widget to the correct column
for row, object in enumerate(self.tasks):
# create items of our custom type only for the column that need to be filled.
# the other cells will be filled with null items.
column = STAGES.index(object.stage)
print('Setting stage {} for {}\n...to r={}, c={}\n***'.format(object.stage, object, row, column))
item = TaskItem(object)
self.tw.setItem(row, column, item)
class TaskDelegate(QStyledItemDelegate):
'''
This delegate take care of Drawing our cells the way we want it to be.
'''
def paint(self, painter, option, index):
'''
Override the Paint function to draw our own cell.
If the QTableWidgetItem does not have our Data stored in it, we do a default paint
:param painter:
:param option:
:param index:
:return:
'''
painter.save()
rect = option.rect
status = index.data(TaskProperty.Stage)
if status is None:
return super(TaskDelegate, self).paint(painter, option, index)
else:
id = STAGES.index(status)
pen = painter.pen()
pen.setBrush(Qt.black)
painter.setPen(pen)
if id == index.column():
rect.translate(3, 3)
newRect = QRect(rect.x(), rect.y(), rect.width() - 6, 20)
infos = [index.data(TaskProperty.ID), index.data(TaskProperty.Name), index.data(TaskProperty.Est)]
painter.setBrush(Qt.red)
painter.drawRect(newRect)
painter.setBrush(Qt.yellow)
for info in infos:
newRect.translate(0, 25)
painter.drawRect(newRect)
painter.drawText(newRect, Qt.AlignHCenter | Qt.AlignVCenter,
str(info))
class TaskItem(QTableWidgetItem):
'''
Subclass QTableWidgetItem.
Probably not needed, since we can set the property when we create the item instead of in the init,
and keep track of which item is attached to which task object using the Column Index of the table.
However, this can be useful if you want to attach more specific procedures to your items
'''
def __init__(self, task):
super(TaskItem, self).__init__()
self._task = task
self.setData(TaskProperty.ID, task.ID)
self.setData(TaskProperty.Name, task.name)
self.setData(TaskProperty.Est, task.est)
self.setData(TaskProperty.Stage, task.stage)
self.objects_labels = {}
def task_move(self, forward=True):
result = self._task.task_move(forward=forward)
self.setData(TaskProperty.Stage, self._task.stage)
return result
class Task(object):
'''
The Task class is now just an object, not a widget.
'''
def __init__(self, ID, name, est):
# Creates a small widget that will be added to a table widget
self.ID = ID
self.name = name
self.est = est
# These cell widgets represent tasks. So each task has a particular 'stage' it is at
self.stage = 'ToDo'
self.stages = ['Backlog', 'ToDo', 'WIP', 'Review', 'Done']
self.objects_labels = {}
def task_move(self, forward=True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
if forward:
print('--->')
if self.stage == self.stages[-1]:
#print('Already at the end of process')
return False
self.stage = self.stages[index + 1]
else:
print('<---')
if self.stage == self.stages[0]:
#print('Already at the start of process')
return False
self.stage = self.stages[index - 1]
return True
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MainWidget()
sys.exit(app.exec_())
It is not necessary to clean and create everything again, instead just move the widget for it we must know if it can be moved or not and for that task_move must indicate if the movement is valid or not. Considering the above, the solution is:
def task_move(self, forward=True):
# The main widget will allow me to change the stage of a particular Task
# The idea is that I update the Table widget to show everything in the right place
# This function finds out what stage it is at and increments/decrements by one
index = self.stages.index(self.stage)
print(self.stages)
print(index)
if forward:
print("---&gt")
if self.stage == self.stages[-1]:
print("Already at the end of process")
return False
self.stage = self.stages[index + 1]
else:
print("&lt---")
if self.stage == self.stages[0]:
print("Already at the start of process")
return False
self.stage = self.stages[index - 1]
return True
def move_task(self, forward=True):
for row, column in self.selected_indices:
widget = self.tw.cellWidget(row, column)
if isinstance(widget, Task) and widget.task_move(forward):
next_column = column + (1 if forward else -1)
# create new task widget
task = Task(widget.ID, widget.name, widget.est)
# remove all task widget
self.tw.removeCellWidget(row, column)
# move task widget
self.tw.setCellWidget(row, next_column, task)
self.tw.clearSelection()
The crashed is because when using clear you are also removing the Task widget so "self.tasks" has objects deleted from C++ that you should not use.

Can't connect pyQTSignal

Is is a piece of my code . I have two classes CheckerScene and Checkers . CHesckers - is my main window . I can't realize EndGameSignal defined in CheckerScene class . When it emits , pySlot can't catch it in class Checkers , as i want . When my EndGameSignal emmits - i want to see a dialog message on my main screen (pyQtSlots functions realized in my code), not on the scene . How can i correct my program to do it .
class CheckerScene(QtWidgets.QGraphicsScene):
EndGameSignal=QtCore.pyqtSignal('QString')
def init(self):
QtWidgets.QGraphicsScene.init(self)
# scene congifuratios
self.setSceneRect(margin, margin, gridCount * gridSlotSize, gridCount * gridSlotSize)
self.addRect(self.sceneRect())
# create signal . It will be emit() from blackboard.crash()
self.signaldel.connect(self.del_item)
#choosing the visual checker and its coordinates
self.current = None
#list of grids and checkers
self.grid = []
self.white_checkers = []
self.black_checkers = []
for row in range(8):
for column in range(8):
# this is a "trick" to make the grid creation easier: it creates
# a grid square only if the row is odd and the column is even,
# and viceversa.
if (not row & 1 and column & 1) or (row & 1 and not column & 1):
# create a gridItem with a rectangle that uses 0-based
# coordinates, *then* we set its position
gridItem = self.addRect(0, 0, gridSlotSize, gridSlotSize)
gridItem.setPos(margin + column * gridSlotSize, margin + row * gridSlotSize)
gridItem.setBrush(QtGui.QColor(QtCore.Qt.lightGray))
self.grid.append(gridItem)
if 3 <= row <= 4:
# don't add checkers in the middle
continue
# create checkers being careful to assign them the gridItem
# as a *parent*; their coordinate will *always* be relative
# to the parent, so that if we change it, they will always
# be centered
if row < 3:
self.black_checkers.append(CheckerItem(0, gridItem))#!
else:
self.white_checkers.append(CheckerItem(1, gridItem))#!
self.additionsl__init__()
self.EndGameSignal.connect(Checkers.handler_EndGameSignal)
self.EndGameSignal.emit('=NAME')
class Checkers(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.Initialization()
def Initialization(self):
layout = QtWidgets.QGridLayout()
self.setLayout(layout)
self.player2Label = QtWidgets.QLabel('Player 2')
layout.addWidget(self.player2Label)
self.player2Label.setAlignment(QtCore.Qt.AlignCenter)
self.checkerView = QtWidgets.QGraphicsView()
layout.addWidget(self.checkerView)
self.checkerScene = CheckerScene()
self.checkerView.setScene(self.checkerScene)
self.checkerView.setFixedSize(gridSize, gridSize)
# set the Antialiasing render hints to paint "smoother" shapes
self.checkerView.setRenderHints(QtGui.QPainter.Antialiasing)
self.player1Label = QtWidgets.QLabel('Player 1')
layout.addWidget(self.player1Label)
self.player1Label.setAlignment(QtCore.Qt.AlignCenter)
#QtCore.pyqtSlot(str)
def handler_EndGameSignal(self, result):
result=QtWidgets.QMessageBox.question(self,f"Выиграл {result}","Сиграть еще раз ?",QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No,QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes :
self.close()
else :
pass
print(f"WINNER {result}")
#QtCore.pyqtSlot(bool)
def handler_EndGameSignal(self, result):
result = QtWidgets.QMessageBox.question(self, f"НИЧЬЯ !", "Сиграть еще раз ?",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
self.close()
else:
pass
print("DRAW")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
checkers = Checkers()
checkers.show()
sys.exit(app.exec_())
As with your previous question, you're still confusing classes and instancies. In your code you connect the signal to the class, while you have to connect it to the instance.
Since you have no reference with the receiver (the Checker instance) from the sender (the scene), you'll have to connect it from the former:
class Checkers(QtWidgets.QWidget):
def Initialization(self):
# ...
self.checkerScene.EndGameSignal.connect(self.handler_EndGameSignal)

QCheckBox state change PyQt4

I'm trying to implement a system in PyQt4 where unchecking a checkbox would call function disable_mod and checking it would call enable_mod. But even though state is changing the checkboxes call the initial function they started with. For this case if an already checked box was clicked it'd always keep calling the disable_mod function! I don't understand why is this happening? Can you guys help me out here a little bit? Here's my code:
from PyQt4 import QtCore, QtGui
from os import walk
from os.path import join
import sys
def list_files_regex(dir):
l = []
for (root, dirnames, filenames) in walk(dir):
for d in dirnames:
list_files_regex(join(root, d))
l.extend(filenames)
return l
directory = "J:/test_pack"
directory = join(directory, "/mods")
count = 0
for y in list_files_regex(directory):
print y
count += 1
print count
class ModEdit(QtGui.QMainWindow):
def __init__(self, title, icon, x, y, w, h):
super(ModEdit, self).__init__()
self.setWindowTitle(title)
self.setWindowIcon(QtGui.QIcon(icon))
self.setGeometry(x, y, w, h)
self.choices = []
self.init()
def init(self):
scroll_widget = QtGui.QScrollArea()
sub_widget = QtGui.QWidget()
v_layout = QtGui.QVBoxLayout()
for y in list_files_regex(directory):
tmp = QtGui.QCheckBox(y, self)
tmp.resize(tmp.sizeHint())
if tmp.text()[len(tmp.text()) - 8: len(tmp.text())] != 'disabled':
tmp.setChecked(True)
# if tmp.isChecked() == 0:
# tmp.stateChanged.connect(self.enable_mod)
# if tmp.isChecked():
# tmp.stateChanged.connect(self.disable_mod)
# v_layout.addWidget(tmp)
self.choices.append(tmp)
print self.choices
for choice in self.choices:
v_layout.addWidget(choice)
if choice.isChecked():
choice.stateChanged.connect(self.disable_mod)
else:
choice.stateChanged.connect(self.enable_mod)
sub_widget.setLayout(v_layout)
scroll_widget.setWidget(sub_widget)
self.setCentralWidget(scroll_widget)
self.show()
def enable_mod(self):
print "ENABLE_TEST"
print self.choices[1].isChecked()
def disable_mod(self):
print "DISABLE_TEST"
print self.choices[1].isChecked()
def test(self):
print 'test'
for ch in self.choices:
if ch.isChecked():
ch.stateChanged.connect(self.disable_mod)
else:
ch.stateChanged.connect(self.enable_mod)
class Rename(QtCore.QObject):
enable = QtCore.pyqtSignal
disable = QtCore.pyqtSignal
app = QtGui.QApplication(sys.argv)
ex = ModEdit("Minecraft ModEdit", "ModEdit.png", 64, 64, 640, 480)
sys.exit(app.exec_())
The problem is that you're only connecting the checkbox stateChanged signal once during initialization. After the state of the checkbox changes, you're not disconnecting the signal and reconnecting it to the correct slot.
You'll need to connect the stateChanged signal to an intermediary slot that will decide which function to call based on the checkbox state. Since you're using the same slot for multiple checkboxes, it's probably best to also pass the checkbox to the slot as well.
from functools import partial
def init(self):
...
for tmp in list_of_checkboxes:
enable_slot = partial(self.enable_mod, tmp)
disable_slot = partial(self.disable_mod, tmp)
tmp.stateChanged.connect(lambda x: enable_slot() if x else disable_slot())
def enable_mod(self, checkbox):
print "ENABLE_TEST"
print checkbox.isChecked()
def disable_mod(self, checkbox):
print "DISABLE_TEST"
print checkbox.isChecked()
Alternatively, since we are now passing the checkbox to the slots, you could just use a single slot and check the checkbox state inside the slot
def init(self):
...
for tmp in list_of_checkboxes:
slot = partial(self.enable_disable_mod, tmp)
tmp.stateChanged.connect(lambda x: slot())
def enable_disable_mod(self, checkbox):
if checkbox.isChecked():
...
else:
...

PyQt - Set model of a proxymodel in QThread

In my project i noticed that the main dialog freezes when it is setting the model of some ProxyModel, so i decided to create a new thread for this task to provide the responsiveness of the window, but right now it keeps popping an error that say:
TypeError: QTableView.setModel(QAbstractItemModel): argument 1 has unexpected type 'tuple'
and i don't know why...
Here's my code:
This is the QThread for updating the proxyModel with the arguments i provide
class ThreadedProxyModel(QThread):
def __init__(self, contacts, contactsProxyModel, groups, groupsProxyModel,
chatSession, chatSessionProxyModel, msgs, msgsProxyModel):
QThread.__init__(self)
self.contacts = contacts
self.contactsProxyModel = contactsProxyModel
self.groups = groups
self.groupsProxyModel = groupsProxyModel
self.chatSession = chatSession
self.chatSessionProxyModel = chatSessionProxyModel
self.msgs = msgs
self.msgsProxyModel = msgsProxyModel
def run(self):
self.contactsProxyModel.setSourceModel(recordsTableModel(self.contacts))
self.contactsProxyModel.setFilterKeyColumn(-1)
self.contactsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.groupsProxyModel.setSourceModel(recordsTableModel(self.groups))
self.groupsProxyModel.setFilterKeyColumn(-1)
self.groupsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.chatSessionProxyModel.setSourceModel(recordsTableModel(self.chatSession))
self.chatSessionProxyModel.setFilterKeyColumn(-1)
self.chatSessionProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.msgsProxyModel.setSourceModel(recordsTableModel(self.msgs))
self.msgsProxyModel.setFilterKeyColumn(-1)
self.msgsProxyModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
def getContactsProxyModel(self):
return self.contactsProxyModel,
def getGroupsProxyModel(self):
return self.groupsProxyModel
def getChatSessionProxyModel(self):
return self.chatSessionProxyModel
def getMsgsProxyModel(self):
return self.msgsProxyModel
And this is the method calling the setProxyModel thread in the dialog class. Notice that all the data (contacts, groups, chatsession...) is fine:
def setProxyModel(self):
progress = QProgressDialog("Initializing UI ...", "Abort", 0, 0, self)
progress.setWindowTitle("WhatsApp Browser ...")
progress.setWindowModality(Qt.WindowModal)
progress.setMinimumDuration(0)
progress.setCancelButton(None)
progress.show()
queryTh = ThreadedProxyModel(self.contacts, self.contactsProxyModel, self.groups, self.groupsProxyModel,
self.chatSession, self.chatSessionProxyModel, self.msgs, self.msgsProxyModel,)
queryTh.start()
while queryTh.isRunning():
QApplication.processEvents()
self.contactsProxyModel = queryTh.getContactsProxyModel()
self.groupsProxyModel = queryTh.getGroupsProxyModel()
self.chatSessionProxyModel = queryTh.getChatSessionProxyModel()
self.msgsProxyModel = queryTh.getMsgsProxyModel()
progress.close()
And this is in the init method in my dialog, i create the proxymodels and call the method for updating them in the Thread and then i set them up in various QTableView:
self.contactsProxyModel = QSortFilterProxyModel(self)
self.groupsProxyModel = QSortFilterProxyModel(self)
self.groupMembersProxyModel = QSortFilterProxyModel(self)
self.chatSessionProxyModel = QSortFilterProxyModel(self)
self.chatMsgsProxyModel = QSortFilterProxyModel(self)
self.msgsProxyModel = QSortFilterProxyModel(self)
self.setProxyModel()
self.contactsTableView.setModel(self.contactsProxyModel)
self.contactsTableView.resizeColumnsToContents()
self.groupsTableView.setModel(self.groupsProxyModel)
self.groupsTableView.resizeColumnsToContents()
self.chatSessionTableView.setModel(self.chatSessionProxyModel)
self.chatSessionTableView.resizeColumnsToContents()
self.chatSessionTableView.clicked.connect(self.setChatMsgsProxyModel)
self.chatMsgsTableView.resizeColumnsToContents()
self.groupsTableView.clicked.connect(self.setGroupMembersProxyModel)
self.groupMembersTableView.resizeColumnsToContents()
self.msgsTableView.setModel(self.msgsProxyModel)
self.msgsTableView.resizeColumnsToContents()
Thank you for any advice, i'm pretty stuck...
Not sure, but it seems that
def getContactsProxyModel(self):
return self.contactsProxyModel,
return a tuple, try to delete the comma

PyGI emulating set_attribute / appropriate signal to emit?

I have an existing project that I'm trying to build a GUI around (using PyGI + Gtk3). There are some native objects that I need to extend slightly to make them renderable. I've boiled the problem down to the simplified code here:
# Simplified Equivalent Code
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import GdkPixbuf
# Pre-existing, complex object
class Move(object):
def __init__(self, color):
self.color = color
# Pre-existing, complex object
class Block(object):
def __init__(self,move=None,**kwds):
self.move = move
# New object created to help render a Block
class BlockGui(Block):
pixbufs = {
'empty' : GdkPixbuf.Pixbuf.new_from_file('block_empty.png'),
'red' : GdkPixbuf.Pixbuf.new_from_file('block_red.png'),
'blue' : GdkPixbuf.Pixbuf.new_from_file('block_blue.png'),
}
def __setattr__(self, name, value):
super(BlockGui, self).__setattr__(name, value)
if name == 'move':
print "Need to emit a signal here"
def get_pixbuf(self):
try:
return BlockGui.pixbufs[self.move.color]
except AttributeError:
return BlockGui.pixbufs['empty']
class BlockRenderer(Gtk.CellRendererPixbuf):
__gproperties__ = {
'block' : (GObject.TYPE_PYOBJECT,
'block to render',
'the block object to be rendered',
GObject.PARAM_READWRITE)
}
def __init__(self):
GObject.GObject.__init__(self)
self.block = None
def do_set_property(self, prop, value):
# What is a GParamBoxed? Should I be checking if prop == 'block' from it somehow?
if isinstance(value, BlockGui):
self.block = value
self.set_property('pixbuf', self.block.get_pixbuf())
GObject.type_register(BlockRenderer)
def destroy(widget, data=None):
Gtk.main_quit()
# Normally do not have access to this assignment
def on_clicked(widget, liststore, treeview):
treeiter = liststore.get_iter(2)
block = liststore.get_value(treeiter, 1)
block.move = Move('red')
def main():
# 3x5 so this demo window has some size
fmt = [GObject.TYPE_PYOBJECT] * 3
liststore = Gtk.ListStore(*fmt)
for r in xrange(5):
liststore.append([BlockGui() for x in xrange(3)])
treeview = Gtk.TreeView(liststore)
for c in xrange(3):
col = Gtk.TreeViewColumn(str(c))
treeview.append_column(col)
cell = BlockRenderer()
col.pack_start(cell, True)
col.add_attribute(cell, 'block', c)
button = Gtk.Button("Change Color!")
button.connect('clicked', on_clicked, liststore, treeview)
vbox = Gtk.VBox()
vbox.add(treeview)
vbox.add(button)
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.connect('destroy', destroy)
window.add(vbox)
window.show_all()
Gtk.main()
if __name__ == '__main__':
main()
When the current code is run, clicking the button yields no immediate result, but running the mouse over the changed row will cause the center square to turn red (as the hover over the row triggers a refresh). Normally, when a 'proper' GObject has a set_attribute called, it will emit some signals to notify the widgets containing it to re-render.
I need to know which signal that emits, to whom it's emitted, and how to emulate that behavior.
If you know the widget that must be redrawn, then you can just call queue_draw(), queue_draw_region() or queue_draw_area() for that widget. That will invalidate that window area and it will be redrawn. If you want more fine grained control, you might want to use Gtk.DrawingArea.
You might want to check the documentation for The GTK+ Drawing Model.

Categories