How to access method of an object in the object? - python

I am trying to condense my code, so I want to create object instead of having to create labels each time I need one.
However, I can't figure out how to be able to change attributes of the object-labels using .config. I've tried using objectvariable.config(...), but that doesn't work. Neither does using a method like in the following:
class title_label():
def __init__(self):
self = tkinter.Label(root)
self.pack(side='left')
def update(self, text):
self.config(text=text)
Error-message is: objectvariable object has no attribute config.
How can I use .config on an object containing a label?

It should be
class title_label():
def __init__(self, root):
self.label = tkinter.Label(root) # <<< 'label' field here
self.label.pack(side='left')
def update(self, text):
self.label.config(text=text)
self hold the reference to the class itself. label is something that your class is supposed to hold not to be. Another approach would be to derive from the Label class, but for what it is worth storing the label in the field should be good enough for you.

If you made your class a subclass of tkinter.Label then it would have inherited a config() method from it.
Here's an example of how that might be done:
import tkinter as tk
class TitleLabel(tk.Label):
def update(self, text):
self.config(text=text)
if __name__ == '__main__':
root = tk.Tk()
title_lbl = TitleLabel(root, text='Initial Text')
title_lbl.pack(side='left')
root.after(1000, lambda: title_lbl.update('CHANGED!')) # Update after 1 sec.
root.mainloop()
But as you can see, there wouldn't really be much point of doing so, since the only thing update() does is forward to call on the base class.

Related

Can you transfer attributes from one class instance to another without using inheritance?

I'm building a Tkinter app and it is currently all one big class. I'm trying to split it into smaller pieces to make it easier to work on. The problem is some of the smaller pieces contain elements that are referenced elsewhere in the larger app.
editing to add more code:
OK so say I am using init_library to make a big complicated frame which then becomes self.library.
class GUI:
def __init__(self):
self.root = tk.Tk()
self.init_library()
self.root.mainloop()
return
def init_library(self):
self.library = tk.Frame(self.root)
[...]
self.library_var = tk.StringVar("HEY")
self.library_label = tk.Label(self.library, textvariable=self.library_var)
[...]
def action(self, new_text):
[...]
self.library_var.set(new_text)
[...]
So assume that init_library is large and I would like to put it in a different file to make life easier. Now instead of a class function, I just instantiate LibraryClass.
from pieces import LibraryClass
class App:
def __init__(self):
self.root = tk.Tk()
self.library = LibraryClass()
self.root.mainloop()
return
The problem is library_var is now a child of self.library instead of App. So we would have to change action.
def action(self, new_text):
[...]
self.library.library_var.set(new_text)
[...]
Is there a way to directly assign the LibraryClass functions and attributes of self.library to App?
(I know it seems like a small thing but it would really make my life easier)

Python Tkinter Avoid using "root" name in lower level function

