Tkinter: subclassing Button with additional arguments - python

I want to subclass Button into OPButtun. OPButton is a regular Button with the capability of writing help messages when the mouse is hovering. OPButton must accept any possible parameter list that thre regular Button constructor would accept, plus two of my own: a message and the Stringvar where to write it in.
This is my code (supposedly runnable)
from tkinter import *
from tkinter import ttk
class OPButton(Button):
""" """
def ___init___(self, parent, string, message, *args, **kwargs):
ttk.Button.__init__(self, parent, *args, **kwargs)
self.bind("<Enter>", command=lambda e: string.set(message))
self.bind("<Leave>", command=lambda e: string.set(""))
if __name__ == '__main__':
root = Tk()
root.str= StringVar()
OPButton(root, root.str, "hovering the button", text="click here").pack()
ttk.Label(root, textvariable=root.str).pack()
root.mainloop()
and the error message:
Traceback (most recent call last):
File "C:\Users\planchoo\oPButton.py", line 19, in <module>
OPButton(root, "Hello World", "Bouton", text="Hello").pack()
TypeError: __init__() takes from 1 to 3 positional arguments but 4 were given
Edit: Below is the corrected code after Bryan's response. Works at perfection (thanks).
from tkinter import *
from tkinter import ttk
class OPButton(Button):
""" """
def __init__(self, parent, string, message, *args, **kwargs):
ttk.Button.__init__(self, parent, *args, **kwargs)
self.bind("<Enter>", lambda e: string.set(message))
self.bind("<Leave>", lambda e: string.set(""))
if __name__ == '__main__':
root = Tk()
root.chaine = StringVar()
OPButton(root, root.chaine, "Bouton", text="Hello").pack()
ttk.Label(root, textvariable=root.chaine).pack()
root.mainloop()

I'm pretty sure the __init__() function you defined was spelled as ___init___().

Related

Letting a user supply the name of a function via tkinter entry

I wrote this program in which I want the user to supply the name of a function which resides in a module (in a folder in his computer for example). For instance I have the function, 'my_sum' which resides in the module 'sum_functions.py' which in turn sits in the 'sum_package' folder. The idea is to use a ttk.Entry to get the function string and supply to 'import_func' which uses the importlib module and getattr to retrieve 'my_sum' . Finally I intend to pass my_sum() to calculate.
I get an error although the program prints 'my_sum' in the end. I would like to know What I am doing wrong or if there is a better more pythonic way?
sum_package # resides in my computer
sum_function.py # resides in sum_package and contains my_sum.py
def my_sum(x, y): # actual function to be called
return x * y
import importlib
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets(self):
self.entry = ttk.Entry(self , width=12)
self.entry.grid(row=3, column=1)
load_func = ttk.Button(self, text="Load function", command = self.import_func).grid(row=3, column=2)
ttk.Button(self, text="Calculate",command=self.calculate).grid(column=3, row=3)
self.quitButton = ttk.Button(self, text="Quit", command=self.quit )
self.quitButton.grid()
def import_func(self, *args):
global f # I made this global so the function could be accessed by other functions in the program
try:
value = str(self.entry.get())
print(value)
module_str = "sum_package." + 'sum_function' # get module str
module = importlib.import_module(module_str) # import module from str
f = getattr(module, value) # get function "function" in module
return f
except:
pass
def calculate(self, *args):
global x, y
try:
f(x, y)
except ValueError:
pass
app = Application()
app.master.title("Sample application")
x, y = 5, 6
app.mainloop()
#Error
# Exception in Tkinter callback
# Traceback (most recent call last):
# File "/home/richard/anaconda3/lib/python3.9/tkinter/__init__.py", line 1892, in __call__
# return self.func(*args)
# File "/home/richard/Dropbox/impedance_fitting_program/gui/test4.py", line 55, in calculate
# f(x, y)
# NameError: name 'f' is not defined
# my_sum

Want to add text in a Entry box to a .txt file on button click but im getting an error that says 'text' not defined

