Modify Tkinter Label from another class in an other file - python

In main.py there is class MainFrame which display Player._money
In lands.py there is class Lands which allows the player to buy more lands.
Inside class Lands, there is a function called def buy_lands(self) that respond to a button.
here's my problem:
When buy_lands(self) is clicked, i've managed to update the label that shows the Player._lands as it's in the same class, same frame. However, the label that display the money is in a different frame, different class and a different file as it's in main.py class MainFrame.
How can i update Player._money in MainFrame from buy_lands(self) in class Lands without having a circular import error?
Here's the code if that's help:
main.py
import tkinter as tk
from tkinter import ttk
from lands import Lands
from player import Player
class MainFrame(ttk.Frame):
def __init__(self, container):
super().__init__(container)
options = {'padx': 5, 'pady': 5}
self.player = Player._name
self.__create_widgets()
# show the frame on the container
self.pack(**options)
def __create_widgets(self):
# Initialize style
s = ttk.Style()
# Frame Top Menu
s.configure('Menu.TFrame', background='blue') # define the style
self.menu = ttk.Frame(self, height=100, width=450, style='Menu.TFrame') # create the frame
self.menu.pack() # place the frame
# Main Frame
s.configure('Main.TFrame', background='red')
self.main = ttk.Frame(self, height=300, width=300, style='Main.TFrame')
self.main.pack()
# Create Widgets
self.name_label = ttk.Label(self.menu, text=f'Welcome {Player._name}')
self.money_label = ttk.Label(self.menu, text=f'money: {Player._money} £')
self.button_1 = ttk.Button(self.menu, text="Button 1")
self.lands_button = ttk.Button(self.menu, text="Lands", command=self.show_lands)
self.button_3 = ttk.Button(self.menu, text="Button 3")
self.button_4 = ttk.Button(self.menu, text="Button 4")
# Display Widgets
self.name_label.grid(column=0, columnspan=1, row=0, sticky='w')
self.money_label.grid(column=2, row=0, columnspan=3, sticky='e')
self.button_1.grid(column=0, row=1)
self.lands_button.grid(column=1, row=1)
self.button_3.grid(column=2, row=1)
self.button_4.grid(column=3, row=1)
def show_lands(self):
for widget in self.main.winfo_children():
widget.destroy()
Lands(self.main)
lands.py
import tkinter as tk
from tkinter import ttk
from player import Player
class Lands(ttk.Frame):
def __init__(self, container):
super().__init__(container)
self.lands = Player._lands
options = {'padx': 5, 'pady': 5}
# show the frame on the container
self.pack(**options)
self.__create_widgets()
def __create_widgets(self):
self.lands_label = ttk.Label(self, text=f'You have {Player._lands} acres of lands')
self.buy_lands_button = ttk.Button(self, text="Buy Lands", command=self.buy_lands)
self.lands_label.pack()
self.buy_lands_button.pack()
def buy_lands(self):
Player._money -= 5000
Player._lands += 10
self.lands_label.config(text=f'You have {Player._lands} acres of lands')
self.lands_label.pack()
print(Player._money)
print(Player._lands)
i've tryed lambda methods but as i'm still learning i'm not too sure how to use it. I've tried global method but again because it's not in the same file, it doesn't work. And i tried to import MainFrame which shows a circular import error.

Related

My scrollbar doesn't work - tkinter in python

