Tkinter - Entry .get() from another class - python

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.

Related

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 widget calling a method from another widget

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.

python tkinter inside class - getting state of widgets

I've written a program and I'm learning about classes and thus, rewriting the code to take advantage of classes and objects.
My problem is if I create a tkinter widget "dropdown menu" from a class, I can not retrieve the get.state variable.
class ...:
def make_drop_menu(self,parent,drop_opts,drop='empty',state=''):
self.parent = parent
self.drop = drop
self.drop_opts = drop_opts
self.state = tk.StringVar()
self.state.set(self.drop_opts[0]) # sets default value on drop (drop down menu)
self.drop = OptionMenu(self.parent, self.state, *self.drop_opts) # completed drop
return self.drop
----------------
class main:
self.widget = self.var.make_drop_menu(self.obj_mainwindow.btm_frame, self.col_opts)
Added for clearity:
window = windowclass() #windowclass is the style for tkinter. Makes a tkwindow.
self.widget = self.windowclass.var.make_drop_Menu() #
I've left out some code to simplify. But my main problem is that I create a widget called self.widget which builds my drop down menu. However, I have NO idea how to get the state of the drop down.
Hopefully the code makes sense.
If your class is named MyClass, then to access the state variable you would use the instance of the class. In your case it appears that self.var is the instance of your class, so it would look something like this:
self.var = MyClass()
self.var.make_drop_menu(...)
...
print(self.var.state.get())
Made the make_drop_menu class independent of any other class object. That way I could make a drop menu, assign the parent tk window, and create a method for getting the state variable when called.
Thanks to Bryan Oakley for the help!

python calling similar variables

I have a big problem using tkinter with self here is my code
Could people please give an answer, thanks! The error I get is something like,self could not be given a variable outside a function.
from tkinter import *
root = Tk()
class start():
global self
self = root
def __init__():
self.title('__init__')
self.geometry('300x300')
__init__(self)
class window_extra():
def canvas(self):
global self
selfc = Canvas(self, bg='black').pack()
canvas(self)
self.mainloop()
Thanks!
You should not use self as a variable name as it is used to specify if something is an attribute of the instance of the class.
You do not need to use global in classes either as class attributes are used in most cases when dealing with variables that are needed through the class.
Judging by the code you have shown I think you are trying to do something like this:
from tkinter import *
class start():
def __init__(self, root):
self.master = root
self.master.title('__init__')
self.master.geometry('300x300')
Canvas(self.master, bg='black').pack()
root = Tk()
start(root)
root.mainloop()
However I believe you are struggling with the OOP method of programing and I would suggest to not use OOP to start with if this is the case.
Maybe take a few tutorials on youtube or hit up Codecadamy.
In response to your comments:
In my Opinion using init properly is a bad idea. I use it as a regular def. I doesn't matter if I use self global, unless the function/class variable is called self.
I respect the proper use of init, but I just find the whole thing with, init and self.master I just don't get any of it!
Lack of understanding a thing does not mean said thing is bad. The use of self.master is there to provide a class attribute that ties back to the root Tk() variable. This allows any method within the class to interact with the instance of Tk(). I can't speak to other programing languages but the use of self is a very important in OOP for python. It may not be 100% required to reserve self for referencing to either the instance of the object or the class attribute but it is the accepted and known use of self and really should not be changed/overwritten.
I restructured for some simplicity, but I think that you need a better understanding of objects in Python before going too much further down the GUI route. I think that you mean something like this:
from tkinter import *
# creates a subclass of Tk() called 'Application'; class names by convention
# use CamelCase; best to stick with convention on this one
class Application(tkinter.Tk):
# you don't have to create an explicit invocation of '__init__', it
# is automatically run when you instantiate your class (last line)
def __init__():
super().__init__() # initialize the super class (the 'tkinter.Tk()')
self.title('__init__') # use 'self' to refer to this object - do not declare it as global! it is only valid within the object!
self.geometry('300x300')
self.my_canvas = tkinter.Canvas(self, bg='black') # save an instance of your canvas for easy reference later
self.my_canvas.pack() # pack as a separate step (only required if you plan to use the canvas later... or ever)
self.mainloop() # begin the tkinter loop
# this 'if __name__ ...' is a good idea in most cases, allows you to import `Application` into other
# files without actually running it unless you want to from that other file
if __name__ == '__main__':
Application() # start your class

