Convert Python Code to Use Buttons instead
Question: I have tried but I am not sure how to make this code work using buttons
instead of the canvas. Its for a calculator using tkinter. I need to make this
work using Buttons but everything i have tried has failed. If someone could
tell me how to do it or even do the whole thing that would be much
appreciated. I am new to this language and its confusing me. Thanks

    
    
    from Tkinter import *
    
    def quit():
        window.destroy()
    
    def buttonclick(event):
        global calcvalue
        global savedvalue
        global operator
        pressed = ""
        if event.x >10 and event.x <70 and event.y > 50 and event.y < 110 : pressed = 7
        if event.x >10 and event.x <70 and event.y > 120 and event.y < 180 : pressed = 4
        if event.x >10 and event.x <70 and event.y > 190 and event.y < 250 : pressed = 1
        if event.x >10 and event.x <70 and event.y > 260 and event.y < 320 : pressed = 0
        if event.x >80 and event.x <140 and event.y > 50 and event.y < 110 : pressed = 8
        if event.x >80 and event.x <140 and event.y > 120 and event.y < 180 : pressed = 5
        if event.x >80 and event.x <140 and event.y > 190 and event.y < 250 : pressed = 2
        if event.x >150 and event.x <210 and event.y > 50 and event.y < 110 : pressed = 9
        if event.x >150 and event.x <210 and event.y > 120 and event.y < 180 : pressed = 6
        if event.x >150 and event.x <210 and event.y > 190 and event.y < 250 : pressed = 3
        if event.x >80 and event.x <140 and event.y > 260 and event.y < 320 : pressed =   "equals"
        if event.x >150 and event.x <210 and event.y > 260 and event.y < 320 : pressed = "clear"
        if event.x >220 and event.x <280 and event.y > 50 and event.y < 110 : pressed = "divide"
        if event.x >220 and event.x <280 and event.y > 120 and event.y < 180 : pressed = "times"
        if event.x >220 and event.x <280 and event.y > 190 and event.y < 250 : pressed = "minus"
        if event.x >220 and event.x <280 and event.y > 260 and event.y < 320 : pressed = "plus"
    
    
        if pressed == 0 or pressed == 1 or pressed == 2 or pressed == 3 or pressed == 4 or  pressed == 5 or pressed == 6 or pressed == 7 or pressed == 8 or pressed == 9 :
            calcvalue = calcvalue * 10 + pressed
    
    
        if pressed == "divide" or pressed == "times" or pressed == "minus" or pressed == "plus" :
            operator = pressed
            savedvalue = calcvalue
            calcvalue = 0
    
        if pressed == "equals":
            if operator == "divide": calcvalue =  savedvalue /calcvalue
            if operator == "times": calcvalue =  savedvalue * calcvalue
            if operator == "minus": calcvalue =  savedvalue - calcvalue
            if operator == "plus": calcvalue =  savedvalue + calcvalue
    
        if pressed == "clear":
            calcvalue = 0
    
        displayupdate()
        canvas.update()
    
    def displayupdate():
        canvas.create_rectangle(10, 10, 280, 40, fill="white", outline="black")
        canvas.create_text(260, 25,  text=calcvalue,font="Times 20  bold",anchor=E)
    
    def main():
        global window
        global tkinter
        global canvas
        window = Tk()
        window.title("Simple Calculator")
        Button(window, text="Quit",  width=5, command=quit).pack()
        canvas = Canvas(window, width= 290, height=330, bg = 'beige')
        canvas.bind("<Button-1>", buttonclick)
    
    
    
    
    #Add the numbers
        canvas.create_rectangle(10, 50, 50, 110, fill="yellow", outline="black")
        canvas.create_text(40, 80,  text="7",font="Times 30  bold")
    
        canvas.create_rectangle(10, 120, 70, 180, fill="yellow", outline="black")
        canvas.create_text(40, 150,  text="4",font="Times 30  bold")
    
        canvas.create_rectangle(10, 190, 70, 250, fill="yellow", outline="black")
        canvas.create_text(40, 220,  text="1",font="Times 30  bold")
    
        canvas.create_rectangle(10, 260, 70, 320, fill="yellow", outline="black")
        canvas.create_text(40, 290,  text="0",font="Times 30  bold")
    
        canvas.create_rectangle(80, 50, 140, 110, fill="yellow", outline="black")
        canvas.create_text(110, 80,  text="8",font="Times 30  bold")
    
        canvas.create_rectangle(80, 120, 140, 180, fill="yellow", outline="black")
        canvas.create_text(110, 150,  text="5",font="Times 30  bold")
    
        canvas.create_rectangle(80, 190, 140, 250, fill="yellow", outline="black")
        canvas.create_text(110, 220,  text="2",font="Times 30  bold")
    
        canvas.create_rectangle(150, 50, 210, 110, fill="yellow", outline="black")
        canvas.create_text(180, 80,  text="9",font="Times 30  bold")
    
        canvas.create_rectangle(150, 120, 210, 180, fill="yellow", outline="black")
        canvas.create_text(180, 150,  text="6",font="Times 30  bold")
    
        canvas.create_rectangle(150, 190, 210, 250, fill="yellow", outline="black")
        canvas.create_text(180, 220,  text="3",font="Times 30  bold")
    
    #Add the operators
        canvas.create_rectangle(80, 260, 140, 320, fill="green", outline="black")
        canvas.create_text(110, 290,  text="=",font="Times 30  bold")
    
        canvas.create_rectangle(150, 260, 210, 320, fill="green", outline="black")
        canvas.create_text(180, 290,  text="C",font="Times 30  bold")
    
        canvas.create_rectangle(220, 50, 280, 110, fill="pink", outline="black")
        canvas.create_text(250, 80,  text="/",font="Times 30  bold")
    
        canvas.create_rectangle(220, 120, 280, 180, fill="pink", outline="black")
        canvas.create_text(250, 150,  text="*",font="Times 30  bold")
    
        canvas.create_rectangle(220, 190, 280, 250, fill="pink", outline="black")
        canvas.create_text(250, 220,  text="-",font="Times 30  bold")
    
        canvas.create_rectangle(220, 260, 280, 320, fill="pink", outline="black")
        canvas.create_text(250, 290,  text="+",font="Times 30  bold")
    
    #Setup the display
        canvas.create_rectangle(10, 10, 280, 40, fill="white", outline="black")
        global calcvalue
        calcvalue = 0
        displayupdate()
    
        canvas.pack()
        window.mainloop()
    
    
    
    
    
    
    
    main()
    


