In PyQt5, I have a window which reads from a database and adds that information to a widget. There is also a second window, which, when opened, adds a new table to the database. What I'm trying to do is reload the main window when the secondary window is closed. I don't want to change the secondary window to a QDialoge and need to use .show(), not .exec_(). Is there any way I could do this.
There is my code:
import sys
import sqlite3
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QListWidget, QVBoxLayout, QWidget
from PyQt5.QtCore import Qt
class SecondWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(SecondWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("App 2")
label = QLabel("INSERTED VALUES")
label.setAlignment(Qt.AlignCenter)
self.setCentralWidget(label)
#Open Database
self.conn = sqlite3.connect("connections.db")
self.cursor = self.conn.cursor()
#Add New Table To Database
self.cursor.execute('''CREATE TABLE `New`
(name text, GD text)''')
self.conn.commit()
self.conn.close()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("App")
self.list = QListWidget()
self.cursorCount = 0
#Open Database
self.conn = sqlite3.connect("connections.db")
self.cursor = self.conn.cursor()
try:
#Try To Create A Table If It Doesn't exit
self.cursor.execute('''CREATE TABLE `Default`
(name text, GD text)''')
except:
pass
#Get A List Of All The Tables
self.cursor.execute('SELECT name FROM sqlite_master WHERE type= "table"')
for table in self.cursor.fetchall():
self.list.insertItem(self.cursorCount, table[0])
self.cursorCount += 1
self.conn.commit()
self.conn.close()
self.list.item(0).setSelected(True)
self.btn = QPushButton()
self.btn.setText("click me!")
self.btn.clicked.connect(self.openWin)
winWidget = QWidget()
self.setCentralWidget(winWidget)
layout = QVBoxLayout()
layout.addWidget(self.list)
layout.addWidget(self.btn)
winWidget.setLayout(layout)
def openWin(self):
self.win = SecondWindow()
self.win.show()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Any help would be appreciated
I am not sure I got your problem right but I think you could use signals from PyQt5 and the closeEvent method. Basically, you should create a signal in the second window that will be emitted when the close event happens, and connect this signal to a slot in the first window.
import with:
from PyQt5.QtCore import pyqtSignal
when declaring the second window class, before init, declare your signal:
class SecondWindow(QMainWindow):
window_closed = pyqtSignal()
create a method named closeEvent that will be called when you close the window, and when it happens it should emit the signal you previously declared:
def closeEvent(self, event):
self.window_closed.emit()
event.accept()
# event.ignore() # if you want the window to never be closed
and modify the MainWindow openWin method to connect the signal from the second window to the method you want to be called:
def openWin(self):
self.win = SecondWindow()
self.win.window_closed.connect(self.do_something)
self.win.show()
def do_something(self):
print("You closed the second window!")
Edit:
The entire code should be something like this (I removed the parts database related to test myself, seems to be working fine).
import sys
from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QListWidget, QVBoxLayout, QWidget
from PyQt5.QtCore import Qt, pyqtSignal
class SecondWindow(QMainWindow):
window_closed = pyqtSignal()
def __init__(self, *args, **kwargs):
super(SecondWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("App 2")
label = QLabel("INSERTED VALUES")
label.setAlignment(Qt.AlignCenter)
self.setCentralWidget(label)
def closeEvent(self, event):
self.window_closed.emit()
event.accept()
# event.ignore() # if you want the window to never be closed
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setWindowTitle("App")
self.btn = QPushButton()
self.btn.setText("click me!")
self.btn.clicked.connect(self.openWin)
winWidget = QWidget()
self.setCentralWidget(winWidget)
layout = QVBoxLayout()
layout.addWidget(self.btn)
winWidget.setLayout(layout)
def openWin(self):
self.win = SecondWindow()
self.win.window_closed.connect(self.do_something)
self.win.show()
def do_something(self):
print("You closed the second window!")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Related
I’m using PyQt5 and I need to have my main window detect when a different window closes. I read here Emit a signal from another class to main class that creating a signal class to serve as an intermediary should work. However I haven’t gotten my example to work.
In my example, clicking the button opens a QWidget window. When the QWidget is closed, the main window is supposed to change from a blue background to a red background. However, the main window remains blue using the script below.
What am I doing wrong?
from PyQt5.QtWidgets import QPushButton
from PyQt5.QtCore import QObject, pyqtSignal
import os, sys
class MySignal(QObject):
signal = pyqtSignal()
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
#Make mainwindow
self.setGeometry(100,100,300,200)
self.setStyleSheet('background-color: blue')
# Make widget and button objects and set connection
self.widget = MyWidget()
self.btn = QPushButton(self)
self.btn.setText('Click')
self.btn.move(175, 150)
self.btn.setStyleSheet('background-color: white')
self.btn.clicked.connect(self.widget.showWidget)
# Make signal object and set connection
self.mySignal = MySignal()
self.mySignal.signal.connect(self.changeToRed)
# Let's start
self.show()
def changeToRed(self):
self.setStyleSheet('background-color: red')
def closeEvent(self, event):
os._exit(0)
class MyWidget(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(500, 100, 200, 200)
self.sig = MySignal()
def showWidget(self):
self.show()
def closeEvent(self, event):
self.sig.signal.emit()
self.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyMainWindow()
app.exec()```
The reason your code fails is that the connected signal object in MyMainWindow is not the same as the one you create in MyWidget, so the signal is never emitted. Here is a modified solution using signals in the normal way:
from PyQt5.QtWidgets import QPushButton, QMainWindow, QWidget, QApplication
from PyQt5.QtCore import QObject, pyqtSignal
import os, sys
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
#Make mainwindow
self.setGeometry(100,100,300,200)
self.setStyleSheet('background-color: blue')
# Make widget and button objects and set connection
self.widget = MyWidget()
self.btn = QPushButton(self)
self.btn.setText('Click')
self.btn.move(175, 150)
self.btn.setStyleSheet('background-color: white')
self.btn.clicked.connect(self.widget.showWidget)
self.widget.sig.connect(self.changeToRed)
# Let's start
self.show()
def changeToRed(self):
self.setStyleSheet('background-color: red')
class MyWidget(QWidget):
sig = pyqtSignal()
def __init__(self):
super().__init__()
self.setGeometry(500, 100, 200, 200)
def showWidget(self):
self.show()
def closeEvent(self, event):
self.sig.emit()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = MyMainWindow()
app.exec()
All you need is to add the connection between the close event and the function that turn your main screen red:
self.widget.closeEvent = self.changeToRed
this line should be in your main Window class
change your changeToRed function so it will except the event too:
def changeToRed(self, e):
There are lots of widgets in the original code and that is why I need to open the file in the main window. Therefore, I need to pass a dataframe (data_df) that comes from a csv file open in the main menu (main class) to 'MyApp' class. I will use the dataframe (input_df) to perform calculations down the road.
How to pass the data from main class to MyApp class?
# Import dependencies
from PyQt5.QtWidgets import (QWidget, QApplication, QTableWidget, QTableWidgetItem, QHBoxLayout, QVBoxLayout, QHeaderView, QPushButton, QCheckBox,
QLabel, QFileDialog, QMainWindow, QAction, QLineEdit, QMessageBox, QComboBox, QSizePolicy)
from PyQt5.Qt import Qt, QPen, QFont
from PyQt5.QtGui import *
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QCategoryAxis
import sys
import pandas as pd
import math
import csv
# Creates a QApplication instance
class MyApp(QWidget):
def __init__(self):
super().__init__()
# Creates layout object
self.layout = QHBoxLayout()
# Create push buttons
self.buttonCalc = QPushButton('Calculate')
self.layout.addWidget(self.buttonCalc)
# Connect button to function
self.buttonCalc.clicked.connect(self.calculate)
def displayInfo(self):
self.show()
# Create a Model to handle the calculator's operation
def calculate(self):
# get dataframe
input_df = df
# Create a subclass of QMainWindow to setup the main GUI
class MainWindow(QMainWindow):
def __init__(self, w):
super().__init__()
self.setWindowTitle('My code')
# for icon, uncomment line below
#self.setWindowIcon(QIcon(r'c:\image.png'))
self.resize(1200, 1200)
self.myApp = MyApp()
self.menuBar = self.menuBar()
self.fileMenu = self.menuBar.addMenu('File')
# import data
importAction = QAction('Open csv File', self)
importAction.setShortcut('Ctrl+O')
importAction.triggered.connect(self.openSeries)
# exit action
exitAction = QAction('Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(lambda: app.quit())
self.fileMenu.addAction(importAction)
self.fileMenu.addAction(exitAction)
self.setCentralWidget(w)
def openSeries(self):
self.filePath = QFileDialog.getOpenFileName(self, 'Open data series csv file', 'C:\', 'CSV(*.csv)')
if self.filePath != ('', ''):
file_data = self.filePath[0]
data_df = pd.read_csv(file_data, encoding='ISO-8859-1')
# I need to pass this dataframe to MyApp class
return data_df
def passInformation(self):
self.myApp.input_df
if __name__ =='__main__':
app = QApplication(sys.argv)
w = MyApp()
window = MainWindow(w)
window.show()
try:
sys.exit(app.exec())
except SystemExit:
print('Closing window...')
You can pass the data from one another through the __init__ method using something like this on your main window class:
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.init_ui()
def goToOtherWindow(self, variable):
self.window = OtherWindow(variable)
self.window.show()
self.close()
On OtherWindow class:
class OtherWindow(QtWidgets.QWidget):
def __init__(self, variable, parent=None):
super(OtherWindow, self).__init__(parent)
self.variable = variable
self.init_ui()
Of course, you have to adapt this function to your specific case.
Hello I have a QWidget and If I click on it, I want to get the object (the QWidget Element I clicked on) is there anyway to do that?
I already found some code but I only get the MouseClickEvent from
self.widget_34.mouseReleaseEvent = lambda event: self.myfunction(event)
Although the solution offered by #Cin is interesting, it has a serious problem: it cancels the mousePressEvent of the widget, so the widget loses the behavior it could have when the widget is pressed, for example the button no longer emits the clicked signal, other widget also They will have the same problem.
A less intrusive solution is to use eventFilter:
import sys
import weakref
from PyQt5 import QtCore, QtWidgets
class ClickListener(QtCore.QObject):
clicked = QtCore.pyqtSignal(QtWidgets.QWidget)
def addWidget(self, widget, other_widget=None):
if not hasattr(self, "_widgets"):
self._widgets = {}
widget.installEventFilter(self)
self._widgets[widget] = widget if other_widget is None else other_widget
weakref.ref(widget, self.removeWidget)
def eventFilter(self, obj, event):
if (
obj in self._widgets
and event.type() == QtCore.QEvent.MouseButtonPress
):
self.clicked.emit(self._widgets[obj])
return super(ClickListener, self).eventFilter(obj, event)
def removeWidget(self, widget):
if hasattr(self, "_widgets"):
if widget in self._widgets:
del self._widgets[widget]
class App(QtWidgets.QWidget):
def __init__(self):
super().__init__()
button = QtWidgets.QPushButton("Press Me")
label = QtWidgets.QLabel("Stack Overflow")
spinBox = QtWidgets.QSpinBox()
te = QtWidgets.QTextEdit()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(button)
lay.addWidget(label)
lay.addWidget(spinBox)
lay.addWidget(te)
listener = ClickListener(self)
listener.clicked.connect(self.onClicked)
listener.addWidget(button)
listener.addWidget(label)
listener.addWidget(spinBox.lineEdit(), spinBox)
listener.addWidget(te.viewport(), te)
def onClicked(self, obj):
print("Clicked, from", obj)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = App()
ex.show()
sys.exit(app.exec_())
I am not sure this will be a proper solution or not but I think, you can use the partial method of functools module. A collable object can be treated as a function for the purposes of this module. Here you can see my example,
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
import functools
class App(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setGeometry(200,200,200,200)
self.button = QPushButton('Button', self)
self.button.move(50,50)
self.label = QLabel(self)
self.label.setText("Label")
self.label.move(100,100)
self.items = [self.button, self.label]
for i in self.items:
i.mousePressEvent = functools.partial(self.getClickedItem, source_object=i)
self.show()
def getClickedItem(self, event, source_object=None):
print("Clicked, from", source_object)
#item text
#print(source_object.text())
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I am unable to get a QFrame to completely surround a QPushButton Like a Border. It only frames the top and left side of the button. I was wondering what I'm doing wrong with the frame.
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class main(QWidget):
def __init__(self):
super().__init__()
layout1 = QVBoxLayout()
btn1 = QPushButton("Test")
frame = QFrame(btn1)
frame.setGeometry(btn1.geometry())
frame.setFrameShape(QFrame.Box)
frame.setFrameShadow(QFrame.Plain)
frame.setLineWidth(4)
layout1.addWidget(btn1)
self.setLayout(layout1)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = main()
window.show()
sys.exit(app.exec_())
The problem is caused because the QFrame does not change the size, instead QPushButton does. In my solution I have resized every time the size is changed in the QPushButton
class FrameButton(QPushButton):
def __init__(self, *args, **kwargs):
QPushButton.__init__(self, *args, **kwargs)
self.frame = QFrame(self)
self.frame.setFrameShape(QFrame.Box)
self.frame.setFrameShadow(QFrame.Plain)
self.frame.setLineWidth(4)
def resizeEvent(self, event):
self.frame.resize(self.size())
QWidget.resizeEvent(self, event)
class main(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.addWidget(FrameButton("Test"))
I am trying to make dynamic widgets and make them clickable. By clicking on one widget it should pass dynamic value to other widget. I have tried sender() and other options accessing the widget but nothing worked.
All the widgets are sending information from the last widget.
Below is the code:
import sys
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QPushButton, QLabel, QMessageBox,QSizePolicy, QLayoutItem,QFrame,QHBoxLayout, QGridLayout, QVBoxLayout
from PyQt5.QtCore import QCoreApplication,QSize,QFileInfo
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from functools import partial
from PyQt5.QtCore import pyqtSlot
import sip
class Screen(QWidget):
def __init__(self):
super(Screen, self).__init__()
self.running_layout = QVBoxLayout()
self.action_layout = QVBoxLayout()
self.layout = QHBoxLayout(self)
self.layout.addLayout(self.running_layout)
self.layout.addLayout(self.action_layout)
self.setLayout(self.layout)
self.show()
self.all_running()
self.actions_1('1')
def buttonClicked(self):
sender = self.sender()
print(sender + ' was pressed')
def all_running(self):
for i in range(5):
button = QLabel("btn "+str(i))
button.mouseReleaseEvent = lambda _ : self.actions_1(str(button.text()).split()[1])
self.running_layout.addWidget(button)
button2 = QLabel("btn 5")
button2.mouseReleaseEvent = lambda _ : self.actions_1(str(button2.text()).split()[1])
self.running_layout.addWidget(button2)
def actions_1(self,value):
while not self.action_layout.isEmpty():
print(self.action_layout.isEmpty())
widget = self.action_layout.itemAt(0).widget()
print("removing", widget)
widget.setVisible(False)
self.action_layout.removeWidget(widget)
del widget
self.action_layout = QVBoxLayout()
self.layout.addLayout(self.action_layout)
val = int(value)
for i in range(val):
actions_item = QLabel(str(i))
self.action_layout.addWidget(actions_item)
app = QApplication(sys.argv)
Gui = Screen()
sys.exit(app.exec_())
Edited: I am trying to pass an int to actions based on the int value that number of widgets need to get populate.
This int is based on the dynamic widget which i generated using the for loop.
For example if the first widget of running_layout "btn 0" is clicked there won't be any widgets in the action_layout, If the second widget of running_layout "btn 1" is clicked there will be one widget "0" in the action_layout and so on based on the widget number.
I am able to send the signal but on whichever dynamically created widget i click it is sending value 4 of last widget "btn 4" to the action_layout.
To simplify the task we will create 2 widgets, the first one will be ClickableWidget that will have a signal indicating the number associated with the widget, whereas the second DynamicWidget will have a method that will be in charge of updating the number of widgets.
To know if an element is pressed we use the method installEventFilter() and eventFilter(), this filters the events giving information of the object and event, we will use setProperty("index", ) to store the number.
In the second widget I have implemented a method that is responsible for cleaning the widgets and creating some new widgets.
class ClickableWidget(QWidget):
clicked = pyqtSignal(int)
def __init__(self, n=5, parent=None):
QWidget.__init__(self, parent)
self.hlayout = QVBoxLayout(self)
for i in range(n):
label = QLabel("btn {}".format(i), self)
label.setProperty("index", i)
self.hlayout.addWidget(label)
label.installEventFilter(self)
def eventFilter(self, obj, event):
if isinstance(obj, QLabel) and event.type() == QEvent.MouseButtonPress:
i = obj.property("index")
self.clicked.emit(i)
return QWidget.eventFilter(self, obj, event)
class DynamicWidget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.hlayout = QVBoxLayout(self)
def changeWidget(self, n):
def clearLayout(layout):
item = layout.takeAt(0)
while item:
w = item.widget()
if w:
w.deleteLater()
lay = item.layout()
if lay:
clearLayout(item.layout())
item = layout.takeAt(0)
clearLayout(self.hlayout)
for i in range(n):
label = QLabel("btn {}".format(i), self)
self.hlayout.addWidget(label)
class Screen(QWidget):
def __init__(self):
super(Screen, self).__init__()
self.layout = QHBoxLayout(self)
c = ClickableWidget(6, self)
d = DynamicWidget(self)
c.clicked.connect(d.changeWidget)
self.layout.addWidget(c)
self.layout.addWidget(d)