Thread designed to delete Tkinter Text Widget Content does not operate. - python

Sorry if this is a rather specific problem, but let me try and explain it well.
I've got a text box that a user can add text to by typing in a box and hitting 'Send'.
The problem is that I need to clean out the textbox after fifteen lines have been used.
Here is what I tried to do:
Whenever the user hits send, to add the text to the textbox, it add's a number to a Variable called textlimiter. Because every send button creates a new line as well. I can use a variable to record the number of times something is 'sent' to the textbox.
When the variable exceeds 15, a thread that is watching the variable will wipe the textbox clean.
Thing is, It never seems to work. I've tried asking for help and I got a little from an awesome guy who suggested I rewrite everything into 'frames', so that I can class all my functions, widgets, and variables into one class.
It appears I've got a conflict ion with variable class's. So I tried to set the variable to 'global' inside my class's, and functions using the variable. But that didn't seem to work either.
I can go and rewrite it all into frames I suppose, but I was just going to ask if anyone has a quick fix they can think of here. (I also don't know when the guy can help me once I'm done and if issues arise)
If my question is inappropriate, or somehow not allowed, Tell me and I'll take it down tomorrow when I get up. Sorry if it's bad.
I've got the code here:
# [Importing Modules]
from tkinter import *
import os
import threading
#_________________| Setting Variables
global textlimiter
textlimiter = 0
#____________| Defining Functions
def CONVERT():
chatbox.insert(INSERT,"You: "+USER_ENTRY.get()+ "\n")
INPUT_BOX.set("")
global textlimiter
textlimiter += 1
#_______| Creating Window |
chat = Tk()
chat.title("Net Send Client [0.4]")
chat.geometry('550x500+200+200')
#________| Title |
title = StringVar()
title.set("Net Send Chat\n Type in box to send.")
title_widget = Label(chat,textvariable = title, height = 4)
title_widget.pack()
#_______________________| User Input Box|
INPUT_BOX = StringVar()
USER_ENTRY = Entry(chat,textvariable = INPUT_BOX)
USER_ENTRY.pack(side=TOP, padx = 10, pady = 20)
#___________________________________________________________________| Send Button|
send_button = Button(chat, text="Send",width = 20, command = CONVERT)
send_button.pack(padx = 10, pady = 10)
#_______________________________________________________| Text Box Widget |
chatbox = Text(width = 60, height = 15, relief = SUNKEN)
left = Frame(chatbox)
right = Frame(chatbox)
s_start = Scrollbar(right)
s_start.pack(side=RIGHT)
chatbox.grid(row = 0, column = 0, columnspan = 3)
chatbox.pack()
#__________________________________________| Chat Wizard Checks Text Limit|
class Chatwizard(threading.Thread):
def Chatmonitor():
global textlimiter
if textlimiter >= 15:
chatbox.set(None)
chatbox.insert(INSERT,"Console: Limit Reached, Chat Wiped" + "\n")
Chatwizard.start
chat.mainloop()
As you can see I've tried setting my variables to Global, but nothing seems to change.
If you try running the program, remember you need to enter and send something 15 times for it to supposedly wipe the chatbox.
Thanks for taking a look if you do. I'm really a little stumped right now.

You definitely do not need a thread to monitor the Text widget
(chatbox), because the chatbox itself can tell you how many lines
of text it contains:
numlines = len(chatbox.get("1.0",END).splitlines())
Moreover, Tkinter is not threadsafe. All GUI widgets should be
touched by one and only one thread.
from Tkinter import *
import os
import threading
def CONVERT():
numlines = len(chatbox.get("1.0",END).splitlines())
if numlines > 2:
chatbox.delete("1.0",END)
chatbox.insert(INSERT,"Console: Limit Reached, Chat Wiped" + "\n")
chatbox.insert(INSERT,"You: "+USER_ENTRY.get()+ "\n")
INPUT_BOX.set("")
chat = Tk()
chat.title("Net Send Client [0.4]")
chat.geometry('550x500+200+200')
title = StringVar()
title.set("Net Send Chat\n Type in box to send.")
title_widget = Label(chat,textvariable = title, height = 4)
title_widget.pack()
INPUT_BOX = StringVar()
USER_ENTRY = Entry(chat,textvariable = INPUT_BOX)
USER_ENTRY.pack(side=TOP, padx = 10, pady = 20)
send_button = Button(chat, text="Send",width = 20, command = CONVERT)
send_button.pack(padx = 10, pady = 10)
chatbox = Text(width = 60, height = 15, relief = SUNKEN)
left = Frame(chatbox)
right = Frame(chatbox)
s_start = Scrollbar(right)
s_start.pack(side=RIGHT)
chatbox.grid(row = 0, column = 0, columnspan = 3)
chatbox.pack()
chat.mainloop()

