Difference between revisions of "CSC111 Lab 14 2015"

From dftwiki3
Jump to: navigation, search
 
(21 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
--[[User:Thiebaut|D. Thiebaut]] ([[User talk:Thiebaut|talk]]) 13:26, 29 April 2015 (EDT)
 
--[[User:Thiebaut|D. Thiebaut]] ([[User talk:Thiebaut|talk]]) 13:26, 29 April 2015 (EDT)
 
----
 
----
=<center>TK Lab</center>=
 
  
 +
 +
=<center>TK Lab (Optional)</center>=
 
<br />
 
<br />
 
<bluebox>
 
<bluebox>
This lab covers TKinter on Python 3.X.  A very good tutorial on TK can be found on Lynda.com.
+
This tutorial is a lab, and covers TKinter on Python 3.X.  A very good tutorial on TK can also be found on Lynda.com, if you have access to it.
 
</bluebox>
 
</bluebox>
 +
<br />
 +
{|
 +
|
 +
__TOC__
 +
|
 +
[[Image:TKinterApp.png|400px]]
 +
|}
 +
<br />
 +
<br />
 +
<br />
 
<br />
 
<br />
 
=TK Skeleton=
 
=TK Skeleton=
Line 180: Line 191:
 
<br />
 
<br />
 
<br />
 
<br />
 +
=Organizing the GUI as a Class=
 +
<br />
 +
* The need to make label a global variable is cumbersome.  If we have many widgets, we may have to do this many times.  It is much more efficient and elegant to use a class for holding all the widgets, and all the actions that are associated with them.
 +
<br />
 +
::<source lang="python">
 +
from tkinter import *
 +
from tkinter import ttk
 +
 +
class GUI:
 +
    def __init__( self, rootWindow ):
 +
        self.label = ttk.Label( rootWindow, text="Hello World!" )
 +
        self.label.grid( row=0, column=0 )
 +
 +
        self.button1 = ttk.Button( rootWindow, text="Hello",
 +
                                  command=self.hello )
 +
        self.button1.grid( row=0, column=1 )
 +
 +
        self.button2 = ttk.Button( rootWindow, text="Bye",
 +
                                  command=self.bye )
 +
        self.button2.grid( row=0, column=2 )
 +
       
 +
    def bye( self ):
 +
        self.label.config( text = "Goodbye World!" )
 +
 +
    def hello( self ):
 +
        self.label.config( text = "Hello World!" )
 +
   
 +
def main():
 +
    global label
 +
    rootWindow = Tk()
 +
 +
    gui = GUI( rootWindow )
 +
    rootWindow.mainloop()
 +
 +
 +
main()
 +
 +
</source>
 +
<br />
 +
=Adding A Checkbox=
 +
<br />
 +
* A checkbox is a widget that has a box that the user can click to either turn it ON, or OFF. 
 +
* A checkbox must be associated with a '''special object''' in the code, such that if the user changes the checkbox by clicking it, then that object will automatically change its value.  Similarly, if we change the value of this object inside the Python code, then the checkbox should automatically show itself as ''checked'' or ''unchecked'', depending on the value of the object.
 +
* This seems complicated.  An example will illustrate the point.
 +
* Let's add a checkbox to your GUI.  Add the code below to your constructor:
 +
<br />
 +
::<source lang="python">
 +
        # create an object to keep the value of the checkbox
 +
        self.buttonsEnabled = IntVar()
 +
        self.buttonsEnabled.set( 1 )
 +
 +
        # create a checkbox and associate it with the object above.
 +
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
 +
        self.check1.grid( row=1, column=0 )
 +
 +
</source>
 +
<br />
 +
* Note that to change the value of the '''buttonsEnable''' object, which is instantiated from the class '''IntVar''', we use its '''set()''' method.  To get the value of such an object, there is another method, called '''get()''', that we could use.
 +
* Modify the '''bye()''' and '''hello()''' methods of your GUI class, as shown below:
 +
<br />
 +
::<source lang="python">
 +
    def bye( self ):
 +
        self.label.config( text = "Goodbye World!" )
 +
        print( "buttonsEnabled = ", self.buttonsEnabled.get() )
 +
 +
    def hello( self ):
 +
        self.label.config( text = "Hello World!" )
 +
        self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
 +
</source>
 +
<br />
 +
* Click on the '''Hello''' button a few times.  Explain the behavior you are observing.  If you don't get it, make sure to ask questions to the lab instructor or TA nearby.
 +
* Click on the '''Bye''' button a few times, and watch the console.  The code above, and the behavior you observe should make sense.
 +
* Finally, click on the '''Enable''' check box to make it checked, or uncheckes, and then click on the appropriate button to see the contents of the self.buttonsEnabled object.
 +
<br />
 +
<br />
 +
<br />
 +
 +
{| style="width:100%; background:silver"
 +
|-
 +
|
 +
==Challenge #4: Controlling the Buttons with the Checkbox==
 +
|}
 +
[[Image:QuestionMark5.jpg|right|120px]]
 +
<br />
 +
* The real purpose of the checkbox, you will have probably guessed, is to control whether the buttons modify the label or not.
 +
* We want the buttons to change the label only when the checkbox is checked, and not change the label when the checkbox is unchecked.
 +
* Modify your GUI class so that you make it adopt this behavior.
 +
<br />
 +
<br />
 +
<br />
 +
=Adding a Text Area=
 +
<br />
 +
* A text-area widget is a rectangular area that can act as a simple editor.  Actually, Idle, which you are probably using to create the code for this lab, '''is written with TKinter''', so the area where you are typing code is probably a text area widget.
 +
* Before adding the text area, make sure your 4 widgets (label, 2 buttons, checkbox) are organized in a grid, all on Row 0.  You will add the text area on Row 1, and make it span all 4 columns.
 +
* Adding a text area is very simple.  Add these lines to the '''constructor''' of your GUI class.
 +
<br />
 +
::<source lang="python">
 +
        self.text = Text( rootWindow, width=80, height=30, background="ivory" )
 +
        self.text.grid( row=1, column=0, columnspan=4 )
 +
</source>
 +
* Run your code.  You should see a larger window than before.  This is because it now contains a text area that can hold 80 characters across, and 30 lines of them.
 +
* Click on the ivory-colored block, and enter some text.  Copy some text from some other window (this lab, for example) and paste it in the text-area of your GUI.  Notice that without any coding, you have added a text editor to your application!
 +
<br />
 +
==Clearing the Text Area==
 +
<br />
 +
* The text area is fully documented on this [http://effbot.org/tkinterbook/text.htm page].  It is tricky to figure out how to access various lines.  Fortunately, clearing the text area requires a simple command:
 +
<br />
 +
::<source lang="python">
 +
    self.text.delete(1.0, END)
 +
</source>
 +
<br />
 +
* Explanations:
 +
::* 1.0 means Line 1, Column 0.  TKinter numbers lines starting at 1, for some strange reason.
 +
::* END is a special constant used by TKinter that means the end of the text in the text area.  So <tt>delete(1.0, END)</tt> will actually clear the whole text area.
 +
<br />
 +
<br />
 +
 +
{| style="width:100%; background:silver"
 +
|-
 +
|
 +
==Challenge #5: Button to Clear the Text Area==
 +
|}
 +
[[Image:QuestionMark6.jpg|right|120px]]
 +
<br />
 +
* Modify your GUI class, and change one of your buttons, so that its text becomes "Clear", and its action clears the whole text area.  The button you pick should not be sensitive to the checkbox any longer.
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
 +
{| style="width:100%; background:silver"
 +
|-
 +
|
 +
==Challenge #6: Challenge of the Day==
 +
|}
 +
[[Image:QuestionMark7.jpg|right|120px]]
 +
<br />
 +
* Modify your GUI class so that the user can paste a list of email addresses in the text area, as a CSV-formatted text.  A sample of the text that could be pasted is shown below:
 +
<br />
 +
::<source lang="text">
 +
1, Ammie, Ammie@hampshire.edu
 +
2, Ayana, Ayana@hampshire.edu
 +
3, Birdie, Birdie@smith.edu
 +
 +
1, Ammie, Ammie@hampshire.edu
 +
2, Anna, Anna@hampshire.edu
 +
3, Candyce, Candyce@smith.edu
 +
4, Clorinda, Clorinda@smith.edu
 +
5, Dorathy, Dorathy@smith.edu
 +
</source>
 +
<br />
 +
* Your program will get the text from the ''text area,'' as a long string, using this construct:
 +
<br />
 +
::<source lang="python">
 +
emailString = self.text.get(1.0, END)
 +
</source>
 +
* Your program will then split this text and extract just the email addresses (3rd field of each valid line), remove the duplicates,
 +
and output the resulting list back in the text area, after erasing the previous text (using the clear technique introduced above). 
 +
* Note the string '''text''' can be added to the content of the text area by using this construct:
 +
<br />
 +
::<source lang="python">
 +
        self.text.insert( INSERT, text )
 +
</source>
 +
<br />
 +
* The result, in this case, should be this list, sorted alphabetically.
 +
::<source lang="text">
 +
Ammie@hampshire.edu
 +
Ayana@hampshire.edu
 +
Birdie@smith.edu
 +
Anna@hampshire.edu
 +
Candyce@smith.edu
 +
Clorinda@smith.edu
 +
Dorathy@smith.edu
 +
</source>
 +
<br />
 +
 +
=Getting a File Name using a Dialog=
 +
<br />
 +
* GUIs rely on ''dialogs'' to obtain information about the file systems, or information from the user.
 +
* An important type of dialog is a ''file dialog.''  A file dialog is a separate popup window that presents the user with a list of files, and allows the user to navigate the file system on the users' disk, so that a particular file can be selected.
 +
* Create a new file in the same directory/folder where your GUI program resides, and call the new file '''emails.txt'''.  Store some text in the file.  It doesn't matter what the text is, but it should be easy to recognize.
 +
* Add a new button to your GUI class, and attach it to a method called '''getFileName()'''.
 +
* Put this code in '''getFileName()''':
 +
<br />
 +
::<source lang="python">
 +
    def getFileName( self ):
 +
          # get the name of a file from the user, presenting her a dialog
 +
          fileName = filedialog.askopenfilename(filetypes = (("Text files", "*.txt"),
 +
                                                        ("All files", "*.*")))
 +
 +
          # display name of file picked by user on the console
 +
          print( fileName )
 +
</source>
 +
<br />
 +
* The '''filetypes = ''' parameter, in askopenfilname, indicates that only files with the extension '''.txt''' should be clickable, and all files visible.  If you do not provide a '''filetypes''' parameter, then all files will be accessible, regardless of extension.
 +
* You also need to add this import statement at the top of the program:
 +
<br />
 +
::<source lang="python">
 +
from tkinter import filedialog
 +
</source>
 +
<br />
 +
: This will insure that the dialog works correctly, as it is in a separate class in tkinter.
 +
* Run the code, and click on your new button.
 +
* Verify that when you pick a file using the dialog, and that the program prints its name on the console.
 +
* This is the sophisticated way of getting input from the user.  Instead of using an input statement, of this type: <tt>input( "Please enter a file name: " )</tt>, the dialog lets the user navigate the file system and pick the one of interest.
 +
<br />
 +
<br />
 +
 +
{| style="width:100%; background:silver"
 +
|-
 +
|
 +
==Challenge #7: Second Challenge of the Day==
 +
|}
 +
[[Image:QuestionMark7.jpg|right|120px]]
 +
<br />
 +
* Modify your GUI program so that it now contains a 3rd button, labeled "Open File", that, when pressed, will open a file dialog, allow the user to pick a text file containing CSV-formatted email data, and display it in the text-area.
 +
* The user will then be able to click on the '''Process''' button to filter the CSV data, and display just the email addresses.
 +
* If you are ambitious, you can use check-boxes to further restrict the filtering of the emails, for example displaying only emails that are Smith email addresses.
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
<br />
 +
 +
 
<!-- ================================================================= -->
 
<!-- ================================================================= -->
 
<br />
 
<br />
 +
 
=Solution Programs=
 
=Solution Programs=
 
<br />
 
<br />
Line 257: Line 497:
 
</source>
 
</source>
 
<br />
 
<br />
 +
==Enable CheckBox==
 
<br />
 
<br />
 +
::<source lang="python">
 +
from tkinter import *
 +
from tkinter import ttk
 +
 +
class GUI:
 +
    def __init__( self, rootWindow ):
 +
        self.label = ttk.Label( rootWindow, text="Hello World!" )
 +
        self.label.grid( row=0, column=0 )
 +
 +
        self.button1 = ttk.Button( rootWindow, text="Hello",
 +
                                  command=self.hello )
 +
        self.button1.grid( row=0, column=1 )
 +
 +
        self.button2 = ttk.Button( rootWindow, text="Bye",
 +
                                  command=self.bye )
 +
        self.button2.grid( row=0, column=2 )
 +
 +
        # create an object to keep the value of the checkbox
 +
        self.buttonsEnabled = IntVar()
 +
        self.buttonsEnabled.set( 1 )
 +
 +
        # create a checkbox and associate it with the object above.
 +
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
 +
        self.check1.grid( row=1, column=0 )
 +
 +
    def bye( self ):
 +
        if self.buttonsEnabled.get()==1:
 +
            self.label.config( text = "Goodbye World!" )
 +
        #print( "buttonsEnable = ", self.buttonsEnabled.get() )
 +
 +
    def hello( self ):
 +
        if self.buttonsEnabled.get()==1:
 +
            self.label.config( text = "Hello World!" )
 +
        #self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
 +
   
 +
def main():
 +
    global label
 +
    rootWindow = Tk()
 +
 +
    gui = GUI( rootWindow )
 +
    rootWindow.mainloop()
 +
 +
 +
main()
 +
 +
</source>
 
<br />
 
<br />
 +
==Clear Text Area==
 
<br />
 
<br />
 +
::<source lang="python">
 +
from tkinter import *
 +
from tkinter import ttk
 +
 +
class GUI:
 +
    def __init__( self, rootWindow ):
 +
        self.label = ttk.Label( rootWindow, text="Hello World!" )
 +
        self.label.grid( row=0, column=0 )
 +
 +
        self.button1 = ttk.Button( rootWindow, text="Hello",
 +
                                  command=self.hello )
 +
        self.button1.grid( row=0, column=1 )
 +
 +
        self.button2 = ttk.Button( rootWindow, text="Clear",
 +
                                  command=self.clear )
 +
        self.button2.grid( row=0, column=2 )
 +
 +
        # create an object to keep the value of the checkbox
 +
        self.buttonsEnabled = IntVar()
 +
        self.buttonsEnabled.set( 1 )
 +
 +
        # create a checkbox and associate it with the object above.
 +
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
 +
        self.check1.grid( row=0, column=3 )
 +
 +
        # add text area
 +
        self.text = Text( rootWindow, width=80, height=30, background="ivory" )
 +
        self.text.grid( row=1, column=0, columnspan=4 )
 +
 +
    def clear( self ):
 +
        self.text.delete( 1.0, END )
 +
 +
    def hello( self ):
 +
        if self.buttonsEnabled.get()==1:
 +
            self.label.config( text = "Hello World!" )
 +
        #self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
 +
   
 +
def main():
 +
    global label
 +
    rootWindow = Tk()
 +
 +
    gui = GUI( rootWindow )
 +
    rootWindow.mainloop()
 +
 +
 +
main()
 +
</source>
 
<br />
 
<br />
 +
==Challenges of the Day==
 
<br />
 
<br />
 +
::<source lang="python">
 +
# challengeOfTheDay.py
 +
# D. Thiebaut
 +
# This app contains a GUI that allows the user to clear the text area, or
 +
# process its contents, and filter out some information, leaving only unique
 +
# email addresses.
 +
# using TKInter.
 +
#
 +
from tkinter import *
 +
from tkinter import ttk
 +
import tkinter.filedialog as fdialog
 +
 +
class GUI:
 +
    def __init__( self, root ):
 +
 +
        self.root = root
 +
       
 +
        # define button to clear the textArea
 +
        button1 = ttk.Button( root, text="Clear", command = self.clear )
 +
        button1.grid( row=0, column=0 )
 +
 +
        # define button to process textArea
 +
        button2 = ttk.Button( root, text="Process", command = self.process )
 +
        button2.grid( row=0, column=1 )
 +
 +
        # set a check box for Smith
 +
        self.showSmith = IntVar()
 +
        self.showSmith.set( 1 )
 +
        check1 = ttk.Checkbutton( root, text="Show Smith", variable=self.showSmith )
 +
        check1.grid( row=0, column=2 )
 +
 +
        # set a check box for Hampshire
 +
        self.showHampshire = IntVar()
 +
        self.showHampshire.set( 0 )
 +
        check2 = ttk.Checkbutton( root, text="Show Hampshire", variable=self.showHampshire )
 +
        check2.grid( row=0, column=3 )
 +
 +
        # add an "Open File" button
 +
        button3 = ttk.Button( root, text="Open File", command = self.openFile )
 +
        button3.grid( row=0, column=4 )
 +
       
 +
        # create the text area
 +
        self.text = Text( root, width=80, height=30, background="ivory" )
 +
        self.text.grid( row=1, column=0, columnspan=5 )
 +
 +
 +
    def openFile( self ):
 +
        filename = fdialog.askopenfilename(filetypes = (("Text files", "*.txt"),
 +
                                                        ("All files", "*.*")))
 +
        text = ""
 +
        try:
 +
            file = open( filename, "r" )
 +
            text = file.read()
 +
            file.close()           
 +
        except:
 +
            return
 +
       
 +
        self.clear()
 +
        self.text.insert( INSERT, text )
 +
 +
       
 +
    def clear( self ):
 +
        self.text.delete(1.0, END)
 +
 +
    def process( self ):
 +
        emails = self.text.get(1.0, END)
 +
        self.clear()
 +
        emailList = []
 +
        for line in emails.split( "\n" ):
 +
            if len( line.strip() ) <= 1:
 +
                continue
 +
            words = line.split( "," )
 +
            emailList.append( words[-1] )
 +
 +
        emailList = list( set( emailList ) )
 +
        emailList.sort()
 +
       
 +
        newList = []
 +
       
 +
        smith = ( self.showSmith.get() != 0 )
 +
        hamp  = ( self.showHampshire.get() != 0 )
 +
 +
        for email in emailList:
 +
            if smith and email.find( "@smith" ) != -1:
 +
                newList.append( email )
 +
            if hamp and email.find( "@hamp" ) != -1:
 +
                newList.append( email )
 +
 +
        self.text.insert( INSERT, "\n".join( newList ) )
 +
       
 +
   
 +
        return
 +
 +
 +
def main():
 +
    rootWindow = Tk()
 +
    rootWindow.title( "CSC111 TK Demo" )
 +
    #rootWindow.resizable(False, False)
 +
   
 +
    gui = GUI( rootWindow)
 +
 +
    rootWindow.mainloop()
 +
 +
 +
main()
 +
 +
</source>
 
<br />
 
<br />
 
<br />
 
<br />
 
[[Category:CSC111]][[Category:Python]][[Category:Labs]]
 
[[Category:CSC111]][[Category:Python]][[Category:Labs]]

Latest revision as of 08:57, 1 May 2015

--D. Thiebaut (talk) 13:26, 29 April 2015 (EDT)



TK Lab (Optional)


This tutorial is a lab, and covers TKinter on Python 3.X. A very good tutorial on TK can also be found on Lynda.com, if you have access to it.


TKinterApp.png





TK Skeleton


  • Create a new program called tkSkel0.py with the code below. You should work in Idle, as this will automatically make tkinter and ttk findable for Python.


from tkinter import *
from tkinter import ttk

 
    
def main():
    global label
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.pack()

    rootWindow.mainloop()

main()


  • Run the program. Be careful, as this will create a tiny window on your screen, which may not be easily visible. But it should be there!
  • Congratulations. This is the "Hello World!" version of Python using the TKinter graphics environment.


Adding a Button


  • Now try adding some more code to your application:


from tkinter import *
from tkinter import ttk
    
def main():
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( rootWindow, text="Change Label" )
    button1.pack()
    
    rootWindow.mainloop()

main()


  • Run the code. Observe that a new button will have appeared. Click on the button a few times. Why is it not doing anything?
  • If your answer is that we haven't define an action for the button, you are absolutely right!
  • Let's add a new function to the program, and "attach" it to the button.


from tkinter import *
from tkinter import ttk


def change():
    print( "change function called" )
    
def main():
    global label
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( rootWindow, text="Change Label", 
                                    command=change )
    button1.pack()
    
    rootWindow.mainloop()


main()


  • Run your code again, and verify that when you click the button, something gets printed in the console.
  • Using the console is not necessarily something we want to do with GUI applications, but, in this case, the console is a good way to debug our program and verify that it works as expected.



Challenge #1: Add a Second Button

QuestionMark1.jpg


  • Add a second button to your GUI. Call it button2.
  • Add a new function and make the new button activate this function. Your new function will print some simple string on the console. Make sure the string and the function are different from the already defined change function, and the string it prints.



Button Changing The text of the Label


  • Let's make the button change the text of the label when it (the button) is clicked:


from tkinter import *
from tkinter import ttk

label = None # this variable will hold the label created by the GUI, and will be accessible
                    # by the change1() function.

def change1():
    global label
    label.config( text = "Goodbye World!" )
    
def main():
    global label
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( rootWindow, text="Bye!", command=change1 )
    button1.pack()
    
    rootWindow.mainloop()


main()


  • Run your code.
  • Verify that clicking on the button makes the label change.



Challenge #2: Add a Second Button That Changes the Label, Too

QuestionMark2.jpg


  • Add or modify the second button, if you have it in your GUI, so that the first button changes the label to "Goodbye World!", and the second button changes the label to "Hello World!".



Placing Widgets in a Grid


  • place() is a very simple way to put the widgets on the GUI window. A more powerful option is to use grid(), which requires have organized all the widgets on paper first, and knowing where they should be relative to each other.
  • Let's put all three widgets (label, button1 and button2) on the same line. This corresponds of a grid with 1 row and 3 columns.
  • Only 3 lines have to change. They are shown below:


    label.grid( row=0, column=0 )

    button1.grid( row=0, column=1 )

    button2.grid( row=0, column=2 )


  • Make the modification and run your App. Do the 3 widgets display in a horizontal alignment?



Challenge #3: Reorganize the Widgets

QuestionMark3.jpg


  • Change the organization of the grid so that the label is between the two buttons.
  • When that works, change the organization of the grid so that the label is on the right hand side of the window.
  • Change the organization some more, so that the label is on a row by itself, and the two buttons are next to each other, on a row below the label.



Organizing the GUI as a Class


  • The need to make label a global variable is cumbersome. If we have many widgets, we may have to do this many times. It is much more efficient and elegant to use a class for holding all the widgets, and all the actions that are associated with them.


from tkinter import *
from tkinter import ttk

class GUI:
    def __init__( self, rootWindow ):
        self.label = ttk.Label( rootWindow, text="Hello World!" )
        self.label.grid( row=0, column=0 )

        self.button1 = ttk.Button( rootWindow, text="Hello",
                                   command=self.hello )
        self.button1.grid( row=0, column=1 )

        self.button2 = ttk.Button( rootWindow, text="Bye",
                                   command=self.bye )
        self.button2.grid( row=0, column=2 )
        
    def bye( self ):
        self.label.config( text = "Goodbye World!" )

    def hello( self ):
        self.label.config( text = "Hello World!" )
    
def main():
    global label
    rootWindow = Tk()

    gui = GUI( rootWindow )
    rootWindow.mainloop()


main()


Adding A Checkbox


  • A checkbox is a widget that has a box that the user can click to either turn it ON, or OFF.
  • A checkbox must be associated with a special object in the code, such that if the user changes the checkbox by clicking it, then that object will automatically change its value. Similarly, if we change the value of this object inside the Python code, then the checkbox should automatically show itself as checked or unchecked, depending on the value of the object.
  • This seems complicated. An example will illustrate the point.
  • Let's add a checkbox to your GUI. Add the code below to your constructor:


        # create an object to keep the value of the checkbox
        self.buttonsEnabled = IntVar()
        self.buttonsEnabled.set( 1 )

        # create a checkbox and associate it with the object above.
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
        self.check1.grid( row=1, column=0 )


  • Note that to change the value of the buttonsEnable object, which is instantiated from the class IntVar, we use its set() method. To get the value of such an object, there is another method, called get(), that we could use.
  • Modify the bye() and hello() methods of your GUI class, as shown below:


    def bye( self ):
        self.label.config( text = "Goodbye World!" )
        print( "buttonsEnabled = ", self.buttonsEnabled.get() )

    def hello( self ):
        self.label.config( text = "Hello World!" )
        self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )


  • Click on the Hello button a few times. Explain the behavior you are observing. If you don't get it, make sure to ask questions to the lab instructor or TA nearby.
  • Click on the Bye button a few times, and watch the console. The code above, and the behavior you observe should make sense.
  • Finally, click on the Enable check box to make it checked, or uncheckes, and then click on the appropriate button to see the contents of the self.buttonsEnabled object.




Challenge #4: Controlling the Buttons with the Checkbox

QuestionMark5.jpg


  • The real purpose of the checkbox, you will have probably guessed, is to control whether the buttons modify the label or not.
  • We want the buttons to change the label only when the checkbox is checked, and not change the label when the checkbox is unchecked.
  • Modify your GUI class so that you make it adopt this behavior.




Adding a Text Area


  • A text-area widget is a rectangular area that can act as a simple editor. Actually, Idle, which you are probably using to create the code for this lab, is written with TKinter, so the area where you are typing code is probably a text area widget.
  • Before adding the text area, make sure your 4 widgets (label, 2 buttons, checkbox) are organized in a grid, all on Row 0. You will add the text area on Row 1, and make it span all 4 columns.
  • Adding a text area is very simple. Add these lines to the constructor of your GUI class.


        self.text = Text( rootWindow, width=80, height=30, background="ivory" )
        self.text.grid( row=1, column=0, columnspan=4 )
  • Run your code. You should see a larger window than before. This is because it now contains a text area that can hold 80 characters across, and 30 lines of them.
  • Click on the ivory-colored block, and enter some text. Copy some text from some other window (this lab, for example) and paste it in the text-area of your GUI. Notice that without any coding, you have added a text editor to your application!


Clearing the Text Area


  • The text area is fully documented on this page. It is tricky to figure out how to access various lines. Fortunately, clearing the text area requires a simple command:


     self.text.delete(1.0, END)


  • Explanations:
  • 1.0 means Line 1, Column 0. TKinter numbers lines starting at 1, for some strange reason.
  • END is a special constant used by TKinter that means the end of the text in the text area. So delete(1.0, END) will actually clear the whole text area.



Challenge #5: Button to Clear the Text Area

QuestionMark6.jpg


  • Modify your GUI class, and change one of your buttons, so that its text becomes "Clear", and its action clears the whole text area. The button you pick should not be sensitive to the checkbox any longer.






Challenge #6: Challenge of the Day

QuestionMark7.jpg


  • Modify your GUI class so that the user can paste a list of email addresses in the text area, as a CSV-formatted text. A sample of the text that could be pasted is shown below:


1, Ammie, Ammie@hampshire.edu
2, Ayana, Ayana@hampshire.edu
3, Birdie, Birdie@smith.edu

1, Ammie, Ammie@hampshire.edu
2, Anna, Anna@hampshire.edu
3, Candyce, Candyce@smith.edu
4, Clorinda, Clorinda@smith.edu
5, Dorathy, Dorathy@smith.edu


  • Your program will get the text from the text area, as a long string, using this construct:


emailString = self.text.get(1.0, END)
  • Your program will then split this text and extract just the email addresses (3rd field of each valid line), remove the duplicates,

and output the resulting list back in the text area, after erasing the previous text (using the clear technique introduced above).

  • Note the string text can be added to the content of the text area by using this construct:


        self.text.insert( INSERT, text )


  • The result, in this case, should be this list, sorted alphabetically.
Ammie@hampshire.edu
Ayana@hampshire.edu
Birdie@smith.edu
Anna@hampshire.edu
Candyce@smith.edu
Clorinda@smith.edu
Dorathy@smith.edu


Getting a File Name using a Dialog


  • GUIs rely on dialogs to obtain information about the file systems, or information from the user.
  • An important type of dialog is a file dialog. A file dialog is a separate popup window that presents the user with a list of files, and allows the user to navigate the file system on the users' disk, so that a particular file can be selected.
  • Create a new file in the same directory/folder where your GUI program resides, and call the new file emails.txt. Store some text in the file. It doesn't matter what the text is, but it should be easy to recognize.
  • Add a new button to your GUI class, and attach it to a method called getFileName().
  • Put this code in getFileName():


    def getFileName( self ):
          # get the name of a file from the user, presenting her a dialog
          fileName = filedialog.askopenfilename(filetypes = (("Text files", "*.txt"),
                                                         ("All files", "*.*")))

          # display name of file picked by user on the console
          print( fileName )


  • The filetypes = parameter, in askopenfilname, indicates that only files with the extension .txt should be clickable, and all files visible. If you do not provide a filetypes parameter, then all files will be accessible, regardless of extension.
  • You also need to add this import statement at the top of the program:


from tkinter import filedialog


This will insure that the dialog works correctly, as it is in a separate class in tkinter.
  • Run the code, and click on your new button.
  • Verify that when you pick a file using the dialog, and that the program prints its name on the console.
  • This is the sophisticated way of getting input from the user. Instead of using an input statement, of this type: input( "Please enter a file name: " ), the dialog lets the user navigate the file system and pick the one of interest.



Challenge #7: Second Challenge of the Day

QuestionMark7.jpg


  • Modify your GUI program so that it now contains a 3rd button, labeled "Open File", that, when pressed, will open a file dialog, allow the user to pick a text file containing CSV-formatted email data, and display it in the text-area.
  • The user will then be able to click on the Process button to filter the CSV data, and display just the email addresses.
  • If you are ambitious, you can use check-boxes to further restrict the filtering of the emails, for example displaying only emails that are Smith email addresses.










Solution Programs


2 Buttons, 1 Label, Pack


from tkinter import *
from tkinter import ttk

label = None # this variable will hold the label created by the GUI, and will be accessible
                    # by the change1() function.

def bye():
    global label
    label.config( text = "Goodbye World!" )

def hello():
    global label
    label.config( text = "Hello World!" )
    
def main():
    global label
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.pack()

    button1 = ttk.Button( rootWindow, text="Hello", command=hello )
    button1.pack()

    button2 = ttk.Button( rootWindow, text="Bye", command=bye )
    button2.pack()
    
    rootWindow.mainloop()


main()


2 Buttons, 1 Label, Grid


from tkinter import *
from tkinter import ttk

label = None # this variable will hold the label created by the GUI, and will be accessible
                    # by the change1() function.

def bye():
    global label
    label.config( text = "Goodbye World!" )

def hello():
    global label
    label.config( text = "Hello World!" )
    
def main():
    global label
    rootWindow = Tk()

    label = ttk.Label( rootWindow, text="Hello World!" )
    label.grid( row=0, column=0 )

    button1 = ttk.Button( rootWindow, text="Hello", command=hello )
    button1.grid( row=0, column=1 )

    button2 = ttk.Button( rootWindow, text="Bye", command=bye )
    button2.grid( row=0, column=2 )
    
    rootWindow.mainloop()


main()


Enable CheckBox


from tkinter import *
from tkinter import ttk

class GUI:
    def __init__( self, rootWindow ):
        self.label = ttk.Label( rootWindow, text="Hello World!" )
        self.label.grid( row=0, column=0 )

        self.button1 = ttk.Button( rootWindow, text="Hello",
                                   command=self.hello )
        self.button1.grid( row=0, column=1 )

        self.button2 = ttk.Button( rootWindow, text="Bye",
                                   command=self.bye )
        self.button2.grid( row=0, column=2 )

        # create an object to keep the value of the checkbox
        self.buttonsEnabled = IntVar()
        self.buttonsEnabled.set( 1 )

        # create a checkbox and associate it with the object above.
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
        self.check1.grid( row=1, column=0 )

    def bye( self ):
        if self.buttonsEnabled.get()==1:
            self.label.config( text = "Goodbye World!" )
        #print( "buttonsEnable = ", self.buttonsEnabled.get() )

    def hello( self ):
        if self.buttonsEnabled.get()==1:
            self.label.config( text = "Hello World!" )
        #self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
    
def main():
    global label
    rootWindow = Tk()

    gui = GUI( rootWindow )
    rootWindow.mainloop()


main()


Clear Text Area


from tkinter import *
from tkinter import ttk

class GUI:
    def __init__( self, rootWindow ):
        self.label = ttk.Label( rootWindow, text="Hello World!" )
        self.label.grid( row=0, column=0 )

        self.button1 = ttk.Button( rootWindow, text="Hello",
                                   command=self.hello )
        self.button1.grid( row=0, column=1 )

        self.button2 = ttk.Button( rootWindow, text="Clear",
                                   command=self.clear )
        self.button2.grid( row=0, column=2 )

        # create an object to keep the value of the checkbox
        self.buttonsEnabled = IntVar()
        self.buttonsEnabled.set( 1 )

        # create a checkbox and associate it with the object above.
        self.check1 = ttk.Checkbutton( rootWindow, text="Enable", variable=self.buttonsEnabled )
        self.check1.grid( row=0, column=3 )

        # add text area
        self.text = Text( rootWindow, width=80, height=30, background="ivory" )
        self.text.grid( row=1, column=0, columnspan=4 )

    def clear( self ):
        self.text.delete( 1.0, END )

    def hello( self ):
        if self.buttonsEnabled.get()==1:
            self.label.config( text = "Hello World!" )
        #self.buttonsEnabled.set( 1-self.buttonsEnabled.get() )
    
def main():
    global label
    rootWindow = Tk()

    gui = GUI( rootWindow )
    rootWindow.mainloop()


main()


Challenges of the Day


# challengeOfTheDay.py
# D. Thiebaut
# This app contains a GUI that allows the user to clear the text area, or
# process its contents, and filter out some information, leaving only unique
# email addresses.
# using TKInter.
#
from tkinter import *
from tkinter import ttk
import tkinter.filedialog as fdialog

class GUI:
    def __init__( self, root ):

        self.root = root
        
        # define button to clear the textArea
        button1 = ttk.Button( root, text="Clear", command = self.clear )
        button1.grid( row=0, column=0 )

        # define button to process textArea
        button2 = ttk.Button( root, text="Process", command = self.process )
        button2.grid( row=0, column=1 )

        # set a check box for Smith
        self.showSmith = IntVar()
        self.showSmith.set( 1 )
        check1 = ttk.Checkbutton( root, text="Show Smith", variable=self.showSmith )
        check1.grid( row=0, column=2 )

        # set a check box for Hampshire
        self.showHampshire = IntVar()
        self.showHampshire.set( 0 )
        check2 = ttk.Checkbutton( root, text="Show Hampshire", variable=self.showHampshire )
        check2.grid( row=0, column=3 )

        # add an "Open File" button
        button3 = ttk.Button( root, text="Open File", command = self.openFile )
        button3.grid( row=0, column=4 )
        
        # create the text area
        self.text = Text( root, width=80, height=30, background="ivory" )
        self.text.grid( row=1, column=0, columnspan=5 )


    def openFile( self ):
        filename = fdialog.askopenfilename(filetypes = (("Text files", "*.txt"),
                                                         ("All files", "*.*")))
        text = ""
        try:
            file = open( filename, "r" )
            text = file.read()
            file.close()            
        except:
            return
        
        self.clear()
        self.text.insert( INSERT, text )

        
    def clear( self ):
        self.text.delete(1.0, END) 

    def process( self ):
        emails = self.text.get(1.0, END)
        self.clear()
        emailList = []
        for line in emails.split( "\n" ):
            if len( line.strip() ) <= 1:
                continue
            words = line.split( "," )
            emailList.append( words[-1] )

        emailList = list( set( emailList ) )
        emailList.sort()
        
        newList = []
        
        smith = ( self.showSmith.get() != 0 )
        hamp  = ( self.showHampshire.get() != 0 )

        for email in emailList:
            if smith and email.find( "@smith" ) != -1:
                newList.append( email )
            if hamp and email.find( "@hamp" ) != -1:
                newList.append( email )

        self.text.insert( INSERT, "\n".join( newList ) )
        
    
        return


def main():
    rootWindow = Tk()
    rootWindow.title( "CSC111 TK Demo" )
    #rootWindow.resizable(False, False)
    
    gui = GUI( rootWindow)

    rootWindow.mainloop()


main()