Modify a variable inside another class and file - python-3.x

(Non-English native)
This is tricky to explain. I have 2 windows, each one with their own class created by PyQt5, on 2 different .py files. I want to open the 2nd window from a button inside the first one, and then I want that 2nd window to destroy itself when closed. However, in order to do this, I believe I have to set a specific variable in the 1st window to None, but since that is instanced I can't find out how:
First window in myfile.py
class MainForm(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.window_nuevocliente = None
self.actionNuevo_Cliente.triggered.connect(self.open_secondwindow)
def open_secondwindow(self):
if self.window_nuevocliente is None:
self.window_nuevocliente = addnewclient_logic.AddNewClientForm(self)
self.window_nuevocliente.setAttribute(Qt.WA_DeleteOnClose, True)
self.window_nuevocliente.show()
myapp = MainForm()
Second window in addnewclient_logic.py
import myfile
class AddNewClientForm(QtWidgets.QMainWindow, Ui_AddNewClient):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
def closeEvent(self, event):
# Do closing stuff
event.accept()
# Set the first class var window_nuevocliente back to None
myfile.myapp.window_nuevocliente = None
And here is where I'm stuck:
That last line doesn't work. When I close the window, the DeleteOnClose will totally destroy the window, but the first window will still have it assigned on the window_nuevocliente var and so it fails to re-create it from scratch. If I instead omit the check if it's None, the window can be opened multiple times at the same time (and I don't want that).

Managed to fix it by adding the destroyed signal.
def open_secondwindow(self):
if self.window_nuevocliente is None:
self.window_nuevocliente = addnewclient_logic.AddNewClientForm(self)
self.window_nuevocliente.setAttribute(Qt.WA_DeleteOnClose, True)
self.window_nuevocliente.destroyed.connect(self.reset_nuevocliente)
self.window_nuevocliente.show()
def reset_nuevocliente(self):
self.window_nuevocliente = None
I will accept a better solution, though :P

You can eliminate the window_nuevocliente attribute like this:
addnewclient_logic.py:
class AddNewClientForm(QtWidgets.QMainWindow, Ui_AddNewClient):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
self.setupUi(self)
myfile.py:
from addnewclient_logic import AddNewClientForm
class MainForm(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtWidgets.QMainWindow.__init__(self, parent)
self.setupUi(self)
self.actionNuevo_Cliente.triggered.connect(self.open_secondwindow)
def open_secondwindow(self):
window_nuevocliente = self.findChild(AddNewClientForm)
if window_nuevocliente is None:
window_nuevocliente = AddNewClientForm(self)
window_nuevocliente.show()
The parent window will hold a reference to the child window until it deletes itself on close - at which point, it will also be removed from the parent's list of children.

Related

Is there an elegant way to delete a widget in QScrollArea in PyQt?

I am learning PyQt5 recently and have problems when I want to delete a widget in QScrollArea. Is there an elegant way to visit the element in QScrollArea and delete it when the "delete" button in that element is clicked? Thank you for any help!
class MyWidget(QWidget):
def __init__(self, id):
super().__init__()
self.layout = QHBoxLayout()
self.layout.addWidget(QPlainTextEdit(id))
self.layout.addWidget(QPushButton('Delete'))
self.setLayout(self.layout)
# connect options
connect_options()
def connect_options(self):
pass
class MyList(QScrollArea):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.widget = QWidget()
for x in range(10):
self.layout.addWidget(MyWidget(str(x)))
self.widget.setLayout(self.layout)
self.setMinimumSize(1024, 500)
self.setWidget(self.widget)
First I would suggest you look into QListWidget, It will provide you various functions to handle situations like this.
for your problem, you will need to delete the widget from its parent layout & then delete it from the GUI
class MyWidget(QWidget):
def __init__(self, id):
super().__init__()
self.layout = QHBoxLayout()
self.layout.addWidget(QPlainTextEdit(id))
del_btn = QPushButton('Delete')
del_btn.clicked.connect(self._delete) # connect the click event to your delete function
self.layout.addWidget(del_btn)
self.setLayout(self.layout)
# connect options
self.connect_options()
def connect_options(self):
pass
def _delete(self):
# here you will delete your widget
parent_layout = self.parent().layout()
parent_layout.removeWidget(self) # remove the widget from its parent layout
self.deleteLater() # lets Qt knows it needs to delete this widget from the GUI
del self

How to get tkinter window to be already transparent when opening?

I want a black window to fade in. When in fullscreen, it perfectly works but I need a specific size and there when its opened, it first appears black before it becomes transparent and the fading starts. Do you have any ideas how to achieve the same smooth effect as for the fullscreen version?
import tkinter as tk
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.attributes("-alpha",0.0)
#self.parent.attributes("-fullscreen",True)
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
alpha = self.parent.attributes("-alpha")
if alpha < 1:
alpha += .01
self.parent.attributes("-alpha", alpha)
self.after(100, self.fade_in)
if __name__ == "__main__":
root = tk.Tk()
root.bind("<Escape>",lambda e: root.destroy())
Fader(root).pack(fill="both", expand=True)
root.mainloop()
You can use withdraw() to hide the window and deiconify() to show to window later on and increase the alpha. But it seems to not work unless you update the tasks or wait for the window to be visible.
Method 1:
Was able to fix this by using update_idletasks(), like:
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.attributes('-alpha',0.0)
self.parent.withdraw() #hiding the window
#self.parent.attributes("-fullscreen",True)
self.parent.update_idletasks()
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
self.parent.deiconify() #bringing it back
..... #same code
Method 2:
Or like said by acw1668, you can use wait_visibility(), like:
class Fader(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.parent.wait_visibility(self.parent)
self.parent.attributes('-alpha',0.0)
self.parent.withdraw()
# self.parent.attributes("-fullscreen",True)
self.parent.geometry("600x800")
self.configure(bg='black')
self.fade_in()
def fade_in(self):
self.parent.deiconify()
...... #same code
A bit more about wait_visibility():
wait_visibility(window=None)
Wait for the given widget to become visible. This is typically used to wait until a new toplevel window appears on the screen. Like wait_variable, this method enters a local event loop, so other parts of the application will still work as usual.
A bit more about update_idletasks():
update_idletasks()
Calls all pending idle tasks, without processing any other events. This can be used to carry out geometry management and redraw widgets if necessary, without calling any callbacks.
Source :- https://effbot.org/tkinterbook/widget.htm

Retrieving variable from another class

I am programming a GUI using Tkinter. In one of the classes I have defined a variable (entry_filename) and would like to use it in another class. A part of the code is as follows:
class Loginpage(tk.Frame,Search):
def __init__(self,parent,controller):
tk.Frame.__init__(self,parent)
self.controller=controller
self.label_user=tk.Label(self, text="Username")
self.label_user.grid(row=0, column=0)
self.label_pass=tk.Label(self, text="Password")
self.label_pass.grid(row=1, column=0)
self.entry_user=tk.Entry(self)
self.entry_user.focus_set()
self.entry_user.grid(row=0, column=1)
self.entry_pass=tk.Entry(self,show="*")
self.entry_pass.grid(row=1, column=1)
self.button=ttk.Button(self, text="Login",command= self.Logincheck)
self.button.grid(columnspan=2)
def Logincheck(self):
global username
global password
try:
username=self.entry_user.get()
password=self.entry_pass.get()
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(server, username=username, password=password)#input your username&password
button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Inputpage))
button1.grid(columnspan=2)
except:
tm.showerror("Login error", "Incorrect username/password")
class Inputpage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.filein_label=tk.Label(self,text="Input file name")
self.filein_label.grid(row=0,column=0)
self.entry_filename=tk.Entry(self)
self.entry_filename.focus_set()
self.entry_filename.grid(row=0,column=1)
self.button1 = ttk.Button(self, text="Click to Continue",command= lambda: self.controller.show_frame(Graphpage))
self.button1.grid(columnspan=2)
class Graphpage(tk.Frame,Inputpage):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller=controller
self.label = tk.Label(self, text="Graph Page!", font=LARGE_FONT)
self.label.pack(pady=10,padx=10)
button1 = ttk.Button(self, text="Back to Input Page",command=lambda: self.controller.show_frame(Inputpage))
button1.pack()
filename=Inputpage.entry_filename.get()
The Graphpage calls the variable filename which is later used to create the graph (that part of the code is omitted here). When the code is run the following error is returned:
TypeError: Cannot create a consistent method resolution
order (MRO) for bases Frame, Inputpage
It seems that I have hit another roadblock in attempting to solve the earlier issue, however, if I can understand the resolution to this, I hope that I can attempt to solve further issues. Thanks for your help
ssh is a local variable inside function LoginCheck so you are not able to retrieve it from another class. One thing possible to do is to define ssh as self.ssh so it will be accessible through instance_of_Loginpage.ssh. It will work only when you will pass an instance of Loginpage into an instance of Graphpage. If you need access to an ssh connection from many places I suggest to create another class just to handle ssh (you can use Borg patter to achieve it).
The culprit is that you should not share
class member variables that way.
If different classes share some common
data, that data is probably another class
and they can inherit from it.
class CommonData():
client = 100
class A(CommonData):
def __init__(self):
print(A.client)
class B(CommonData):
def __init__(self):
print(B.client)
a = A()
b = B()
CommonData.client = 300
print(a.client)
print(b.client)
In above case every instance of A and every instance of B
share all the CommonData class variables, like client.
CommonData.client = 400
class C():
pass
You can use multiple inheritance too.
define all common data as CommonData attributes
and use CommonData as a class to hold data, like
in above example, don't create instances from it:
class D(C, CommonData):
def __init__(self):
print(D.client)
c = C()
d = D()
A simpler option would be to just define
a variable CommonData in the outer scope and
use it from anywhere:
common_data = 500
class A():
def __init__(self):
global common_data
print(common_data)
common_data = 200
# ...
But global variables are generally seen as a bad thing in a program as their use can become a problem for several reasons.
Yet another way is to pass the variable to the object initializer.
That makes the instance to keep its own value copied from
the creation value:
common_data = 600
class A():
def __init__(self, data):
self.common = data
print(self.common)
a = A(common_data)
common_data = 0
print(a.common)
If you run all the code above it will print
100
100
300
300
400
600
600
Edit:
See my comment to your answer and a simple example here.
Here I opt for two global references to tkinter StringVars.
The stringvars exist themselves in the Tk() namespace, like the
widgets; besides they are global Python names.
import tkinter as tk
from tkinter import ttk
class Page1(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page1')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Toplevel):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.title('Page2')
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=input_file2)
self.button1 = ttk.Button(self, text='Copy Here', command=copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename():
input_file2.set(input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
root.iconify()
input_file1 = tk.StringVar()
input_file2 = tk.StringVar()
page1 = Page1(root)
page2 = Page2(root)
root.mainloop()
Now in the next example see how I turn the stringvars into variables
of Page1 and Page2 instances (not classes), making them local instead
of global. Then I am forced to pass a reference for the widget page1
object into the widget page2 object.
This looks more close to what you are asking.
About MRO trouble, if you avoid multiple inheritance
it won't happen.
Or you deal with it usually by using super()
In your case the error is because you store the widget in
the object/instance (in self.somename), and then you try
to invoke a widget method qualifying with the class name.
There is no widget there in the class for you to use a method.
So the search using the method resolution order fails,
because there is no corresponding name there.
Note that I have not used multiple inheritance, so I could
have just written tk.Frame. instead of calling super. I like
super because it makes clear in the text that I am invoking the parent
class but super is really needed only when there are multiple parents
and various levels of subclassing (usually forming a diamond shape).
Now the example:
import tkinter as tk
from tkinter import ttk
class Page1(tk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.input_file1 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file1)
self.label1.pack(side=tk.LEFT)
self.entry1.pack()
class Page2(tk.Frame):
# note the page1 reference being
# passed to initializer and stored in a var
# local to this instance:
def __init__(self, parent, page1, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.page1 = page1
self.input_file2 = tk.StringVar()
self.label1 = ttk.Label(self, text='Filename:')
self.entry1 = ttk.Entry(self, textvariable=self.input_file2)
self.button1 = ttk.Button(self, text='Copy Here',
command=self.copy_filename)
self.label1.pack(side=tk.LEFT)
self.entry1.pack(side=tk.LEFT)
self.button1.pack()
def copy_filename(self):
# see how the page1 refernce is used to acess
# the Page1 instance
self.input_file2.set(page1.input_file1.get())
root = tk.Tk() # has to exist for the StringVars to be created
page1 = Page1(root)
page2 = Page2(root, page1) # pass a reference to page1 instance
page1.pack(side=tk.LEFT)
page2.pack(side=tk.LEFT)
root.mainloop()

How to pass variables from one QWizardPage to main QWizard

I am trying to figure out how to pass variables from (e.g.: an openFile function) inside a QWizardPage class to the main QWizard class. I have also read about signals and slots but can't understand how and if this is the ideal way to do it when using PyQt5.
Here follows a simplified example of the code:
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.closeEvent)
def closeEvent(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QPushButton("Import Edge List")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
def openFileNameDialog(self, parent):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileName, _ = QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.parent.variable = fileName
if you want to access QWizard from QWizardPage you must use the wizard() method, on the other hand closeEvent() is an event that is triggered when the window is closed that should not be invoked by an additional signal that is not necessary, the correct thing is to create a slot that connects to the finished signal.
from PyQt5 import QtCore, QtWidgets
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.onFinished)
#QtCore.pyqtSlot()
def onFinished(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QtWidgets.QPushButton("Import Edge List")
self.comboBox = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
#QtCore.pyqtSlot()
def openFileNameDialog(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.wizard().variable = fileName
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = ImportWizard()
w.show()
sys.exit(app.exec_())

Setting label value from OptionMenu in tkinter

I would like to set a label value to the value of the current optionmenu value. If the latter changes I want the former to change too. My issue is that this gui elements are defined in separate classes (and I want them to be like that), but I do not know how to connect them together. Without classes I know I can use the OptionMenu's command method to set the value of the Label. But putting them into Frame containers I am stuck.
Here is a simplistic and functioning code what I want to resolve:
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
self.var = StringVar(self)
self.var.set(opt[0])
self.om = OptionMenu(self, self.var, *opt)
self.om.pack(side=TOP)
self.var.trace('w', self.getValue)
def getValue(self, *args):
return(self.var.get())
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.labstring = StringVar(self)
self.lab = Label(self, textvariable = self.labstring, bg='white')
self.lab.pack(side=TOP)
self.labstring.set('hello')
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()
Could you give me some help how to proceed. Many thanks.
According to #j_4321's suggestion, here I post the solution that resolved my issue. I provide explanation in comments in between code lines.
from tkinter import *
root = Tk()
opt=['Jan', 'Feb', 'March']
var = StringVar(root) # initialization of a common StringVar for both OptionMenu and Label widgets
class MyOptMenu(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
var.set(opt[0]) # give an initial value to the StringVar that will be displayed first on the OptionMenu
self.om = OptionMenu(self, var, *opt)
self.om.pack(side=TOP)
var.trace('w', self.getValue) # continuously trace the value of the selected items in the OptionMenu and update the var variable, using the function self.getValue
def getValue(self, *args):
return(var.get()) # return the current value of OptionMenu
class MyLabel(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack()
self.lab = Label(self, textvariable = var, bg='white') # use the same StringVar variable (var) as the OptionMenu. This allows changing the Label text instantaneously to the selected value of OptionMenu
self.lab.pack(side=TOP)
a = MyOptMenu(root)
b = MyLabel(root)
root.mainloop()

Resources