Off “Base”

Several years ago I took a look at Base, the database component of OpenOffice and now, LibreOffice. It seemed extremely rudimentary compared to MS Access. For some reason, it came into my head to check it out again recently. Base seems to have come a long way since my first encounter. I noted that it has the ability to construct complicated SQL queries graphically, and build forms that include drop-down boxes and lookups. It meets my main qualifications for writing essential apps such as my current finance management project: it’s open source and cross platform. For a few days, it appeared so suited to what I’m trying to accomplish that it seemed this blog’s title would have to change to “My LibreOffice Base Adventure.” But it wasn’t meant to be. The main reason is that as I continued to experiment with Base, it soon became apparent that, in order to do what I want, I would need to go beyond using only tables, forms and queries, and learn to code in LibreOffice Basic — LibreOffice’s equivalent to Microsoft’s Visual Basic that allows more sophisticated programming of the behavior of Base’s database objects. Why, I asked myself, after coming as far as I have with Python, then PySide, would I want to set all that aside to learn yet ANOTHER language? To be honest, what I really needed to do was get back on track with what I’ve already built, and take it to the next level. Amazing how the thrill of learning something new can sometimes distract from persevering with what I already have began learning. So now, back to work.  I found this PySide tutorial this past week and hope to use it to brush up on what I’ve already learned, and then continue forward.

SIMply Distracting

Sometime in the last month, I ran across one of my all-time favorite PC games: SimCity 4. I was quickly pulled back into the wonderful (for me, at least) world of creating cities and watching them grow, carefully managing resources and the economy so as to attract as many “Sim citizens” as possible. Heck, the thought even crossed my mind to write a Python app to calculate the optimal procedure for growing a huge SimCity 4 metropolis with skyscrapers! One little problem: I’m absolutely clueless as to how to even begin such a task, or even think about it. Besides, I haven’t finished my little data management project yet. I like to think of myself as a firm believer in finishing one project before moving on to the next, though I adhere to this principle less than perfectly.

West Oceanside-Mar. 24, 1341404263254

America’s next great city?

Now back to Python land. As recounted in my last post, about a month ago I started dabbling in the sorcery known as ‘Qt’, and in doing so discovered the QSqlRelationalTableModel, an object that lets me query multiple Sqlite tables, complete with combo boxes providing lookup values from the “foreign” tables, without writing one single line of SQL. Next goal: drop a TableView based on a QSqlRelationalTableModel into my lovingly designed, Microsoft Access mimicking main form.

In other words, I want to get this:
TransactionsForm

…into this:

Capture

..and code the button and drop down boxes to insert, filter and sort transactions. The view based on the QSqlRelationalTableModel will go in the rectangular box. I just need to plan some time away from my responsibilities as Mayor so I can re-focus on my coding. Target date: Saturday, July 12. Nothing like a public commitment to help me focus on a goal! Wish me luck. I’ll have an update no matter what happens (or does not happen).

Marvelous MVC with QSRTM

UPDATE 2014-06-01: Added links to code examples that helped me learn how to do this.

Here it is, people…brought to you by the magic of the QSqlRelationalTableModel: my editable transactions display widget, complete with combo boxes for selecting categories and accounts from the ‘accounts’ table, coded in elegant, model-view-controller style and in less than 70 lines of code, counting the blank lines added for readability. I’m viewing data from multiple Sqlite tables, even editing, without a single line of SQL. All I have to do is tell ‘QSRTM’ (my affectionate nickname for my new friend, QSqlRelationalTableModel) the name of the table and what to display, and he obediently gets what I want the way I want it. Meanwhile Tkinter is throwing a tempter tantrum in the corner when I try to ask it nicely to please synchronize row selection across a set of list boxes.

While documentation such as Pyside’s is excellent and thorough, I find I need examples of how to use a class. I experiment with altering example code to fit what I am trying to accomplish, and when I feel like I have a grasp of the class’s basics, I go back to the documentation to get a full appreciation of all its capabilities. Two table model examples in particular were essential to me figuring out how to use the ‘QSRTM’: this one which demonstrates general use of a table model, and this one which is a ‘QSRTM’ example.