When I run this I get the error
File "C:/Users/plarkin2020334/PycharmProjects/untitled1/venv/Start.py", line 24, in add_list
file.write(text)
NameError: name 'text' is not defined
I don't know why it won't run it but I think the basis of the code is right.
import tkinter
from datetime import datetime
from time import strftime
from tkinter import *
from tkinter import PhotoImage
from tkinter import ttk
from tkinter.ttk import *
root = tkinter.Tk()
class FrameStart1(tkinter.Frame):
image = PhotoImage(file='C:/Users/plarkin2020334/Pictures/DQ_Logocp.png')
def __init__(self, parent, *args, **kwargs):
tkinter.Frame.__init__(self, parent, *args, **kwargs)
Label(self, image=self.image).place(relx=0, rely=0, anchor=NW)
Label(self, text="Enter any additional Instructiuons for the day:", background="#3f49e5").place(relx=.0, rely=.45)
Button(self, text="Add to Todays List", command=self.add_list()).place(relx=.30, rely=.51)
Entry(self).place(relx=.0, rely=.51)
text = Entry.get()
def add_list(self):
file = open("List.txt", "w")
file.write(text)
def runStart1():
MyFrameStart1 = FrameStart1(root)
MyFrameStart1.pack(expand='true', fill='both')
MyFrameStart1.configure(background="#3f49e5")
root.geometry("500x300")
runStart1()
root.mainloop()```
I'm new to using classes, but wouldn't it have to be referenced as self.text since you are defining it in the __init__ method?

Declared variables not initialised when using 'simpledialog'

I have a problem with some code that I have been working on where I am trying to pass a variable to a 'simpledialog' box. However, when I declare the variable in the __init__ section, the variable cannot be accessed from any other method in the class.
I have created a simplified working example in which I am trying to pass a string to an Entry box so that when the 'simpledialog' is created, the Entry box is already populated. The value can then be changed and the new value is printed to the console.
from tkinter import *
from tkinter.simpledialog import Dialog
class App(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
Button(parent, text="Press Me", command=self.run).grid()
def run(self):
number = "one"
box = PopUpDialog(self, title="Example", number=number)
print(box.values)
class PopUpDialog(Dialog):
def __init__(self, parent, title, number, *args, **kwargs):
Dialog.__init__(self, parent, title)
self.number = number
def body(self, master):
Label(master, text="My Label: ").grid(row=0)
self.e1 = Entry(master)
self.e1.insert(0, self.number) # <- This is the problem line
self.e1.grid(row=0, column=1)
def apply(self):
self.values = (self.e1.get())
return self.values
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
When the code is run, and the 'Press Me' button is pressed, I get the following error message:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1533, in __call__
return self.func(*args)
File "C:/Python/scratch.py", line 14, in run
box = PopUpDialog(self, title="Example", number=number)
File "C:/Python/scratch.py", line 20, in __init__
Dialog.__init__(self, parent, title)
File "C:\Python34\lib\tkinter\simpledialog.py", line 148, in __init__
self.initial_focus = self.body(body)
File "C:/Python/scratch.py", line 26, in body
self.e1.insert(0, self.number)
AttributeError: 'PopUpDialog' object has no attribute 'number'
If I comment out the self.e1.insert(0, self.number), the code will otherwise work.
There seems to be little documentation on the 'simpledialog', and I've been using the examples on effbot.org to try and learn more about dialog boxes.
As a side note, if I insert a print(number) line in the __init__ method of the PopUpDialog class, the number will print to the console. Also, if I initialise the self.number variable (eg, self.number = "example") in the body() method, the code works as expected.
I'm sure I'm missing something silly here, but if you could offer any suggestions as to what might be happening, it would be most appreciated.
The problem is in your PopUpDialog class, At the function __init__ you call the line Dialog.__init__(self, parent, title) that calls the body method. The problem is that you initialize the self.number at the next line and that's why self.number is not initialized yet at the body method.
If you switch the lines it will work for you, just like this:
class PopUpDialog(Dialog):
def __init__(self, parent, title, number, *args, **kwargs):
self.number = number
Dialog.__init__(self, parent, title)
EDIT:
As you can see at the __init__ method of the Dialog there is the above line :
self.initial_focus = self.body(body) that calls your body method.

I keep getting TypeError: count_function() missing 1 required positional argument: 'self'

I'm trying to create a little counter app, but I seem to get stuck when trying to create an actual button which makes the counter go up.
Code:
from tkinter import *
class CounterClass(Frame):
buttonFrame = Frame(height=200, width=200)
counterStatus = Label(buttonFrame, text="0")
def count_function(self):
i = int(self.counterStatus.cget("text"))
i += 1
self.counterStatus.config(text=str(i))
counter = Button(buttonFrame, text="+1", command=count_function)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.buttonFrame.pack()
self.counterStatus.pack()
self.counter.pack()
if __name__ == "__main__":
root = Tk()
c = CounterClass(master=root)
c.mainloop()
root.destroy()
When I click the button, it gives me this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/tkinter/__init__.py", line 1533, in __call__
return self.func(*args)
TypeError: count_function() missing 1 required positional argument: 'self'
However, when I create a module that should do exactly the same, but I don't use a class it works fine:
from tkinter import *
root = Tk()
def count_function():
i = int(counterStatus.cget("text"))
i += 1
counterStatus.config(text=str(i))
buttonFrame = Frame(height=200, width=200)
counterStatus = Label(buttonFrame, text="0")
counterButton = Button(buttonFrame, text="+1", command=count_function)
buttonFrame.pack()
counterStatus.pack()
counterButton.pack()
root.mainloop()
counter = Button(buttonFrame, text="+1", command=count_function)
When you click the button, this will try to call the count_function without any argument. But since it’s an instance method, it requires the (implicit) self parameter.
To fix this, you should move the creation of your elements inside of the __init__ method. This not only prevents them from being stored as (shared) class members, but will also allow you to specify bound methods:
class CounterClass(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.buttonFrame = Frame(height=200, width=200)
self.counter = Button(self.buttonFrame, text="+1", command=self.count_function)
self.counterStatus = Label(self.buttonFrame, text="0")
self.pack()

How to get progressbar start() info from one window (class) to other?

There is a main window with menu and the progressbar. A correspondence window with OK button opens upon menu command and the OK button starts the process (here: 3 sec. sleep).
The correspondence window is created via inheritance from a class I have not provided here (If required for answer, please let me know). The methods apply and ok override existing methods in the mother class.
Now my problem: Since the progressbar sits in the main window (class App) and progressbar(start) and progressbar(stop) in the correspondence window I somehow have to pass (start) and (stop) via the mother class tkSimpleDialog.Dialog to class App. So I thought I also override the __init__(self..) method, provide self. to progressbar.
How can I make this work?
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading
class App:
def __init__(self, master, progressbar):
self.progress_line(master)
def progress_line (self, master):
self.progressbar = ttk.Progressbar(master, mode='indeterminate')
self.progressbar.place(anchor = 'ne', height = "20", width = "150", x = "175", y = "30")
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
def menu_bar(self):
menu_bar = Tkinter.Menu(self.master)
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff = False)
self.create_menu.add_command(label = "do", command = self.do)
self.menu_bar.add_cascade(label = "now", menu = self.create_menu)
def do(self):
do1 = Dialog(self.master, progressbar)
class Dialog(tkSimpleDialog.Dialog):
def __init__(self, parent, progressbar):
tkSimpleDialog.Dialog.__init__(self, parent, progressbar)
self.transient(parent)
self.parent = parent
self.result = None
self.progressbar = progressbar
body = Frame(self)
self.initial_focus = self.body(body)
body.pack(padx=5, pady=5)
self.buttonbox()
self.grab_set()
if not self.initial_focus:
self.initial_focus = self
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.geometry("+%d+%d" % (parent.winfo_rootx()+50, parent.winfo_rooty()+50))
self.initial_focus.focus_set()
self.wait_window(self)
def ok(self, event=None):
self.withdraw()
self.start_foo_thread()
self.cancel()
def apply(self):
time.sleep(5)
def start_foo_thread(self):
global foo_thread
self.foo_thread = threading.Thread(target=self.apply)
self.foo_thread.daemon = True
self.progressbar.start()
self.foo_thread.start()
master.after(20, check_foo_thread)
def check_foo_thread(self):
if self.foo_thread.is_alive():
root.after(20, self.check_foo_thread)
else:
self.progressbar.stop()
master = Tkinter.Tk()
progressbar = None
app = App(master, progressbar)
appmenu = AppMenu(master, progressbar)
master.mainloop()
error messages:
first after clicking ok:
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
File "ask-progressbar.py", line 57, in ok
self.start_foo_thread()
File "ask-progressbar.py", line 66, in start_foo_thread
self.progressbar.start()
AttributeError: Dialog2 instance has no attribute 'progressbar'
second: after closing app
Exception in Tkinter callback
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1410, in __call__
File "ask-progressbar.py", line 26, in do
do1 = Dialog2(self.master, progressbar)
File "ask-progressbar.py", line 33, in __init__
self.transient(parent)
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1652, in wm_transient
TclError: can't invoke "wm" command: application has been destroyed
Below is a working version of your code. There were a number of issues I had to fix because you didn't change a number of things in the code from my answer to your other question about progressbars.
The answer to your main question here is basically that you have to pass the instance around and remember it when necessary in the various class instances involved so that their methods will have it available through theself argument when they need it. Also, the way you were trying to derive and override the tkSimpleDialog.Dialog base class methods was both over-complicated and incorrect as well.
Usually the best (and simplest) thing to do is just supply your own validate() and apply() methods since that's how it was designed to work. If you also need your own __init__() constructor, it's important to only pass parameters to the base class' method that it understands from within the one in the subclass. If you need more functionality, it can usually be provided via additional derived-class-only methods, that only it or other classes you've also created know about.
Anyway, here's what I ended-up with:
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time, threading
class App:
def __init__(self, master):
self.progress_line(master)
def progress_line(self, master):
# the value of "maximum" determines how fast progressbar moves
self._progressbar = ttk.Progressbar(master, mode='indeterminate',
maximum=4) # speed of progressbar
self._progressbar.place(anchor='ne', height="20", width="150",
x="175", y="30")
#property
def progressbar(self):
return self._progressbar # return value of private member
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
self.progressbar = progressbar
def menu_bar(self):
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
self.create_menu.add_command(label="do", command=self.do)
self.menu_bar.add_cascade(label="now", menu=self.create_menu)
def do(self):
Dialog(self.master, self.progressbar) # display the dialog box
class Dialog(tkSimpleDialog.Dialog):
def __init__(self, parent, progressbar):
self.progressbar = progressbar
tkSimpleDialog.Dialog.__init__(self, parent, title="Do foo?")
def apply(self):
self.start_foo_thread()
# added dialog methods...
def start_foo_thread(self):
self.foo_thread = threading.Thread(target=self.foo)
self.foo_thread.daemon = True
self.progressbar.start()
self.foo_thread.start()
master.after(20, self.check_foo_thread)
def check_foo_thread(self):
if self.foo_thread.is_alive():
master.after(20, self.check_foo_thread)
else:
self.progressbar.stop()
def foo(self): # some time-consuming function...
time.sleep(3)
master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()
Hope this helps.
Here's another, simpler, solution that doesn't require the use of threading -- so could be easier to use/adapt in your case. It calls the progressbar widget's update_idletasks() method multiple times during the time-consuming foo() function. Again, it illustrates how to pass the progressbar around to the various parts of the code that need it.
import Tkinter, ttk, tkFileDialog, tkSimpleDialog, time
class App:
def __init__(self, master):
self.progress_line(master)
def progress_line(self, master):
self._progressbar = ttk.Progressbar(master, mode='indeterminate')
self._progressbar.place(anchor='ne', height="20", width="150",
x="175", y="30")
#property
def progressbar(self):
return self._progressbar # return value of private member
class AppMenu(object):
def __init__(self, master, progressbar):
self.master = master
self.menu_bar()
self.progressbar = progressbar
def menu_bar(self):
self.menu_bar = Tkinter.Menu(self.master)
self.master.config(menu=self.menu_bar)
self.create_menu = Tkinter.Menu(self.menu_bar, tearoff=False)
self.create_menu.add_command(label="do foo", command=self.do_foo)
self.menu_bar.add_cascade(label="now", menu=self.create_menu)
def do_foo(self):
confirm = ConfirmationDialog(self.master, title="Do foo?")
self.master.update() # needed to completely remove conf dialog
if confirm.choice:
foo(self.progressbar)
class ConfirmationDialog(tkSimpleDialog.Dialog):
def __init__(self, parent, title=None):
self.choice = False
tkSimpleDialog.Dialog.__init__(self, parent, title=title)
def apply(self):
self.choice = True
def foo(progressbar):
progressbar.start()
for _ in range(50):
time.sleep(.1) # simulate some work
progressbar.step(10)
progressbar.update_idletasks()
progressbar.stop()
master = Tkinter.Tk()
master.title("Foo runner")
app = App(master)
appmenu = AppMenu(master, app.progressbar)
master.mainloop()

Categories

Resources