Recommend way to handle unit conversion

I'm making a GUI with pyqt4 and python. Right now I have a QLineEdit and QComboBox, where the QLineEdit displays the values and the QComboBox can be used to change units. I'm using signals and slots to handle real time unit/value feedback for the user but I'm having problems understanding how to programmatically work with the values as I need them all to be in standard units. Here's what I've got so far, the combo_box_line_edit_list is a list of list where I wrap the combo box and line list together
class UnitConverterSignaler(QtCore.QObject):
def __init__(self, combo_box_line_edit_list):
super(QtCore.QObject, self).__init__()
self.combo_box_line_edit_list = combo_box_line_edit_list
self.combo_box_list = [line_edit_combo_box[0] for line_edit_combo_box in combo_box_line_edit_list]
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].connect(line_edit.convert_units)
line_edit.store_unit_state(combo_box.currentText())
line_edit.standard_unit = combo_box.itemText(1)
def convert_to_standard(self):
for combo_box in self.combo_box_list:
combo_box.setCurrentIndex(0)
def convert_to_international(self):
for combo_box in self.combo_box_list:
combo_box.setCurrentIndex(1)
def toggle_unit_conversion(self, hold_line_values_steady):
for combo_box in self.combo_box_list:
if hold_line_values_steady:
combo_box.do_not_convert_units_on_change()
else:
combo_box.convert_units_on_change()
def convert_units_on_change(self):
"""
Changes the value of the line edit each time the combo box is changed
"""
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].connect(line_edit.convert_units)
combo_box.currentIndexChanged['QString'].disconnect(line_edit.store_unit_state)
def do_not_convert_units_on_change(self):
"""
Holds the line edit value constant in spite of combo box changes
"""
for combo_box, line_edit in self.combo_box_line_edit_list:
combo_box.currentIndexChanged['QString'].disconnect(line_edit.convert_units)
combo_box.currentIndexChanged['QString'].connect(line_edit.store_unit_state)
Instantiated & used in another class
self.lockCellCheckBox.toggled.connect(self.unit_converter_signaler.toggle_unit_conversion)
self.internationalPushButton.clicked.connect(self.unit_converter_signaler.convert_to_international)
self.standardPushButton.clicked.connect(self.unit_converter_signaler.convert_to_standard)
I've also monkey patched the QLineEdit instead of subclassing so I can make quick changes with QtDesigner.
# monkey patch slot onto line_edit
def convert_units(line_edit, end_unit):
converted_unit_value = line_edit.unit_registry.convert(float(line_edit.text()), line_edit.stored_unit_state, str(end_unit))
line_edit.setText(str(converted_unit_value))
line_edit.stored_unit_state = str(end_unit)
# monkey patch slot onto line_edit
def store_unit_state(line_edit, unit):
line_edit.stored_unit_state = str(unit)
Would the most generalized way to get the standard units out in my main program be the creation of a signal for each combo box/line edit in the UnitConverter?
From what I understood so far: you have many combo-box/line-edit pairs and the entered values should always be converted to standard units (e.g. displayed on a third QLabel or whatever).
Would the most generalized way to get the standard units out in my main program be the creation of a signal for each combo box/line edit in the UnitConverter?
No, you don't have to. A slot in python (or especially in pyqt) can be any callable object. A callable object is an object with method __call__(self) implemented.
Therefore I would suggest you to create a class which takes the related object(s) as parameter(s) in the contructor and changes them in __call__(self). Something like this:
class ConverterSignal:
def __init__(whatever_you_want_to_refer_to):
self.whatever_you_want_to_refer_to = whatever_you_want_to_refer_to
def __call(self)__:
""" Here you can refer to whatever_you_want_to_refer_to and do whatever you want with it """
The connection is done as following (for the combo box as an example):
self.connect(combo_box, QtCore.SIGNAL('activated(int)'), ConverterSignal(whatever_you_want_to_refer_to))
Here an instance of the class ConverterSignal is created and will be called if the corresponding signal is emitted.

Categories

Resources