Screenshot below, and my code is here.

.
TransactionsForm

 

Unbounded by a binding

Once upon a time, fresh out of Code Academy, I Googled for a way to design a GUI in Python. I came across Qt Designer. Cool! It reminded me of Microsoft Access and Visual Basic. I could lay out windows, buttons, boxes and so on just the way I wanted, set their properties and add some code. I began learning about “slots” and “signals”, Qt’s system of connecting what a widget is supposed to do to what the user does to it. But before long, it hit me:

The code I was using to create this wonderful GUI was not Python; it was Qt.

Somewhere along the line I’d read that Qt could be used to make a GUI for use in Python, yet Qt and Python are completely separate languages, so how, I asked myself, could I possibly use a GUI created in Qt, in Python? It sounded about as likely as running a Mac OS application on my Android phone. For whatever reason at that time, I couldn’t find a clear answer to this, and other options such as wxPython and Tkinter drifted into my view, so Qt was quickly forgotten. wxPython looked cool but when I decided it would be best for me as a newbie to learn in Python 3, wxPython quickly dropped off my radar. I noted Tkinter is included with Python and has been around forever, so it seemed to be the best option of the three. Unfortunately, I found Tkinter’s documentation to be fragmented and in some cases, ancient. Trying to produce a simple table object gradually wore me down over a period of months, which taught me the hard way that Tkinter is apparently just fine for GUIs with simple buttons, text boxes and lists, but nearly useless for data objects any more complex than a listbox. The one or two times I’ve run across table-like objects for Tkinter, I found they were not compatible with Python 3.

Luckily, I was only one more frustrated Google search away from discovering an amazing concept that brings diverse languages together in harmony, letting them play ball joyously with one another and get along like BFFs:

Bindings.

I eventually discovered Pyside, which re-introduced me to its friend Qt, and now Qt and I are buddies again! The secret? Lay out, code and design the GUI of my dreams in Qt, and when done, it’s only one magical command-line incantation away form being transformed into a Python class object, ready to be called upon any time! Pyside changed the direction of my DataQ project and injected it, and my Python learning adventure, with new life. Before long I was able to finally create a real GUI widget for my app and even able to my data into it.

Next step: wrapping my brain around model-view programming.

TableForm

My original mockup idea..


Capture

So close!

QTableView knocks Tkinter off the ‘table’

Capture

Hallelujah!

Have I got a deal for you! How about a GUI widget with over double the functionality, with less than half the code? Here it is:

import sys
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtSql import *

# My function for assembling SQL queries for my 'transactions' table. I'll share the code another time.
from dq_sql import *

app = QApplication(sys.argv)

class DqTableForm(QWidget):
    def __init__(self, parent = None):
        super(DqTableForm,self).__init__(parent)

        self.setWindowTitle('Transactions')
        self.setMinimumWidth(950)
        self.setMinimumHeight(300)

        db = QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName("dataq.db")
        db.open()

        self.DqModel = QSqlQueryModel()

# My TransQry function takes parameters and returns a SQL query using the parameters as criteria for filtering my 'transactions' table.      

self.DqModel.setQuery(TransQry("2014-03-01","2014-03-31","DESC","","","202",""), db)

        self.DqTable = QTableView()
        self.DqTable.setModel(self.DqModel)
        self.DqTable.hideColumn(0)
        self.DqTable.resizeColumnsToContents()
        self.DqTable.resizeRowsToContents()

        grid = QGridLayout()
        grid.addWidget(self.DqTable,0,0)
        self.setLayout(grid)

form = DqTableForm()
form.show()
app.exec_()

