Proper way to pass variables within a tkinter GUI - python-2.7

I am trying to write a simple GUI to plot data from a csv file, which I read into a pandas DataFrame. I am completely new to GUI programming, and I am having a hard time getting my head around event-driven setups like Tkinter.
As a simple exercise, I want to set up a couple of buttons, one to open a file and read in the DataFrame, and another to print out the resulting DataFrame. My first naive attempt didn't work:
import pandas as pd
import tkFileDialog
import Tkinter as tk
def open_events_db():
file_path_string = tkFileDialog.askopenfilename()
eventsdb = pd.read_csv(file_path_string,encoding='utf-8')
return eventsdb
def print_events_db(eventsdb):
print eventsdb
def main():
root=tk.Tk()
eventsdb = tk.Button(text='File Open', command=open_events_db).pack(fill=tk.X)
tk.Button(text='Print DB', command=lambda:print_events_db(eventsdb)).pack(fill=tk.X)
tk.mainloop()
if __name__=="__main__":
main()
I can read in the file fine, and open it, but in hindsight obviously I can't return eventsdb from the file open button and have it as an argument to the print button.
I don't think it's unreasonable to have buttons that operate on that DB, though, so what is the proper way to pass variables around within the GUI?

Functions called from buttons and event handers don't return their data. Instead, they must set global variables or class attributes.
def open_events_db():
global eventsdb
file_path_string = tkFileDialog.askopenfilename()
eventsdb = pd.read_csv(file_path_string,encoding='utf-8')
def print_events_db():
global eventsdb
print eventsdb
...
tk.Button(text='Print DB', command=print_events_db).pack(fill=tk.X)
It's generally considered poor programming to rely on global variables. Since python is an object oriented language, it makes sense to write your app as a class. You would then use instance attributes rather than global variables.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
open_button = tk.Button(text='File Open', command=self.open_events_db)
print_button = tk.Button(text='Print DB', command=self.print_events_db)
open_button.pack(fill=tk.X)
print_button.pack(fill=tk.X)
def open_events_db(self):
file_path_string = tkFileDialog.askopenfilename()
self.eventsdb = pd.read_csv(file_path_string,encoding='utf-8')
def print_events_db():
print self.eventsdb
def main():
root=tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
if __name__=="__main__":
main()

Related

Non-blocking print redirection to Tkinter text widget

I've been able to get print statements redirected to a Tkinter text widget based on the answer to this question. It describes a class to redirect stdout:
class TextRedirector(object):
def __init__(self, widget, tag="stdout"):
self.widget = widget
self.tag = tag
def write(self, str):
self.widget.configure(state="normal")
self.widget.insert("end", str, (self.tag,))
self.widget.configure(state="disabled")
Utilizing the TextRedirector class, I have the following code
import sys
import time
from tkinter import *
root = Tk()
t = Text(root, width = 20, height = 5)
t.grid()
old_stdout = sys.stdout
sys.stdout = TextRedirector(t)
for i in range(5):
print i + 1
time.sleep(1)
sys.stdout = old_stdout
However, this solution seems to be blocking (the text shows up all at once once the for-loop ends).
I have successfully put together a non-blocking solution using Popen and threading, but this isn't an ideal solution due to some unrelated constraints.
Is there a way to have the print statements show up in the text box in real-time?

how to get ticking timer with dynamic label?

