Can't Properly "connect" a class with its objects - python

I wanted to make a class that would be the template of a "custom widget" for a "game" I'm trying to make. The problem is that I can only get it to work if when I call the class, I specify the Frame object that holds everything together. I've been searching my whole afternoon and couldn't find a concrete answer...
The following code works but instead of just needing to write StatusButton().grid() I have to use StatusButton().frame.grid()
from tkinter import *
from tkinter.ttk import Progressbar
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__()
self.frame = Frame(master, padx = 10, pady = 10, bd= 5, relief = RAISED)
self.label = Label(self.frame, text = "Hunger Bar")
self.pgbar = Progressbar(self.frame)
self.button = Button(self.frame, text = "Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady = 5, ipadx = 15)
return
root = Tk()
buttonslist = [StatusButton(root) for x in range(16)]
for r in range(4):
for c in range(4):
StatusButton(root).frame.grid(row = r, column = c)
root.mainloop()
I'm guessing I'm not properly "conecting" the frame object to the class, even though the former is inside the latter, because when the previous code gets executed, but with StatusButton().grid() instead, the TKinter window pops up normally, but without any content, like if i was "gridding" an empty Frame object. How can I fix this, so that when StatusButton().grid() is run, my "custom widget thing" appears?
Sorry if this is a noob error, it's my first week in programming

The problem is that you aren't taking advantage of your subclass. You initialize Frame with no arguments and then create a new one with arguments. You then create widgets with self.frame as a parent instead of self. Change it to this:
class StatusButton(Frame):
def __init__(self, master):
super(StatusButton, self).__init__(master, padx=10, pady=10, bd=5, relief=RAISED)
self.label = Label(self, text="Hunger Bar")
self.pgbar = Progressbar(self)
self.button = Button(self, text="Eat")
self.label.pack()
self.pgbar.pack()
self.button.pack(pady=5, ipadx=15)

Related

Have a warning with a class attribute variable but unsure if i should ignore or not (python)

I have a warning pop for when i try to define a class attribute but am unsure if i should just ignore it or not.
import tkinter
main_window = tkinter.Tk()
main_window.title('Main Window')
main_window.geometry('640x480+400+200')
main_window.configure(background='white')
class MakeFrame:
def __init__(self, window, row, column, sticky, borderwidth=30, background='#c2cdff'):
self.window = window
self.borderwidth = borderwidth
self.background = background
self.row = row
self.column = column
self.sticky = sticky
self.frame = tkinter.Frame(self.window, borderwidth=self.borderwidth, background=self.background).grid(
row=self.row, column=self.column, sticky=self.sticky)
text_frame = MakeFrame(main_window, 0, 0, 'nsew')
button = tkinter.Button(text_frame.frame, text='Rock', height=1, width=8, background='#f4bbcc', fg='blue')
button.grid(row=0, column=0, sticky='sew')
main_window.mainloop()
For the attribute 'self.frame=....' i get the warning "Function 'grid' doesn't return anything" from my IDE ant it wants to remove the "self.frame =' portion of the line, but if i remove that the code wont work. Is this something i can ignore or is it bad practice writing it the way i have?
The return type of grid() is None, so assigning it to a variable won't be helpful. You should define the variable in one line and call grid() in a separate line, like this:
self.frame = tkinter.Frame(self.window, borderwidth=self.borderwidth, background=self.background)
self.frame.grid(row=self.row, column=self.column, sticky=self.sticky)

Change main class label's text from different class using toplevel in tkinter

Just picked up tkinter recently
I have a program where when a user click a [...] button, it will display a toplevel window containing a calendar and [OK] button inside it.
When the user click the [OK] button, I want it to change [startDate] variable, and [labelStartDate] label in the main window.
I need the [startDate] variable for my next data process. and [labelStartDate] label is to show user that the date is changed.
How to achieve that?
I tried to use command=lambda or stringvar, but honestly I am kinda lost trying to apply it to my program.
from datetime import date
from textwrap import fill
import tkinter as tk
from tkinter import ttk
from tkinter import Toplevel
from tkinter import font
from tkcalendar import Calendar
from turtle import color, width
# Define the GUI
class App(tk.Tk):
def __init__(self):
super().__init__()
# root window
self.title('Main Window')
self.geometry('620x570')
global startDate #variable that I want to use for later data processing
startDate = date.today().strftime("%Y/%m/%d/")
#DATE MENU FRAME
DateMenuBar = ttk.LabelFrame(self.master, borderwidth = 1, text='Setting')
subFrame2 = tk.Frame(DateMenuBar, borderwidth = 1, relief = tk.FLAT, pady=0, padx=0)
#SUB FRAME 2
labelStart = tk.Label(subFrame2, text='Start',font=('meiryoui', 15))
labelStartDate = tk.Label(subFrame2, text=startDate,font=('meiryoui', 15))
btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)
labelStart.pack(side = tk.LEFT, ipadx=10)
labelStartDate.pack(side = tk.LEFT, padx=(30,10))
btnOpenCalendar1.pack(side = tk.LEFT)
subFrame2.pack(fill = tk.X,padx=0, pady=10)
DateMenuBar.pack(fill = tk.X,padx=20, ipadx=20, ipady=20)
def changeStartDate(self):
window = Window(self)
window.grab_set()
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Pick Date")
self.geometry("250x250")
def selectStartDate():
startDate = cal.get_date()
#I got stuck here, trying to figure out how to change the labelStartDate's text
cal = Calendar(self, selectmode = 'day')
cal.pack(padx=20, pady=10)
frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))
btnOK.pack(side = tk.RIGHT, padx=(10,0))
btnCancel.pack(side = tk.RIGHT)
frame.pack(fill = tk.X)
if __name__ == "__main__":
app = App()
app.mainloop()
Edit Note:
I added the missing code to my program so that it can be run by others :)
You can first use tkinter.StringVar() and set the label textvariable to the same, inorder to be able to modify the label's text.
self.labelStartDateVar = tk.StringVar() # Initalizing the text variable
self.labelStartDateVar.set(startDateData.start_date) # Setting initial value of the textvariable.
# Added textvariable as labelStartDateVar
self.labelStartDate = tk.Label(subFrame2, textvariable = labelStartDateVar, font = ('meiryoui', 15))
Further, using some knowledge from this post(of Observer Pattern), it is possible to call a function when a change in the startDate is detected. We do so by defining a new class and using a startDateData object as the global object, and to get the value of startDate itself, we simply need to access it's start_date property startDateData.start_date to set it the same property needs to be set like so -:
startDateData.start_date = cal.get_date()
The full code will look something like this -:
class startDate(object):
def __init__(self):
# Setting the default value as in the OP.
self._start_date = date.today().strftime("%Y年 %m月 %d日")
self._observers = []
return
#property
def start_date(self):
return self._start_date
#start_date.setter
def start_date(self, value):
self._start_date = value
for callback in self._observers:
print('announcing change')
callback(self._start_date)
return
def bind_to(self, callback):
print('bound')
self._observers.append(callback)
startDateData = startDate() # global startDateData object.
# Define the GUI
class App(tk.Tk):
def __init__(self):
super().__init__()
# root window
self.title('Main Window')
self.geometry('620x570')
global startDateData #variable that I want to use for later data processing
###
self.labelStartDateVar = tk.StringVar()
self.labelStartDateVar.set(startDateData.start_date)
startDateData.bind_to(self.updateStartDate) # Binding the updateStartDate function to be called whenever value changes.
###
#SUB FRAME 2
self.labelStart = tk.Label(subFrame2, text='開始',font=('meiryoui', 15))
# Added textvariable as labelStartDateVar
self.labelStartDate = tk.Label(subFrame2, textvariable = self.labelStartDateVar, font = ('meiryoui', 15))
self.btnOpenCalendar1 = tk.Button(subFrame2, height=1, background='#eeeeee', text='...', font=('meiryoui', 8), command=self.changeStartDate)
self.labelStart.pack(side = tk.LEFT, ipadx=10)
self.labelStartDate.pack(side = tk.LEFT, padx=(30,10))
self.btnOpenCalendar1.pack(side = tk.LEFT)
subFrame2.pack(fill = tk.X,padx=0, pady=10)
def updateStartDate(self, startDate) :
self.labelStartDateVar.set(startDate)
return
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Pick Date")
self.geometry("250x250")
# Globally fetch the startDateData object.
global startDateData
def selectStartDate():
# All binded callbacks will be called, when the value is changed here.
startDateData.start_date = cal.get_date()
cal = Calendar(self, selectmode = 'day')
cal.pack(padx=20, pady=10)
frame = tk.Frame(self, borderwidth = 1, relief = tk.FLAT, pady=10, padx=20)
btnOK = tk.Button(frame, height=2,width=8, background='#eeeeee', text='OK', font=('meiryoui', 9),command=selectStartDate)
btnCancel = tk.Button(frame, height=2,width=8, background='#eeeeee', text='Cancel', font=('meiryoui', 9))
btnOK.pack(side = tk.RIGHT, padx=(10,0))
btnCancel.pack(side = tk.RIGHT)
frame.pack(fill = tk.X)
NOTE: As the code provided in the OP, was not adequate enough to be able to test whether this solution works. Further, as the initial code provided seemed to be incomplete, the full code given in the answer at the end may also seem incomplete but still implements all the features present in the code given in the OP.
EDIT: The previous placement of the line startDateData = startDate() was wrong as it was trying to construct an object of a class before it is defined, now the line has been shifted below the class definition of startDate.
EDIT 2: Fixed some of the typos, as mentioned in the comments by #Mario Ariyanto.