Related

cant change title of program in tkinter

this is my main module(this is for a point of sale system)
from tkinter import *
from SettingsMenuPOS import *
from Globalvariables import *
root = Tk() #mainprogram
root.iconbitmap('D:/Gatlabs logo.ico')
opensettingsmenu = Button(root, text= "Open Settings", command = settingsmain)
root.title(Mname)
opensettingsmenu.grid(row= 0, column= 0)
enter_button = Button( root , text = "ENTER", padx = 20, pady = 10, command= EnterEvent)
enter_button.grid(row= 0, column= 1)
root.mainloop()
im importing a setting module which changes the title of the program
from Globalvariables import *
from tkinter import *
#Mart Name
#user accounts
def settingsmain():
settingmenu = Toplevel()
settingmenu.iconbitmap('D:/Gatlabs logo.ico')
global entryformartname
labelformartname = Label(settingmenu, text = "Enter name of your store")
entryformartname = Entry(settingmenu)
entryformartname.grid(row = 0, column = 0)
setmartname = Button(settingmenu, text = "setname", command = setname)
setmartname.grid(row= 0, column = 1)
settingmenu.mainloop()
def setname():
global Mname, entryformartname
Mname = entryformartname.get()
and im using a global variable Mname to display title of the program.
ive set the variable Mname = "Gatlabs" and the user can change it through entry.
but everytime i try to set the title it doesnt change :(
i hope to get better at coding but i suck. pls help im stuck
I think the problem here is that you are thinking that using -:
root.title(var)
would make it so that anytime you change the var the title will change, but actually its that the title is changed only once and to the value of the var at the time it was supplied.
So if you want it to change everytime you change your var, then rather put it in the same function as the one where you change your var.
def update_title(title) :
global var
var = title
root.title(title)
return
and now every time you change the title run this down with the desired title as the argument.
I hope this solves your problem.
Also i hope you're safe in the time of an ongoing pandemic.
Change setname function like this:
def setname():
global Mname, entryformartname, setmartname
Mname = entryformartname.get()
parent_name = setmartname.winfo_parent()
parent = setmartname._nametowidget(parent_name)
parent.title(Mname)
In your code, you just update Mname variable and window title stays unchanged.
Update:
You can get the master from button widget which is the window itself. Then, change its title.

Issue with adding images to button in tkinter application

I am helping with a small project where we want to add and take items away from a store. The code is below:
from tkinter import *
import tkinter
####################
# Variables
eggs = 0
milk = 0
butter = 0
lemon = 0
guiSize = "800x1280"
def newItemGUI():
main.withdraw()
def addEgg():
global eggs
eggs += 1
updateLabels()
def menu():
global eggs
update(eggs)
itemWindow.destroy()
main.deiconify()
def updateLabels():
eggLabel = Label(itemWindow, text = eggs)
eggLabel.grid(row = 3,column = 0)
itemWindow = Toplevel()
itemWindow.geometry(guiSize)
itemWindow.title("Add a new item")
itemWindow.configure(background = "#a1dbcd")
heading = Label(itemWindow, text="Add new items", font=("Arial",20),background = "#a1dbcd")
heading.grid(row=0, columnspan = 3)
egg = PhotoImage(file ="images/egg.gif")
eggButton = Button(itemWindow, image = egg, command = addEgg)
eggButton.grid(row = 2, column = 0,padx = 10, pady = 10)
eggLabel = Label(itemWindow, text = eggs).grid(row = 3,column = 0)
back = Button(itemWindow, text = "Main Menu", command = menu, width = 15)
back.grid(row = 4, column = 0, padx = 20, pady = 20)
def update(eggs):
items.delete("1.0",END)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
main=tkinter.Tk()
main.geometry(guiSize)
bgColour = "#DDA0DD"
main.configure(background = bgColour)
button1 = Button(main, text="Input new products", width = 20, height = 5, command = newItemGUI)
button1.grid(column = 1, row =2, padx = 20, pady = 20)
label2 = Label(main,text = "Items in the fridge :", font =("Arial,20"), background = bgColour)
label2.grid(row=4, columnspan = 2)
items = Text(main, width = 60, height = 10)
items.insert(END,"Eggs \t:")
items.insert(END,eggs)
items.insert(END,"\n")
items.grid(row=5, columnspan = 4)
main.mainloop()
When you click on the input new products button, this takes you to a new screen. On the screen should be a photo of an egg with a count underneath. For some reason the image of the egg is not showing and the button will not click.
If I change the eggButton from an image to:
eggButton = Button(itemWindow, text = "egg", command = addEgg)
this seems to allow me to click and the variable and it increases. Any idea as to what/where we have gone wrong? I know the path is correct as I can get the button to display a picture of an egg in a Label.
The problem is because the PhotoImage is being stored in a variable named egg which is local to the newItemGUI() function, so it (and associated image object) are being deleted when the function returns. This is a fairly common problem, so your question is likely a duplicate of another (and may get closed).
This PhotoImage documentation mentions avoiding this potential issue the way shown below in the Note near the end.
Regardless, to prevent that from happening, you can store the value somewhere else such as in an attribute of the Toplevel window. I would also recommend changing its name to something more descriptive like egg_image.
Here are changes to your code showing what how it could be done:
itemWindow.egg_image = PhotoImage(file="images/egg.gif")
eggButton = Button(itemWindow, image=itemWindow.egg_image, command = addEgg)

Python/Tkinter dynamically update label

I am currently trying to make a GUI to an existing python program using Tkinter. The program gives the user two options from which the user must choose to either accept or decline. Before using Tkinter the options were placed in the terminal and awaited for a raw_input. (y/n). How can I make this so the canvas text updates with the new data and awaits for the users button click?
To make my question more specific: How can I run another programs code while the Tkinter mainloop is running and make these two interact?
Example code below.
from Tkinter import *
root = Tk()
root.resizable(width=False, height=False)
root.geometry('{}x{}'.format(500,550))
root.wm_title("Tkinter test")
BtnFrame = Frame (root)
BtnFrame.pack(side = BOTTOM)
BtnFrame.place(y=450, x=20)
canvas_1 = Canvas(root, width = "200", height ="300")
canvas_2 = Canvas(root, width = "250", height ="300")
canvas_1.pack(side = LEFT)
canvas_2.pack(side = RIGHT)
textfield_1 = canvas_1.create_text(100,50)
textfield_2 = canvas_2.create_text(100,50,)
def update_textfiel_1(text):
global textfield_1
canvas_1.delete(textfield_1)
textfield = canvas.create_text(100,50,text = text)
def update_textfiel_2(text):
global textfield_2
canvas_2.delete(textfield_2)
textfield1 = canvas1.create_text(100,50,text = text)
Accept = Button(BtnFrame, text="Accept", width=25)
Decline = Button(BtnFrame, text="Decline", width=25)
Accept.pack(side = LEFT)
Decline.pack(side = RIGHT)
root.mainloop()
First off you have some inconsistent variable names in your update_textfiel functions, you can greatly simplify it by using .itemconfigure (documentation for methods on canvas widget)
def update_textfiel_1(new_text):
canvas_1.itemconfigure(textfield_1, text=new_text)
def update_textfiel_2(new_text):
canvas_2.itemconfigure(textfield_2, text=new_text)
If I understand correctly you want a way to have a function that will simply wait for the user to press one of the buttons and then return the result, this is very easy with tkMessageBox:
question = """Do you accept {}?
if you say no you will instead get {}"""
#this message can GREATLY be improved
# But I really don't understand what you are using it for...
def user_confirmation(name1, name2):
response = tkMessageBox.askyesno("Accept or Decline",question.format(name1,name2))
print(response)
if response: # == True
return name1
else:
return name2
I have not yet found a way to make a blocking function that works with the window you have currently...

how to restart a program in tkinter

I am programming a simple game using tkinter and want an option at the end to be able to replay the game, but I am not sure how I would go about doing it. I would like to be able to go back to the beginning where i define init. Also is there a way I could do this and not have to quit it so I could save scores as the player keeps playing.
from tkinter import *
import random
class App:
def __init__(self, master):
player_dice = []
for i in range(1,6):
x = random.randint(1,6)
player_dice.append(x)
self.label = Label(master, text = x , fg = "red").grid(row =0, column =i+1)
self.label = Label(master, text = "Dice:" , fg = "red").grid(row =0, column =1)
self.hi_one = Button(master, text="one", command= lambda: say_one(player_dice)).grid(row = 1, column = 1)
self.hi_two = Button(master, text="two", command= lambda: say_two(player_dice)).grid(row = 1, column = 2)
Here is where I would like to add a button to loop back to init.
def num(var, die, dlist):
new_window = Toplevel(root)
if die == 1:
if guess == total:
result = Message(new_window, text = "You Win").grid(row = 1, column = 1)
else:
result = Message(new_window, text = "You Lose").grid(row = 1, column = 1)
restart = Button(new_window, text = "Play Again", command = ?????).grid(row=1, column = 2)
Easiest way:
Just add a function resetBoard that resets your game.
Obviously there are various parts of the UI that don't need to be re-set, so those can go into __init__, the rest can go into resetBoard() and you can (possibly) call resetBoard() from within __init__.
Correct way:
Implement an MVC or MVP pattern: Separate your data and logic from your UI. Your view (UI) should reflect whatever is in your model, then reseting the game is just a question of reseting the model and firing the correct events so the view is updated (highly simplistic, but the very useful model-view-XXX patterns cannot be properly explained in just a few words.)

Tkinter Radiobutton spawns frames

I'm new to this GUI business with python2.7 and Tkinter. I'm trying to create a new frame depending on which Radiobutton the user choose, like a menu. When I click on a radiobutton it creats a new frame just like I want, but if I continue to click on the same radiobutton, it will create another frame, and another frame, etc. Can't seem to figure out on how to check if the Radiobutton is already marked (clicked on just once).
Hope I made myself clear, thankful for help!
class Books:
""" Books() is the main class for creating the whole interface """
def __init__(self):
""" Initialize the first function in class Books() """
self.library = "library.txt"
self.filepath = os.getcwd() + "/" + self.library
self.window = Tk()
self.window.title("Personal library")
self.window.wm_iconbitmap(default="myicon.ico")
userChoice = Frame(self.window, height = 1, bd = 1, relief = RIDGE)
userChoice.pack(side = TOP, pady = 10, padx = 5)
self.menuChoice = IntVar()
btAddBooks = Radiobutton(userChoice, text = "Add a new book to the library", value = 1, variable = self.menuChoice, command = self.processChoice)
btAddBooks.grid(row = 1, sticky = W)
btFindBooks = Radiobutton(userChoice, text = "Print info about a book", value = 2, variable = self.menuChoice, command = self.processChoice)
btFindBooks.grid(row = 2, sticky = W)
btPrintBooks = Radiobutton(userChoice, text = "Print all book titles in library", value = 3, variable = self.menuChoice, command = self.processChoice)
btPrintBooks.grid(row = 3, sticky = W
def processChoice(self):
""" Used to handle user choice of Radiobuttons """
if self.menuChoice.get() == 1:
self.processAddBooks()
elif self.menuChoice.get() == 2:
self.processFindBook()
elif self.menuChoice.get() == 3:
self.processShowBooks(self.filepath)
def processAddBooks(self):
""" Add a new book to the library. """
# Create a new frame
questions = Frame(self.window, height = 1, bd = 1, relief = SUNKEN)
questions.pack(fill = X, pady = 10, padx = 5)
# Do stuff with frame here...
Well, if you only need one frame to be open at a time, you can call frame.destroy() on the previous frame before you instantiate the new frame. However, this approach will require that there be something initialized for Tkinter to destroy the first time one of the buttons is selected, otherwise you'll get an error. For my purpose, I just created a throwaway class with a destroy method that did nothing, then used an instance of that class as a placeholder bound to that variable until my Toplevel widget was created for the first time. If you want multiple frames open at the same time, just not duplicates of the same option, try using a different variable name for each frame and only creating the frame if not frame.winfo_exists()--though I'm not 100% sure this wouldn't be susceptible to the same issue of needing a placeholder assigned to that variable until the frame is created the first time. If such is needed, the placeholder class would need a winfo_exists() method that would return False.

Categories

Resources