See? Less than 45 lines. Sure beats the nearly 100-line, marginally useful alternative, which I have been sweating and struggling under for months. Lesson learned: there’s a very fuzzy boundary between challenging myself toward productive learning, and finding the most efficient solution to a need. In this instance, given the complex data filtering and sorting I’m trying to accomplish, Qt’s QTableView soundly beats anything Tkinter is even remotely capable of without astronomically more code. Shout out to Bo’s Qt GUI tutorials for introducing me to Qt, the documentation for the PySide Python Qt bindings for helping me figure out QTableView, and to my friend and constant companion Google, for helping me evolve.

Revolution! (tossing out Tkinter)

QtDesigner

Wow, a real, live Python visual app designer!

In the last few weeks I realized I have a long way to go to figure out how the MultiListbox code works…it seems like even the simple function of selecting a line of data requires torturous hand-coding. Almost two months after setting out on my quest for a Python data management application, I am still tripping and stumbling over a single GUI element. And yet all it does is display columns of data, something Microsoft Access does in its sleep, and the MultiListbox isn’t even editable. I’ve become distracted and discouraged. There are functions in the MLB class that I’m not even close to understanding, and there is no detailed explanation of the code anywhere. Lately I’ve thinking, there’s got to be a better way. There’s got to be a way where I can focus my coding on what I want the GUI widgets to DO, instead of struggling just to bring them into functional existence.

So, this afternoon, I once again Googled “Python GUI builder” or something like this and found this series of videos on building Python GUI applications with the Qt framework. https://www.youtube.com/user/Deusdies2.

The first series of videos walk the user through how to download and install a Python interface to the Qt development framework, in this case PySide. The author of this video series uses PyCharm on Windows 7, so his desktop and mine are nearly identical, meaning it couldn’t be easier to follow along with him.  The first few videos walk through the creation of simple Qt-based GUI applications such as a calculator. Eventually it moves on to QtDesigner, which, wonder of wonders, is a “drag and drop” GUI designer very much like Microsoft Access and Visual Basic. Where Tkinter requires 100% hand-coding of just about every step of a widget’s function, even PySide coding by itself without the graphical Qt Designer seems much simpler and less labor intensive.

Yes, I have spent many months on Tkinter. Yes, it seemed like I was on the right track when I discovered the MultiListbox. I enjoyed, for a time, the challenge of trying to figure it out. But somewhere along the line, it wasn’t fun anymore. It was a chore. This made it all too easy to become distracted with other projects. That was the signal that something was wrong. I like a challenge and I have loved learning Python, but when it’s no longer enjoyable, it’s no longer worth doing.  It’s a little intimidating to think of learning a completely new GUI builder.  However once I saw that drag and drop designer, that shimmering promise of instantly knowing my app will look the way I want it to without all the trial and error, and each item I drop into my app already knowing how to do what it’s supposed to and all I have to to is give it parameters,  I thought, “This could make Python fun again.” So off I go on my next, unexpected Python path. I’m going spend the next phase of my Python adventure getting as much as I can out of Mr. Milanovic’s videos, and of course, sharing the results.

A slow but sure learning process

It would be easy to grab a chunk of someone else’s code, modify it only as necessary, and through a lot of trial and error get it to do what I want.  That is what I originally set out to do when I discovered the MultiListbox custom widget in this project. Using this method I might be able to patch together something functional, without ever learning in depth about how the code actually works. This course of action is a continual temptation to my impatient self, especially now as I am grappling with the most complex GUI object in my DataQ application.

But that is not my goal. I don’t just want to learn how to use Python for specific tasks; I want to learn how Python WORKS. The MultiListbox fascinated me because it’s not just any old Tkinter widget; it’s a custom widget made out of other widgets — in this case, a series of Listboxes put together to display rows of data. In examining the MultiLisbox code, I realized I knew very little how even a single Listbox works, let alone a group of them squashed together and synchronized. I therefore decided to learn about and experiment with Listboxes, learning how they work from the ground up. I have found it hard to stay focused. Sometimes I get away from Python completely for days at time because my brain just feels fried by it.