Well, I'm adding many buttons to my Canvas called "itemsFrame" in order to test the ScrollBar, but it doesn't work
Well, I haven't tried much because I don't know about the subject, I've investigated how to do it but it only comes out with small buttons without being able to resize them
from tkinter import Frame, Label, Button, Entry, messagebox, ttk, Scrollbar, Canvas
import base64
import json
class WatchNewsFrame(Frame):
name = "WatchNewsFrame"
def __init__(self, parent):
super().__init__()
self.Parent = parent
self.initializecomponents()
pass
def set_news(self):
data = json.load(open(self.file))["News"]
cont = 1
for key in range(10):
Panel = Button(self.itemsFrame)
Panel.config(bg="#656565", activebackground="#808080")
Panel.place(relwidth=.95, relheight=0.245, rely=.028+key*0.25, relx=.5, anchor="n")
pass
def initializecomponents(self):
Frame.__init__(self, self.Parent)
self.itemsFrame = Canvas(self)
self.ItemsScrooll = Scrollbar(self, orient="vertical")
# this frame
self.config(background=self.Parent["background"])
self.place(relwidth=0.95, relheight=0.95, relx=0.5, rely=0.5, anchor="center")
# ItemsScrooll
self.ItemsScrooll.pack(side="right", fill="y")
# itemsFrame
self.itemsFrame.config(bg="#606060", highlightbackground="#FFFFFF", yscrollcommand=self.ItemsScrooll.set)
self.itemsFrame.place(relheight=0.85, relwidth=0.9, relx=0.5, rely=0.5, anchor="center")
self.itemsFrame.bind_all("<MouseWheel>", self.on_mouse_wheel)
self.ItemsScrooll.config(command=self.itemsFrame.yview)
# events
self.set_news()
pass
pass

How do I prevent prevent multiple instances of the same window opening?

I'm currently using tkinter to create a GUI for my program. If I open the golf quiz window and open the help window, then close the golf quiz window and re-open it, I am able to click the help window button and open another instance of the help button. How do I set the Help button to be disabled while the Help window is open?
from tkinter import *
from functools import partial
class Welcome_Screen:
def __init__(self, parent):
self.welcome_screen_frame = Frame(width=200, height=200, pady=10)
self.welcome_screen_frame.grid()
self.quiz_welcome_screen_label = Label(self.welcome_screen_frame, text = "quiz game", font="Arial 20 bold", padx=10)
self.quiz_welcome_screen_label.grid(row=0)
self.welcome_screen_buttons_frame = Frame(self.welcome_screen_frame)
self.welcome_screen_buttons_frame.grid(row=2)
self.golf_quiz_welcome_screen_button = Button(self.welcome_screen_buttons_frame, text="Golf Quiz", font="Arial 10 bold", command=self.golf_quiz_game, padx=10, pady=10)
self.golf_quiz_welcome_screen_button.grid(row=2, column=0, padx=5)
def golf_quiz_game(self):
get_golf_quiz_game = golf_quiz_game(self)
class golf_quiz_game:
def __init__(self, partner):
partner.golf_quiz_welcome_screen_button.config(DISABLED)
self.golf_quiz_box = Toplevel()
self.golf_quiz_box.protocol('WM_DELETE_WINDOW', partial(self.close_golf_quiz_game, partner))
self.golf_quiz_frame = Frame(self.golf_quiz_box)
self.golf_quiz_frame.grid()
self.golf_quiz_heading = Label(self.golf_quiz_frame, text="Golf Quiz game",
font="arial 18 bold", padx=10, pady=10)
self.golf_quiz_heading.grid(row=0)
self.golf_quiz_history_help_dismiss_buttons_frame = Frame(self.golf_quiz_frame)
self.golf_quiz_history_help_dismiss_buttons_frame.grid(row=6, pady=10)
self.help_button = Button(self.golf_quiz_history_help_dismiss_buttons_frame, text="Help", font="Arial 10 bold",command=self.Help, padx=10, pady=10)
self.help_button.grid(row=6, column=1, padx=5)
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
def Help(self):
get_help = Help(self)
class Help:
def __init__(self, partner):
partner.help_button.config(state=DISABLED)
self.help_box = Toplevel()
self.help_box.protocol('WM_DELETE_WINDOW', partial(self.close_Help, partner))
self.help_frame = Frame(self.help_box)
self.help_frame.grid()
self.help_heading = Label(self.help_frame, text="Help", font="arial 18 bold")
self.help_heading.grid(row=0)
self.help_text = Label(self.help_frame, text="Test",
width=60, wrap=400)
self.help_text.grid(row=1)
self.help_button = Button(self.help_frame, text="Dismiss", width=10, font="Arial 10 bold", command=partial(self.close_Help, partner), padx=10, pady=10)
self.help_button.grid(row=2, pady=10)
def close_Help(self, partner):
if partner.help_button.winfo_exists():
partner.help_button.config(state=NORMAL)
self.help_box.destroy()
# main routine
if __name__ == "__main__":
root = Tk()
root.title("quiz game")
something = Welcome_Screen(root)
root.mainloop()
Here is how you can do that (this is also the size (linewise (approx.)) of the minimal reproducible example which you should have provided):
from tkinter import Tk, Toplevel, Button
def close_top(top):
btn.config(state='normal')
top.destroy()
def open_help():
btn.config(state='disabled')
top = Toplevel(root)
top.protocol('WM_DELETE_WINDOW', lambda: close_top(top))
top.focus_force()
# put the rest of help stuff here
root = Tk()
btn = Button(root, text='Help', command=open_help)
btn.pack()
root.mainloop()
Class based approach:
from tkinter import Tk, Toplevel, Button
# this would be the window from where you open the help window
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.btn = Button(self, text='Help',
command=lambda: self.open_help(self.btn))
self.btn.pack()
def open_help(self, btn):
HelpWindow(self, btn)
# this would be the help window
class HelpWindow(Toplevel):
def __init__(self, master, button):
Toplevel.__init__(self, master)
self.button = button
self.button.config(state='disabled')
self.focus_force()
self.protocol('WM_DELETE_WINDOW', self.close)
def close(self):
self.button.config(state='normal')
self.destroy()
MainWindow().mainloop()
Few things:
First of you can simply inherit from container and window classes that way you don't have to separately create them in the class and you can easily reference them in the class using just self
.focus_force() does what it says, it forces focus on the widget
Important (suggestions)
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.
You need to make use of class variables inside golf_quiz_game in order to have only one instance of Help window:
class golf_quiz_game:
# class variables
_help_button = None # reference to instance "Help" button
_help_win = None # reference to instance "Help" window
def __init__(self, partner):
...
# set button state based on whether "Help" window is open or not
self.help_button.config(state="normal" if self.__class__._help_win is None else "disabled")
# update class reference of "Help" button
self.__class__._help_button = self.help_button
def close_golf_quiz_game(self, partner):
partner.golf_quiz_welcome_screen_button.config(state=NORMAL)
self.golf_quiz_box.destroy()
# update class reference of "Help" button
self.__class__._help_button = None
def Help(self):
if self.__class__._help_win is None:
# no "Help" window is open, create one
self.__class__._help_win = Help(self)
def help_closed(self):
if self.__class__._help_button:
# enable the "Help" button
self.__class__._help_button.config(state="normal")
# update "Help" window status
self.__class__._help_win = None
class Help:
...
def close_Help(self, partner):
self.help_box.destroy()
# notify partner that "Help" window is closed
partner.help_closed()

