How to add multiple scenes to a graphicsview or layers to a scene that would allow me to just hide/show certain marks in PyQt5 - pyqt5

I want to know how to keep scene widgets after add them to scene for later.
After user's click on marker button, marker feature active and with rightclick can mark points in QGraphicscene.
Here is my code:
from PyQt5.QtWidgets import QMainWindow, QToolButton, QGraphicsScene, QGraphicsPixmapItem
from PyQt5.QtGui import QPixmap, QIcon, QPainter
from PyQt5 import uic
from PyQt5.QtCore import Qt
class MarkerButton(QToolButton):
def __init__(self, *args, **kwargs):
super(MarkerButton, self).__init__(*args, **kwargs)
def mousePressEvent(self, event):
if event.buttons() & Qt.LeftButton:
self.deleteLater()
super().mousePressEvent(event)
class MarkerScene(QGraphicsScene):
def __init__(self, *args, **kwargs):
super(MarkerScene, self).__init__(*args, **kwargs)
self.marker_widgets = dict()
def mousePressEvent(self, event):
if event.buttons() & Qt.RightButton:
self.start_point = event.scenePos()
self.update_path()
super().mousePressEvent(event)
def update_path(self):
if not self.start_point.isNull():
pushButton_icon = QIcon()
pushButton_icon.addPixmap(QPixmap('assets/mark.png'))
new_marker = MarkerButton()
new_marker.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
new_marker.move(int(self.start_point.x()), int(self.start_point.y()))
new_marker.setIcon(pushButton_icon)
new_marker.setText(str(len(self.marker_widgets)))
self.addWidget(new_marker)
self.marker_widgets[new_marker] = self.start_point
class Ui(QMainWindow):
def __init__(self):
super(Ui, self).__init__() # Call the inherited classes __init__ method
self.Window_obj= uic.loadUi('main.ui')
self.Window_obj.setWindowFlags(Qt.FramelessWindowHint)
pix = QPixmap('assets/data.png')
item = QGraphicsPixmapItem(pix)
scene = QGraphicsScene(self)
scene.addItem(item)
self.Window_obj.graphicsViewScene1.setScene(scene)
self.Window_obj.show()
def AddMarker(self):
if not self.active_toolbox_markers:
self.active_toolbox_markers = True
self.main_Window_obj.graphicsViewScene1.setRenderHint(QPainter.Antialiasing)
self.Window_obj.graphicsViewScene1.setMouseTracking(True)
scene = MarkerScene()
self.main_Window_obj.graphicsViewScene1.setScene(scene)
else:
self.active_toolbox_markers = False
I need to save this markers for later but user have to be able to hide them, the reason is because I have another tool that I want not conflict with this markers.
At the end, after add markers, the main Qpixmap removed.
my app something like this

Related

How to get log and show it on GUI from multiprocessing work?