Despite the challenges, I’m happy to report that I’m making progress. I’ve even started designing my very own MultiListbox class (mostly) without even looking at the original code! My version makes each individual column in the MultiListbox its own class, and I use the .grid() geometry manager instead of the often unpredictable .pack(). Here is the ‘rough draft’: Rob’s MultiListbox.

A Quest Begins

I recently stumbled across two little applications that, along with my recent advances in figuring out Tkinter, have finally given a much needed boost of inspiration — and a goal to strive for.

First there was this:

phoneapp

This is a step-by-step tutorial for a simple phone list using Tkinter and SQLite.  The user selects a name from the list and presses the “Load” button to load it into the form at the top.  The form is used to make changes to the data; the list is just for viewing and selecting.  This is what I have wanted to accomplish, boiled down to its absolute simplest level.  This little app also answered a big question for me; how do I get data from an SQLite database table into a Tkinter form for viewing and editing?  The short answer: the Tkinter Listbox widget, which has an INSERT method.

Even better, I found this:

inventory

Now we’re talking! Even though it’s a bit buggy, it’s the closest thing I have yet seen to what I have wanted to accomplish. It’s got a system of relational tables: customers, invoices and items. It has forms that are called when the user wants to add or change data. But its most intriguing item is a custom widget called a MultiListbox:
multilist

I found this object fascinating, because it opened my eyes to the power of Tkinter. It’s actually possible to make custom widgets out of the basic ones provided. The MultiListbox is a set of regular Listboxes squashed together in a frame and synchronized. It lines up each data field in its own column. It accepts data input in the form of tuples, for example: (‘Date’,’Item Number’,’Quantity’,’Price’). Each tuple is a row of data in the MultiListbox.

So, now, at last, I’ve found my quest!

holygrail

I plan to build an app around two basic objects: what I call a “TableForm”, for filtering, viewing and selecting data….
TableForm

..and what I have named a “RecordForm”, which will be used to edit, add, delete or update a record…
RecordForm

These two base objects could be duplicated for each table in the underlying SQLite database.

So here’s the short version of my plan. I’m starting with the coding of the TableForm, since it’s really the heart of my app. I have broken the Table Form down into component classes, starting with the MultiListbox (the most complex part). I plan to code and test each object separately, then piece them all together to make the TableForm class. I’ll then repeat this process with the RecordForm. This rudimentary data management setup could be applied to an endless array of applications, from personal finance to getting data from the Web to tracking my running

In true Python spirit, I wanted to name my app “DataQuest”, but found out there is a company called Dataquest. I didn’t want to get in trouble with them or cause confusion, so I’ve decided to call my app “DataQ.” I can already see the most time-consuming part of development is just figuring out how stuff works. When I first saw the code for the MultiListbox, it might as well have been in Chinese. It has taken some in-depth study of my Tkinter links and a lot of trial-and-error experiments with the code to get it deciphered. I feel like one of the great seafaring explorers of the 15th and 16th centuries, crossing a turbulent, uncharted ocean to a new world, or a biologist trying to decode DNA sequences.

See below for what I have so far in my version of the MultiListbox base class, along with comments I wrote in the code that explain the various parts as I was able to figure out what they did.

# A custom widget consisting of multiple Listbox widgets, with each listbox containing a column of data.
# Based on MultiListBox code used by suhailvs@gmail.com (https://sourceforge.net/projects/pyinvoice/)
# The code I was able to figure out is explained in the comments.
# Functions with comment ???? are ones I haven't figured out yet.

