I have written some Tkinter dialogs that are called from within an app running in a mainloop. My dialog initializer usually looks like this:
class OKCANCEL( Toplevel ):
def __init__( self, parent, title ):
Toplevel.__init__( self, parent )
self.parent = parent
self.title( title )
...
I call this from within my app as:
o = OKCANCEL( self.mainWindow, mtext = QUIT_REALLY )
where self.mainWindowis a Frame within my App window (a class inheriting from Frame).
Now, every time that I am calling the dialog, a smaller window appears and then the intended window replaces the small one. The display duration is short, however, it is annoying to have this appening.
Am I missing the obvious?
Can someone point me to my mistake?
Thanks!
Edit: I just wrote up a small example and tried to reproduced the behaviour, however, either it was not present or it happened too fast. I am kind of confused, the small window that appears even has a window title that is nowhere to be found in the code (the big, huge chunk of code that is not on this page).
# -*- coding: UTF-8 -*-
import os
from Tkinter import *
class WARNING(Toplevel): #ok only dialog
def __init__(self,parent,wtext,wtitle=''):
Toplevel.__init__(self,parent)
self.parent=parent
self.protocol("WM_DELETE_WINDOW",self._NULL) #we need to include a check again...
self.title(wtitle)
self.attributes('-topmost',True)
self.transient()
mainWindow=Frame(self)
tFrame=Frame(mainWindow)
tLabel=Label(tFrame,text=wtext,height=3,bd=10)
tLabel.pack()
bFrame=Frame(mainWindow,bd=5)
bOK=Button(bFrame,bd=1,text='Ok',command=self._DONE)
bOK.pack(side='left')
tFrame.grid(row=0,column=0)
bFrame.grid(row=1,column=0,sticky=E)
mainWindow.pack()
def _DONE(self,Event=None):
self.destroy()
def _NULL():
pass
class WarnApp(Frame):
def __init__(self,parent):
Frame.__init__(self, parent)
self.parent=parent
self.initUI()
def initUI(self):
self.parent.title("Warning test")
self.mainWindow = Frame(self.parent)
B1=Button(self.mainWindow,command=self.Warn,text='Warn-Me')
B1.pack(fill=BOTH)
self.mainWindow.pack(fill=BOTH)
self.pack(fill=BOTH)
def Warn(self):
warn=WARNING(self,wtext='This is a warning',wtitle='generic warning dialog')
self.wait_window(warn)
def main():
root=Tk()
app = WarnApp(root)
app.mainloop()
if __name__ == "__main__":
main()
`
Related
I'm trying to call the init function of the screen I'm changing my screen index to
For an example, i have this code:
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from sys import argv as sysArgv
from sys import exit as sysExit
arialLarge = qtg.QFont("Arial", 18)
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# Current screen label;
mainWindowLabel = qtw.QLabel("This is the main window", self)
mainWindowLabel.setFont(arialLarge)
mainWindowLabel.move(20, 40)
# Button for going to the HelloWindow screen;
gotoHelloWindowButton = qtw.QPushButton("Go to hello window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()+1))
gotoHelloWindowButton.move(100, 100)
class HelloWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# EG: print hello world when I visit this page
print("hello world")
# Current screen label;
helloWindowLabel = qtw.QLabel("This is the hello window", self)
helloWindowLabel.setFont(arialLarge)
helloWindowLabel.move(20, 40)
# Button for going to the MainWindow screen;
gotoMainWindowButton = qtw.QPushButton("Go to main window", self, clicked=lambda: appStack.setCurrentIndex(appStack.currentIndex()-1))
gotoMainWindowButton.move(100, 100)
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = qtw.QStackedWidget()
appStack.addWidget(MainWindow())
appStack.setFixedSize(300, 300)
appStack.show()
appStack.addWidget(HelloWindow())
sysExit(app.exec())
If im visiting the HelloWindow from the MainWindow, how can i run the init function of the HelloWindow screen so I can run whatever code I want in there?
I need to be able to do this as on the app im working on as on the mainpage i have dynamically created buttons that all have functions parameters with different indexes to my server, and i need to be able to fetch the data from server based off the clicked button's data index so on the other page I can view the desired data.
The __init__ of a python class is what is called when an instance is created (using SomeClass()), so you should not try (or even think) to call it again, as it could create serious problems and bugs that are hard to track.
I strongly suggest you to read the documentation about classes in Python, as you cannot ignore that aspect in object oriented programming.
If you need to call something everytime the index is changed, then you should better subclass QStackedWidget and control everything from there.
A good solution is to create a standardized function that will be called everytime the page is presented, and ensure that the stack widget correctly calls it.
class FirstPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.nextButton = QtWidgets.QPushButton('Next')
self.doSomething()
def doSomething(self):
...
class SecondPage(QtWidgets.QWidget):
def __init__(self):
super().__init__(self)
# ...
self.prevButton = QtWidgets.QPushButton('Previous')
self.doSomething()
def doSomething(self):
...
class Stack(QtWidgets.QStackedWidget):
def __init__(self):
super().__init__(self)
self.first = FirstPage()
self.first.nextButton.clicked.connect(self.goNext)
self.addWidget(self.first)
self.second = SecondPage()
self.second.prevButton.clicked.connect(self.goPrev)
self.currentChanged.connect(self.initCurrent)
def goNext(self):
self.setCurrentIndex(1)
def goPrev(self):
self.setCurrentIndex(0)
def initCurrent()
if self.currentWidget():
self.currentWidget().doSomething()
if __name__ == "__main__":
app = qtw.QApplication(sysArgv)
appStack = Stack()
appStack.setFixedSize(300, 300)
appStack.show()
sysExit(app.exec())
Note that adding a QMainWindow to a parent is not a good idea, as Qt main windows are intended to be used as top level windows; also note that using fixed geometries (positions and sizes) is often considered bad practice, and you should use layout managers instead.
I have a main tkinter window that can take up to a few seconds to load properly. Because of this, I wish to have a splash screen that shows until the init method of the main class has finished, and the main tkinter application can be shown. How can this be achieved?
Splash screen code:
from Tkinter import *
from PIL import Image, ImageTk
import ttk
class DemoSplashScreen:
def __init__(self, parent):
self.parent = parent
self.aturSplash()
self.aturWindow()
def aturSplash(self):
self.gambar = Image.open('../output5.png')
self.imgSplash = ImageTk.PhotoImage(self.gambar)
def aturWindow(self):
lebar, tinggi = self.gambar.size
setengahLebar = (self.parent.winfo_screenwidth()-lebar)//2
setengahTinggi = (self.parent.winfo_screenheight()-tinggi)//2
self.parent.geometry("%ix%i+%i+%i" %(lebar, tinggi, setengahLebar,setengahTinggi))
Label(self.parent, image=self.imgSplash).pack()
if __name__ == '__main__':
root = Tk()
root.overrideredirect(True)
progressbar = ttk.Progressbar(orient=HORIZONTAL, length=10000, mode='determinate')
progressbar.pack(side="bottom")
app = DemoSplashScreen(root)
progressbar.start()
root.after(6010, root.destroy)
root.mainloop()
Main tkinter window minimum working example:
import tkinter as tk
root = tk.Tk()
class Controller(tk.Frame):
def __init__(self, parent):
'''Initialises basic variables and GUI elements.'''
frame = tk.Frame.__init__(self, parent,relief=tk.GROOVE,width=100,height=100,bd=1)
control = Controller(root)
control.pack()
root.mainloop()
EDIT: I can use the main window until it has finished loading using the .withdraw() and .deiconify() methods. However my problem is that I cannot find a way to have the splash screen running in the period between these two method calls.
a simple example for python3:
#!python3
import tkinter as tk
import time
class Splash(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.title("Splash")
## required to make window show before the program gets to the mainloop
self.update()
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.withdraw()
splash = Splash(self)
## setup stuff goes here
self.title("Main Window")
## simulate a delay while loading
time.sleep(6)
## finished loading so destroy splash
splash.destroy()
## show window again
self.deiconify()
if __name__ == "__main__":
app = App()
app.mainloop()
one of the reasons things like this are difficult in tkinter is that windows are only updated when the program isn't running particular functions and so reaches the mainloop. for simple things like this you can use the update or update_idletasks commands to make it show/update, however if the delay is too long then on windows the window can become "unresponsive"
one way around this is to put multiple update or update_idletasks command throughout your loading routine, or alternatively use threading.
however if you use threading i would suggest that instead of putting the splash into its own thread (probably easier to implement) you would be better served putting the loading tasks into its own thread, keeping worker threads and GUI threads separate, as this tends to give a smoother user experience.
I'm trying to build an app that incorporates wxwidgets (just for the tray icon) and Tkinter (for the rest of the GUI).
import wx
import Tkinter
TRAY_TOOLTIP = 'System Tray Icon'
TRAY_ICON = 'icon.png'
frm = False
class TaskBarIcon(wx.TaskBarIcon):
def __init__(self):
super(TaskBarIcon, self).__init__()
self.set_icon(TRAY_ICON)
self.Bind(wx.EVT_TASKBAR_LEFT_DOWN, self.on_left_down)
def set_icon(self, path):
icon = wx.IconFromBitmap(wx.Bitmap(path))
self.SetIcon(icon, TRAY_TOOLTIP)
def on_left_down(self, event):
createframe()
class Frame(Tkinter.Tk):
def __init__(self, parent):
Tkinter.Tk.__init__(self, parent)
self.parent = parent
self.protocol('WM_DELETE_WINDOW', self.closewindow)
self.grid()
def maximize(self):
# supposed to try to hide and bring a window back up
# full code removes the icon from the task bar, so I needed another way to make the window visible again
self.withdraw()
self.deiconify()
def closewindow(self):
self.destroy()
global frm
frm = False
def createframe():
global frm
if isinstance(frm, Tkinter.Tk): # if a window is open, it goes through this if statement
frm.maximize() # and crashes here.
else:
frm = Frame(None)
frm.title('Frame')
frm.mainloop()
def main():
app = wx.App()
TaskBarIcon()
app.MainLoop()
if __name__ == '__main__':
main()
You can run this code and hopefully see the problem. When you left-click the tray icon, a window pops up, you can close it and reopen it, however if you minimize the window (or just click the tray icon while the window is open), the app crashes. I suppose frm.maximize() is the problem, since I can call self.maximize() from within the class without trouble, but I was not able to find a solution.
I had the same problem when I was trying to do frm.destroy() from the TaskBarIcon class (while frm.quit() worked just fine), so maybe that's a hint?
You can't combine wxpython and tkinter in the same program.
In PyQt4 I have a main window which when the settings button is clicked opens the settings dialog
from PyQt4 import QtCore, QtGui
import ui_Design, ui_Settings_Design
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.settingsBtn.clicked.connect(lambda: self.showSettings())
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
The above works and my SettingsDialog is displayed but I cant get the setPageIndex to work
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(self.setPageIndex)
#QtCore.pyqtSlot()
def setPageIndex(self):
print 'selected'
self.settingsStackedWidget.setCurrentIndex(0)
The bookSettingsBtn is a QToolButton
self.bookSettingsBtn = QtGui.QToolButton(self.navigationFrame)
And the settingsStackedWidget is a QStackedWidget
self.settingsStackedWidget = QtGui.QStackedWidget(SettingsDialog)
At this point I am pretty confused on signals and slots and nothing I have read clears this up - if anyone can point out what I am doing wrong above and also potentially direct me to a good (beginners) guide / tutorial on signals and slots it would be greatly appreciated
I would also like to know how to make setPageIndex work as follows:
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)
I'm not sure why you're doing the following, but that's the issue:
def showSettings(self):
dialog = QtGui.QDialog()
dialog.ui = SettingsDialog()
dialog.ui.setupUi(dialog)
dialog.exec_()
SettingsDialog itself is a proper QDialog. You don't need to instantiate another QDialog.
Right now, you're creating an empty QDialog and then populate it with the same ui as SettingsDialog (i.e. setupUi(dialog)), then you show this dialog. But... The signal connection is for SettingsDialog, and the dialog you're showing doesn't have that.
Basically, you don't need that extra QDialog at all. The following should be enough:
def showSettings(self):
dialog = SettingsDialog()
dialog.exec_()
Ok. So here is an example how you pass an argument to a slot
from functools import partial
# here you have a button bookSettingsBtn:
self.bookSettingsBtn = QtGui.QPushButton("settings")
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, self.bookSettingsBtn.text()))
#pyqtSlot(str) # this means the function expects 1 string parameter (str, str) 2 string parameters etc.
def setPageIndex(self, selection):
print "you pressed button " + selection
In your case the argument would be an int. Of course you have to get the value from somewhere
and then put it in the partial part as the argument (here I just used the text of the button),
but you can use int, bool etc. Just watch the slot signature.
Here is a tutorial that helped me:
http://zetcode.com/gui/pyqt4/
I hope this helps.
Hey here I have a fully running example (just copy paste it in a python file and run it):
Maybe this helps you. It's a small example but here you see how it works.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from functools import partial
class MyForm(QMainWindow):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
button1 = QPushButton('Button 1')
button2 = QPushButton('Button 2')
button1.clicked.connect(partial(self.on_button, button1.text()))
button2.clicked.connect(partial(self.on_button, button1.text()))
layout = QHBoxLayout()
layout.addWidget(button1)
layout.addWidget(button2)
main_frame = QWidget()
main_frame.setLayout(layout)
self.setCentralWidget(main_frame)
#pyqtSlot(str)
def on_button(self, n):
print "Text of button is: " + str(n)
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
form = MyForm()
form.show()
app.exec_()
So I dont really understand why but changing the way the settingsDialog is called from the MainWindow has fixed my problem. I guess the parent window needed to be specified??:
class MainDialog(QtGui.QMainWindow, ui_Design.Ui_arbCrunchUI):
....
def showSettings(self):
self.settingsDialog = QtGui.QDialog(self)
self.settingsDialog.ui = SettingsDialog(self)
self.settingsDialog.ui.show()
class SettingsDialog(QtGui.QDialog, ui_Settings_Design.Ui_SettingsDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.setupUi(self)
self.bookSettingsBtn.clicked.connect(partial(self.setPageIndex, 1))
#QtCore.pyqtSlot(int)
def setPageIndex(self, selection):
self.settingsStackedWidget.setCurrentIndex(selection)
I'm trying to use pyqt to show a custom QDialog window when a button on a QMainWindow is clicked. I keep getting the following error:
$ python main.py
DEBUG: Launch edit window
Traceback (most recent call last):
File "/home/james/Dropbox/Database/qt/ui_med.py", line 23, in launchEditWindow
dialog = Ui_Dialog(c)
File "/home/james/Dropbox/Database/qt/ui_edit.py", line 15, in __init__
QtGui.QDialog.__init__(self)
TypeError: descriptor '__init__' requires a 'sip.simplewrapper' object but received a 'Ui_Dialog'
I've gone over several online tutorials, but most of them stop just short of showing how to use a non built-in dialog window. I generated the code for both the main window and the dialog using pyuic4. What I think should be the relevant code is below. What am I missing here?
class Ui_Dialog(object):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class Ui_MainWindow(object):
def __init__(self, dbConnection):
global c
c = dbConnection
def launchEditWindow(self):
print "DEBUG: Launch edit window"
dialog = QtGui.QDialog()
dialogui = Ui_Dialog(c)
dialogui = setupUi(dialog)
dialogui.show()
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
conn = sqlite3.connect('meds.sqlite')
c = conn.cursor()
self.ui = Ui_MainWindow(c)
self.ui.setupUi(self)
def main():
app = QtGui.QApplication(sys.argv)
program = StartQT4()
program.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Bonus question: since it looks like you can't pass arguments in pyqt function callbacks, is setting something which would otherwise be passed as an argument (the poorly named "c") to be global the best way to get information into those functions?
I've done like this in the past, and i can tell it works.
assuming your button is called "Button"
class Main(QtGui.QMainWindow):
''' some stuff '''
def on_Button_clicked(self, checked=None):
if checked==None: return
dialog = QDialog()
dialog.ui = Ui_MyDialog()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
This works for my application, and I believe it should work with yours as well. hope it'll help, it should be pretty straight forward to do the few changes needed to apply it to your case.
have a good day everybody.
Ui_Dialog should inherent from QtGui.QDialog, not object.
class Ui_Dialog(QtGui.QDialog):
def __init__(self, dbConnection):
QtGui.QDialog.__init__(self)
global c
c = dbConnection
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
Why QtGui.QWidget.__init___ ???
Use insted:
class StartQT4(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
You must call __init__ methon from base class (name in parenthesis '()')
QDialog have two useful routins:
exec_()
show()
First wait for closing dialog and then you can access any field form dialog. Second show dialog but don't wait, so to work properly you must set some slot/signals connections to respond for dialog actions.
eg. for exec_():
class Dialog(QDialog):
def __init__(self, parent):
QDialog.__init__(parent)
line_edit = QLineEdit()
...
dialog = Dialog()
if dialog.exec_(): # here dialog will be shown and main script will wait for its closing (with no errors)
data = dialog.line_edit.text()
Small tip: can you change your ui classes into widgets (with layouts). And perhaps problem is that your __init__ should be __init__(self, parent=None, dbConnection)
Because when you create new widget in existing one PyQt may try to set it as children of existing one. (So change all init to have additional parent param (must be on second position)).