I try to get logs from multiprocessing work and show them on GUI.
Based on this document
gui.py:
from PyQt5 import QtCore, QtWidgets
import logging
from log_test import main
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot
class Signaller(QtCore.QObject):
signal = Signal(str, logging.LogRecord)
class QtHandler(logging.Handler):
def __init__(self, slotfunc, *args, **kwargs):
super().__init__(*args, **kwargs)
self.signaller = Signaller()
self.signaller.signal.connect(slotfunc)
def emit(self, record):
s = self.format(record)
self.signaller.signal.emit(s, record)
class Worker(QtCore.QObject):
finished = Signal()
#Slot()
def start(self):
main()
self.finished.emit()
class Ui_Dialog(QtCore.QObject):
def __init__(self):
super().__init__()
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.setEnabled(True)
Dialog.resize(530, 440)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.button = QtWidgets.QPushButton(Dialog)
self.button.setText("start working")
self.verticalLayout.addWidget(self.button)
self.logWidget = QtWidgets.QPlainTextEdit(Dialog)
self.logWidget.setReadOnly(True)
self.verticalLayout.addWidget(self.logWidget)
self.handler = QtHandler(self.update_log_gui)
logging.getLogger('log').addHandler(self.handler)
self.button.clicked.connect(self.start_work)
#Slot(str, logging.LogRecord)
def update_log_gui(self, status, record):
self.logWidget.appendPlainText(status)
def config_thread(self):
self.worker_thread = QtCore.QThread()
self.worker_thread.setObjectName('WorkerThread')
self.worker = Worker()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.start)
self.worker.finished.connect(self.worker_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
self.worker_thread.finished.connect(lambda: self.button.setEnabled(True))
pass
def start_work(self):
self.config_thread()
self.worker_thread.start()
self.button.setEnabled(False)
if __name__ == "__main__":
import sys
QtCore.QThread.currentThread().setObjectName('MainThread')
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
log_test.py (where multiprocessing work happens)
import logging
import time
from multiprocessing import Pool
def f(name):
logger = logging.getLogger('log.' + name)
logger.error('hello there 1')
time.sleep(0.5)
logger.error('hello there 2')
time.sleep(0.5)
logger.error('hello there 3')
time.sleep(0.5)
def main():
with Pool(5) as p:
p.map(f, ['aaa', 'bbb', 'ccc'])
At first time, I thought working in single thread causing the problem. So I added QThread to this.
Later I discovered in debug, it seems to QtHandler.emit() works fine at receiving log messages. But the connected slot function, update_log_gui() does not work somehow.
I solved it myself.
#Alexander was right. Indeed my QtHandler has a problem when multiprocessing but I don't know exactly why. Rather, you wanna implement QueueHandler. An example in this article (Written in Korean) helped me.
from PyQt5 import QtCore, QtWidgets
import logging
import multiprocessing
from log_test import main
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot
QThread = QtCore.QThread
class Signaller(QtCore.QObject):
signal = Signal(logging.LogRecord)
class Worker(QtCore.QObject):
finished = Signal()
def __init__(self, q):
super().__init__()
self.q = q
#Slot()
def start(self):
main(self.q)
self.finished.emit()
class Consumer(QThread):
popped = Signaller()
def __init__(self, q):
super().__init__()
self.q = q
self.setObjectName('ConsumerThread')
def run(self):
while True:
if not self.q.empty():
record = self.q.get()
self.popped.signal.emit(record)
class Ui_Dialog(QtCore.QObject):
def __init__(self, app):
super().__init__()
self.app = app
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.setEnabled(True)
Dialog.resize(530, 440)
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
self.verticalLayout.setObjectName("verticalLayout")
self.button = QtWidgets.QPushButton(Dialog)
self.button.setText("start working")
self.verticalLayout.addWidget(self.button)
self.logWidget = QtWidgets.QPlainTextEdit(Dialog)
self.logWidget.setReadOnly(True)
self.verticalLayout.addWidget(self.logWidget)
self.button.clicked.connect(self.start_work)
self.q = multiprocessing.Manager().Queue()
self.consumer = Consumer(self.q)
self.consumer.popped.signal.connect(self.update_log_gui)
self.consumer.start()
app.aboutToQuit.connect(self.shutdown_consumer)
#Slot(logging.LogRecord)
def update_log_gui(self, record):
self.logWidget.appendPlainText(str(record.msg))
def config_thread(self):
self.worker_thread = QtCore.QThread()
self.worker_thread.setObjectName('WorkerThread')
self.worker = Worker(self.q)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.start)
self.worker.finished.connect(self.worker_thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
self.worker_thread.finished.connect(lambda: self.button.setEnabled(True))
def start_work(self):
self.config_thread()
self.worker_thread.start()
self.button.setEnabled(False)
def shutdown_consumer(self):
if self.consumer.isRunning():
self.consumer.requestInterruption()
self.consumer.quit()
self.consumer.wait()
if __name__ == "__main__":
import sys
QtCore.QThread.currentThread().setObjectName('MainThread')
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog(app)
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())

PyQt5 QProgressBar Does Not Appear when run in QThread