Pararell instance of tkinter application window python

I want to create some simple tkinter python app (like StickyNotes on Windows), i have create the class mainApplication and i do not know how to by just simply triggering the button create another instance of this class which will be displayed pararell to other window (or even multiple windows). I know how to assigned function to pushButton, and other simple stuff but the problem is with this pararell window displaying. Thanks in advance for help.
class mainApplication(Frame):
_ids = count(0)
def __init__(self, parent):
""" """
self.id = next(self._ids)
Frame.__init__(self, parent)
self.parent = parent
self.parent.minsize(width=200,height=100)
self.parent.geometry(('%dx%d+%d+%d' % (200, 100, 1700, 0+self.id*100)))
self.initUI()
def initUI(self):
""" """
self.parent.title("a2l")
self.pack(fill=BOTH, expand=True)
style = Style()
style.configure("TFrame", background="#333")
frame1 = Frame(self, style="TFrame")
frame1.pack(fill=X)
self.lbl0 = Label(frame1, text="api", width=7, background="#333", foreground = "red")
self.lbl0.pack(side=TOP, padx=5, pady=5)
self.closeButton = Button(self, text="new", command = self.createNewInstance)
self.closeButton.pack(side=RIGHT, padx=5, pady=5)
#=======================================================================
# self.generateButton = Button(self, text="GENERATE", command = self.)
# self.generateButton.pack(side=RIGHT, padx=5, pady=5)
#=======================================================================
def createNewInstance(self):
y = mainApplication()
return y
if __name__ == "__main__":
root = Tk()
x = mainApplication(root).pack(side="top", expand=False)
Tk().mainloop()
You shouldn't create more than one Tk() window in one application with tkinter. Instead tkinter provides a widget called Toplevel which can be useful for this kind of thing.
It creates another window which can exist along side the Tk() window and alongside other Toplevel widgets.
You could use this to create a series of persistent windows with whatever text or widgets you wanted on them at any kind of trigger including a Button.
See my example below for a demonstration:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.top = [] #list to contain the Toplevel widgets
self.entry = Entry(self.root)
self.button = Button(self.root, text="Create window", command=self.command)
self.entry.pack()
self.button.pack()
def command(self): #called when button is pressed
self.top.append(Toplevel(self.root)) #adds new Toplevel to the list
Label(self.top[len(self.top)-1], text=self.entry.get()).pack() #Adds label equal to the entry widget to the new toplevel
root = Tk()
App(root)
root.mainloop()