Answer: You don't say why you're having a problem using buttons, so I don't know what
problem you're trying to solve. Certainly it's possible to create a grid of
buttons, and certainly it's possible for buttons to call functions when
clicked.

To me, the real challenge here is to write code that is compact,
understandable, and easy to maintain. You want it to be easy to add new
functions and/or new operators without having to rework the whole GUI.

Personally I would use an object-oriented approach, creating custom objects
that represent numerals, functions and operators. Since you're using a non-OO
approach, I recommend creating some helper functions to abstract out some of
the details.

You have three types of buttons: numbers, operators ("+", "-", etc) and
functions ("C", "="). I would create a pair of functions for each type: one
function to create the button, and one function to respond to the button.
Doing this avoids having a single monolithic function to handle all button
presses.

I would also add a helper function to lay out the buttons, just to make it
easier to visualize the final product in the code.

Let's start with the number buttons. We know that all the number button has to
do is insert that number into the calculator. So, let's first write a function
to do that. In this case I'll assume you have an entry widget to hold the
value, since working with canvas text objects is cumbersome:

    
    
    def do_number(n):
        global entry # the entry where we should insert the number
        entry.insert("end", n)
    

If we call this function with `do_number("3")`, it will insert "3" into the
calculator. We can use this same function for all of the buttons, all we have
to do is pass in what number to insert. You can create similar functions named
`do_operator` and `do_function`, which take the label of the button and do the
appropriate thing. You could just as well have a unique function for each
button if you want.

To create the button, we want a little helper function that can tie a button
to that function. This example uses `lambda`, but you can use
`functools.partial`, or a function factory. `lambda` requires the fewest extra
steps, so we'll go with that.

    
    
    def number(number):
        global frame
        b = Button(frame, text=label, command=lambda: do_number(number))
        return b
    

When we call this function like `number("4")`, it will create a button with
"4" as the label. It will also call the function `do_number` with the number
"4" as an argument, so that it will insert the proper string. You can then
create similar functions named "operator" and "function" to create buttons
that act as operators and those that act as functions.

> Note 1: I'm generally against using global variables in this manner.
> Generally speaking it better to pass in the containing frame rather than
> rely on a global. In this specific case the use of the global makes the code
> a bit more compact, as you'll see in another paragraph or two. If we were
> building this calculator as class, we could use an instance variable instead
> of a global.
>
> Note 2: declaring the variable as global here has no real effect. Globals
> must only be declared when you want to modify the variable. However, I put
> the global statement in to serve as a declaration that I intend for the
> variable named `frame` to be global. This is purely a matter of personal
> preference

So, now we can create buttons of each time, and have them call a function with
a unique parameter. Now all that is left is to use `grid` to organize the
buttons. Another helper function will make this easier:

    
    
    def make_row(row, *buttons):
        for column,button in enumerate(buttons):
            button.grid(row=row, column=column, sticky="nsew")
    

This helper function lets us pass in a list of buttons, and it will lay them
out in a row. Now, combining this with our helper functions to create the
buttons, we can create the GUI with something like this:

    
    
    frame = Frame(window, bg="beige")
    entry = Entry(frame, borderwidth=1, relief="solid")
    entry.grid(row=0, column=0, columnspan=4, sticky="nsew")
    make_row(1, number("7"),   number("8"),   number("9"), operator("/"))
    make_row(2, number("4"),   number("5"),   number("6"), operator("*"))
    make_row(3, number("1"),   number("2"),   number("3"), operator("-"))
    make_row(4, number("0"), function("="), function("C"), operator("+"))
    

Notice how it's now possible to see the layout of the buttons in the code. If
you want to add another column, add another row, or rearrange the existing
buttons, it should be completely self-evident how to do that.

Now, of course, this isn't the only way to accomplish this. You can do all of
this without helper functions, and just create a few hundred lines of code. By
taking a little extra time to break the code down into logical chunks you can
create code that is considerably easier to read and maintain.

