#1
  1. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2013
    Location
    Chicago
    Posts
    17
    Rep Power
    0

    Tkinter - Creating Class


    Hi Guys.
    I am learning Tkinter-GUI for python, and most of the examples are focusing on creation of classes for the GUI portion.
    I have created and interface that i am happy with, however, no classes were setup. Would some one be able to explain what would be the proper way to encapsulate this into the "correct" way? I posting code below. Using Pything 2.73

    Brief about the code:

    Develop a simple GUI for user to generate a folder structure. The main 3-text field are used to setup the MAIN ROOT folder's name, script's Functions will eventually create subdirectories.
    The LISTBOX will scan all folder located within the root directory and populate the fields.

    Code:
    Python Code:
     
    from Tkinter import *
     
    master = Tk()
     
    #Frame
    separator = Frame(height=3, bd=1, relief=SUNKEN).grid(row = 1, columnspan = 2, sticky = "EW")
    separator2 = Frame(height=3, bd=1, relief=SUNKEN).grid(row = 6, columnspan = 2, sticky = "EW")
     
    #Main Lables
    mainLable = Label(master, text = "New Project").grid(row = 0, sticky=W)
    mainLable2 = Label(master, text = "Existing Project").grid(row = 7, sticky=W)
    # Text Fields labeles
    prjNameLbl = Label(master, text = "Project Name").grid(row = 2, sticky=W)
    prjShotLbl = Label(master, text = "Project Shot #").grid(row = 3, sticky=W)
    prjVersionLbl = Label(master, text = "Project Version #").grid(row = 4, sticky=W)
     
    #Text Fields
    projNameTxt = Entry(master)
    prjShotTxt = Entry(master)
    prjVersionTxt = Entry(master)
     
    projNameTxt.grid(row = 2, column = 1)
    prjShotTxt.grid(row = 3, column = 1)
    prjVersionTxt.grid(row = 4, column = 1)
     
     
    #Buttons
    createBtn = Button(master, text = "Create").grid(row = 5, column = 1, ipadx = 10, sticky = W)
    quitBtn = Button(master, text = "Cancel", command = master.quit).grid(row = 5, column = 1, ipadx = 10, sticky = E)
    browseBtn = Button(master, text = "Browse").grid(row= 8, ipadx = 10, sticky = NW)
     
     
    #List of Existing Projects
    projectsList = Listbox(master)
     
    for i in ["Project One", "Project Two", "Project Three", "Project Four", "Project Five"]:
        projectsList.insert(END, i)
     
    projectsList.grid(row = 8, column = 1)
     
    mainloop()
    master.destroy()
    Last edited by stascrash; August 13th, 2013 at 09:10 AM. Reason: specifying pythong version
  2. #2
  3. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,897
    Rep Power
    481
    Here's one way to package your code into a class. It doesn't work better (except for subtle bug repaired), but the new widget is easier to reuse. Oh, note that the amount of processing you dos between calls to master.update() controls the GUI responsiveness.
    Code:
    try:
        import Tkinter as tkinter
    except ImportError:
        import tkinter
    
    import sys, random
    
    class F:
        def __init__(self, message):
    	self.message = message
        def __call__(self):
    	print(self.message)
    
    class Project:
    
        def shutdown(self):
    	m = self.master
    	m.quit()
    	m.destroy()
    
        def __len__(self): # used in place of __bool__ or __nonzero__
    	try:
    	    self.master.winfo_exists()
    	except:
    	    return 0
    	return 1
    
        def __init__(self, n = 5):
    	self.master = m = tkinter.Tk()
    
    	#Frame.  Put the frames into the top level widget.
    	# since you didn't reuse the frames, I didn't bother saving their identifications.
    	# You can still find them through children of the master.
    	tkinter.Frame(m, height=3, bd=1, relief=tkinter.SUNKEN).grid(row = 1, columnspan = 2, sticky = "EW")
    	tkinter.Frame(m, height=3, bd=1, relief=tkinter.SUNKEN).grid(row = 6, columnspan = 2, sticky = "EW")
    
    	#Main Labels  # spelling!
    	tkinter.Label(m, text = "New Project").grid(row = 0, sticky=tkinter.W)
    	tkinter.Label(m, text = "Existing Project").grid(row = 7, sticky=tkinter.W)
    	# Text Fields labels
    	tkinter.Label(m, text = "Project Name").grid(row = 2, sticky=tkinter.W)
    	tkinter.Label(m, text = "Project Shot #").grid(row = 3, sticky=tkinter.W)
    	tkinter.Label(m, text = "Project Version #").grid(row = 4, sticky=tkinter.W)
    
    	self.status = s = tkinter.Label(m)
    	s.grid(row = 3, column = 9)
    	self.set_status('initial')
    
    	#Text Fields
    	self.projNameTxt = pN = tkinter.Entry(m)
    	self.prjShotTxt = pS = tkinter.Entry(m)
    	self.prjVersionTxt = pV = tkinter.Entry(m)
    
    	pN.grid(row = 2, column = 1)
    	pS.grid(row = 3, column = 1)
    	pV.grid(row = 4, column = 1)
    
    	#Buttons
    	tkinter.Button(m, text = "Create", command = F('OUCH!')).grid(row = 5, column = 1, ipadx = 10, sticky = tkinter.W)
    	tkinter.Button(m, text = "Cancel", command = self.shutdown).grid(row = 5, column = 1, ipadx = 10, sticky = tkinter.E)
    	tkinter.Button(m, text = "Browse", command = F('that tickles')).grid(row= 8, ipadx = 10, sticky = tkinter.NW)
    
    	#List of Existing Projects
    	self.projectsList = pL = tkinter.Listbox(m)
    
    	for i in ["One", "Two", "Three", "Four", "Five"][:max(min(n, 5), 1)]:
    	    pL.insert(tkinter.END, 'Project '+i)
    
    	pL.grid(row = 8, column = 1)
    
    
        def set_status(self, n):
    	self.status.config(text = str(n))
    
        def __call__(self):
    	# With update rather than mainloop you can continue to process in your main program.
    	self.master.update()
    	# self.master.destroy() # removed: raised exception when window destroyed by window manager.
    
    if __name__ == '__main__':
        process_project = Project(3)
        i = 0
        while process_project: # move the mainloop into your main program.
    	process_project()
    	if random.randrange(3*9999) == 7:
    	    print('Main program does some work here')
    	    sys.stdout.flush()
    	    if process_project:
    		i += 1
    		process_project.set_status(i)
    Last edited by b49P23TIvg; August 13th, 2013 at 02:15 PM. Reason: Oh!
    [code]Code tags[/code] are essential for python code and Makefiles!
  4. #3
  5. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2013
    Location
    Chicago
    Posts
    17
    Rep Power
    0
    @b49P23TIvg:
    Thank You This looks like a new program now hahaha.

    I will study your code, and will post questions here. thank you for taking time and sorting things our for me.
  6. #4
  7. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2013
    Location
    Chicago
    Posts
    17
    Rep Power
    0
    ok, so i read through the code, while still understanding the classes that the purpose, i was able to understand majority of the code, however, i do have a few questions, if you allow:
    (i will go from TOP-DOWN through the sections of the code)
    Code:
    try:
    	import Tkinter as tkinter
    except ImportError:
    	import tkinter
    Code above is used as a way to adopt between python3 and 2.7, right? since the names were changed to lower-case for the TK module... just want to make sure.

    Code:
    class F:
    This class F used for PRINTING only, so i guess any output that i want to give to console will go here via MESSAGE variable?
    Code:
    def __init__(self, message):
    def __call__(self):
    the __init__ is a constructor (this is from readings), and if i am not mistaken, this is being called as a first thing when calling the class, but what about __call__ ? when and how is this being called? From brief reading - this is a class-method override, would you be able to point to some more readings about it?

    Code:
    def shutdown(self):
    Why did you set the QUIT and DESTROY from the button command to its own function? in my understanding, you planned to use it in another code, thus setting this up in as class module would make it easier to reuse, Correct ?

    Code:
    def __len__
    def __call__(self):
    This is sort of a reapeat of the question above - but how does this work? I understand what is happening inside the function, but i dont understand when and how it is being called by the program. Thank you
    Code:
    def set_status(self, n):
    What is the set_status for?

    Code:
    if __name__ == '__main__':
    	process_project = Project(3)
    	i = 0
    	while process_project: # move the mainloop into your main program.
    		process_project()
    		if random.randrange(3*9999) == 7:
    			print('Main program does some work here')
    			sys.stdout.flush()
    		if process_project:
    			i += 1
    			process_project.set_status(i)
    I don't understand the last section here,
    process_project = Project(3) - when this is changed, i get different number of entries in my list box, how did you have this planned?

    when you created a WHILE look - i assume it is running while the "3" is not used up? (not sure how to word it),
    random range also confuses me, is this to show that interface is still active?

    and the last bit with the set_status(i).

    I am sorry if this is too much, but i don't really have anyone to around me to explain, and reading material, makes sense, however still leaves me with a ton of questions. i would really appreciate your help here. Thanks a lot!
    -stas
  8. #5
  9. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,897
    Rep Power
    481

    Changes.


    You can elicit the subtle bug by closing the GUI window of your original program using the X window manager delete button. The new shutdown function takes care of the problem.

    Removed unused assignments.

    Rewrote necessary assignments for object access. self.

    Whimsically inserted a constructor argument to show how you might use data to control the initial window configuration.

    The usual GUI design feels quite wrong to me. And the usual design is to call
    GUI.mainloop()
    and then the work of your program takes place through call backs (the command=function argument) or by timing triggers. Using update as I showed, the GUI is subordinate to the main program. And that's the way it should be! I inserted a bit of code to randomly (by time) change the display with the sole purpose of demonstration.

    I removed import *

    All the rest really is just your code.

    If you don't understand __len__ or __call__ do, in the interpreter,

    >>> dir(1)
    [code]Code tags[/code] are essential for python code and Makefiles!
  10. #6
  11. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2010
    Posts
    153
    Rep Power
    5
    I don't do extravagant things in tkinter, but I tend to just subclass Tk when I create a tkinter application, like so:

    Code:
    import tkinter
    
    class MyApplication(tkinter.Tk):
    
        def __init__(self):
            Tk.__init__(self)
            somebutton = tkinter.Button(self, text="somebutton")
            #etc
    I don't know that it makes any difference to do that, but it keeps the code a little cleaner IMHO.

    Also, I'd recommend also importing ttk (in python2) or tkinter.ttk (in python3), which contains themeable versions of most widgets. See http://tkdocs.com for more details.
  12. #7
  13. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,897
    Rep Power
    481
    I didn't know you could do that cleanly. That is, I thought that with
    Code:
    class c0(m.c1):
        # refer to c1.f as m.c1.f
    I'll definitely use c1.f in future.
    [code]Code tags[/code] are essential for python code and Makefiles!
  14. #8
  15. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,897
    Rep Power
    481
    Either python 2 or python 3. Correct.
    try: import Tkinter as tkinter
    except ImportError: import tkinter

    The point is that F(representable) is callable.
    class F: __call__=lambda s:print(self.message) # roughly
    Yes, command=lambda s:print(s) also works in python 3. I like to write without anonymous functions. I don't, however, mind explicitly naming anonymous functions as in the example this paragraph.

    __call__ and others, reference you requested.

    shutdown: I have not, and still don't, know what it takes to kill a tkinter/tcl application. The function comes by trial and error. As written, I seemed to get tcl to shutdown on a windows 7 box, and I eliminated all the errors I found. And you're the first person who has gotten me to admit this cluelessness. (3000 python posts here.)

    Often I like to make classes have a meaningful Boolean state. True or False.
    The other day I posted with a class having valid Boolean state in either python 2 or python 3. Well, that's a nuisance because in python 2 __nonzero__ determines the Boolean state but in python 3 the function is called __bool__. As usually happens to me, pressing the "submit" (or "send") button causes me to remember something better. In both python 2 and python 3 if there isn't a (as appropriate) __nonzero__ or __bool__ method then python looks for __len__. Instead of using __bool__ or __nonzero__ I can use __len__ in both cases. That's why I implemented __len__.

    Suppose you just gave a 25 minute presentation. 5 minutes of questions and answers remain. Someone asks a question. NEVER, NEVER! say "good question" even if it does allow you to stall while thinking of a question. As for set_status, good question. I don't recall. Have to look at the code. (also, don't fill the time with "ummmm". listen to Ellen DeGeneris talk show and count her "ummm"s. This will fix you permanently. She's an idiot---(an idiot wealthier than me as most are). I actually wrote to her once about how I watch her show to count how many times she says "ummm" but that the one stunt was actually funny. She asked participants super easy dumb questions. When they failed, which was usually, the floor opened and they disappeared. It was actually funny. That was before I saw that show on the scifi channel where the contestants have to identify things in the dark, and the loser vanishes down a trap door. Join your local Toastmasters club.)
    What is the set_status for?
    When you run the code there's a label to the right of those you created. It changes. The set_status method handles it.

    I don't understand the last section here,
    process_project = Project(3) - when this is changed, i get different number of entries in my list box, how did you have this planned?
    Search the __init__ method for n. In emacs, with C-s set as isearch-forward-regexp which I cannot live without, search for \<n\> to find
    for i in ["One", "Two", "Three", "Four", "Five"][:max(min(n, 5), 1)]:
    Does this help?
    cut from my ~/.emacs file---
    (global-set-key "\C-s" 'isearch-forward-regexp)
    (global-set-key "\C-r" 'isearch-backward-regexp)
    (global-set-key "\M-%" 'query-replace-regexp)
    (global-set-key "\M-w" 'clipboard-kill-ring-save)


    On my computer this is true a couple times per second.
    if random.randrange(3*9999) == 7:
    It's a random time interval, but I tried to point out that it's different from
    time.sleep(linear_random_time_from_0.0001_to_1_second)
    because, by frequently calling process_object.__call__, which is essentially master.update(), the application is quite responsive. It gives those tickles and pain messages right away. I think.
    Last edited by b49P23TIvg; August 13th, 2013 at 08:43 PM. Reason: no smilies
    [code]Code tags[/code] are essential for python code and Makefiles!
  16. #9
  17. No Profile Picture
    Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Jul 2010
    Posts
    153
    Rep Power
    5
    Originally Posted by b49P23TIvg
    I didn't know you could do that cleanly. That is, I thought that with
    Code:
    class c0(m.c1):
        # refer to c1.f as m.c1.f
    I'll definitely use c1.f in future.
    I think I just forgot the extra tkinter there; I don't know for sure that it will work as written. being hasty.
  18. #10
  19. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Mar 2013
    Location
    Chicago
    Posts
    17
    Rep Power
    0
    @b49P23TIvg:
    Thanks!
    Ok, this starting to make sense! I was reading all night about method overriding, and i am slowly getting it.

    I think what confused me most with those, was the fact that i never thought about its practical applications, such as in the case you reworked the code.

    So the __CALL__ as i now came to realize, will be addressed sort-off automatically (well, button triggers), but since this is a built in method, its faster to access and process (correct me if i am wrong), I tried to create a simple class that would just print messages, however, i ran into a little problem, confusion.

    The code:
    Code:
    class F:
        def __init__(self, message):
            self.message = message
        def __call__(self):
            print(self.message)
    if i access it like you had in, by typing
    Code:
    >>> F("Some text to print")
    <__main__.F instance at 0x00000000037064C8>
    >>>
    but if i use:
    Code:
    >>> F("Some text to print").__call__()
    Some text to print
    >>>
    I didn't understand how it functions in your version, while doesn't do it in mine. How does __init_, after setting MESSAGE VAR forwards the data to the CALL ?

    however, another version using:
    Code:
    class Callee:
        def __call__(self,*pargs, **kargs):
            print('Called:', pargs, kargs)
    when executed
    Code:
    >>> var = Callee()
    >>> var("This is me")
    ('Called:', ('This is me',), {})
    >>>
    So i took the F-class, and created an object-instance, and when executed got :
    Code:
    >>> var2 = F("My Message")
    >>> var2()
    My Message
    >>>
    So, confused on that one

    --------------
    shutdown actually does make a little sense, however, when i did my tests, using the ".quit" at button command, i didn't notice any errors... but i will try to use your approach in the future.

    __len__ - perfect, thank you for this breakdown. Yes, makes sense, so, since __len__ checks for number of items (i would assume this is also used when i am trying to find out the size of array len(myArray)??), in this case it checks if window is there or not, thuse getting 1 or 0 back, and that determines the __BOOL__/NONEZERO values), ok, i am good on that.

    You know, i still don't understand that last section, about the def __init__(self, n = 5)

    I looked carefully through the code, and towards the end, where i am working with array, what is the proper name of this type of expression? the [:max(min(n, 5), 1)] ? I remember seeing this at some point in books, but i dont remember what is it called.
    Thanks for follow up on this thread
  20. #11
  21. Contributing User
    Devshed Demi-God (4500 - 4999 posts)

    Join Date
    Aug 2011
    Posts
    4,897
    Rep Power
    481
    You made an object and python displayed its representation.
    repr(o) calls the object o's __repr__ method.
    Python always prints the object left by an expression using its __repr__ method. All objects have __repr__ methods.
    Exception: if the result of an expression is None python doesn't show it.
    Assignment is a statement not an expression, and thus is not verbose.
    >>> F("Some text to print")
    <__main__.F instance at 0x00000000037064C8>



    For the command= tkinter configuration you provide a callable.
    The BOUND cases have an object to refer to. This is the first argument
    in a method, usually named self.
    In the UNBOUND cases you need to provide a first argument, an object
    of acceptable class. I'm not sure what constitutes an acceptable class.
    >>> F("Some text to print").__call__() # explicitly choose the method
    Some text to print
    >>> F("Some text to print")() # obj() implicitly calls the method *****
    Some text to print
    >>> len('abc') # implicit length. BOUND
    3
    >>> 'abc'.__len__() # explicit BOUND
    3
    >>> str.__len__('abc') # access the method from the class providing a str argument. UNBOUND
    3


    This is the same as ***** above, except that you stored the object
    in a variable.
    >>> var = Callee()
    >>> var("This is me")
    ('Called:', ('This is me',), {})
    >>>


    Again, same as *****. Maybe all the parentheses look funny. C(args)()
    With a variable in between the code looks "normal".
    >>> var2 = F("My Message")
    >>> var2()
    My Message
    >>>



    About def __init__(self, n = 5):
    assuming the default argument is problematic, here is a session from idle showing that you don't need to supply values for default arguments.
    Code:
    >>> def clip(a, minimum=1, maximum=5):
    	if a < minimum:
    		return minimum
    	if maximum < a:
    		return maximum
    	return a
    
    >>> for i in range(-2,12,3):
    	print(i, clip(i), clip(i,maximum=99))
    
    	
    -2 1 1
    1 1 1
    4 4 4
    7 5 7
    10 5 10
    >>> def clip2(a, minimum=1, maximum=5):
    	return max(min(a, maximum), minimum)
    
    >>> for i in range(-2,12,3):
    	print(i, clip(i), clip(i,maximum=99))
    
    	
    -2 1 1
    1 1 1
    4 4 4
    7 5 7
    10 5 10
    >>>


    Slices:
    imagine you have two spatulas and a package of neatly stacked crackers.
    Number the crackers from left to right 0 1 2 3 ....
    Put a spatula to the left of crackers a and b.
    crackers[a:b]
    The crackers between the spatulas become the objects of the new iterable.
    a defaults to 0.
    b defaults to len(crackers).
    Once you've got that, stride and negative indexing aren't too hard.
    [edit]um, if there are 3 crackers, [0, 1, 2] then len(crackers) == 3 and to the left of cracker 3 is the same as to the right of cracker 2. You'll have to imagine the virtual cracker 3. Or something like that. no wonder it's so confusing. No, really, it's simple.[/edit]
    Last edited by b49P23TIvg; August 14th, 2013 at 07:18 PM.
    [code]Code tags[/code] are essential for python code and Makefiles!

IMN logo majestic logo threadwatch logo seochat tools logo