What im trying to do is that whenever cursor is on label it must show the time elapsed since when it is created it does well by subtracting (def on_enter(i)) the value but i want it to be ticking while cursor is still on label.
I tried using after function as newbie i do not understand it well to use on dynamic labels.
any help will be appreciated thx
code:
from Tkinter import *
import datetime
date = datetime.datetime
now = date.now()
master=Tk()
list_label=[]
k=[]
time_var=[]
result=[]
names=[]
def delete(i):
k[i]=max(k)+1
time_var[i]='<deleted>'
list_label[i].pack_forget()
def create():#new func
i=k.index(max(k))
for j in range(i+1,len(k)):
if k[j]==0:
list_label[j].pack_forget()
list_label[i].pack(anchor='w')
time_var[i]=time_now()
for j in range(i+1,len(k)):
if k[j]==0:
list_label[j].pack(anchor='w')
k[i]=0
###########################
def on_enter(i):
list_label[i].configure(text=time_now()-time_var[i])
def on_leave(i):
list_label[i].configure(text=names[i])
def time_now():
now = date.now()
return date(now.year,now.month,now.day,now.hour,now.minute,now.second)
############################
for i in range(11):
lb=Label(text=str(i),anchor=W)
list_label.append(lb)
lb.pack(anchor='w')
lb.bind("<Button-3>",lambda event,i=i:delete(i))
k.append(0)
names.append(str(i))
lb.bind("<Enter>",lambda event,i=i: on_enter(i))
lb.bind("<Leave>",lambda event,i=i: on_leave(i))
time_var.append(time_now())
master.bind("<Control-Key-z>",lambda event: create())
mainloop()
You would use after like this:
###########################
def on_enter(i):
list_label[i].configure(text=time_now()-time_var[i])
list_label[i].timer = list_label[i].after(1000, on_enter, i)
def on_leave(i):
list_label[i].configure(text=names[i])
list_label[i].after_cancel(list_label[i].timer)
However, your approach here is all wrong. You currently have some functions and a list of data. What you should do is make a single object that contains the functions and data together and make a list of those. That way you can write your code for a single Label and just duplicate that. It makes your code a lot simpler partly because you no longer need to keep track of "i". Like this:
import Tkinter as tk
from datetime import datetime
def time_now():
now = datetime.now()
return datetime(now.year,now.month,now.day,now.hour,now.minute,now.second)
class Kiran(tk.Label):
"""A new type of Label that shows the time since creation when the mouse hovers"""
hidden = []
def __init__(self, master=None, **kwargs):
tk.Label.__init__(self, master, **kwargs)
self.name = self['text']
self.time_var = time_now()
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
self.bind("<Button-3>", self.hide)
def on_enter(self, event=None):
self.configure(text=time_now()-self.time_var)
self.timer = self.after(1000, self.on_enter)
def on_leave(self, event=None):
self.after_cancel(self.timer) # cancel the timer
self.configure(text=self.name)
def hide(self, event=None):
self.pack_forget()
self.hidden.append(self) # add this instance to the list of hidden instances
def show(self):
self.time_var = time_now() # reset time
self.pack(anchor='w')
def undo(event=None):
'''if there's any hidden Labels, show one'''
if Kiran.hidden:
Kiran.hidden.pop().show()
def main():
root = tk.Tk()
root.geometry('200x200')
for i in range(11):
lb=Kiran(text=i)
lb.pack(anchor='w')
root.bind("<Control-Key-z>",undo)
root.mainloop()
if __name__ == '__main__':
main()
More notes:
Don't use lambda unless you are forced to; it's known to cause bugs.
Don't use wildcard imports (from module import *), they cause bugs and are against PEP8.
Put everything in functions.
Use long, descriptive names. Single letter names just waste time. Think of names as tiny comments.
Add a lot more comments to your code so that other people don't have to guess what the code is supposed to do.
Try a more beginner oriented forum for questions like this, like learnpython.reddit.com

making a function staticmethod in python is confusing

Hi I have a GUI written using Tkinter and the code template is as follows. My question is PyCharm gives me warnings on my functions (def func1, def func2) that they are static. To get rid of the warnings I placed #staticmethod above the functions. What does this do and is it necessary?
# Use TKinter for python 2, tkinter for python 3
import Tkinter as Tk
import ctypes
import numpy as np
import os, fnmatch
import tkFont
class MainWindow(Tk.Frame):
def __init__(self, parent):
Tk.Frame.__init__(self,parent)
self.parent = parent
self.parent.title('BandCad')
self.initialize()
#staticmethod
def si_units(self, string):
if string.endswith('M'):
num = float(string.replace('M', 'e6'))
elif string.endswith('K'):
num = float(string.replace('K', 'e3'))
elif string.endswith('k'):
num = float(string.replace('k', 'e3'))
else:
num = float(string)
return num
if __name__ == "__main__":
# main()
root = Tk.Tk()
app = MainWindow(root)
app.mainloop()
You can also turn off that inspection so that PyCharm doesn't warn you. Preferences -> Editor -> Inspections. Note that the inspection appears in the JavaScript section as well as the Python section.
You are right about #staticmethod being confusing. It is not really needed in Python code and in my opinion should almost never by used. Instead, since si_units is not a method, move it out of the class and remove the unused self parameter. (Actually, you should have done that when adding #staticmethod; the posted code will not work right with 'self' left in.)
Unless one has forgotten to use 'self' when it needs to be used, this is (or at least should be) the intent of the PyCharm warning. No confusion, no fiddling with PyCharm settings.
While you are at it, you could condense the function and make it easily extensible to other suffixes by using a dict.
def si_units(string):
d = {'k':'e3', 'K':'e3', 'M':'e6'}
end = string[-1]
if end in d:
string = string[:-1] + d[end]
return float(string)
for f in ('1.5', '1.5k', '1.5K', '1.5M'): print(si_units(f))

