
2.2. SCROLLING AND DELETING 15
2.2.1 Canvases and Frames
With this re-write, I have introduced some new components - a Canvas and two Frames. A Canvas is a
powerful general-use widget with many capabilities (usually graphical). We are using it here for its ability
to scroll, which we need if we want to add a lot of apps to our list. A Frame is a layout component which
can be used to group together multiple other widgets. As you will see in this case, we can actually use
the Canvas to draw a Frame into our window, which is then able to bundle together all of our to-do items,
allowing them to scroll independently of the Text widget we use to add new tasks.
2.2.2 __init__
As above, we now create a Canvas and two Frames, with one Frame parented to the canvas, and the other to
the main window. We then make a Scrollbar object to allow scrolling of the page. We set the orientation
and command to tell tkinter that we want a vertical scrollbar, scrolling in the y direction. We also configure
our canvas to accept the Scrollbar’s values. We once again set the window title and size, and create our
Text widget - this time parented to one of the frames (which will be packed to the bottom). Our Canvas
is packed with instruction to fill all available space and expand as big as it can, and our Scrollbar follows,
filling up the vertical space.
The next line looks a little strange. We use our Canvas to create a new window inside itself, which is
our Frame holding the tasks. We create it at the coordinates (0,0) and anchor it to the top of the Canvas
(the "n" here is for "north", so top-left would require "nw", and so on). One thing to note is that we do
not pack our tasks_frame, as it will not appear, and we will be left scratching our heads as to where it is.
This is something I learned the hard way!
After that, we pack our Text into its frame and then pack its frame to the BOTTOM of the window,
with both filling the X direction. The default task is created and we bind the self.remove_task function
to it being clicked (this will be covered below). We pack this, and then move on to a big block of binds.
The <MouseWheel>, <Button-4> and <Button-5> binds handle scrolling, and the <Configure> binds handle
keeping the Canvas as big as possible as the window changes size. The <Configure> event is fired when
widgets change size (and on some platorms, location) and will provide the new width and height. The
<Return> bind and colour_schemes remain from the previous example.
2.2.3 Handling Tasks
The add_task method is almost the same as the previous iteration, but the code for choosing the styling
has been moved into a separate method - set_task_colour - so that it can be re-used after deleting
tasks. Speaking of which, we have a remove_task method which will handle getting rid of the Label widget
associated with the task. To avoid accidental removal, we use an askyesno pop-up message to double-check
with the user that they wanted to delete that task (make sure you don’t miss the new import tkinter.
messagebox as msg statement at the top of the file). This will create a small notice with the title "Really
Delete?" and the message "Delete <task>?" (where <task> will be the text within the Label) with the
options "yes" and "no". Using the if statement around this means the indented code will only happen if the
user presses "yes". Upon deletion, we recolour all remaining tasks in our alternating pattern, as otherwise
the pattern would be broken by the removal.
2.2.4 Adjusting the canvas
Our on_frame_configure method is bound to our root’s <Configure> action, and will be called whenever
the window is resized. It sets the scrollable region for our canvas, and uses the bbox (bounding box) to
specify that we want the entire canvas to be scrollable. The task_width method is bound to the Canvas’s
<Configure>, and is responsible for ensuring the task Labels stay at the full width of the canvas, even after
stretching the window.