Tkinter pack() geometry manager confusion

Just when I thought I understood the pack() manager, I came upon the following problem:
I created two frames (each with a Button) and want to pack them horizontally in another frame (which is the 'main' frame). For some reason, they appear vertically. If I replace the first frame by a simple button, the button + second frame are packed horizontally, using the same pack() instructions.
Here's a distilled (but working) version of the program:
from Tkinter import *
class QuitBtn1(Button):
def __init__(self):
Button.__init__(self)
self["text"] = "Quit"
self["fg"] = "red"
self["command"] = self.quit
class QuitBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Quit", fg = "red", command = self.quit)
b.pack()
class HiBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Hi there", fg = "blue", command = self.quit)
b.pack()
class App(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def say_hi(self):
print "hi there, everyone!"
def createWidgets(self):
self.QUIT = QuitBtn().pack(side = LEFT)
self.hi_there = HiBtn().pack(side = LEFT)
root = Tk()
app = App(master = root)
app.mainloop()
root.destroy()
As run, it produces two, vertically packed, frames. Just switching the names of QuitBtn1 and QuitBtn (which changes the frame into a simple button), changes the packing to horizontal.
I've looked at tens of examples, which seem to somehow avoid this exact structure. Am I doing something wrong here?
The problem is that you aren't giving any of your widgets a parent, so they all default to the root window. Even though you think the buttons are inside the frames, they are not.
Because the default for pack is to put things along the top, when you call pack() on the buttons, they are being stacked in the root window. Your two frames, because nothing is in them, have a size of 1x1 so you can't see them. The are being packed on the left, you just can't see them.
The solution is to properly nest your widgets. For example:
class QuitBtn(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
b = Button(self, text = "Quit", fg = "red", command = self.quit)
b.pack()
def createWidgets(self):
self.QUIT = QuitBtn(self)
...
self.QUIT.pack(side = LEFT)
...
The above creates a button widget inside the QuitBtn frame, and packs it to the top of that frame. Later, that frame (and it's contents) will be packed to the left side of the main frame.
It's also important to separate the creation of your widgets from the layout of your widgets. When you do something like self.QUIT = QuitBtn(...).pack(...), self.QUIT will be set to None because that is what pack(...) returns. Plus, in my experience, moving all of your layout for a given containing frame into a separate block makes it much easier to manage your layouts.
One way is to tell pack what you want to pack each widget in, using the in_= parameter. So:
from Tkinter import *
class QuitBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Quit", fg = "red", command = self.quit)
b.pack(in_=self) # Here
class HiBtn(Frame):
def __init__(self):
Frame.__init__(self)
b = Button(text = "Hi there", fg = "blue", command = self.quit)
b.pack(in_=self) # Here
class App(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def say_hi(self):
print "hi there, everyone!"
def createWidgets(self):
self.QUIT = QuitBtn().pack(side = LEFT)
self.hi_there = HiBtn().pack(side = LEFT)
root = Tk()
app = App(master = root)
app.mainloop()
root.destroy()
Produces a horizontal arrangement of buttons within frames (each within the App frame).
I don't think I've ever needed this myself, but I don't remember ever declaring widgets using top-level classes either.
As a side note, thank you so much for supplying a distilled, version of your code that demonstrates the same issue! I wish this were more common.

How to use scrollbar widget function in another Tkinter class and bind it to buttons

I've created a Tkinter class, Ext() with a scrollbar for any text in the variabel self.text in the create_scrollbar() method. I want to use this method by binding it to Buttons in the class Application so that when the Button is pressed the text shows up in a scrollbar. I've tried to implement this as one class first but it lead to issues when using two init in the same class. How do I invoke create_scrollbar in Ext in the callback, self.callback in the Applications class ?
from Tkinter import *
import Tkinter as tk
class Ext(tk.Frame):
""" a scrollbar creation class, extends the class Application"""
def __init__(self, master):
""" init the frame """
tk.Frame.__init__(self)
self.text = tk.StringVar()
tk.Frame.__init__(self, master)
self.scrollbar = tk.Canvas(master, borderwidth=0, background="#ffffff")
self.frame = tk.Frame(self.scrollbar, background="#ffffff")
self.txt = tk.Scrollbar(master, orient="vertical", command=self.scrollbar.yview)
self.scrollbar.configure(yscrollcommand=self.txt.set)
self.scrollbar.configure(width=500, height=200)
self.txt.pack(side="right", fill="y")
self.scrollbar.pack(side="left", fill="both", expand=True)
self.scrollbar.create_window((0,0), window=self.frame, anchor="nw",
tags="self.frame")
self.create_scrollbar()
def create_scrollbar(self):
tk.Label(self.frame, text=self.text).grid(column=1)
class Application(Frame):
def __init__(self, root):
""" init the frame """
Frame.__init__(self)
self.grid()
self.create_widgets()
def create_widgets(self):
Button = Button(self, text = "Display scrollbar!", command = self.callback)
Button.grid(row = 10, column = 1, sticky = W)
def callback(self):
self.text = "this is a test sentence
print create_scrollbar(t) # Print scrollbarwidget from the Ext() class when the Button is pressed
#Main
root = Tk()
root.title("Maltparser1.0_demo")
root.geometry("900x700")
root.mainloop()
app = Application(root)
master = tk.Tk()
master = Ext(master)
master.title("Instructions")
master.geometry("800x300")
Ext(master).pack(side="top", fill="both", expand=True)
master.mainloop()
To answer your specific question, to call the create_scrollbar method you first need to create an instance of the class. For example:
def callback:
e = Ext(self)
e.create_scrollbar()
However, this won't do what you think it will, because you never tell it what text to display. In callback, self.text lives in the App class, not the Ext class. Perhaps you want to pass the string or a textvariable to the constructor of the Ext class.
As an aside, you code is very confusing. You create a frame named self.frame -- which makes sense -- but a canvas named self.scrollbar, a scrollbar named self.txt, and a stringvar named self.text. You then have a method called create_scrollbar which creates a label. And finally, you create a button from the class Button, and then name it Button. You can't have both a class and an instance with the same exact name.
Maybe those names make sense to you, but as someone who is not you, I think your name choices make your code extremely hard to understand.

Categories

Resources