from tkinter import *
class MultiListbox(Frame):
    '''MultiListbox made by Labels as table header and Listbox as table columns'''
    def __init__(self, master, lists):
        Frame.__init__(self, master)
        self.lists = [] # A list of listboxes
        self.footers = [] # List of listbox footers
        for l,w in lists: # l=label, w=width for each listbox
            # Each listbox gets its own frame
            frame = Frame(self); frame.pack(side=LEFT, expand=YES, fill=BOTH)

            # Label = listbox name
            Label(frame, text=l, borderwidth=1, relief=RAISED).pack(fill=X)

            # Create each listbox
            lb = Listbox(frame, width=w, borderwidth=0, selectborderwidth=0,
                 relief=FLAT, exportselection=FALSE)
            lb.pack(expand=YES, fill=BOTH)

            # Add a footer for each listbox. Available for calculated totals.
            footer = Label(frame)
            footer.pack(fill=X)

            # Lists = list of numerical references to each listbox (ex .43982632.44021632.44021744.44022080)
            # Keeps track of listboxes after they are created.
            self.lists.append(lb)
            self.footers.append(footer)

            # Each listbox gets these bindings

            # left mouse button pressed. Required so left click selects entire line.
            lb.bind('', lambda e, s=self: s._select(e.y))

            # double click will eventually be what brings up the transaction edit form.
            lb.bind('', lambda e, s=self: s.double_click())

            # These came with the original MLB code but don't seem to do anything.
                #lb.bind('', lambda e, s=self: s._select(e.y)) # left mouse button pressed while moving
                #lb.bind('', lambda e: 'break') # mouse pointer leaves the widget
                #lb.bind('', lambda e, s=self: s._b2motion(e.x, e.y))
                #lb.bind('', lambda e, s=self: s._button2(e.x, e.y)) #right mouse button pressed

        # The frame that will contain the individual list boxes, each with its own frame (see above).
        frame = Frame(self); frame.pack(side=LEFT, fill=Y)
        Label(frame, borderwidth=1, relief=RAISED).pack(fill=X)
        sb = Scrollbar(frame, orient=VERTICAL, command=self._scroll)
        sb.pack(expand=YES, fill=Y)

        # lists[0] = the first (left most) list box.
        self.lists[0]['yscrollcommand']=sb.set

    def double_click(self):
        print(self.item_selected)

    def _select(self, y):
        row = self.lists[0].nearest(y) # integer index of selected row

        # Calls the selection_clear function to un-select whatever row was selected before left button was clicked.
        self.selection_clear(0, END)

        # Calls the selection_set function to select the line clicked on.
        self.selection_set(row)
        return 'break'

    def _button2(self, x, y): #????
        for l in self.lists: l.scan_mark(x, y)
        return 'break'

    def _b2motion(self, x, y): #????
        for l in self.lists: l.scan_dragto(x, y)
        return 'break'

    def _scroll(self, *args): #????
        for l in self.lists:
            l.yview(*args)

    def curselection(self): #????
        return self.lists[0].curselection()

    def delete(self, first, last=None):
        for l in self.lists:
            l.delete(first, last)

    def get(self, first, last=None):
        result = []
        for l in self.lists:
            result.append(l.get(first,last))
        if last: return list(map(*[None] + result))
        return result

    def index(self, index):
        self.lists[0].index(index)

    def insert(self, index, *elements): #Elements = the fields in a row.

        # Loop through each field in the row
        for e in elements:

            # Each field will have an index number. Ex: e[0] = ID, e[1] = Date, etc.
            i = 0
            for l in self.lists:
                # Insert each field in its applicable list box
                # Insert a blank if the field is None, otherwise boxes won't align.
                if e[i] == None:
                    l.insert(index, " ")
                else:
                    l.insert(index, e[i])
                i = i + 1

    def size(self):
        return self.lists[0].size()

    def see(self, index):
        for l in self.lists:
            l.see(index)

    def selection_anchor(self, index):
        for l in self.lists:
            l.selection_anchor(index)

    def selection_clear(self, first, last=None):
        # Unselects the entire line by looping over each of the list boxes that make up the MLB.
        for l in self.lists:
            l.selection_clear(first, last)

    def selection_includes(self, index):
        return self.lists[0].selection_includes(index)

    def selection_set(self, first, last=None):
        # first = the index of the selected row

        # self.item_selected = a list whose 1st item is the index of the row, followed by the entire row.
        # Example:
        # [6,134,'2014-02-03','Publix','',188,'$0.00','$3.48',197,1]

        self.item_selected=[first,]+self.get(first)

        for l in self.lists:
            # Sets the selection to the same row in all list boxes
            l.selection_set(first, last)

    def not_focus(self):
        for l in self.lists:
            l['takefocus']=False

    def calc_total(self,column):
        # Returns the total of the selected column number. Column must contain numeric values only.
        total = 0
        for n in range(0,self.lists[column].size()):
            total = total + float(self.lists[column].get(n))
        return total

    def footer_val(self,column,value):
        # Sets the text to be displayed in the footer of the column specified.
        v = StringVar()
        self.footers[column].config(textvariable=v)
        v.set(value)

