0

Beginner here trying to make sense of classes. Below is the code for my class Cell:

import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()

class Cell:
     def __init__(self, x, y, r):
        self.x = random(x)
        self.y = random(y)
        self.r = 200

     def show(self):
        canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")

top.mainloop()

I'm attempting to draw the cell in my main program by calling the function show from the class. Here is the code for my main window:

import tkinter
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()
from Cell import Cell

cell = Cell()
cell.show()

top.mainloop()

This is resulting in the canvas being drawn correctly, but the oval is nowhere to be found. I am not getting any errors either.

Any help would be appreciated. Thank you!

====================

Turns out, I misunderstood the arguments for create_oval. I found some code that converts the clunky create_oval function into a function which receives a set of coordinates for the center of the oval and a radius.

In addition to this, the help I received in understanding classes and other Python functionality helped significantly as well. Thanks to those who helped!

This is my revised code which works as intended.

import tkinter as tk
import random
top = tk.Tk()
canvas = tk.Canvas(top, width=400, height=400, bg="grey")
canvas.grid()

def _create_circle(self, x, y, r, **kwargs):
    return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle

class Cell:  
    def __init__(self, canvas, x, y, r):
        self.canvas = canvas
        self.x = x
        self.y = y
        self.r = r

    def show(self):
         self.canvas.create_circle(self.x, self. y, self.r, fill = "blue")


cell = Cell(canvas, random.randrange(50, 350), random.randrange(50, 350), 25)
cell.show()

top.mainloop()

2 Answers2

2

The problem here is that your main script and your Cell module are both creating a new Tk instance, adding a Canvas to it, and then calling its mainloop method.

If you trace through the order in which statements get executed, you'll find that the cell = Cell() and cell.show() don't happen until after the first top.mainloop() returns, and mainloop() doesn't return until you quit the program. (In fact, if your code did get that far, it would fail with a TypeError, which I'll get to below.)

But, more generally, you only want one Tk in your program, and everyone else should refer to that.

And, in this case, you want the same for the Canvas: just one of them, packed onto the one Tk main window.


So, how can Cell.show refer to the canvas global from another module?

The best solution is to not refer to it as a global at all, and instead pass it in to the initializer, the same way you do with x, y, and r:

class Cell:
     def __init__(self, canvas, x, y, r):
        self.canvas = canvas
        self.x = random(x)
        self.y = random(y)
        self.r = 200

     def show(self):
        self.canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")

And then in the main script:

cell = Cell(canvas, ?, ?. ?)
cell.show()

But notice those ?s I put there. Your Cell class definition demands x, y, and r values in its initializer, but your Cell() constructor call doesn't pass any. That will raise a TypeError complaining that you're missing required arguments.

What do you want to pass here? Since the canvas is 400x400, maybe you want to pass something like 400, 400, 200? If so:

cell = Cell(canvas, 400, 400, 200)
cell.show()

Going back to that initializer, you've got some other problems there:

self.x = random(x)
self.y = random(y)

That random is a module. You can't call a module. You probably wanted something like this:

self.x = random.randrange(x)

That calls a function from the random module, one which is defined to return a random number in range(0, x), which seems like what you want.

Also:

self.r = 200

Why take an r parameter, just to ignore it? You probably wanted this:

self.r = r

Or, maybe you didn't actually want x, y, and r as parameters? Maybe you want to hardcode randrange(400), randrange(400), and 200, or maybe you want to compute them from the width and height of the canvas parameter, or… you can do almost anything you want, you just have to think through what you want, and make sure the interface you declare in the def matches the way you call it in the Cell(…) later.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Let me explain what exactly I'm trying to do so we have more context. I'm attempting to create an oval at a random location (x, y) on the canvas with a fixed radius of 200. My method for generating this oval is probably wrong on a basic level. I would still like to keep a Cell class, though, so that I can manipulate it later. – Cody Ramey Jul 17 '18 at 03:16
  • @CodyRamey OK, but should it be up to the main code to know those values `400, 400, 200`, or should the `Cell` code know them, or should the `Cell` code figure them out by examining the `canvas` object, or…? The key to using classes is thinking about objects as not just dumb values, but values that know certain information, and know how to use that information to do this, so, what does a `Cell` know about the world, and what does it have to be told? – abarnert Jul 17 '18 at 03:20
  • Thank you for the help, @abarnet. I came to a solution thanks to you. – Cody Ramey Jul 17 '18 at 04:26
1

I think your execution path is never getting to the cell.show() line.

When you import Cell, you have code at the top level top.mainloop(). This enters the main loop and never exits, so you never get to the lines below it.

It's a good rule of thumb to avoid putting code at the base level. Leave that for defining classes and functions. If you want code to run when the file is called like a script, put it in a if __name__ == __main__: condition.

You also had some syntax issues using random and calling the Cell constructor. The example below works as expected.

import tkinter
import random
top = tkinter.Tk()
canvas = tkinter.Canvas(top, bg="grey", height=400, width=400)
canvas.pack()

class Cell:
     def __init__(self, x, y, r):
        self.x = x
        self.y = y
        self.r = r

     def show(self):
        canvas.create_oval(self.x, self.y, self.r, self.r, fill = "blue")

if __name__ == "__main__":
    cell = Cell(100, 50, 5)
    cell.show()
    top.mainloop()
Brennen Sprimont
  • 1,564
  • 15
  • 28