How to reassign object to a variable in python? - python

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.

Related

Tkinter: How do I properly set parameters of extended Button class?

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()

Expand Python Tkinter Object Types

There is this question to discover existing types:
Getting Python Tkinter Object Type
However I've just developed tooltips (balloons) I've assigned to some buttons and would like to be able to recall all of them as a unique type. Also down the road I'd like to hand-craft canvas objects which will operate like pseudo buttons with <enter>, <leave>, <active>, <press> and <release> events. How might I declare a new object type for them?
If I understand your question correctly you want to check if an instance created is a instance of the custom class, it can be directly done using isinstance:
class CustomTooltip():
pass
cwidget = CustomTooltip()
btn = tk.Button(root)
print(isinstance(cwidget, CustomTooltip)) # True
print(isinstance(b, CustomTooltip)) # False
print(type(cwidget) == CustomTooltip) # Not recommended
It is recommended to use isinstance rather than type, from the docs:
The isinstance() built-in function is recommended for testing the type of an object, because it takes subclasses into account.
I did not quite get your 2nd question, also it is better to keep a single question focused around one main question rather than asking more than one question.
Object Oriented Programming is probably the solution.
If I want to create a "new type" of tkinter button I can sub-class it.
class MySpecialButton(tkinter.Button):
pass
This doesn't do anything special at the moment but will give it a unique type (If it have understood your interpretation correctly)
The following example from another one of my answers, creates a special button with custom behaviour for hovering over the button
class HoverButton(tk.Button):
def __init__(self, master, **kw):
tk.Button.__init__(self,master=master,**kw)
self.defaultBackground = self["background"]
self.bind("<Enter>", self.on_enter)
self.bind("<Leave>", self.on_leave)
def on_enter(self, e):
self['background'] = self['activebackground']
def on_leave(self, e):
self['background'] = self.defaultBackground
With regard to canvas object, You can obviously create classes for these too which can contain methods for moving/resizing the object. As to how to create custom events for these, you can use tag_bind(item, event=None, callback=None, add=None) to bind a call back to a canvas object. A quick example below
import tkinter as tk
class CanvasShape:
def __init__(self, canvas, callback = None):
self.canvas = canvas
self.id = canvas.create_oval(10,10,50,50,fill='blue')
self.canvas.tag_bind(self.id,'<Button-1>',callback)
def clicked(e):
print("You clicked on a shape")
root = tk.Tk()
c = tk.Canvas(root,width=200,height=200)
c.grid()
shape = CanvasShape(c,callback=clicked)
root.mainloop()
This will create a circle that when you click on it will fire an event that is received by the clicked function.

Tkinter - Entry .get() from another class

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.

Re-binding “select all” in Entry widget

My question is related to this one where a Text widget is used.
However, in my case I want to rebind the select all on the entry widget.
I tried the following which allows me to use Ctrl+w to select all input in the entry field:
self.frmSearch = Frame()
self.txtSearch = Entry(self.frmSearch, bd=1, width=35)
self.txtSearch.bind('<Control-w>',lambda e: self.txtSearch.select_range(0, END))
However, once I change Ctrl+w to Ctrl+a this does not work anymore and no text is selected. Does anyone have an explanation why?
It is because you are putting the binding on the widget rather than the widget class, and by default the bindings on the class fire after the bindings on the widget.
The way Tkinter processes events is to first see if there is a binding on a widget, then on a class, and then on the toplevel window, and then finally on the special class "all". The events are processed in order unless you break the chain of events, so to speak. So, your control-w binding happens, but then the binding on the class happens and effectively undoes what you you did in your binding.
The best solution is to 1) not use lambda, but instead use a real method or function, and 2) do a "return 'break'" which prevents the class and other bindings from firing. Or, if you want this binding to affect all entry widgets in your application rather than just a specific one, use bind_class giving the class name of 'Entry'.
The question you refer to you in your question has an answer that gives an example of changing the class binding.

how to update a wxlistbox from a different class?

i have a wxlistbox in a class and i want to update the data inside the listbox from a different class.Is i possible to reload the class while leave the control from another class?if yes ,how?
eg:
i have two classes,Class A and Class B.In class A there is a wxlistbox.while starting the program class A initilise the wxlistbox and bind some values.when a button inside class A clicked it call another frame class B.while close the frame B the wxlistbox inside class A should update.
My question is how to refresh listbox while close the frame B?
I would use the SetItems() method, which according to the docs does the following: "Clear and set the strings in the control from a list".
Edit: myListCtrl.SetItems(ListOfStrings)
That will replace all the items in the control with whatever is in the list.

Categories

Resources