This question was deleted, but I updated the code to an MRE. I have run it on my terminal and it does not have any compilation/runtime errors, but behaves as I explain below. Since the moderators have not responded to my original request to reopen my question after I have corrected it, I have deleted the old question and am placing this new one here.
My signals update the progress value, but the progress bar itself never appears. Is there an error in my code?
(To recreate, please place the code for each file listed below in the project structure shown below. You will only need to install PyQt5. I am on Windows 10 and using a Python 3.8 virtual environment with poetry. The virtual environment and poetry are optional)
Main
# main.py
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from app.controller.controller import Controller
from app.model.model import Model
from app.view.view import View
class MainApp:
def __init__(self) -> None:
self.controller = Controller()
self.model: Model = self.controller.model
self.view: View = self.controller.view
def show(self) -> None:
self.view.showMaximized()
if __name__ == "__main__":
app: QApplication = QApplication([])
app.setStyle("fusion")
app.setAttribute(Qt.AA_DontShowIconsInMenus, True)
root: MainApp = MainApp()
root.show()
app.exec_()
View
# view.py
from typing import Any, Optional
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal
class ProgressDialog(QtWidgets.QDialog):
def __init__(
self,
parent_: Optional[QtWidgets.QWidget] = None,
title: Optional[str] = None,
):
super().__init__(parent_)
self._title = title
self.pbar = QtWidgets.QProgressBar(self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.pbar)
self.setLayout(layout)
self.resize(500, 50)
def on_start(self):
self.setModal(True)
self.show()
def on_finish(self):
self.hide()
self.setModal(False)
self.pbar.reset()
self.title = None
def on_update(self, value: int):
self.pbar.setValue(value)
print(self.pbar.value()) # For debugging...
#property
def title(self):
return self._title
#title.setter
def title(self, title_):
self._title = title_
self.setWindowTitle(title_)
class View(QtWidgets.QMainWindow):
def __init__(
self, controller, parent_: QtWidgets.QWidget = None, *args: Any, **kwargs: Any
) -> None:
super().__init__(parent_, *args, **kwargs)
self.controller: Controller = controller
self.setWindowTitle("App")
self.container = QtWidgets.QFrame()
self.container_layout = QtWidgets.QVBoxLayout()
self.container.setLayout(self.container_layout)
self.setCentralWidget(self.container)
# Create and position widgets
self.open_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DirOpenIcon)
self.open_action = QtWidgets.QAction(self.open_icon, "&Open file...", self)
self.open_action.triggered.connect(self.controller.on_press_open_button)
self.toolbar = QtWidgets.QToolBar("Main ToolBar")
self.toolbar.setIconSize(QtCore.QSize(16, 16))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.open_action)
self.file_dialog = self._create_open_file_dialog()
self.progress_dialog = ProgressDialog(self)
def _create_open_file_dialog(self) -> QtWidgets.QFileDialog:
file_dialog = QtWidgets.QFileDialog(self)
filters = [
"Excel Documents (*.xlsx)",
]
file_dialog.setWindowTitle("Open File...")
file_dialog.setNameFilters(filters)
file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
return file_dialog
Model
# model.py
import time
from typing import Any
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QObject, pyqtSignal
class Model(QObject):
start_task: pyqtSignal = pyqtSignal()
finish_task: pyqtSignal = pyqtSignal()
update_task: pyqtSignal = pyqtSignal(int)
def __init__(
self,
controller,
*args: Any,
**kwargs: Any,
) -> None:
super().__init__()
self.controller = controller
def open_file(self, files: str) -> None:
self.start_task.emit()
for ndx, file_ in enumerate(files):
print(file_) # In truth, here, I'm actually performing processing
time.sleep(1) # Only here for simulating a long-running task
self.update_task.emit(int((ndx + 1) / len(files) * 100))
self.finish_task.emit()
Controller
# controller.py
from typing import Any
from app.model.model import Model
from app.view.view import View
from PyQt5 import QtCore, QtGui, QtWidgets
class Controller:
def __init__(
self,
*args: Any,
**kwargs: Any,
) -> None:
self.model = Model(controller=self, *args, **kwargs)
self.view = View(controller=self, *args, **kwargs)
def on_press_open_button(self) -> None:
if self.view.file_dialog.exec_() == QtWidgets.QDialog.Accepted:
file_names = self.view.file_dialog.selectedFiles()
self.view.progress_dialog.title = "Opening files..."
self.thread = QtCore.QThread()
self.model.moveToThread(self.thread)
self.thread.started.connect(lambda: self.model.open_file(file_names))
self.thread.finished.connect(self.thread.deleteLater)
self.model.start_task.connect(self.view.progress_dialog.on_start)
self.model.update_task.connect(
lambda value: self.view.progress_dialog.on_update(value)
)
self.model.finish_task.connect(self.view.progress_dialog.on_finish)
self.model.finish_task.connect(self.thread.quit)
self.model.finish_task.connect(self.model.deleteLater)
self.model.finish_task.connect(self.thread.deleteLater)
self.thread.start()
When I run the above in a folder of 6 files, it's not running through things too fast (I'm actually performing processing which takes a total of about 5 seconds). It completes successfully and my terminal outputs:
16
33
50
66
83
100
but my ProgressDialog window is just this for the whole process:
If I add self.progress_dialog.show() at the end of __init__() in View (snipped for brevity)
# view.py
# Snip...
class View(QtWidgets.QMainWindow):
def __init__( ... ):
# Snip...
self.progress_dialog.show()
then a progress bar is added:
and upon opening files, the dialog behaves as expected:
An enlightening talk was given at Kiwi Pycon 2019 that helped me identify the problem: "Python, Threads & Qt: Boom!"
Every QObject is owned by a QThread
A QObject instance must not be shared across threads
QWidget objects (i.e. anything you can "see") are not re-entrant. Thus, they can only be called from the main UI thread.
Point 3 was my problem. Qt doesn't prevent one from calling a QWidget object from outside the main thread, but it doesn't work. Even moving my ProgressDialog to the created QThread will not help. Hence, showing and hiding the ProgressDialog MUST be handled by the main thread.
Furthermore, once a QObject has been moved to a separate thread, rerunning the code will give the error:
QObject::moveToThread: Current thread (0xoldbeef) is not the object's thread (0x0).
Cannot move to target thread (0xnewbeef)
because it does not create a new model object, but reuses the old object. Hence, the code must be moved into a separate worker object unfortunately.
The correct code would be to:
Move on_start and on_finish from ProgressDialog to View (I rename them show_progress_dialog and hide_progress_dialog)
Create put the open_file logic in a separate QObject worker
Call view.progress_dialog.show() by itself (the thread can call hide or open when thread.finished is emited though; I guess it's because of special logic implemented in Qt when the thread ends)
View
from typing import Any, Optional
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal
class ProgressDialog(QtWidgets.QDialog):
def __init__(
self,
parent_: Optional[QtWidgets.QWidget] = None,
title: Optional[str] = None,
):
super().__init__(parent_)
self._title = title
self.pbar = QtWidgets.QProgressBar(self)
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.pbar)
self.setLayout(layout)
self.resize(500, 50)
def on_update(self, value: int):
self.pbar.setValue(value)
#property
def title(self):
return self._title
#title.setter
def title(self, title_):
self._title = title_
self.setWindowTitle(title_)
class View(QtWidgets.QMainWindow):
def __init__(
self, controller, parent_: QtWidgets.QWidget = None, *args: Any, **kwargs: Any
) -> None:
super().__init__(parent_, *args, **kwargs)
self.controller: Controller = controller
self.setWindowTitle("App")
self.container = QtWidgets.QFrame()
self.container_layout = QtWidgets.QVBoxLayout()
self.container.setLayout(self.container_layout)
self.setCentralWidget(self.container)
# Create and position widgets
self.open_icon = self.style().standardIcon(QtWidgets.QStyle.SP_DirOpenIcon)
self.open_action = QtWidgets.QAction(self.open_icon, "&Open file...", self)
self.open_action.triggered.connect(self.controller.on_press_open_button)
self.toolbar = QtWidgets.QToolBar("Main ToolBar")
self.toolbar.setIconSize(QtCore.QSize(16, 16))
self.addToolBar(self.toolbar)
self.toolbar.addAction(self.open_action)
self.file_dialog = self._create_open_file_dialog()
self.progress_dialog = ProgressDialog(self)
def _create_open_file_dialog(self) -> QtWidgets.QFileDialog:
file_dialog = QtWidgets.QFileDialog(self)
filters = [
"Excel Documents (*.xlsx)",
]
file_dialog.setWindowTitle("Open File...")
file_dialog.setNameFilters(filters)
file_dialog.setFileMode(QtWidgets.QFileDialog.ExistingFiles)
return file_dialog
def show_progress_dialog(self):
self.progress_dialog.setModal(True)
self.progress_dialog.show()
def hide_progress_dialog(self):
self.progress_dialog.hide()
self.progress_dialog.setModal(False)
self.progress_dialog.pbar.reset()
self.progress_dialog.title = None
Model
# model.py
import time
from typing import Any, Optional
from PyQt5.QtCore import QObject, pyqtSignal
class Model:
def __init__(
self,
controller,
*args: Any,
**kwargs: Any,
) -> None:
super().__init__()
self.controller = controller
class OpenFileWorker(QObject):
update: pyqtSignal = pyqtSignal(int)
finished: pyqtSignal = pyqtSignal()
def __init__(self) -> None:
super().__init__()
def open_file(self, files: str) -> None:
for ndx, file_ in enumerate(files):
print(file_) # In truth, here, I'm actually performing processing
time.sleep(1) # Only here for simulating a long-running task
self.update.emit(int((ndx + 1) / len(files) * 100))
self.finished.emit()
Controller
# controller.py
from typing import Any
from app.model.model import Model, OpenFileWorker
from app.view.view import View
from PyQt5 import QtCore, QtGui, QtWidgets
class Controller:
def __init__(
self,
*args: Any,
**kwargs: Any,
) -> None:
self.model = Model(controller=self, *args, **kwargs)
self.view = View(controller=self, *args, **kwargs)
def on_press_open_button(self) -> None:
if self.view.file_dialog.exec_() == QtWidgets.QDialog.Accepted:
file_names = self.view.file_dialog.selectedFiles()
self.view.progress_dialog.title = "Opening files..."
self.thread = QtCore.QThread()
self.open_worker = OpenFileWorker()
self.open_worker.moveToThread(self.thread)
self.view.show_progress_dialog()
self.thread.started.connect(lambda: self.open_worker.open_file(file_names))
self.open_worker.update.connect(
lambda value: self.view.progress_dialog.on_update(value)
)
self.open_worker.finished.connect(self.view.hide_progress_dialog)
self.open_worker.finished.connect(self.thread.quit)
self.thread.finished.connect(self.open_worker.deleteLater)
self.thread.start()

