I am experimenting with creating a customer relationship management program using tkinter and python and would like to have buttons generated based on tables in the database.
The idea is that there is a button for each table in the database, so that the table can be viewed and edited if needed.
I want to have each button look the same and, when clicked, generate a list of table entries into the main frame of my program. To do this, I want to extend the Button() class so that I can keep some attributes concurrent while also defining the display_items function:
class TabButton(Button):
def __init__(self, *args, **kwargs):
super().__init__(Button)
self['bg'] = '#a1a1a1'
self['font'] = ('Agency', 24)
def display_items(self, tab):
pass
#mycursor.execute('SELECT * FROM (%s)', tab)
This last line (above) is what selects data from the correct table in my database - I have commented it out while I figure out the rest of the class. I know what *args and **kwargs do, but I'm not sure what purpose they have in this __init__ function (I'm not very familiar with classes and copied this class from another Stack Overflow post).
To generate the buttons, I referenced a dict instance and assigned each key to a button:
tabs = {
'Table1': '',
'Table2': '',
'Table3': '',
}
for tab in tabs:
row = 0
tabs[tab] = TabButton(side_frame, command=lambda: TabButton.display_items(tab))
tabs[tab].grid(row=row, column=0)
row += 1
The problem is, when I run the program I get this error:
AttributeError: type object 'Button' has no attribute 'tk'
Any and all guidance is welcome!
If you notice any other mistakes in my code, could you please point them out? I'm very new to programming and it will save me making another post on Stack Overflow. :p
Thanks,
J
Super returns a temporary object of that class and let you access its content. Super itself dosent accept any arguments.
Also see the prupose of self in that context.
self represents the instance of the class
Often, the first argument of a method is called self. This is nothing
more than a convention: the name self has absolutely no special
meaning to Python. Note, however, that by not following the convention
your code may be less readable to other Python programmers
Another issue is your use of lambda. Your argument tab will be overwritten (if it isnt stored) by each iteration of your loop. Another issue that you might not intent to use the class for this method you rather want to be calles by the instance, therefor I added the argument self to your method and changed your lambda to make use of the instance.
import tkinter as tk
tabs = {'Table1': '',
'Table2': '',
'Table3': '',
}
root=tk.Tk()
class TabButton(tk.Button):
def __init__(self,master, *args, **kwargs):
#self is a reference to the instance of that object
super().__init__(master)#super dosent need a reference
self['bg'] = kwargs.get('bg','#a1a1a1')
self['font'] = kwargs.get('font',('Agency', 24))
def display_items(self,item):
print(f'{item} to show')
for tab in tabs:
b = TabButton(root) #returns a reference to that object
b.configure(command=lambda btn=b, argument=tab:btn.display_items(argument))
b.pack()
root.mainloop()
Related
How can I change an attribute on the object when the attribute is Frame? I want to change the color of the frame.
class MyFrame:
def __init__(self, bg_color)
self.window = tk.Frame(self.parent, bg=bg_color)
mainApp:
frame_obj = MyFrame("blue")
#want to change the color after the frame obj has been created
frame_obj.window.bg = "red" #this does not work
Tkinter is based on different progamming language, named tcl and thats why things seem a bit unpythonic here.
The tk.Frame object isnt a pure python object, rather its an wrapped object in a tcl interpreter and thats why you cant address the attributes you intuitivly think you can. You need to adress the attributes in a way the tcl interpreter is abel to handle and therefor methods are created like widget.configure.
To achive what you want with your current code, it would look like:
import tkinter as tk
root = tk.Tk()
class MyFrame():
def __init__(self, bg_color):
self.window = tk.Frame(width=500,height=500,bg=bg_color)
frame_obj = MyFrame('blue')
frame_obj.window.pack(fill=tk.BOTH)
frame_obj.window.configure(bg='yellow')
root.mainloop()
In addition, the proper way for an OOP approach for a frame would look like this:
class MyFrame(tk.Frame):
def __init__(self,master,**kwargs):
super().__init__(master)
self.configure(**kwargs)
frame_obj = MyFrame(root,width=500,height=500,bg='green')
frame_obj.pack(fill=tk.BOTH)
This way your class becomes a child of the tk.Frame object and you can adress it directly. The syntax self in this context refers directly to the tk.Frame object. Also it is good practice to use the format of
def __init__(self,master,**kwargs):
while its the same for the parent/tk.Frame. It has a single positional argument, named master and keywords arguments for the configuration of the Frame.
Please take a look at a tutorial for tkinter and for OOP. If you had you would know that. Please dont feel offended, but StackOverflow requiers a brief reasearch and that includes to read documentation and take tutorials.
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.
If I assign a variable (in this case b) to a button and put it on screen with pack(), then later reassign b to another data type, how do I access that button so that it can be deleted or otherwise referred to? I guess the question could be asked about any data type or object, but this is case stands out because the button is left on the screen. Example:
import tkinter as tk
root=tk.Tk()
root.geometry('300x300')
b=tk.Button(root, text="button b")
b.pack()
b=1
root.mainloop()
... then later reassign b to another data type, how do I access that button so that it can be deleted or otherwise referred to?
Short answer: you can't. In order to refer to something you must keep a reference. That being said,tkinter provides some alternatives.
If you're wanting to refer to the button in the callback, you can pass it in. This requires creating the button and then defining the callback in a separate step:
b = tk.Button(...)
b.configure(command=lambda button=b: do_something)
...
def do_something(button):
print("the widget is:", button)
Also, if you're using bindings, when the bound function is called it will be passed an event object that has an attribute that contains a reference to the widget.
b.bind("<1>", do_something)
...
def do_something(event):
print("the widget is:", event.widget)
Finally, you can ask tkinter for a list of child widgets (eg: root.winfo_children), and if you know whether you've used pack, place or grid, you can use the slaves method to query all of the widgets being managed inside a particular container (eg: root.grid_slaves(), root.pack_slaves(), root.place_slaves())
Save a reference somewhere with another assignment.
b = tk.Button(root, text="button b")
saved = b
b = 1
saved.pack()
You can access all widgets of the root window with the method:
list_of_widgets = root.pack_slaves()
and then check for the type of the object. ( Which is not a good practice ... )
Thus, i would recommend you to save the required references in a separeate object, for clearly seperating the interface to tkinter from your application. From then on, you should only access the widgets through your interface object. This creates much cleaner code.
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.
Update 2013-02-08
I have an idea now why I haven't been able to reproduce this problem in a small piece of test code. In a small program, Python's garbage collector isn't very active. I believe the problem is that Python is collecting some objects that are only referenced in GObject. I think it's a regression involving this bug, or a new similar bug.
I figured this out because I encountered the same problem again, but with my own class (which has references only from GObject objects) -- this time the entire dict is getting wiped out on the object. Uf I use the code here to monitor one of the attributes that dissappears, it doesn't disappear! It seems the extra reference keeps the attributes around. That smells like a garbage collector problem. I confirmed this by having the object add itself to a global list during initialization... that also fixes the problem as it occurs now.
Original Problem
I am experiencing some bizarre behavior with a PyGTK GUI. I have an object which is consistently losing a large number of attributes. I am trying to determine if this is a bug in my code, the Python interpreter, or PyGTK.
I make no calls to delattr(). I have tried detecting if anything is calling the __delattr__() method of my object by overriding __delattr__() with code that always raises an exception. I am able to reproduce the event which causes the object to lose its attributes but the exception is never raised. I'm not sure of another way to find out what function calls (if any) are causing the object to lose attributes.
The object in question is working perfectly at all times up until it suddenly loses attributes I'm trying to access.
The attribute loss occurs consistently after performing some actions in the GUI that have nothing to do with the object that is losing attributes. I discovered it by accident; there may be other actions that cause the object to lose its attributes.
I have added print id(self) to the method which access the disappearing attribute. The id that is printed is the same before and after the attribute disappears.
Any suggestions on how to track down the source of this problem?
Reference code below: (Note: I will update this code if/when I come up with a simplified test case that reproduces the problem. Right now the total code required to reproduce the bug is too big to post here.)
Here is the class for my object which loses its attributes. This is obviously a minimized version of the real functional code, but I am using this for debugging and the problem still occurs.
It is a subclass of my custom MenuBar class.
Note that the on_file_import__activate() method is connected to the signal for the menu item by one of the parent classes.
class FluidClassManagerWindowMenu(MenuBar):
menu_items = [("File",("Import",))]
def __init__(self, parent):
# XXX: different name than self.parent to see if it stops disappearing
self._xxx_my_parent = parent
MenuBar.__init__(self, parent)
def __delattr__(self,attr):
# XXX: trying to find the cause for lost attributes
traceback.print_stack()
def on_file_import__activate(self, widget=None, data=None):
# XXX: this is the same before and after the attributes disappear
print id(self)
# XXX: print the list of attributes to see what disappears
print dir(self)
# XXX: this works until the _xxx_my_parent attribute disappears
print self._xxx_my_parent
If you're curious, here is the complete source for my MenuBar class. It is a pygtkhelpers SlaveView, which inherits from GObject. The pygtkhelpers delegate does the automagic signal connection to the on_file_import__activate method above.
class MenuBar(pygtkhelpers.delegates.SlaveView):
def __init__(self, parent):
SlaveView.__init__(self)
self.parent = parent
def create_ui(self):
menu_bar = gtk.MenuBar()
menu_bar.set_pack_direction(gtk.PACK_DIRECTION_LTR)
for menu_name, items in self.menu_items:
menu = gtk.Menu()
submenu = gtk.MenuItem(menu_name)
submenu.set_submenu(menu)
for item_name in items:
if not item_name:
menu.append(gtk.MenuItem())
continue
menuitem = gtk.MenuItem(item_name)
fixed_item_name = item_name.lower().replace(' ','_')
fixed_menu_name = menu_name.lower().replace(' ','_')
attr_name = '%s_%s' % (fixed_menu_name,fixed_item_name)
# set an attribute like self.edit_vial_layout
# so pygtkhelpers can find the widget to connect the signal from
setattr(self,attr_name,menuitem)
menu.append(menuitem)
menu_bar.append(submenu)
self.vbox = gtk.VBox()
self.vbox.pack_start(menu_bar)
self.vbox.show_all()
self.widget.add(self.vbox)
List of attributes which disappear from the object:
'_model', '_props', '_toplevel', '_xxx_my_parent', 'file_import', 'parent', 'slaves', 'testtesttest', 'vbox', 'widget'
The attribute parent is what originally was disappearing; I tried assigning its value to _xxx_my_parent in
ManagerWindowMenu.__init__() but it disappears as well. I also added a new attribute in MenuBar.__init__ that I never access, called testtesttest, and it disappears too.
Keep in mind that objects in PyGTK often inherit from GObject. There is likely activity occuring within the GObject framework that is causing you to lose the attributes.
I had a very similar problem. I had a class (SearchWorker) that built a widget for adding to the GUI at runtime. In that widget, there was a button whose "clicked" signal was connected to one of the SearchWorker functions. Whenever the "clicked" signal was fired, many of the attributes of the SearchWorker self object were gone.
I was creating the SearchWorker object in another handler of a different class like this:
worker = SearchWorker()
I presume that once that handler exited something odd happened to the object behind the worker reference. Changing the creation of SearchWorker to:
self.worker = SearchWorker()
solved my problem.
How strange. BTW, using delattr and __delattr__ call is not very common, so I suspect if you are not dealing with two different objects by themselves, getting one while expecting another. Also it may not be a problem with the interpreter, it would crash at much lower level if there was a problem.