i have a folderscreen that displays buttons from a loop, the buttons have title of the files saved, when i click the buttons, it opens a new page that shows the content of the file, but when i go back to the folder screen, 2 sets of the button in the loop are added to the existing buttons.
what i want is everytime i leave the folderscreen, i want the buttons cleared, so that when i go back, it will run the loop code again and shows only the buttons from the loop without repetition.
or may be there is a way i can stop the loop code from running if the boxlayout that house the buttons is not empty. here is my code:
def on_enter(self, *args):
objf = mm.Storenote()
objf.tif(mm.DB.hod)
for i in range(len(mm.Storenote.ht)):
self.b = Button(text= (mm.Storenote.ht[i]), font_size = "25sp")
#self.b.background_normal = ""
self.b.background_color = 0,0,1,1
self.b.ids = {"id":mm.Storenote.nid[i]}
self.b.size_hint=(1, None)
self.b.size = (dp(370), dp(50))
self.b.bind(on_press=self.build_clickk(self.b))
self.ids.lpl.add_widget(self.b)
#self.ids.lpl.add_widget(self.l[i])
def build_clickk(self, b):
def clickk(*args):
ci = b.ids.id
print(ci)
objff = mm.Show()
objff.getter(ci)
self.manager.current = "Readpage"
return clickk
def on_leave(self, *args):
self.ids.lpl.remove_widget(self.b)
the def on_leave function only remove one of the buttons and add 2 new sets of the button each time i go back to the folderscreen
You can use self.ids.lpl.clear_widgets() right before your for-loop. This way you ensure that the layout is empty before adding new widgets.
If you add many widgets, try using def on_pre_enter(). kivy will render the widgets before entering the screen and this may prevent "flickering" when building all widgets.
Related
Im new to python and started to create a gui with tkinter. My code works fine for usage in one class. But to have a good structure i want to put all Toplevels into an own class. Im facing following problem:
The function whatever in class A is creating a toplevel with options listed in a combobox via another class. I want to wait for an option to be selected until the following code in function whatever is execetued. But this doesnt work.
I was trying around with loops, threads and tkinter to solve this problem, but it didnt work.
I dont even now which way is right..
class OptionWindow:
def __init__(self, root, options, option)
#Toplevel with Combobox & Options
self.option = option
button = Button(toplevel, text = "Select", command=self.get_option)
def GetOption(self):
self.option = combobox.get
if self.option !="":
toplevel.destroy()
class A:
def __init__(self, root):
# GUI with Labels, TreeView, Buttons..
self.options = [A,B,C,D]
self.option = ""
def whatever(self):
OptionWindow(self.root, self.options, self.option)
#How to wait till self.option != "" ?
print("Option: ",option)
# continuing code were option is used
while loop till var is set: TopLevel pops up, but without combobox|
thread with flag: not even the toplevel is showing up|
tkinter wait variable, doesnt work ether, but im not sure how to use it correctly
I am currently learning about discord.ui buttons and i want them to be subclassed since I find them to static otherwise.
I have come a long way of figuring it out but I can't quiet get the view to reset correctly (I have commented the code so you will see where the problem lies).
This is the code of the ButtonCreation subclass with Button from discord.ui import (this is the old code (I leave it as reference))
class ButtonCreation(Button):
def __init__(self, label, style, emoji):
super().__init__(label=label, style=style, emoji=emoji)
async def callback(self, interaction):
# define view again... this will make it empty. Some way to pass on the content of original view?
# or maybe subclassing View instead of Button
view = discord.ui.View()
# checking if current pressed buttons style equals "success"/green
if self.style == discord.ButtonStyle.success:
# change the style to gray
self.style = discord.ButtonStyle.gray
view.add_item(self)
# edit the original message with new view
await interaction.response.edit_message(view=view)
else:
await interaction.response.send_message('This Button has the style: ' + str(self.style))
This is the command cog where the button gets created by passing the arguments.
(arguments were changed to how the new version needs them to be)
#client.command()
async def myroles(self, ctx):
# Buttons being created
button1 = ButtonCreation(ctx=ctx, label='TSLinked', style=discord.ButtonStyle.green, custom_id="tslinked", emoji=None, row=0)
button2 = ButtonCreation(ctx=ctx, label='TSLinked', style=discord.ButtonStyle.gray, custom_id="tsadmin", emoji=None, row=1)
button3 = ButtonCreation(ctx=ctx, label='TSLinked', style=discord.ButtonStyle.danger, custom_id="tsserveradmin", emoji=None, row=2)
# defining view plus adding buttons into view
view = View()
view.add_item(button1)
view.add_item(button2)
view.add_item(button3)
# send view with text above
await ctx.channel.send('Test!', view=view)
There is actually everything working except that I can't get the original view over to the callback to get all of the original buttons.
So the current flow is that all 3 buttons are shown with their according colour and when you press button1 (since green == "ButtonStyle.success") the button colour is changed to gray and gets put into the view which afterwards gets send to the channel.
What should happen: After the green button being pressed the buttons should stay the same except the green button which should switch to gray.
Thanks in advance to anyone willing to lend a hand.
Solution
# ButtonCreation subclass with Button from discord.ui import
class ButtonCreation(Button):
# init needs ctx and at best custom_id or row to do all you need
def __init__(self, ctx, label, style, emoji, custom_id, row):
super().__init__(label=label, style=style, custom_id=custom_id, emoji=emoji, row=row)
self.ctx = ctx
async def callback(self, interaction):
# checks if Button style is green
if self.style == discord.ButtonStyle.green:
# changes button to grey
self.style = discord.ButtonStyle.grey
# self.ctx can interact with channel and user just like ctx would in normal commands
await self.ctx.channel.send('test')
# remove your button (self) before adding it again since you can't "replace" it just like that
self.view.remove_item(self)
# add the Button (self) to the view by using self.view.add_item.
self.view.add_item(self)
# add the view to the edit or send_message method with self.view
await interaction.response.edit_message(view=self.view)
If you need a little clarification:
self is the Button thats how you call the arguments.
the Button also has "View" as object so you can actually get all the buttons that are saved in it.
so before adding the button to the view (self.view) you can change the values of the button and add them again.
Things like row are fixed for the single purpose of the buttons actually needing to stay in the same position, otherwise they would be added behind all others.
Really hope this helps others aswell.
I'm trying to create a small GUI application where both the user and the computer are clicking the button widget turn by turn.
First, the user Click the Button and the Button text changes, then the computer clicks the button and the different texts appear on the button for the machine
Here's the code.
from tkinter import *
globals()['status'] = True
class test11(Tk):
def __init__(self):
super().__init__()
self.geometry('200x200')
self.btn_1 = Button(self,text="CLICK ME",command=lambda :self.test_function(1,2))
self.btn_1.grid()
if not (globals()['status']):
self.machine()
def test_function(self,aa,bb):
self.btn_1['text'] = f'Dont CLICK ME {aa} {bb} HUMAN'
globals()['status'] = False
def machine(self):
self.btn_1['text'] = f'Computer just clicked me'
globals()['status'] = True
if __name__ == '__main__':
a = test11()
a.mainloop()
But the problem is the turn of the machine never comes even though the user has already pressed the button.
As the user presses the button the values of global variable changes which should have let the machine function to call isn't it?
I would like to repeatedly get the contents of a Text widget, so I can analyse it and gets stats about what's been entered. These stats would need to be updated in real time as the user types, hence why I need the variable currentContent to update every loop. What I'd like to do is something like this.
main = tk.Tk()
# Y'know, all the typical window setup stuff.
currentContent = inputBox.get(0.0,END)
textBlobContent = TextBlob(currentContent)
# Basically here I'd do a bunch of stuff using TextBlob.
main.mainloop()
However, that doesn't work. It gets the content once, as soon as the window loads, and then stops. Surely mainloop runs repeatedly, and it should keep getting the contents of the Text widget?
A simple solution that works most of the time would be to put a binding on <KeyRelease>. That will enable a function to be called whenever the user is typing. This won't trigger the callback whenever data is pasted with the mouse, or inserted via other means (such as a toolbar button).
A more robust solution is to set up a proxy for the widget, so that an event is generated whenever anything is inserted or deleted in the widget. This proxy can look at what is being done with the widget (insert, delete, selection changed, etc) and generate an event. You can then bind to this event to do whatever you want.
Here's an example of a custom text class that generates a <<TextModified>> event whenever data is inserted or deleted:
import tkinter as tk
class CustomText(tk.Text):
def __init__(self, *args, **kwargs):
"""A text widget that report on internal widget commands"""
tk.Text.__init__(self, *args, **kwargs)
# create a proxy for the underlying widget
self._orig = self._w + "_orig"
self.tk.call("rename", self._w, self._orig)
self.tk.createcommand(self._w, self._proxy)
def _proxy(self, command, *args):
cmd = (self._orig, command) + args
result = self.tk.call(cmd)
if command in ("insert", "delete", "replace"):
self.event_generate("<<TextModified>>")
return result
This proxy does four things:
First, it calls the actual widget command, passing in all of the arguments it received.
Next it generates an event for every insert and every delete
Then it then generates a virtual event
And finally it returns the results of the actual widget command
You can use this widget exactly like any other Text widget, with the added benefit that you can bind to <<TextModified>>.
For example, if you wanted to display the number of characters in the text widget you could do something like this:
import tkinter as tk
# ... import of definition of CustomText goes here ...
root = tk.Tk()
label = tk.Label(root, anchor="w")
text = CustomText(root, width=40, height=4)
label.pack(side="bottom", fill="x")
text.pack(side="top", fill="both", expand=True)
def onModification(event):
chars = len(event.widget.get("1.0", "end-1c"))
label.configure(text="%s chars" % chars)
text.bind("<<TextModified>>", onModification)
root.mainloop()
When opening a new tkinter window, I only want the user to be able to click buttons on the new window. They should not be able to click on buttons from other windows that are part of the application. How would I accomplish this?
Here is a snip of my code:
def exportEFS(self):
self.exportGUI = Toplevel()
Button(self.exportGUI, text='Backup', command=self.backup).pack(padx=100,pady=5)
Button(self.exportGUI, text='Restore', command=self.restore).pack(padx=100,pady=5)
def backup(self):
self.backupWindow = Toplevel()
message = "Enter a name for your Backup."
Label(self.backupWindow, text=message).pack()
self.entry = Entry(self.backupWindow,text="enter your choice")
self.entry.pack(side=TOP,padx=10,pady=12)
self.button = Button(self.backupWindow, text="Backup",command=self.backupCallBack)
self.button.pack(side=BOTTOM,padx=10,pady=10)
In this snip, once the backupWindow is opened, the exportGUI remains open, but the user should not be able to click "Backup" or "Restore" while the backupWindow is opened.
Thanks!
You will want to call grab_set on the TopLevel window so that all keyboard and mouse events are sent to that.
def exportEFS(self):
self.exportGUI = Toplevel()
Button(self.exportGUI, text='Backup', command=self.backup).pack(padx=100,pady=5)
Button(self.exportGUI, text='Restore', command=self.restore).pack(padx=100,pady=5)
def backup(self):
self.backupWindow = Toplevel()
self.backupWindow.grab_set()
message = "Enter a name for your Backup."
Label(self.backupWindow, text=message).pack()
self.entry = Entry(self.backupWindow,text="enter your choice")
self.entry.pack(side=TOP,padx=10,pady=12)
self.button = Button(self.backupWindow, text="Backup",command=self.backupCallBack)
self.button.pack(side=BOTTOM,padx=10,pady=10)
What you can do is set the state to disabled. As so:
self.button.config(state="disabled")
And to enable it, you just use:
self.button.config(state="normal")
However, you must assign your buttons to variables first, like this:
self.backup=Button(self.exportGUI, text='Backup', command=self.backup)
self.backup.pack(padx=100,pady=5)
self.restore=Button(self.exportGUI, text='Restore', command=self.restore)
self.restore.pack(padx=100,pady=5)
so you would disable these using:
self.backup.config(state="disabled")
self.restore.config(state="disabled")
and re-enable using:
self.backup.config(state="normal")
self.restore.config(state="normal")
Please note however, that while the button is disabled, nothing can be changed to that button, both through the code, or through the user using it. So that means if you wanted to change the text of that button, you would have to change the state of the button to "normal" before changing it (if it already isn't in that state, which by default, all widgets are in that state when first created).
Cheers :)