I've got a tkinter window that have 3 features: background color,foreground color, and a text label. These features is in a text config file (properties.conf) in my home folder. I want to update window features when the config file changed. I watch the changes in config file with pyinotify and I want to update window when it changes. This is the code:
#!/usr/bin/python
import threading
from Tkinter import *
import os
import ConfigParser
import pyinotify
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
Gui().updateGUI()
print 2
wm = pyinotify.WatchManager()
wm.add_watch('/home/mnrl/window_configs', pyinotify.IN_CLOSE_WRITE, onChange)
notifier = pyinotify.Notifier(wm)
notifier.loop()
class ConfigParse():
def __init__(self):
self.confDir = os.path.join(os.getenv('HOME'), 'window_configs/')
self.confFile = os.path.join(self.confDir + "properties.conf")
self.config = ConfigParser.ConfigParser()
if os.path.isfile(self.confFile):
self.config.read(self.confFile)
else:
if not os.path.exists(self.confDir):
os.makedirs(self.confDir)
self.config.add_section('bolum1')
self.config.set('section1', 'setting1', 'green')
self.config.set('section1', 'setting2', 'red')
self.config.set('section1', 'setting3', 'sample text')
with open(self.confFile, 'wb') as self.confFile:
self.config.write(self.confFile)
class Gui(object):
def __init__(self):
self.root = Tk()
self.lbl = Label(self.root, text=ConfigParse().config.get('section1', 'setting3'), fg=ConfigParse().config.get('section1', 'setting1'), bg=ConfigParse().config.get('section1', 'setting2'))
self.lbl.pack()
def updateGUI(self):
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
self.lbl["fg"] = ConfigParse().config.get('bolum1', 'ayar1')
self.lbl["bg"] = ConfigParse().config.get('bolum1', 'ayar2')
self.root.update()
WatchFile().start()
Gui().root.mainloop()
But whenever properties.conf file changes a new window more appears near the old tkinter window. So tkinter window not updates, new windows open. How can I correct it?
The problem is that in WatchFile.run() you're doing this:
def onChange(ev):
Gui().updateGUI()
This does something different than what you're expecting. It creates a new GUI instance, and then immediately calls the updateGUI() method on it. Hence the two windows.
What you need to do instead, is something along the lines of:
#!/usr/bin/env python
gui = None
class WatchFile(threading.Thread):
def run(self):
def onChange(ev):
gui.updateGUI()
[...]
WatchFile().start()
gui = Gui()
gui.root.mainloop()
Here, a variable gui is created, and has an instance of your GUI class assigned to it. Later, the updateGUI() method is called on the same instance.
This problem is more or less repeated with your usage of your ConfigParser class, for example:
self.lbl["text"] = ConfigParse().config.get('bolum1', 'ayar3')
In this case, it 'works' because your ConfigParse() class can be executed twice without side-effects (such as opening windows), but it's not very efficient. You're reading the same file multiple times.
What would be better, is to just use a function (a class with only a __init__ defined is effectively the same), run this once, and return a dict.
Related
When I run my Tkinter code the results end up giving me two Tkinter windows. One has the widgets/items my code says but one is a completely blank window.
file 1-
from tkinter import*
class Screen:
def __init__(self, items):
self.Tk = Tk()
self.items = items
self.Tk.mainloop()
file 2-
from tkinter import*
from Screen import*
class Module1:
def __init__(self):
Test_button = Button(text="Test")
Test_button.pack()
items = Module1()
Mainscreen = Screen(items)
I just want to make a modular Tkinter screen so my code is not that messy đŸ˜…
-Just want one/common Tkinter window
I think the trouble is you're going about things backwards a bit. You'll want to import your modules into the Python file containting your "root" window class (aka Screen), rather than the other way around.
The typical way to handle a "root" window class in tkinter usually looks like this, where the class inherits directly from Tk()
# this is the first file...
import tkinter as tk # avoid star imports!
from file2 import Module1 # import your widget classes here
# note that depending on your project structure you may need to do
# 'from .file2 import Module1' instead (this is called a 'relative import')
class Screen(tk.Tk): # the 'Screen' class inherits from 'tk.Tk'
def __init__(self):
super().__init__() # initialize tkinter.Tk()
# instance your module widgets by calling the class like so
# (you'll need to update Module1 as shown below here)
self.mod1 = Module1(self) # 'self' refers to 'Screen', the main window
You'll need to update the Module1 class so it can be bound to your parent Screen
# this is the second file...
import tkinter as tk # again, star imports bad...
class Module1:
def __init__(self, parent): # add the parent parameter
# whenever you instance your 'Module1' class, it will expect a 'parent';
# above, we used self (i.e. 'Screen') - this is where your 'button' is going!
self.parent = parent
test_button = tk.Button(self.parent, text="Test")
test_button.pack()
And you'll run the mainloop() method on an instance of your Screen class at the end of your main Python file like so
# this is the first file again...
if __name__ == '__main__': # if this script is being run at the top level...
app = Screen() # get an instance of your Screen class
app.mainloop() # run the GUI loop
Using the following code, the Tkinter root window will be hidden:
def main():
root = Tkinter.Tk()
root.iconify()
a = open(tkFileDialog.askopenfilename(), 'r')
main()
However, using this variation, the root window will not be hidden:
class Comparison:
def __init__(self, file=open(tkFileDialog.askopenfilename(),'r')):
self.file = file
self.length = sum(1 for _ in self.file)
def main():
root = Tkinter.Tk()
root.iconify()
a = Comparison()
main()
Why does calling tkFileDialog.askopenfilename with the constructor cause this behaviour? I have tried both root.withdraw() and root.iconify() and experienced the same behaviour.
It may be worth noting that I am on OSX 10.11.6.
Thanks!
When you do this:
def __init__(self, file=open(tkFileDialog.askopenfilename(),'r')):
That immediately runs open(tkFileDialog.askopenfilename(),'r'), because default arguments are evaluated when the function is defined. Therefore, when you run the second code block, the interpreter creates a necessary Tkinter root window and opens that file chooser while it's still defining that class. After that, you define a function main. Finally, you call main(), which creates a root object, withdraws it, and instantiates an object of the Comparison class. The root window you explicitly created with root = Tkinter.Tk() is hidden. The older one, that Python was forced to create in order for the file dialog to exist, however, was not.
To fix this, put the default behavior into the method body rather than its signature:
class Comparison:
def __init__(self, file=None):
if file is None:
self.file = open(tkFileDialog.askopenfilename(),'r')
else:
self.file = file
self.length = sum(1 for _ in self.file)
Good afternoon all,
Rather than using the command line or hard coding a file name in Python, I'd like to have a separate class that I can use in any other Python program that allows a file to be chosen with the Tkinter file dialog box. Here is what I have so far:
import Tkinter as tk
import tkFileDialog
import tkFont
###################################################################################################
class MyFileDialog(tk.Frame):
# class level variables #######################################################################
my_form = tk.Tk() # declare form
my_button = tk.Button(my_form) # declare button
chosen_file = ""
# constructor #################################################################################
def __init__(self):
# create button event
self.my_button.configure(text = "press here", command = self.on_my_button_click)
self.my_button.pack() # add button to form
# make the button font bigger so I don't have to squint
updated_font = tkFont.Font(font = self.my_button['font'])
updated_font['size'] = int(float(updated_font['size']) * 1.6)
self.my_button.configure(font = updated_font)
###############################################################################################
def on_my_button_click(self):
self.chosen_file = tkFileDialog.askopenfilename() # get chosen file name
return
###############################################################################################
def get_chosen_file(self):
return self.chosen_file # return chosen file name
###################################################################################################
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# I need to get the chosen file here, when the file is chosen, NOT when the window is closed !!
# please help !!
print my_file_dialog.get_chosen_file() # this does not print until the window is closed !!!
The current problem with this code is (as the comment towards the end indicates) that the test code does not receive the chosen file name until the window is closed, however I need the test code to receive the file name when the file is chosen.
I should mention that in production use the test code would be in a different file altogether that would import and instantiate this class, therefore the modularity of the class should be maintained. Anybody out there have a solution? Please help !
In a nutshell, mainloop() blocks all other calls until the window is destroyed (more on this: Tkinter understanding mainloop). This is why your print statement waits until the window is closed.
if __name__ == "__main__":
my_file_dialog = MyFileDialog()
my_file_dialog.my_form.mainloop()
# this line will not run until the mainloop() is complete.
print my_file_dialog.get_chosen_file()
One simple solution might be to change your chosen_file variable to a tk.StringVar(), which you could then attach a callback method to.
import Tkinter as tk
import tkFileDialog
import tkFont
class MyFileDialog(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
# class variables
self.chosen_file = tk.StringVar()
# ui elements
self.my_button = tk.Button(self)
# configure button
self.my_button.configure(\
text = "press here", command = self.on_my_button_click)
# make button font bigger
# ...
# add button to frame
self.my_button.pack()
# attach a callback to chosen_file with trace
self.chosen_file.trace("w", self.callback)
# execute this method whenever chosen_file gets written
def callback(self, *args):
print("MyFileDialog: chosen_file is %s" % self.chosen_file.get())
def on_my_button_click(self):
f = tkFileDialog.askopenfilename()
# set chosen_file here to trigger callback (wherever it is)
self.chosen_file.set(f)
if __name__ == "__main__":
root = tk.Tk()
app = MyFileDialog(root).pack()
root.mainloop()
Note that you don't have to place the callback method in your MyFileDialog class: you could just as easily create the callback in another class once you have instantiated a MyFileDialog object.
class MyApp(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.mfd = MyFileDialog(master)
self.mfd.pack()
# attach callback to chosen_file from MyFileDialog
self.mfd.chosen_file.trace("w", self.callback)
def callback(self, *args):
print("MyApp: chosen_file is %s" % self.mfd.chosen_file.get())
Goal of the script:
(3) different windows, each in its own class, with its own widgets and layout, are created via Toplevel and callbacks.
When a new (Toplevel) window is created, the previous one is destroyed. Thus, only one window is visible and active at a time.
Problem?
Basically, I've tried many things and failed, so I must understand too little of ["parent", "master", "root", "app", "..."] :(
Note on raising windows:
I have implemented a successful example of loading all frames on top of each other, and controlling their visibility via the .raise method.
For this problem, however, I don't want to load all the frames at once.
This is an abstracted version of a quiz program that will require quite a lot of frames with images, which makes me reluctant to load everything at once.
Script (not working; bugged):
#!/usr/bin/env python
from Tkinter import *
import tkMessageBox, tkFont, random, ttk
class First_Window(Frame):
"""The option menu which is shown at startup"""
def __init__(self, master):
Frame.__init__(self, master)
self.gotosecond = Button(text = "Start", command = self.goto_Second)
self.gotosecond.grid(row = 2, column = 3, sticky = W+E)
def goto_Second(self):
self.master.withdraw()
self.master.update_idletasks()
Second_Window = Toplevel(self)
class Second_Window(Toplevel):
"""The gamewindow with questions, timer and entrywidget"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.gotothird = Button(text = "gameover", command = self.goto_Third)
self.gotothird.grid(row = 2, column = 3, sticky = W+E)
def goto_Third(self):
Third_Window = Toplevel(self)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, *args):
Toplevel.__init__(self)
self.focus_set()
self.master = First_Window
self.gotofirst = Button(text = "startover", command = self.goto_First)
self.gotofirst.grid(row = 2, column = 3, sticky = W+E)
def goto_First(self):
self.master.update()
self.master.deiconify()
self.destroy()
def main():
root = Tk()
root.title("Algebra game by PJK")
app = First_Window(root)
root.resizable(FALSE,FALSE)
app.mainloop()
main()
The problem is not really a Tkinter problem, but a basic problem with classes vs. instances. Actually, two similar but separate problems. You probably need to read through a tutorial on classes, like the one in the official Python tutorial.
First:
self.master = First_Window
First_Window is a class. You have an instance of that class (in the global variable named app), which represents the first window on the screen. You can call update and deiconify and so forth on that instance, because it represents that window. But First_Window itself isn't representing any particular window, it's just a class, a factory for creating instances that represent particular windows. So you can't call update or deiconify on the class.
What you probably want to do is pass the first window down through the chain of windows. (You could, alternatively, access the global, or do various other things, but this seems cleanest.) You're already trying to pass it to Second_Window, you just need to stash it and pass it again in the Second_Window (instead of passing self instance, which is useless—it's just a destroyed window object), and then stash it and use it in the Third_Window.
Second:
Second_Window = Toplevel(self)
Instead of creating an instance of the Second_Window class, you're just creating an instance of the generic Toplevel class, and giving it the local name Second_Window (which temporarily hides the class name… but since you never use that class, that doesn't really matter).
And you have the same problem when you try to create the third window.
So:
class First_Window(Frame):
# ...
def goto_Second(self):
# ...
second = Second_Window(self)
class Second_Window(Toplevel):
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_Third(self):
third = Third_Window(self.first)
self.destroy()
class Third_Window(Toplevel):
"""Highscores are shown with buttons to Startmenu"""
def __init__(self, first, *args):
Toplevel.__init__(self)
self.first = first
# ...
def goto_First(self):
self.first.update()
self.first.deiconify()
self.destroy()
I'm creating a simple class named Dialog. I only want it to return when init method is already finished.
from tkinter import *
class Dialog:
def __set_data(self):
self.data = self.entry_.get()
self.top.destroy()
def __init__(self, prompt_text = "Prompt:", submit_text = "OK", text_size = 5, button_size = 5, main_window = Tk()):
main_window.withdraw()
top = self.top = Toplevel(main_window)
Label(top, text=prompt_text).pack()
self.entry_ = Entry(top)
self.entry_.pack(padx = text_size)
button = Button(top, text = submit_text, command = self.__set_data)
button.pack(pady=button_size)
def get_data(self):
return self.data
data = 0
a = Dialog();
print (a.get_data())
If you run this code, you will get the output 0. I want to the output only show up after the user input. How can I do this?
Firstly, I don't think that you really want to postpone the __init__ method returning. If you do that, your code will never get to tk.mainloop(), and will never actually appear on screen.
Instead, what you want to do is be notified when the data in the Entry widget changes. This is a pretty common thing to do in GUI toolkits; the way tkinter handles it is with Events and Bindings. You can read about them generally here.
One way of performing your particular task (showing the data in self.entry once it has been changed by the user) might be to use the method shown in this question.
Alternatively, you could add a command to your submit button (see here) and read the value of the entry in that method.