Adding notebook tabs in tkinter - how do I do it with a class-based structure? (Python 3)

I wrote a tkinter application that had widgets displayed on two frames, similar to this example, which successfully runs.
from tkinter import *
from tkinter import ttk
root = Tk()
root.title("Example")
notebook = ttk.Notebook(root)
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)
notebook.add(frame1, text="Frame One")
notebook.add(frame2, text="Frame Two")
notebook.pack()
#(The labels are examples, but the rest of the code is identical in structure).
labelA = ttk.Label(frame1, text = "This is on Frame One")
labelA.grid(column=1, row=1)
labelB = ttk.Label(frame2, text = "This is on Frame Two")
labelB.grid(column=1, row=1)
root.mainloop()
I decided that I should try to restructure the program to use a class (which I'm admittedly not very familiar with). However, I'm unsure what I should do to allow the widgets to appear on different frames (everything else works okay). For instance, the following produces a "TypeError: init() takes from 1 to 2 positional arguments but 3 were given." So presumably I'd need to initialise with an extra argument, but I'm not sure how the notebook would be worked into that, or if that's even the approach I should be taking. (The program will run if the "frame1" and "frame2" arguments are removed from the labels, it will, however, display the same thing on both frames).
from tkinter import *
from tkinter import ttk
class MainApplication(ttk.Frame):
def __init__(self, parent):
ttk.Frame.__init__(self, parent)
self.labelA = ttk.Label(self, frame1, text = "This is on Frame One")
self.labelA.grid(column=1, row=1)
self.labelB = ttk.Label(self, frame2, text = "This is on Frame Two")
self.labelB.grid(column=1, row=1)
root = Tk()
root.title("Example")
notebook = ttk.Notebook(root)
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)
notebook.add(frame1, text="Frame One")
notebook.add(frame2, text="Frame Two")
notebook.pack()
MainApplication(root).pack()
root.mainloop()
I'm interested in a solution, but I'm also interested in learning what the class is doing differently compared to the standalone widgets.
This would be one way to generalize the application as a class. You want to eliminate the repeated code.
from tkinter import *
from tkinter import ttk
class Notebook:
def __init__(self,title):
self.root = Tk()
self.root.title(title)
self.notebook = ttk.Notebook(self.root)
def add_tab(self,title,text):
frame = ttk.Frame(self.notebook)
self.notebook.add(frame,text=title)
label = ttk.Label(frame,text=text)
label.grid(column=1,row=1)
self.notebook.pack()
def run(self):
self.root.mainloop()
nb = Notebook('Example')
nb.add_tab('Frame One','This is on Frame One')
nb.add_tab('Frame Two','This is on Frame Two')
nb.run()
I suggest you split the different Frames within notebook into separate files.
I used from tab2 import * because I wanted this to remain in the namespace without adding the file/class prefix ie. I didn't want to write: tab1.Tab1(notebook)
main.py
import tkinter as tk
from tkinter import ttk
from tab1 import *
from tab2 import *
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
notebook = ttk.Notebook(parent)
Tab1frame = Tab1(notebook)
Tab2frame = Tab2(notebook)
notebook.add(Typ1frame, text='TAB1')
notebook.add(Typ2frame, text='TAB2')
notebook.pack()
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()
tab1.py
import tkinter as tk
from tkinter import ttk
class Typ14(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
shell_frame=tk.LabelFrame(self, text="Sample Label Frame", padx=5,pady=5)
shell_frame.grid(row=0,column=0,padx=5,pady=5)
To ensure that your code is clear and purposeful, you should create a class for your main application, and a class for each tab. You don't need to separate tab classes into separate files, but if you're working with other developers it would be in your best interest.
The code below shows your code reformatted to have 3 classes: 1 for the main app (MainApplication), and 2 for each tab (Frame1 and Frame2). In addition, I've imported tkinter as tk for referential clarity.
import tkinter as tk
from tkinter import ttk
class MainApplication(tk.Tk):
def __init__(self):
super().__init__()
self.title("Example")
self.geometry('300x300')
self.notebook = ttk.Notebook(self)
self.Frame1 = Frame1(self.notebook)
self.Frame2 = Frame2(self.notebook)
self.notebook.add(self.Frame1, text='Frame1')
self.notebook.add(self.Frame2, text='Frame2')
self.notebook.pack()
class Frame1(ttk.Frame):
def __init__(self, container):
super().__init__()
self.labelA = ttk.Label(self, text = "This is on Frame One")
self.labelA.grid(column=1, row=1)
class Frame2(ttk.Frame):
def __init__(self, container):
super().__init__()
self.labelB = ttk.Label(self, text = "This is on Frame Two")
self.labelB.grid(column=1, row=1)
if __name__ == '__main__':
app = MainApplication()
app.mainloop()
As you can imagine, this will allow you to create additional classes to add frames to your tab classes. The code below shows an alteration to class Frame1 above, and the addition of class Frame1FrameA which does just this.
class Frame1(ttk.Frame):
def __init__(self, container):
super().__init__(container)
self.labelA = ttk.Label(self, text = "This is on Frame One")
self.labelA.grid(column=1, row=1)
self.frame = Frame1FrameA(self)
self.frame.grid(row=1, columnspan=2)
class Frame1FrameA(ttk.Frame):
def __init__(self, container):
super().__init__(container)
self.LabelA = ttk.Label(self, text="LabelA in FrameA in tab Frame1")
self.LabelA.grid(column=0, row=0)
self.LabelB = ttk.Label(self, text="LabelB in FrameA in tab Frame1")
self.LabelB.grid(column=1, row=0)

IntVar().trace() not working

I'm just getting started coding in Python/Tkinter for a small Pymol plugin. Here I'm trying to have a toggle button and report its status when it is clicked. The button goes up and down, but toggleAVA never gets called. Any ideas why?
from Tkinter import *
import tkMessageBox
class AVAGnome:
def __init__(self, master):
# create frames
self.F1 = Frame(rootGnome, padx=5, pady=5, bg='red')
# checkbuttons
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(self.F1, text='AVA', indicatoron=0, variable=self.AVAselected)
# start layout procedure
self.layout()
def layout(self):
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
#entry and buttons
self.AVAbutton.pack(side=LEFT)
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
def __init__(self):
open_GnomeUI()
def open_GnomeUI():
# initialize window
global rootGnome
rootGnome = Tk()
rootGnome.title('AVAGnome')
global gnomeUI
gnomeUI = AVAGnome(rootGnome)
I tested your code with Pymol.
Problem is because you use Tk() to create your window. You have to use Toplevel() and then it will work correctly with trace() or with command=.
Pymol is created with tkinter which can have only one window created with Tk() - it is main window in program. Every other window has to be created with Toplevel().
I have attached a working version of your code below. You can refer to it to learn where you went wrong. Generally, you have to mind how you structure your code if you are using a class format.This will help you visualize your code and debug better. You can read this discussion to help you.
from Tkinter import *
import tkMessageBox
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
if __name__ == '__main__':
rootGnome = Tk()
rootGnome.title('AVAGnome')
gnomeUI = AVAGnome(rootGnome)
gnomeUI.pack(fill="both", expand=True)
gnomeUI.mainloop()
Update: The above code structure is for standalone tkinter programme. I am attempting to convert this working code to follow Pymol plugin example. Revised code is posted below and is susceptible to further revision.
# https://pymolwiki.org/index.php/Plugins_Tutorial
# I adapted from the example in the above link and converted my previous code to
#
from Tkinter import *
import tkMessageBox
def __init__(self): # The example had a self term here.
self.open_GnomeUI()
class AVAGnome(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
# create frames
self.F1 = Frame(self, padx=5, pady=5, bg='red')
# checkbutton
self.AVAselected = IntVar()
self.AVAselected.trace("w", self.toggleAVA)
self.AVAbutton = Checkbutton(
self.F1, text='AVA', indicatoron=0, width=10,
variable=self.AVAselected)
# start layout procedure
self.F1.pack(side=TOP, fill=BOTH, anchor=NW)
self.AVAbutton.pack(side=LEFT) #entry and buttons
def toggleAVA(self, *args):
if (self.AVAselected.get()):
avastatus = "selected"
else:
avastatus = "unselected"
tkMessageBox.showinfo("AVA status", avastatus)
# Note, I added a "self" term throughout function.
# Try w/ & w/o "self" to see which works.
def open_GnomeUI(self):
self.rootGnome = Tk()
self.rootGnome.title('AVAGnome')
self.gnomeUI = AVAGnome(self.rootGnome)
self.gnomeUI.pack(fill="both", expand=True)
self.gnomeUI.mainloop()

Python GUI - Linking one GUI in a class to another class

What I am trying to do is to link a GUI from one class in a separate file to another.
My first class is a main menu which will display a few buttons that will link to another window.
The second class displays a different window, but the problem I am having at the moment is that I don't know how to link the button in the first class to call the second class.
Here's the code I have so far:
First file, the main menu:
from tkinter import *
import prac2_link
class main:
def __init__(self,master):
frame = Frame(master, width=80, height=50)
frame.pack()
self.hello = Label(frame, text="MAIN MENU").grid()
self.cont = Button(frame,text="Menu option 1", command=prac2_link.main2).grid(row=1)
root = Tk()
application = main(root)
root.mainloop()
second file:
from tkinter import *
class main2:
def __init__(self):
frame1 = Frame(self, width=80, height=50)
frame1.pack()
self.hello = Label(frame1, text="hello, its another frame").grid(row=0,column=0)
To create a new window, you have to use a Toplevel widget. You can use it as a superclass for your main2 class:
class main2(Toplevel):
def __init__(self):
Toplevel.__init__(self)
self.frame= Frame(self, width=80, height=50)
self.label = Label(self.frame, text='this is another frame')
self.frame.grid()
self.label.grid()
Then you only have to create an instance in the event handler of the Button in the other class:
class main1:
def __init__(self, master):
# ...
self.cont = Button(frame,text="Menu option 1", command=self.open_main2).grid(row=1)
def open_main2(self):
prac2_link.main2()

Categories