How can I add Icons to a QListWidget

I have a program that has you drag and drop files into a QListWidget box and then you click a button to upload those files to a bucket. I would like to say which files have been uploaded and which one is currently being uploaded with an icon. Is there a way to add Icons inside/next to the QListWidget box?
Here is some of the code for the QListWidget and the drag and drop feature. I am just hoping there is a way to add icons
import sys, os
from PyQt5.QtWidgets import QApplication, QMainWindow, QListWidget, QListWidgetItem, QPushButton
from PyQt5.QtCore import Qt, QUrl
class ListBoxWidget(QListWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.resize(600, 600)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls:
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
# https://doc.qt.io/qt-5/qurl.html
if url.isLocalFile():
links.append(str(url.toLocalFile()))
else:
links.append(str(url.toString()))
self.addItems(links)
else:
event.ignore()
class AppDemo(QMainWindow):
def __init__(self):
super().__init__()
self.resize(1200, 600)
self.listbox_view = ListBoxWidget(self)
self.btn = QPushButton('Get Value', self)
self.btn.setGeometry(850, 400, 200, 50)
self.btn.clicked.connect(lambda: print(self.getSelectedItem()))
def getSelectedItem(self):
item = QListWidgetItem(self.listbox_view.currentItem())
return item.text()
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
I have tried adding QIcon to the self.addItems(links) line but it continues to give me an error about arguments.
Instead of adding items using addItems, create indivual QListWidgetItems and add them one by one using addItem(QListWidgetItem).
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(Qt.CopyAction)
event.accept()
for url in event.mimeData().urls():
# https://doc.qt.io/qt-5/qurl.html
if url.isLocalFile():
address = str(url.toLocalFile())
icon = QIcon('localIcon.png')
else:
address = str(url.toString())
icon = QIcon('remoteIcon.png')
self.addItem(QListWidgetItem(icon, address))
If you want to change the icon of an existing item, access it using item() and use setIcon():
def setIconForItem(self, row, icon):
self.listbox_view.item(row).setIcon(icon)

PyQt5: drawing multiple rectangles using mouseEvents by implementing QGraphicsScene, QGraphicsView and QGraphicsItem [duplicate]

I have a scene like this
class Scene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(Scene, self).__init__(parent)
def mousePressEvent(self, event):
print('scene pressed')
self.wid = MyRect(event.pos(), event.pos())
self.addItem(self.wid)
self.wid.show()
I would like class MyRect(QtWidgets.QGraphicsRectItem) with painter, mouse event and so on to be a draggable rectangle.
all stuff in MyRect
So then I could have many Rectangle to the scene and even after draw line between them and so on (kind of diagram app), but keeping objects related editable options in MyRect, MyLine , ....
I thought :
class MyRect(QtWidgets.QGraphicsRectItem):
def __init__(self, begin, end, parent=None):
super().__init__(parent)
self.begin = begin
self.end = end
def paintEvent(self, event):
print('painting')
qp = QtGui.QPainter(self)
qp.drawRect(QtCore.QRect(self.begin, self.end))
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
def mouseMoveEvent(self, event):
self.end = event.pos()
self.update()
def mouseReleaseEvent(self, event):
self.begin = event.pos()
self.end = event.pos()
self.update()
But I does not work (paint event not initiated whereas mousepressed event in scene is intiated)
I did not find what I wanted through the web so started totry do it by myself. I'm pretty sure it is a must known starting point but I cannot find it
First of all a QGraphicsItem is not a QWidget, so it has those events and does not handle them directly, that's what QGraphicsView and QGraphicsScene do. For example you say that you want to have a moveable rectangle because that task is simple is QGraphicsView, it is not necessary to overwrite:
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
rect_item = QtWidgets.QGraphicsRectItem(QtCore.QRectF(0, 0, 100, 100))
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
scene.addItem(rect_item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
If you want to change the way you paint the rectangle you must overwrite the paint() method as shown below:
from PyQt5 import QtCore, QtGui, QtWidgets
class RectItem(QtWidgets.QGraphicsRectItem):
def paint(self, painter, option, widget=None):
super(RectItem, self).paint(painter, option, widget)
painter.save()
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(QtCore.Qt.red)
painter.drawEllipse(option.rect)
painter.restore()
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene = QtWidgets.QGraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
rect_item = RectItem(QtCore.QRectF(0, 0, 100, 100))
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
scene.addItem(rect_item)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Update:
from PyQt5 import QtCore, QtGui, QtWidgets
class GraphicsScene(QtWidgets.QGraphicsScene):
def __init__(self, parent=None):
super(GraphicsScene, self).__init__(QtCore.QRectF(-500, -500, 1000, 1000), parent)
self._start = QtCore.QPointF()
self._current_rect_item = None
def mousePressEvent(self, event):
if self.itemAt(event.scenePos(), QtGui.QTransform()) is None:
self._current_rect_item = QtWidgets.QGraphicsRectItem()
self._current_rect_item.setBrush(QtCore.Qt.red)
self._current_rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.addItem(self._current_rect_item)
self._start = event.scenePos()
r = QtCore.QRectF(self._start, self._start)
self._current_rect_item.setRect(r)
super(GraphicsScene, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if self._current_rect_item is not None:
r = QtCore.QRectF(self._start, event.scenePos()).normalized()
self._current_rect_item.setRect(r)
super(GraphicsScene, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
self._current_rect_item = None
super(GraphicsScene, self).mouseReleaseEvent(event)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
scene =GraphicsScene(self)
view = QtWidgets.QGraphicsView(scene)
self.setCentralWidget(view)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())

Python Tkinter Inherit DataFrame between classes

I'm having an issues and I would kindly ask you to help me as I am not able to find a solution.
Basically I am trying to inherit the DataFrame loaded into a variable in one Class (Window1) and I'm trying to use the same DataFrame in another Class (Window1). Please find below my current simplified code (Thank you):
import tkinter as tk
from PIL import ImageTk, Image
from tkinter import filedialog, messagebox
import time, os, random, string
from datetime import datetime
from time import gmtime, strftime
import pandas as pd
class Page(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container=tk.Frame(self)
container.grid()
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames={}
for F in (PageOne, PageTwo, PageThree):
frame=F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def show_frame(self, cont):
frame=self.frames[cont]
frame.tkraise()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
start_btn = tk.Button(self, text = "Start >>>", command=lambda:controller.show_frame(PageTwo), width = 10, activebackground = "#ffffff", relief="flat").grid()
pass
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.browse_btn = tk.Button(self, text=" Browse ", command=self.select_file)
self.browse_btn.grid(row=4, column=0, padx=290, pady=10, columnspan=3, sticky="w")
self.browse_entry = tk.Entry(self, text="", width=30)
self.browse_entry.grid(row=4, column=0, columnspan=3, padx=100, pady=10, sticky="w")
self.continue_btn = tk.Button(self, text="Continue >>", borderwidth=2, width=10, bg="#00c441", fg="#ffffff", command=lambda:[self.print_df(), controller.show_frame(PageThree)])
self.continue_btn.grid(row=19, column=0, columnspan=3, padx=312, pady=5, sticky="w")
self.continue_btn.config(state=tk.NORMAL)
def select_file(self):
self.path = filedialog.askopenfilename(defaultextension="*.csv", filetypes = (("csv files","*.csv"),("All Files", "*.*")))
self.browse_entry.delete(0, tk.END)
self.browse_entry.insert(0, self.path)
###following DataFrame I would like to use in in the PageThree class.
self.df = pd.read_csv(self.path)
def print_df(self):
return self.df.head()
class PageThree(PageTwo):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
PageTwo.__init__(self, parent, controller)
start_btn = tk.Button(self, text = "Data Frame", command=self.funct01, width = 10).grid()
def funct01(self):
##this is where I would like to utilize the DataFrame (inside the function)
instance=PageTwo(parent,controller)
print(instance.select_file(self))
if __name__=="__main__":
app=Page()
app.geometry("400x400+100+100")
app.mainloop()
You have to ask the Page instance (controller) to return the PageTwo instance, and you need to store the controller in the PageTwo "constructor".
I was not aware that you can hash a class definition (use it has the key of a dictionary).
import tkinter as tk
from PIL import ImageTk, Image
from tkinter import filedialog, messagebox
import time, os, random, string
from datetime import datetime
from time import gmtime, strftime
import pandas as pd
class Page(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container=tk.Frame(self)
container.grid()
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames={}
for F in (PageOne, PageTwo, PageThree):
frame=F(container, self)
self.frames[F]=frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(PageOne)
def show_frame(self, cont):
frame=self.frames[cont]
frame.tkraise()
def getFrame(self, frame):
return self.frames[frame]
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
start_btn = tk.Button(self, text = "Start >>>", command=lambda:controller.show_frame(PageTwo), width = 10, activebackground = "#ffffff", relief="flat").grid()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.browse_btn = tk.Button(self, text=" Browse ", command=self.select_file)
self.browse_btn.grid(row=4, column=0, padx=290, pady=10, columnspan=3, sticky="w")
self.browse_entry = tk.Entry(self, text="", width=30)
self.browse_entry.grid(row=4, column=0, columnspan=3, padx=100, pady=10, sticky="w")
self.continue_btn = tk.Button(self, text="Continue >>", borderwidth=2, width=10, bg="#00c441", fg="#ffffff", command=lambda:[self.print_df(), controller.show_frame(PageThree)])
self.continue_btn.grid(row=19, column=0, columnspan=3, padx=312, pady=5, sticky="w")
self.continue_btn.config(state=tk.NORMAL)
def select_file(self):
self.path = filedialog.askopenfilename(defaultextension="*.csv", filetypes = (("csv files","*.csv"),("All Files", "*.*")))
self.browse_entry.delete(0, tk.END)
self.browse_entry.insert(0, self.path)
###following DataFrame I would like to use in in the PageThree class.
self.df = pd.read_csv(self.path)
def print_df(self):
return self.df.head()
class PageThree(PageTwo):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
PageTwo.__init__(self, parent, controller)
start_btn = tk.Button(self, text = "Data Frame", command=self.funct01, width = 10).grid()
def funct01(self):
instance = self.controller.getFrame(PageTwo)
dataFrame = instance.df
print(dataFrame.head())
if __name__=="__main__":
app=Page()
app.geometry("400x400+100+100")
app.mainloop()