So I'm trying to build a small game of tic tac toe and I'm using kivy to make it happen. The problem I run into is I have made a grid of 9 buttons (3x3) and now I am binding them (or trying to).
I do all of this within the __init__ method of my class BoardGrid since this should happen only when the program runs the first time.
class BoardGrid(GridLayout):
def __init__(self, **kwargs):
super(BoardGrid, self).__init__(**kwargs)
self.board = []
self.buttons = []
for i in range(9):
self.board.append('')
self.buttons.append(Button(text=self.board[i]))
self.buttons[i].bind(on_press=BoardGrid.callback(????, i))
self.add_widget(self.buttons[i])
def callback(self, btn):
print(btn)
The problem is that the on_press takes a function that it will direct to and this works fine if I let BoardGrid.callback take no arguments, but since I want it to know which button was pressed I want to send in i. This leads to a problem where I need to also pass a BoardGrid into the callback function which I havnt created within the class?
Maybe I'm attacking this from the wrong angle here, I thought I could bind my buttons to a specific input in the callback funciton but that might not be possible.
Any advice on how to get the bindings to work would be much appreciated
EDIT: Seems to be working now, sorry I couldnt reply to everyone, it's my first post and I can't quite seem to understand it yet hehe. Thanks very much for the replies!
from functools import partial
class BoardGrid(GridLayout):
def __init__(self, **kwargs):
...
self.buttons[i].bind(on_press=partial(self.callback, i))
...
def callback(self, btn):
print(btn)
Related
I am trying to make an app using kivy module in python. It involves different screens.
but for some reason the second screen does not get initialised.
class Some(Screen):
def __int__(self, **kwargs):
super(Some, self).__init__(**kwargs)
self.layout = MDBoxLayout()
self.layout.add_widget(Label(text='somEWHIoihat'))
self.add_widget(self.layout)
wadwaduhawuhdawudh
return 1 + 1
This is the class. Here I tried returning in init function which does not give an error. I even have some random letter in there which also does not give any errors.
self.second_screen = Some(name='2')
This is what I am trying to do. But it runs without any errors but no widgets are displayed on screen. When I added print functions in the init function of Some, it doesnot print anything which I think is because init function is not being called
Whilst working on a tkinter application (Tcl/Tk 8.6 and Python 3.9.2) I recently encountered an error that I was able to resolve, but I think the existence of the error highlights some gaps in my knowledge and potential weaknesses in my approach.
A reproducible example of the error is below - this code will not work and returns the error AttributeError: 'parent_class' object has no attribute 'first'.
from tkinter import *
from tkinter import ttk
class child_one(ttk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.x = 10
def print_x(self):
print(self.x)
class child_two(ttk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.b = ttk.Button(self, text='Button 1',
command=parent.first.print_x).grid()
class parent_class(ttk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.grid()
self.second = child_two(self)
self.first = child_one(self)
self.first.grid()
self.second.grid()
if __name__ == '__main__':
root = Tk()
w = parent_class(root)
root.mainloop()
However the code will work if I reverse the order in which the instances of child_one and child_two are created i.e. replacing
self.second = child_two(self)
self.first = child_one(self)
with
self.first = child_one(self)
self.second = child_two(self)
in the definition of parent_class.
I'd really appreciate any explanations or link to resources to help me understand the program flow which causes this to happen - it appears to me that when I create w and get to the line self.second = child_two(self) Python is just looking at the part of the instance of parent_class which has already been created, and not the whole definition of the class.
Would this happen if this was not the first instance of parent_class to be created? Is it specific to tkinter? (I was only able to create a simple reproducible example with tkinter widgets, not with classes more generally.)
I suppose another solution would be to make print_x a (static?) method of parent_class? I also assume there's not enough detail here to definitively state if that (or alternative structures) would be preferable to facilitate interface between the components of my application, but would be interested to hear any suggestions for good practices in this space.
There's really no mystery here. If you do self.first = child_one(self) first, it defines self.first. When you then call child_two(self), it is able to access parent.first since it was previously created.
However, if you call child_two(self) first, at that point in time parent.first doesn't exist. Thus, when you do command=parent.first.print_x, parent.first doesn't exist.
it appears to me that when I create w and get to the line self.second = child_two(self) Python is just looking at the part of the instance of parent_class which has already been created
That is correct. You can't reference things that haven't been created yet.
Would this happen if this was not the first instance of parent_class to be created? Is it specific to tkinter?
I'm not quite sure what you're asking in the first part of that question. It will always happen if you try to reference any object attribute before that attribute has been created. And no, this isn't specific to tkinter. It's a fundamental aspect of the way that python works.
This is a good example of why it's generally best to create proper functions rather than using lambda. lambda is good when you need to pass arguments, but you don't need to do that in this case. Even then, a proper function is better than directly referencing some other object at the time the button is defined. An arguably better way would be to use a function so that self.parent.first doesn't need to be resolved until you actually click the button.
For example:
class child_two(ttk.Frame):
def __init__(self, parent, **kwargs):
super().__init__(parent, **kwargs)
self.parent = parent
self.b = ttk.Button(self, text='Button 1', command=self.print_x)
self.b.grid()
def print_x(self):
self.parent.first.print_x()
When you say "Python is just looking at the part of the instance of parent_class which has already been created, and not the whole definition of the class", you seem to be expecting python to have built a static description of your class before the program starts running.
Python does not work that way, it's a dynamic language. As Bryan just said, the first variable is created only when you assign to it for the first.
I have code structure something like this:-
def send_message(msg):
print msg + "\n"
x.new_message("You",msg)
class GUI(Frame):
def createWidgets(self):
self.input.bind('<Key-Return>',self.send)
def send(self, event):
send_message(self.contents.get())
self.contents.set("")
def new_message(self,sender, msg):
line = sender+": "+msg+"\n"
self.chat.contents.set(self.chat.contents.get()+line)
def __init__(self):
self.createWidgets()
x = GUI()
As you can see, this has some circular dependancies. Function send_message requires instance x as well as new_message method of GUI. GUI definition needs send_message. Thus it is not possible to satisfy all the constraints. What to do?
In the complete code you showed in die comments we can see that you call self.mainloop() in GUI.__init__. This will start the event handling of the gui and will probably not terminate until the program in finished. Only then the assignment x = GUI() will finish and x will be available.
To circumvent this you have multiple options. Generally doing an endless loop in __init__ is probably a bad idea. Instead call mainloop() after the GUI is instantiated.
def __init__(self):
# only do init
x = GUI()
x.mainloop()
As jasonharper said in python variables in functions are only looked up when you execute that function, not when defining them. Thus circular dependencies in runtime are most of the time not a problem in python.
Names within Python functions are not required to refer to anything at the time the function is defined - they're only looked up when the function is actually called. So, your send_message() is perfectly fine as it is (although it would probably be better to make x a parameter rather than a global variable).
Your GUI class is going to fail to instantiate as shown, due to references to widgets you didn't show the creation of - self.input for example. I cannot tell how much of that is due to you stripping the code down for posting.
i've recently come across a problem thats bugging me with the tkinter entry .get() function, I have put together an example code so you can see what i'm trying to do, I have two classes, a class for each window. In the first window(main window) I have an entry box, in the second window I am attempting to get the entry box text from the first window.
Here's the code: (Trying to get entry box info from the first class in the second class)
from Tkinter import *
class window_1(object):
def __init__(self):
self.app = Tk()
self.app.title("Window One")
def entrybox(self):
self.ent = Entry(self.app) #This is the text i'm trying to get in 2nd class
def button(self):
def ODV(self):
class window_2(object):
def __init__(self):
self.app2 = Tk()
self.app2.title("Window Two")
def labels(self):
self.label_0 = Label(self.app2, text = "Name: ")
def info(self):
self.fetch_name = self.ent.get()#Here is my problem
def gridder(self):
self.label_0.grid(row = 0, column = 0)
self.fetch_name.grid(row = 0, column = 1)
rooter = window_2()
rooter.labels()
rooter.info()
rooter.gridder()
open_data_viewer = lambda: ODV(self)
self.but = Button(self.app, text = "Save", command = open_data_viewer)
def packer(self):
self.ent.pack(anchor = W)
self.but.pack(anchor = W)
def App_Runner(self):
self.app.mainloop()
root = window_1()
root.entrybox()
root.button()
root.packer()
root.App_Runner()
Your first problem is that you're creating more than one instance of Tk. You can't do that, tkinter isn't designed to work that way. If you want multiple windows, create instances of Toplevel. A tkinter program should always have exactly one instance of Tk, exactly one call to mainloop, and there should be little to no code following the call to mainloop.
Second, there is absolutely no value in embedding the definition of a class inside a function. Move it out, it will make your code easier to understand, and easier to write and maintain.
Third, for an instance of one object to access a method on another object, the first object needs to know about the second object or needs to know about a central "controller" object. This isn't a tkinter problem, it's a normal thing to consider when writing OO code.
From a practical standpoint, you need to pass in a reference either to the entry widget, or the object that contains the entry widget, when you create the second object. For example:
class window_2(object):
def __init__(self, other):
...
self.other = other
...
def info(self):
self.fetch_name = self.other.ent.get()
...
rooter = window_2(self) # pass "self" to the new object
This produces a tight coupling between the two objects -- the second object knows about the inner workings of the first object. This is not very good design, though for very, very simple programs it's not so bad. The problem is this: if you change the layout of the first widget, perhaps renaming "self.ent" to "self.some_other_frame.ent", you have to modify the other class too.
A better solution is to define in your first class a function that gets it's own value. Of course, ent serves that purpose, but again, that is a tight coupling. better to have a helper function:
class window_1(object):
...
def get_string(self):
return self.ent.get()
class window_2(object):
def info(self):
self.fetch_name = self.other.get_string()
This still has a loose coupling, but one that is much easier to manage because the coupling isn't tied to the specific internal layout and names of the first window. You can change the widgets all you want, as long as you continue to provide a get_string method that does what the other class expects. Your first class is providing a contract to the second class: a promise that no matter how else the window may change over time, it promises to provide this interface.
This is the class where I'm trying to implement a simple expression evaluator:
class mainWindow(QtGui.QMainWindow, mainGui.Ui_MainWindow):
def __init__(*args, **kwargs)
super(mainWindow, self).__init__(*args, **kwargs)
self.setupUi(self)
self.connecting_range_displays()
def connecting_range_displays(self):
ranges = num_ranges()
first_range = num_ranges.ones_range
second_range = num_ranges.tens_range
print first_range
print second_range
self.top_lineEdit.setText(str(first_range))
self.bottom_lineEdit.setText(str(second_range))
ex_a = first_range + second_range
print "this is expressions answer", ex_a
self.answer_lineEdit.returnPressed.connect(self.ex_evaluator)
def ex_evaluator(self, right_answer):
answer = self.answer_lineEdit.text()
if ex_a == right_answer:
print "Correct answer bucko"
In the above code for a simple arithmetic practice application I use two lines to display random numbers in a certain range, and the third QlineEdit i.e answer_lineEdit I use it to get user input and then to check if its correct I use the Signal returnPressed and connect it to the ex_evaluator which will then tell you if you have given a correct response, the problem seems to be that I cannot pass the ex_a argument to the function to be able to evaluate it, could it be namespace issue, I'm a pretty new at coding and my philosophy is to only learn the least amount to get me to where I want so I'm wondering if I will have to learn how to subclass the QLineEdit which I have no idea how to do, I saw that maybe decorators might help which is another bucket of trouble I don't want to get into.
You need to do one of the following:
make ex_a a class attribute by changing it to be "self.ex_a". Then it can be used from anywhere within the class
pass it to the slot (ex_evaluator) using lambda or functools.partial
For the second option, I wrote a tutorial that describes both ways of doing this: http://www.blog.pythonlibrary.org/2013/04/10/pyside-connecting-multiple-widgets-to-the-same-slot/
To use a lambda, do something like this:
self.answer_lineEdit.returnPressed.connect(lambda ans=ex_a: self.ex_evaluator(ans))
For the functools.partial, it would be something like this:
import functools
callback = partial(self.ex_evaluator, ex_a)
self.answer_lineEdit.returnPressed.connect(callback)
If you don't understand that last one, you might want to take a look at the documentation.