Maya + PyQt dialog. How to run a single copy of Qt window?

use this simple code for run a window based on qdialog:
import maya.OpenMayaUI as mui
import sip
from PyQt4 import QtGui, QtCore, uic
#----------------------------------------------------------------------
def getMayaWindow():
ptr = mui.MQtUtil.mainWindow()
return sip.wrapinstance(long(ptr), QtCore.QObject)
#----------------------------------------------------------------------
form_class, base_class = uic.loadUiType('perforceBrowserWnd.ui')
#----------------------------------------------------------------------
class PerforceWindow(base_class, form_class):
def __init__(self, parent=getMayaWindow()):
super(base_class, self).__init__(parent)
self.setupUi(self)
#----------------------------------------------------------------------
def perforceBrowser2():
perforceBrowserWnd = PerforceWindow()
perforceBrowserWnd.show()
perforceBrowser2()
every time you run the function perforceBrowser2() there is a new copy of windows.
how to find whether a window is already running and not to open a new copy of it, and go to the opened window? or just do not give a script to run a second copy of window?
ps. maya2014 + pyqt4 + python2.7
Keep a global reference to the window:
perforceBrowserWnd = None
def perforceBrowser2():
global perforceBrowserWnd
if perforceBrowserWnd is None:
perforceBrowserWnd = PerforceWindow()
perforceBrowserWnd.show()
Using global's are not the preferred way and there are ton's of articles why it is a bad idea.
Why are global variables evil?
It is cleaner to remember the instance in a static var of the class and whenever you load the UI, check if it does already exist and return it, if not create it. (There is a pattern called Singleton that describes this behaviour as well)
import sys
from Qt import QtGui, QtWidgets, QtCore
class Foo(QtWidgets.QDialog):
instance = None
def __init__(self, parent=None):
super(Foo, self).__init__(parent)
self.setWindowTitle('Test')
def loadUI():
if not Foo.instance:
Foo.instance = Foo()
Foo.instance.show()
Foo.instance.raise_()
loadUI()
Whenever you call loadUI it will return the same UI and will not recreate each time you call it.

How to add url to bookmark?

I am using PyQt4 for creating a custom browser using QtWebKit, but I am stuck on saving bookmarks from the browser. Does anyone know how to achieve that?
You're a little vague on how you want this done, so I'll say we wanted to use a button imported from a UI file called bookmarks_Btn. You'll need to use the pickle module.
Here's the example code...
from PyQt4 import QtCore, QtGui, QtWebKit, uic
import pickle
class window(QtGui.QWidget):
def __init__(self, parent=None):
super(httpWidget, self).__init__(parent)
self.ui = uic.loadUi('mybrowser.ui')
self.ui.setupUi(self)
def bookmarksLoad(self):
print 'Loading bookmarks'
try:
bookOpen = open('bookmarks.txt', 'rb')
bookmarks = pickle.load(bookOpen)
bookOpen.close()
print bookmarks # Not necessary, but for example purposes
# Here you decide how "bookmarks" variable is displayed.
except:
bookOpen = open('bookmarks.txt', 'wb')
bookmarks = 'http://www.stackoverflow.com'
bookWrite = pickle.dump(bookmarks, bookOpen)
bookOpen.close()
print bookmarks # Not necessary, but for example purposes
# Here you decide how "bookmarks" variable is displayed.
QtCore.QObject.connect(self.ui.bookmarks_Btn, QtCore.SIGNAL('clicked()'), self.bookmarksLoad)
self.ui.show()
def bookmarks():
url = input 'Enter a URL: '
bookOpen = open('bookmarks.txt', 'wb')
bookOpen.write(url)
bookOpen.close()
print 'Website bookmarked!'
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
run = window()
bookmarks()
sys.exit(app.exec_())
# You add on here, for example, deleting bookmarks.
However, if you wanted it to be retrieved from an address bar (named address, make the following changes...
# In the bookmarks function...
global url # Add at beginning
# Remove the input line.
# Add at end of __init__ in window class:
url = self.ui.address.text()
global url
That's pretty much the basics. Please note I normally program in Python 3 and PyQt5 so if there are any errors let me know :)