If you scrolled all the way through that huge block of code to get here, I’ll bet you are the kind of person who sits in a movie theater and waits for all the credits to finish, hoping for something fun or entertaining at the end, or a sneak peak at a new movie. So as to not disappoint you, here’s an relevant item:
Monty-Python-and-the-Holy-Grail-monty-python-and-the-holy-grail-4968359-845-468

Coolness to the people! [resuming my tinkering with Tkinter]

In my quest to perfect a data-centric GUI app, I have chosen Tkinter as my GUI tool set of choice.  I’ve seen it criticized for being ancient. Heck, according to this, Tkinter was invented in 1994, and the Tk toolkit it came from goes all the way back to 1988! On the other hand, the way I see it, the fact that it’s still used at all is a testimony to its staying power. How many other software tools from 1988 are still around? Ironically, Tkinter is compatible with Python 3, while some competing, newer GUI toolkits such as wxPython are not. I also like Tkinter because it comes with Python — no special downloads or installations required.

Speaking of Python 3, I realize there’s controversy on how to get the Python world fully migrated to Python 3, but it seems there is agreement that Python 3 is the direction we need to move in sooner or later. Therefore, being a newbie, I have decided to go with learning Python 3 for my own coding. I want to save myself the grief of learning how to do everything I ever dreamed of in Python 2, then, down the road, having to convert all my code to Python 3. I’ll still use Python 2 for learning and experimentation, for example, if a course requires it or a library I want to play with doesn’t work with 3.

Okay, now down to business. Here’s my latest progress report on The Adventure..

A while back I was all happy faced and giggly about having conjured up my very own graphical app. Never mind that it didn’t do anything; it was delightful eye candy to me compared to command-line based Python apps. I found that learning Tkinter took a lot of focus. Gone were the days of dragging, dropping and sizing graphical elements on a form like in Microsoft Access. With Tkinter, every box and button must be conjured up with code and programmatically assigned its place in the application window. I found this challenging at first. Just coming up with the pretty interface and making it not look weird required a lot of trial and error. Then I got distracted with my CSV-parsing project.

Now, finally, I am back on track thanks to Real Python’s chapter on simple Tkinter programming, as well as this Tkinter tutorial (note it’s almost TEN YEARS OLD but still works!), along with Tk Docs. I was finally able to get my cool app to perform the cool task of making people cool. Here it is, now in Python 3, with enough coolness to reverse global warming. Of course, unless you have the exact same friends as me, you’ll have to edit the list of cool people as needed.

# Apply Coolness 3.0
# Python 3

from tkinter import *

def coolness():
    """ This function makes people cool.  It will return an
            error if you attempt to use it on someone who is
            already cool."""
    name = txtEntry.get()
    #List of people who are already cool. This is a tuple, meaning
    #these individuals are unchangingly cool.
    cool_people = ("tony",
                   "kevin",
                   "bryan",
                   "matt",
                   "victor",
                   "chris",
                   "ricardo")
    for cool_person in cool_people:
        # If the user inadvertently entered the name of someone
        # who is already cool.
        if cool_person == name.lower():
            result = "ERROR! Invalid usage. \n {} is already cool.".format(name)
            break
        else:
            result = "{} is now a cool person! \n CONGRATULATIONS, {}!"\
                     .format(name,name)
    txtResult.config(text=result)
    txtEntry.config(text="")

