I am trying to create a custom text entry box that has a text entry field in the center and can return the user's input. I'm specifically trying to make something that can be imported and re-used in other programs. My current iteration uses an "inputdialog" class that works fine for getting the input when it's in it's own .py file, but if I try to import the module into another script, the "ok" button seemingly does nothing, and the "x" button throws an "ImportError: cannot import name 'inputdialog'" error after closing the pop-up.
The following is my code which currently works if run as a standalone script:
from tkinter import *
class inputdialog:
def __init__(self):
self.value = None
self.root = Tk()
self.root.withdraw()
self.top = Toplevel(self.root)
Label(self.top, text="Value").pack()
self.e = Entry(self.top)
self.e.pack(padx=5)
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5, padx=5, side="right")
self.root.mainloop()
def ok(self):
self.value = self.e.get()
self.root.destroy()
if __name__ == "__main__":
test = inputdialog()
print(test.value)
The following is how I've imported this module into another program, which does not currently work:
if __name__ == "__main__":
# These two lines below are needed to make sure that "askopenfilename"
# doesn't show it's top level tkinter window
root = Tk()
root.withdraw()
entdbemp = askopenfilename(title="Please select a file: ")
# Here is my non-working module call
master = inputdialog()
print(master.value)
Why would my "OK" button cease to function when imported? Is there any way I can fix my code to allow it to be imported? Is there a better way of trying to make an importable text entry module using tkinter?
The problem has nothing to do with importing. The problem is that in your second example you call Tk() twice: once in each file. Also, when you call 'destroy' you are only removing the GUI from the screen, you are not exiting the mainloop.
To make this work, you need to remove the lines that create a root in the inputdialog class, move the mainloop() call to a point after starting the class, and then call the quit method for the toplevel window (not the root window):
from tkinter import *
class inputdialog:
def __init__(self, master=None):
self.value = None
self.top = Toplevel(master)
Label(self.top, text="Value").pack()
self.e = Entry(self.top)
self.e.pack(padx=5)
b = Button(self.top, text="OK", command=self.ok)
b.pack(pady=5, padx=5, side="right")
def ok(self):
self.value = self.e.get()
self.top.quit()
if __name__ == "__main__":
root = Tk()
root.withdraw()
master = inputdialog(root)
root.mainloop()
print(master.value)
If you want to be neat and proper: rather than making a class that wraps around a different class, in OOP and GUIs we like to make a subclass:
import tkinter as tk
class inputdialog(tk.Toplevel):
def __init__(self, master=None):
tk.Toplevel.__init__(self, master)
self.value = None
tk.Label(self, text="Value").pack()
self.e = tk.Entry(self)
self.e.pack(padx=5)
b = tk.Button(self, text="OK", command=self.ok)
b.pack(pady=5, padx=5, side="right")
def ok(self):
self.value = self.e.get()
self.quit()
if __name__ == "__main__":
root = tk.Tk()
root.withdraw()
master = inputdialog(root)
root.mainloop()
print(master.value)
Also, wildcard imports (from module import *) are ugly and against PEP8; don't use them.
BTW, the easygui package has done all this already; you may just want to install and use that.
Related
I am building a real time monitoring project where information is given in the first window and that's keep on updating in the second window. I am trying to monitor the updated information in parallel from a different window using the same code, but as I pressed the new button and given the new information it is updating in the previous window also but I wanted monitor window to be completely different, so that I can monitor the different information in parallel using the same code. Please have a look at the sample code and help me with the ideas.
The sample code:
import time
import threading
import tkinter.messagebox
from tkinter import ttk
import queue
from tkinter import *
class Demo1:
data=[]
def __init__(self, master):#Python scrollbar on text widget
self.master = master
self.t=tkinter.Text(self.master,height=20,width=50)
self.t.grid(row=1, column=1)
self.button = tkinter.Button(self.master,height=3,width=10, text="OK", command = self.new_window)
self.button.grid(row=2,column=1)
def new_window(self):
self.inputValue=self.t.get("1.0",'end-1c')
Demo1.data1=self.inputValue.split("\n")
self.master.destroy() # close the current window
self.master = tkinter.Toplevel() # create another Tk instance
self.app = Demo2(self.master) # create Demo2 window
self.master.mainloop()
class Demo2:
t1 = []
s1 = True
display = []
display1 = []
i=0
kas = True
num=0
j1=0
def __init__(self, master):
self.master = master
self.button = tkinter.Button(self.master,height=2,width=11, text="new",command=self.new).place(x=0,y=0)
self.label = tkinter.Label(self.master, text="monitor", font=("Arial",20)).grid(row=0, columnspan=3)
cols = ('aa','bb')
self.listBox = ttk.Treeview(self.master, columns=cols)
for col in cols:
self.listBox.heading(col, text=col)
self.listBox.column(col,minwidth=0,width=170)
self.listBox.grid(row=1, column=0)
self.a()
def a(self):
self._img=tkinter.PhotoImage(file="green1.gif")
a=Demo1.data1
for i,(a) in enumerate(a): #here I have some function which runs repeatedlly
self.listBox.insert('', 'end',image=self._img, value=(a))
threading.Timer(1.0, self.a).start()
def new(self):
main()
def main():
root = tkinter.Toplevel()
app = Demo1(root)
root.mainloop()
if __name__ == '__main__':
main()
I have given the pppp information to monitor but as a pressed new button and added the xx information its updating in the previous window also. Please help me with the idea so that the link between these window will be vanished.
Output:
You have some major issues with your program. Including how you are trying to use your classes. The Toplevel() object was giving me issue, so I used Tk(). This should show you how to properly use the classes with the window. Most importantly your second window needs to be created from global not the first window. Also Demo1.data is a reference to your class definition not the actual data you loaded. I hope this example is helpful.
from tkinter import *
# your second window should be created in global
def create_demo2():
global app, app2
root2 = Tk()
app2 = Demo2(root2, app)
class Demo1:
def __init__(self, window):
self.window = window
self.data = ""
self.button = Button(self.window, text="New Window",
command=create_demo2)
self.button.pack()
def set_data(self):
self.data = "data"
class Demo2:
# you could just use app from global scope, but I like to pass so that it is explicit.
def __init__(self, window, app1):
self.window = window
self.button_set = Button(self.window, text="Set", command=app1.set_data)
self.button_use = Button(self.window, text="Use", command=self.use_data)
self.app = app1
self.label = Label(self.window)
self.button_set.pack()
self.button_use.pack()
self.label.pack()
def use_data(self):
self.label.configure(text=self.app.data)
root = Tk()
app = Demo1(root)
app2 = None
root.mainloop()
I need to be able to change the button position when I click on it. The position changes by a random amount each time I click on it. But all i get is an error. Here's my code:
from tkinter import *
from random import randrange
class Window(Frame):
def position(self):
return randrange(0,400),randrange(0,300)
def __init__(self,master=None):
Frame.__init__(self,master)
self.master = master
self.__init__window()
def __init__window(self):
self.master.title("GUI")
self.pack(fill=BOTH, expand=1)
Button1 = Button(self, text="Click me if you can",command=self.Message)
Button1.place(*position())
menu=Menu(self.master)
self.master.config(menu=menu)
file = Menu(menu)
file.add_command(label="Exit", command=self.client_exit)
menu.add_cascade(label="File",menu=file)
edit = Menu(menu)
edit.add_command(label="Show text", command=self.showText)
menu.add_cascade(label="Edit", menu=edit)
def Message(self):
print("Hello world")
def showText(self):
text = Label(self, text="Hey there!")
text.pack()
def client_exit(self):
exit()
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
"Button1.place" is the position of the button that needs to be changed but I am completely clueless how else to do this. I used variables as well.
It seems place() wants keyword arguments. You can let the function position() return a dict and unpack it in the place() statement:
def position(self):
return {'x':randrange(0,400),'y':randrange(0,300)}
Placing the button with:
self.Button1.place(**self.position())
You also need to prefix the button name with "self" to be able to access it from outside the function __init__window().
Then simply add a copy of the place() statement in the button callback function:
def Message(self):
print("Hello world")
self.Button1.place(**self.position())
That works fine for me at least (Python 3.6.5 under win10).
You'll have to reduce the random values generated for x and y or parts of the button will occationally be outside the window...
I have ran into some trouble.
I'm quite new to OOP and working with tkinter and GUI's in general.
I have managed to find some code on the Internet and meshed it all together to create something and I'm nearly where I want to be.
So what I want is some help figuring this out.
How can I assign results of askdirectory to a variable I can use elsewhere?
# coding=utf-8
import tkinter as tk
from tkinter import font as tkfont
from tkinter import filedialog
class MainApp(tk.Tk):
....
class SelectFunction(tk.Frame):
....
class FunctionChangeName(tk.Frame):
....
a = Gui(self)
# this gets me the askdirectory but how to add it to a variable?
Above is the call to run askdirectory code, and it works, just need to find out how to save it to a variable so I can use it, I have tried to print it in several ways, but all I get is something along the lines .!frame.!functionchangename.!gui.
class SelectDir:
def __init__(self, container, title, initial):
self.master = container
self.initial = initial
self.selected = initial
self.options = {'parent': container,'title': title,'initialdir':initial,}
def show(self):
result = filedialog.askdirectory()
if result:
self.selected = result
def get(self):
return self.selected
class Gui(tk.Frame):
def __init__(self, container):
tk.Frame.__init__(self, container)
frame = tk.Frame(container)
frame.pack()
self.seldir = SelectDir(self, "Select directory", "D:\\MyPgm\\Python\\Tiles_8")
button = tk.Button(frame, text="Select directory", command=self.select_dir)
button.grid(column=0, row=0)
self.act_dir = tk.StringVar()
self.act_dir.set("D:\\MyPgm\\Python\\Tiles_8")
entry = tk.Entry(frame, textvariable=self.act_dir, width=30)
entry.grid(column=0, row=1)
def select_dir(self):
self.seldir.show()
self.act_dir.set(self.seldir.get())
# or
# result = seldir.show()
# self.act_dir.set(result)
if __name__ == "__main__":
app = MainApp()
app.mainloop()
I had an idea:
example, if you have f inside a function, you can make it global to have access as variable
def print_path():
# select working directory
global f #make f global to access the path
f = filedialog.askdirectory(parent=root, initialdir="/", title='Select Dir')
I'm learning to use tkinter in Python 3.6.4. I am creating a GUI with multiple instances of buttons. Two such instances are:
def createWidgets(self):
# first button
self.QUIT = Button(self)
self.QUIT["text"] = "Quit"
self.QUIT["command"] = self.quit
self.QUIT.pack()
# second button
self.Reset = Button(self)
self.Reset["text"] = "Reset"
self.Reset["command"] = "some other function, tbd"
What I want to learn is how to abstract the instantiation of buttons such that each instance in the createWidgets method is based on a method something like this:
createButton( self, text, command, fg, bg, hgt, wth, cursor ):
What I don't know is how to control the naming of the button as:
self.QUIT
self.Reset
where the property or name following the "." operator can be passed to the createButton as a property by which the button is created and named.
Simply expanding on what Brian said, this code will get you going. The button objects are stored in a widget dictionary. Here is one way to put this together:
import tkinter as tk
import sys
root = tk.Tk()
class CustomButton(tk.Button):
def __init__(self, parent, **kwargs):
tk.Button.__init__(self, parent)
for attribute,value in kwargs.items():
try:
self[attribute] = value
except:
raise
def doReset():
print("doRest not yet implemented")
if __name__ == '__main__':
widget = {}
widget['quit'] = CustomButton(root, text='Quit', command=sys.exit)
widget['reset'] = CustomButton(root, text='Reset', command=doReset)
for button in widget.keys():
widget[button].pack()
root.mainloop()
I am creating 2 window in my program and i am using two class, since the code is complex, i separate it in 2 different python file. After i imported the second window file, how can i make sure it open without having this error which show in this picture
The original result should look like this after the new window button clicked:
Coding for Main Window:
from tkinter import *
import classGUIProgram
class Window(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
self.initialize()
def initialize(self):
self.geometry("600x400+30+30")
self.wButton = Button(self, text='newWindow', command = self.OnButtonClick)
self.wButton.pack()
def OnButtonClick(classGUIProgram):
classGUIProgram.top = Toplevel()
master = Tk()
b = classGUIProgram.HappyButton(master)
master.mainloop()
if __name__ == "__main__":
window = Window(None)
window.title("title")
window.mainloop()
Coding for Second Window:
from tkinter import *
class HappyButton:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.printButton = Button(frame, text="Print message", command=self.printMessage)
self.printButton.pack(side=LEFT)
self.quitButton = Button(frame, text="Quit", command= quit)
self.quitButton.pack(side=LEFT)
self.downloadHistoryCB=Checkbutton(frame, text="Download History")
self.downloadHistoryCB.pack(side=LEFT)
def printMessage(self):
print("Wow this actually worked!")
master = Tk()
b = HappyButton(master)
master.mainloop()
You're creating extra Tk windows. Here is an example of using Toplevel widgets and another file.
mainWindow.py
import tkinter as tk
import secondWindow as sW
class MainWindow(tk.Tk):
def __init__(self):
super().__init__()
self.title("Main Window")
self.geometry("600x400+30+30")
tk.Button(self, text = "New Window", command = self.new_window).pack()
tk.Button(self, text = "Close Window", command = self.close).pack()
self._second_window = None
def new_window(self):
# This prevents multiple clicks opening multiple windows
if self._second_window is not None:
return
self._second_window = sW.SubWindow(self)
def close(self):
# Destory the 2nd window and reset the value to None
if self._second_window is not None:
self._second_window.destroy()
self._second_window = None
if __name__ == '__main__':
window = MainWindow()
window.mainloop()
secondWindow.py
import tkinter as tk
class SubWindow(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.title("Sub Window")
self.geometry("400x300+30+30")
# Change what happens when you click the X button
# This is done so changes also reflect in the main window class
self.protocol('WM_DELETE_WINDOW', master.close)
tk.Button(self, text = "Print", command = self.printMessage).pack()
def printMessage(self):
print("Wow this actually worked!")
When using another file be sure to not have any global code you don't want running. Your classes don't have to inherit from Tk and Toplevel, this is just an example. But you need to ensure you only ever have one instance of Tk otherwise you get the behaviour you encountered