Tkinter Triangle-Area GUI

Due: Tuesday, July 24 at 8pm

In this assignment you will construct a simple Tkinter program that displays the area of a triangle selected by the user. The user interface will look something like this:

heron4.png

Complete your work for this assignment in the file heron.py, and submit this file via csman when you are finished. Make sure to write clear and complete docstrings for all functions you create in this assignment.


2-D points and Heron's formula

In your program, you will represent a point as a tuple of two numbers. So here are examples of points:

  (0, 0)
  (34, 57)
  (141, 271)

The first element of the tuple will be the X coordinate of the point, and the second will be the Y coordinate. You can use the square bracket notation on tuples just like with lists and strings (tuples are sequences), so if a point is p = (34, 57), then the X coordinate is p[0] and the Y coordinate is p[1]. In this assignment, the numbers will be integers because they correspond to pixel values in a Tkinter canvas.

You will need to define a function called distance(p1, p2) which computes and returns the distance between the points p1 and p2. (This function will require the math.sqrt function.)

Here are some examples of it at work:

  >>> p1 = (3, 4)
  >>> p2 = (6, 8)
  >>> distance(p1, p2)
  5.0
  >>> distance(p2, p1)  # They'd better be the same...
  5.0

Next, you should define a function called compute_triangle_area(p1, p2, p3) which uses Heron's formula to find the area of the triangle specified by the three points. Heron's formula is a clever little formula to find the area of a triangle specified by its three points. (You can read about Heron's formula here.) Note that Heron's formula works fine if two or more points are the same; it will simply return an area of 0, as it should.

You can test your function like this:

  >>> p1 = (10, 6)
  >>> p2 = (7, 15)
  >>> p3 = (12, 14)
  >>> compute_triangle_area(p1, p2, p3)
  21.0
  >>> compute_triangle_area(p1, p1, p2)
  0.0
  >>> compute_triangle_area(p1, p1, p1)
  0.0

Make sure that these two functions are working properly before you attempt the rest of the assignment! Test them using WingIDE or from the command prompt by typing:

  python -i heron.py

and running the above examples manually.


Heron's Formula User Interface

Next you will construct a basic user interface for this program using Tkinter.

Initializing the User Interface

Normally, in Python programs that define user interfaces, the user interface is specified as one or more Python classes, which are user-definable object types. However, since we haven't covered classes in the lectures, we'll keep it simple here and define the user interface in terms of functions and some initialization code that will exist outside of any function. We'll start by describing the initialization code, which you should put at the end of your program. (You don't have to put it there, but your program will be much easier to read and understand if you do.)

Initialize Tkinter by creating the root window as follows:

  root = Tk()

Next we need to set up the state for the application. In computer programming, the word state refers to the variables that contain data that changes as the program runs. Here, this includes two global variables:

Setting this up only requires a couple of lines of code.

Then we need to create the GUI widgets we will need for our program. They will include:

  1. the label on the top of the window, which will describe the current status of the program
  2. a canvas in the middle, where the points and lines will be drawn
  3. a button at the bottom of the window, which will clear the list of points

Use a grid layout manager to place the widgets on the window. The grid will be very simple: 3 rows and 1 column. Use the grid method of the label, canvas and button objects to specify their positions on the grid. Put the label in row 0, the canvas in row 1, and the button in row 2. The label needs to use the StringVar for its text; this can be done by specifying the textvariable option when constructing the label. Also, set the button's text to "Clear Points" or something like that.

The canvas will bind a left mouse button click (<Button-1>) to an event-handling function called handle_click, described below. Clicking on the button at the bottom of the window will call a function called do_clear, also to be described below (see lecture 13 for how to assign a function as the command to be executed when a button is clicked).

After all this is done, initialize the user interface state by calling a function called update_ui_state, also to be described below. Once this is done, the user interface is set up. Add a call to the root.mainloop() method and the program can now be run. To exit the program, close the window.

By this time, you've done everything except define these functions:

  1. handle_click
  2. do_clear
  3. update_ui_state

We'll define the first two functions, which are event-handling functions, in the next section. We'll define update_ui_state after that. For now, write stub versions of these functions as follows:

def handle_click(event):
    pass