# Create the root window
window = Tk()
window.title("Apply Coolness 3.0")

# Create the frame to hold the boxes & button
mainframe = Frame(window)

# We will be using grid() to place widgets in the frame, so..
mainframe.grid()

# Set the variable for the name entered by the user
name = StringVar()

# Label that tells the user what to do.
lblPrompt = Label(mainframe, text="Enter the name of the person to be made cool:")
lblPrompt.grid(column=1, row=1, columnspan=2, padx=3, pady=3)

# Box in which user enters a person's name.
txtEntry = Entry(mainframe)
txtEntry.grid(column=1, row=2, padx=3, pady=3)

# The button that activates the coolness function.
btnCool = Button(mainframe,text="Make Cool!",command=coolness)
btnCool.grid(column=2, row=2, padx=3, pady=3)

lblResult = Label(mainframe, text="Result:")
lblResult.grid(column=1,row=3, sticky=W, padx=3, pady=3)

# Box that displays the result of the procedure.
txtResult = Label(mainframe, width = 20, height = 5, bd="3", relief=SUNKEN)
txtResult.grid(column=1, row=4, columnspan=2, sticky=(E,W))

# Set the focus to the entry box so the user won't have to
# click on it first.
txtEntry.focus()
window.mainloop()

Screenshot:
coolness-rob
Oops:
coolness-tony

Getting warmer at getting cooler.

In my last post, I attempted to create a Tkinter GUI for my “Coolness” app,  placing the widgets (that is, the buttons, boxes and other objects that make up a GUI interface) using the pack() method only.  This seemed to do little more than make a mess.  Luckily there is another, more precise method in Tkinter for placing widgets: the grid() method.  This is how to tell the widgets where to go when you are serious about getting them to obey you. I therefore sent pack() packing and rewrote my code. Here is Apply Coolness 2.0:

# Apply Coolness 2.0
# Import my cool function first!
from cool_tools import coolness
from Tkinter import *
import ttk

# Create the root window
root = Tk()
root.title("Apply Coolness")

# Create the frame to hold the boxes & button
mainframe = ttk.Frame(root)

# "Sticky" tells the frame to expand in all directions
# to fill up the root window.
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))

# Set the variable for the name entered by the user
name = StringVar()

# Label that tells the user what to do.
lblPrompt = ttk.Label(mainframe, text="Enter the name of the person to be made cool:")

# Row 0, Column 0 = upper left corner of mainframe.
# The 'grid' option for each successive widget will use row
# and column coordinates in relation to this one.
lblPrompt.grid(column=0, row=0)

# Box in which user enters a person's name.
txtEntry = ttk.Entry(mainframe,width=7)

# column 0, row 1 = place the entry widget directly
# below the label widget, which is row 0.
txtEntry.grid(column=0, row=1, sticky=(N, W, E, S))

# The button that activates the coolness function.
btnCool = ttk.Button(mainframe,text="Make Cool")

# Row 2 = the row below the entry box.
btnCool.grid(column=0, row=2, sticky=(N, W, E, S))

# Box that displays the result of the procedure.
txtResult = Text(mainframe, width = 20, height = 10)
txtResult.grid(column=0, row=3, sticky=(E, W))

# Set the focus to the entry box so the user won't have to
# click on it first.
txtEntry.focus()
root.mainloop()

And here is the result:

ApplyCoolness2

Now I’m getting somewhere! See the comments in the code for details on how and why I did what I did. This is pretty much the GUI I wanted. We have an entry box for a person’s name, a decidedly cool button to press to apply coolness to that person and a large box to display the result of the operation, including a warning in case the user is so carelessly uncool to attempt to apply coolness to someone who is already cool.

Next, of course, is the complicated part: getting the entry box, the button and the result box to all communicate with each other and the user to accomplish their noble function. Guess what — I haven’t learned in detail how to do that yet, so stay tuned!