Inspired by this 300+ vote closed Q&A: Best way to structure a tkinter application?, I'm looking to avoid explicitly using root in a function within a class. I think it should be implicitly declared through self or parent or something like that. Here is the code in question:
I have this code...
self.label_this = tk.StringVar()
self.label_last = tk.StringVar()
self.label_total = tk.StringVar()
tk.Label(count_frame, textvariable=self.label_this, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_last, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
tk.Label(count_frame, textvariable=self.label_total, \
font=(None, MON_FONTSIZE)).pack(anchor=tk.W)
self.update_cnt_labels()
Then later on...
''' Get list of Window ID's on monitor now '''
new_windows = self.windows_on_monitor(new_windows)
new_windows_cnt = len(new_windows) / WIN_CNT
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return
Then later on...
class FlashMessage:
def __init__(self, widget, message, count=5, on=500, off=300):
self.delay_show (1, widget, message)
for i in range(count):
self.delay_show (on, widget, "")
self.delay_show (off, widget, message)
def delay_show(self, ms, widget, message):
root.after(ms, widget.set(message))
root.update_idletasks()
I want to avoid using root in the last two lines and use self or something similar.
My program call chain is something like:
the traditional: root = tk.Tk()
bunch of mainline initialization stuff.
the class: ResizingCanvas(mycanvas)
mainline function: popup(event) which is bound to <ButtonPress-1>
Dynamically formatted menu.tk_popup(event.x_root, event.y_root)
the class: RemoveNewWindows()
the function: remove()
the class: FlashMessage() (show above)
the function: self.delay_show() (shown above)
Each class and function has haphazard self, positional parameters, *args and **kwargs which mostly serve no purpose. Indeed even the __init__ above might be unnecessary. This is a result of copying code all over stack overflow.
Every second word in the program seems to be self but the word parent is only used in the class ResizingCanvas(). Do I have to propagate parent down the call list and use it somehow?
You can call after and update_idletasks on any widget. There are many such functions that can be called on any widget but which have a global effect.
In your case, you'll need to pass some widget into the FlashMessage constructor and save the reference. You can then use the reference to call the functions.
You're passing something called widget that doesn't actually contain a widget. You need to rename it to something more appropriate (eg: var), and then pass in an actual widget.
(Note: you also are calling after incorrectly, which I've fixed in the following example)
For example:
class FlashMessage:
def __init__(self, widget, var, message, count=5, on=500, off=300):
self.widget = widget
...
def delay_show(self, ...):
self.widget.after(ms, var.set, message)
self.widget.update_idletasks()
Then, whenever you create an instance of FlashMessage you need to add a widget as the first parameter.
For example, assuming that count_frame is defined in the context where you create an instance of FlashMessage and it is an actual widget, it might look something like this:
if self.old_windows_cnt == new_windows_cnt :
FlashMessage (count_frame, self.label_this, "No new windows to remove...", \
3, 750, 250)
self.update_cnt_labels()
return

Trigger Tkinter events from a different class in Python

So I've been trying to get into using classes in my tkinter projects, but I seem to have trouble understanding exactly how classes interact. Especially when tkinter is involved. I can accesses variables and values and pass those around, but I can't seem to figure out how to do triggers.
My current problem is with trying to trigger an event from a different class.
A simple version of the problem is this:
from tkinter import *
class Area:
def __init__(self):
self.can = Canvas(width=100, height=100, bg="green")
self.can.pack(in_=window)
self.can.bind("<Button-3>", self.test)
self.square = self.can.create_rectangle(25, 25, 75, 75)
def test(self, event):
self.can.delete("all")
class Trigger:
def __init__(self):
self.button = Button(text="click me", command=?)
self.button.pack(in_=window)
window = Tk()
Area()
Trigger()
window.mainloop()
It creates a green canvas with a square in the middle. When you right click the canvas, the square is removed. I then try to trigger that same behavior from a different class, demonstrated here with a button.
Problem is, I can't for the life of me figure out what to have as a command on the button.
I've tried command=Area.test, but then I get "
TypeError: test() missing 2 required positional arguments: 'self' and
'event'"
I've tried command=Area.test(Area, "event") and command=Area.test(self, "event"), but they return:
AttributeError: type object 'Area' has no attribute 'can'
and
AttributeError: type object 'Area' has no attribute 'can'
I also tried Area().test("event), which gave no error but gave me 2 instances of the canvas, one with the square and one without. The button did nothing then.
Looked into inheritance, so I tried that by putting Area as inheritance on the Trigger class, then do command=self.test("event")
But then got:
AttributeError: 'Trigger' object has no attribute 'can'
So I'm out of ideas.. Am I doing the __init__part wrong?
First, if you want to use a function both as a target of an event and as the value for a command attribute, you should make the event argument optional. You can do that by giving it a default value of None:
def test(self, event=None):
self.can.delete("all")
Second, the Trigger object needs to be given the instance of the Area object so that it can call methods on that object. There are several ways to do this, but the most straight-forward is to pass it in when creating the object. This means you need to modify Trigger.__init__ to accept the parameter, and then you need to pass it in when creating the object.
This is how to modify the __init__ method:
class Trigger:
def __init__(self, area):
self.button = Button(text="click me", command=area.test)
self.button.pack(in_=window)
This is how to pass the Area object to the Trigger object:
area=Area()
Trigger(area)

How to access variables in other classes

In Functions class, I would like to access the variable of the Frame class.
Please tell me if there is any way.
class Functions():
def changeText():
...
...
I want to change the 'text' in the Frame class
ex )Frame.text.SetFont('change text')
GUI element
class Frame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, ....)
....
....
self.text = wx.StaticText(panel, .....)
You can do this by sending an instance of the class to the function:
class myClass(object):
def __init__(self, text):
self.text = text
def changeText(input):
input.text = "world"
example = myClass("hello")
changeText(example)
You will have to tell your objects what to work on. Out of thin air your Functions instance will not know (how should it?) what Frame should be. You could make Frame a global, but I do not think that is a good idea (it will break if you want to work with more than one frame instance). So you would write:
class Functors:
...
def set_text(txt_frame, the_text):
"""txt_frame has to be a :class:`my_txt_frm` instance with ``self.text`` being a ``StaticText`` instance."""
txt_frame.text.SetLabel(the_text)
class my_txt_frm(wx.Frame): # do not name the derived class Frame to make more clear it is derived!
def __init__(# ...
...
self.text = wx.StaticText(#...
So now comes the interesting part: how to tie the parts together? You have to have something like that somewhere in your code:
funct = Functors() # the class which know how to do things on our GUI elements
frm = my_txt_frm(#...
Some lines later...
funct.set_text(frm, 'thenewtext')
So for your app which has the bigger picture it is necessary to keep references to the building blocks to be able to tie them together later.
An orderly way to tie things together is called MVC (see a great example in the wxPython wiki). Even if you do not want to model your app after this paradigm, you can learn from it how to reason about separation of concerns.

Python - Using a binder key to move the entry cursor in tinter

So I asked a question earlier in which #TigerhawkT3 assisted me, however now that I am implementing what was suggested over a program involving 2 classes I am running into an error. The code I am working with is as follows:
from tkinter import *
class Calc:
def __init__(self,parent):
self.displayentry = StringVar()
self.display=Entry(parent, textvariable=self.displayentry)
self.display.pack()
parent.bind('<Return>', CalculatorLogic.equal_input)
class CalculatorLogic:
def equal_input(self, event):
self.display.icursor(END)
root = Tk()
RunGUI=Calc(root)
root.mainloop()
The error I am having with the above code tells me, "TypeError: equal_input() takes exactly 2 arguments (1 given)" when I press the enter key.
Thank you so much for your time and assistance.
You cannot call CalculatorLogic.equal_input. That method is an instance method, and so must be called on an instance.
The way you do this is for your Calc class (or something else...) to create an instance of CalculatorLogic. Also, since equal_input depends on a widget created in Calc, you need to tell that function which instance of Calc to use. To do that, pass in self:
class Calc:
def __init__(self, parent):
self.calc = CalculatorLogic(self)
...
parent.bind('<Return>', self.calc.equal_input)
...
class CalculatorLogic:
def __init__(self, calc):
self.calc = calc
def equal_input(self, event):
self.calc.display.icursor(END)
Note: this isn't the only way to do this. The concept is correct (CalculatorLogic needs a handle to Calc and visa versa), but the implementation can differ. For instance, you could have a third "controller" class that creates an instance of each, and is responsible for communicating between them.

Categories