def do_clear():
    pass

def update_ui_state()
    pass

A stub function is a function with an empty body. It's there to allow the program to be tested even though major pieces haven't been implemented yet. In this case, if the user clicks on the canvas, the handle_click event-handling function will be called, which will currently do nothing (because of the pass statement). Similarly, clicking on the button will call the do_clear function which will also do nothing. We will replace the pass statements with working code below, but having stub functions like these at least allows us to run our program and test that the user interface looks the way we want it to look.

NOTE: Don't put the stub functions after the call to root.mainloop()! The call to root.mainloop() has to be the last line in the program, because once it executes, the program will run until the window is manually closed.

After you have created these steps, you should be able to get a basic view of your application by doing this:

  python heron.py

If you've set up the GUI code as we've described above, your new user interface should pop up! It may be a little sparse at the moment, but we will solve that next. Close the window to exit the program.

Handling UI Events

Next you need to start handling events from user interactions. You have two operations to handle: the user clicking in the canvas, and the user clicking on the "Clear Points" button.

First, write the handle_click function for handling mouse-click events on the canvas. This function will need to do the following things:

  1. If there are already three points in the application's list of points, just return immediately from the event handler.
  2. Otherwise, create a point with the x and y coordinates specified in the event.
  3. Add this new point to the (global) list of points. You will need to use the python global statement (see lecture 12 for details).
  4. Update the user interface with the new point. This will involve a call to the update_ui_state function, described below.

Once you've added this code, make sure to remove the pass statement that was in the stub version before, since it's not needed anymore.

Make sure that the handle_click function has been attached to the canvas to handle left mouse-clicks (we mentioned this above). You'll need to call the canvas' bind method, binding the '<Button-1>' event type.

Next, write the do_clear function that clears the application's (global) list of points, then calls update_ui_state. Make sure that the clear button calls do_clear when the button is clicked. This is kind of like using the bind method, but you don't have to use bind; see lecture 13 for details. Again, don't forget to remove the pass statement you put there before when you wrote the stub function. The final version of your code shouldn't have any pass statements.

Updating the User Interface

Now you are down to the last function to write! This function is the update_ui_state function referred to above. It is responsible for updating the text in the label at the top of the UI, and also for drawing the points of the triangle. Here is how the label should be managed:

It should not be possible to have more than three points in the list; this would be a good thing to assert!

The canvas will be a bit more complicated to update. Regardless of the initial state of the canvas, the update_ui_state function should first clear the canvas of any current content. That can be done with these lines of code:

    for id in canvas.find_all():
        canvas.delete(id)

The find_all method of the canvas object finds all the items on the canvas and deletes them. (You may need to change the name canvas if you called your canvas something else.) This code should be called at the start of update_ui_state.

Next, the canvas should be updated like this:

Note that the line/polygon should be drawn before the circles, so that the circles always show up on top.

Example

To make sure this is perfectly clear, here is a sequence of interactions with the application. Before anything is clicked, the user interface looks like this:

heron1.png

After one point has been selected, it looks like this:

heron2.png

Notice that the label has been updated to indicate how many points remain.

After two points have been clicked, the UI will look like this:

heron3.png

And, after a third point has been clicked, the UI looks like this.

heron4.png

At this point, no more button-clicks should be recorded, at least not until the "Clear Points" button is clicked to reset the user interface. Then, it should go back to the first picture in this sequence.


Hints

Here are a few things you need to keep in mind as you write the code for this assignment:

  1. Do not use the pack method on the graphical objects (also known as widgets). If you use the grid method, the pack method is not necessary and may cause problems. Both pack and grid are examples of what is called layout managers, and there should be only one layout manager being used for any widget.

  2. Make sure that every widget has the root window as its parent.

  3. Don't specify the geometry of the root window. It isn't necessary, because Tkinter will expand it to fit the widgets that go inside it. Setting the geometry explicitly can cause problems because there may not be enough space to place all the widgets.


Running the Application

At this point, you should be able to run your GUI by typing this at the command prompt:

  python heron.py

When you type the last line, your application should start up.

After everything is working properly, submit your heron.py file via csman.


Copyright (c) 2012, California Institute of Technology. All rights reserved.