Python Tkinter Removing a Frame on button click - python

I'm trying to make an application that has the functionality to add and remove frames based on a button click. For now, I'm trying to get it to remove the last frames added from the root and will add in the functionality to specify what frame to delete later.
When I create the frame, I add it to a list of objects like this:
def AddFrame(self):
newFrame= customFrame(len(self.frameList))
self.frameList.append(newFrame)
newFrame.pack()
As I'm trying to delete it by reference, I'm using pop and pack_forget to both remove it from the list and removing it from the GUI. My basic function is:
def RemoveLastFrame(self):
if(len(self.frameList)>0):
self.frameList.pop().pack_forget()
I tried the pack_forget by testing it on a specified frame that I made in the init method. I'm not sure if this issue is coming from me trying to do this on a list of frame objects, or if it's due to the frame object's being a child of tk.Frame. Here is the child class in full:
class customFrame(tk.Frame):
def __init__(self, index):
super(customFrame, self).__init__()
self.index = index
self.buttons= []
self.addButton = tk.Button(command=self.AddButton,text='Add Button to Frame')
self.addButton.pack()
def AddButton(self):
newButton = customButton(len(self.buttons))
newButton.config(text=('Button '+str(newButton.index)))
self.buttons.append(newButton)
newButton.pack()
class customButton(tk.Button):
def __init__(self,index):
super(customButton, self).__init__()
self.index = index

I am an idiot. It was working, but it didn't unpack the children of the frame as well as I expected it to. Once I changed that, it works perfectly

Related

Why are attributes of a tk object being 'retroactively' changed?

Personal project, I'm thinking it would be cool to be able to create a one to has many relationship between windows, so when a "parent" window is closed all of its "children" are also also closed.
So here is the window class that creates new windows via the Tk() function:
from tkinter import *
class Window:
def __init__(self, title):
self.create(title)
def create(self,title):
self.window = Tk()
self.window.title(title)
self.window.protocol("WM_DELETE_WINDOW",self.delete)
def child(self, title):
self.create(title)
def delete(self):
print(f'Destroying: {self.window.title()}')
self.window.destroy()
parentclass1 = Window("ParentClass1")
parentclass2 = Window("ParentClass2")
parentclass3 = Window("ParentClass3")
print(parentclass1.window.title())
print(parentclass2.window.title())
print(parentclass3.window.title())
mainloop()
This works fine. Each window opens, and when its title is queried each instance returns the correct title:
print(parentclass1.window.title()) #=\> "ParentClass1"
print(parentclass2.window.title()) #=\> "ParentClass2"
print(parentclass3.window.title()) #=\> "ParentClass3"
What I want to be able to do is call the child method on the parentclass2 instance and instantly set up a relationship between parentclass2 and the newly created instance. I.e parentclass2 is the parent and the newly created instance is the child of parentclass2.
However before I get even to setting up this relationship via an array, a very weird thing happens when I use the child method:
parentclass2.child("ChildOfParentClass2")
print(parentclass1.window.title()) #=> "ParentClass1"
print(parentclass2.window.title()) #=> "ChildOfParentClass2"
print(parentclass3.window.title()) #=> "ParentClass1"
parentclass2.window.title() now returns the string "ChildOfParentClass2".
This is odd. self.window = Tk() is clearly being called twice, separately, and yet somehow setting the title of "ChildOfParentClass2" is "going up the stack" and is renaming ParentClass2 to ChildOfParentClass2?
I don't think its the .title method that's doing this. I think parentclass2.window is literally being turned into childofparentclass2.window.
I am aware that tkinter is behaving weirdly because I'm trying to force it into my object orientated approach...but it would be cool to use it this way so would appreciate an answer.
Can any one explain this weird behaviour, and maybe how it could be solved and I'll be able to call parentclass2.child("ChildOfParentClass2") and have it work as expected?
I've tried using Toplevel() in child and Tk() in init but exactly the same weird behavior occurs:
def __init__(self, title):
self.window = Tk()
self.create(title)
def create(self,title):
self.window.title(title)
self.window.protocol("WM_DELETE_WINDOW",self.delete)
def child(self, title):
self.window = Toplevel() # thought this would work tbh
self.create(title)
The reason for the odd behavior is that in create you're redefining self.window to be the newly created window. It no longer represents the original window. So, when you print the title of what you think is the main window you actually are printing the title of the child window.
If you want to create a child of a root window, you need to create instances of Toplevel. You can then pass the root window in as the master of the Toplevel to create the parent/child relationship.
def child(self, title):
new_window = Toplevel(master=self.window)
new_window.title(title)
When you do this, child windows will automatically be deleted when the parent dies. You don't have to do anything at all to make that happen, that's the default behavior of tkinter widgets.
Bear in mind that if you create more than one instance of Tk, each is isolated from the other. Images, variables, fonts, and other widgets created in one cannot communicate with or be moved to another. Each gets their own separate internal tcl interpreter.

Layout in a QStandartItem for QTreeView

I am searching for a way to have a QTreeView that contains hierarchical items which themselfs have a layout that is propperly drawn.
I tried to inherit from both QStandartItem and QWidget (to have a layout) but the second i set the layout on the widget part of this class the programm is shutting down when it tries to render.
class modPackItem(qtg.QStandardItem,qtw.QWidget):
def __init__(self,txt:str='',image_path:str='./assets/defaultModPack.jpg'):
super().__init__()
fnt = qtg.QFont('Calibri',12)
fnt.setBold(True)
self.setEditable(False)
self.setForeground(qtg.QColor(0,0,0))
self.setFont(fnt)
self.setText(txt)
self.horLayout = qtw.QHBoxLayout()
self.horLayout.addWidget(qtw.QLabel("test"))
#self.setLayout(self.horLayout) #this breaks the rendering
modPack_image = qtg.QImage(image_path)
self.setData(modPack_image.scaled(64,64,qtc.Qt.AspectRatioMode.KeepAspectRatioByExpanding),qtc.Qt.ItemDataRole.DecorationRole)
Is there a possible way to have all items in the QTreeView contain layouts (For example with multiple texts[description,tag-words,etc]).
Note: I also considered switching to a simple List of widgets which have children containing the hierarchical items. But that would increase complexity of my app-structure a lot and therefore i would like to avoid that.
Edit: To clearify what i want to do:
I want to build a mod(pack) manager in the style of the technic-launcher for minecraft mods but instead for any kind of game in any kind of infrastructure(steam, local instal,etc). By clicking different buttons i add new "modpacks" or "mods" (optimally custom QStandartItem with Layout for all the data) in an hierarchical fashion (therefore treeview). Adding the items and the steam-subsrciption or filecopy logic is no problem but i would like to see all infos (Name,descritpion, custom tags) on the overview (like in the example pic). I know i could bind the QStandartItem selection method to a new popup showing all infos but that would be inconvinient.
Edit2: On terms of implementation i just add the QStandartItem-object as an additional row to the root-node before setting the model. I allready tested adding new objects to the rootnode by clicking on a button and that worked fine. Just setting the layout in the object crashes the application at start.
class SteamModManager_Dialog(qtw.QDialog):
window: Ui_SteamModManagerFrame
treeModel: qtg.QStandardItemModel
rootNode: qtg.QStandardItem
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.window = Ui_SteamModManagerFrame()
self.window.setupUi(self)
self.window.label_footer.setText("")
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
On the behalf of #musicamente here the solution that worked out for me:
I created a widget in the designer (as usual, not posting the full ui code here).
Then i implemented the following code into the Dialog:
self.treeModel = qtg.QStandardItemModel()
self.rootNode = self.treeModel.invisibleRootItem()
modPack = modPackItem('Dont Starve Together')
testMod = modItem("TestMod")
modPack.appendRow(testMod)
self.rootNode.appendRow(modPack)
self.window.tView_modPacks.setModel(self.treeModel)
self.window.tView_modPacks.expandAll()
modPackWidget = qtw.QWidget()
ui = Ui_modPackWidget()
ui.setupUi(modPackWidget)
self.window.tView_modPacks.setIndexWidget(self.treeModel.index(0,0),modPackWidget)
This code resulted setting the custom widget to the treeview item. Here the final look:

How to move items between two QListWidgets?

I want to create a pyqt5 GUI in which the user should pick the wanted variables on the left and they should show up on the right hand side. This is what the GUI looks like:
Basically, I want to click the variables on the left listWidget, and they should show up in the list widget on the right (chosenitem_list), and then ideally dissapear from the left listWidget. Otherwise, I could add a button once all the desired variables have been selected which transfers them to the other side. I am trying to do this using the item_clicked method in my code below, but when I click on them nothing happens so I am stuck. What am I doing wrong?
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
loadUi('browse.ui',self)
self.listWidget.clicked.connect(self.item_clicked)
def item_clicked(self):
item = QtWidgets.QListWidgetItem()
item = self.listWidget.currentItem()
self.chosenitem_list.addItem(item)
You have to clone the QListWidgetItem:
def item_clicked(self, index):
item = self.listWidget.itemFromIndex(index)
it = item.clone()
self.chosenitem_list.addItem(it)

Tkinter Binding Multiple Scrollbar's to MouseWheel

I'm building something that uses multiple scrollbars. This class is actually a wrapper for the tk.ScrollBar and it's used to create scrollable frames. What I want is to be able to set a "default" scroll-container.
Assume 90% of the time someone using the application would want to scroll some frame (Which we will call main_frame), and 10% of the time they would want to scroll a different frame. (normal_frame)
It would make sense to have the mousewheel scroll the normal_frame only when the user was hovering over it with the mouse, but to have the mousewheel scroll the main_frame in all instances except when hovering over normal_frame.
The problem is that whenever you call bind_all upon "<Enter>"(ing) the normal_frame, when you "<Leave>" it, the main_frame no longer scrolls. Any suggestions?
class ScrollThing(object):
def __init__(self, some_frame, default=False):
self.default = default
self.canvas = tkinter.Canvas(some_frame)
self.view_window = tkinter.Frame(self.canvas)
#things and stuff to setup scroll bar
def setup_view_window(self):
if self.default:
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
else:
self.view_window.bind('<Enter>', self.focus_in)
self.view_window.bind('<Leave>', self.focus_out)
def focus_in(self, *args):
del args
# I'm a seperate ScrollThing from main_frame
# I don't have access to the other ScrollThing because
# we are both dynamically created.
# I want to save the current bound arguments somehow
# but .bindtags() returns only MY bindings
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
def focus_out(self, *args):
self.canvas.unbind_all('<MouseWheel>', self.on_mousewheel
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-1*(event.delta//120)), 'units')
I just found a solution involving handing the function and the canvas back to the Master.
class Master(object)
def __init__(self, *args, **kwargs):
#Things and stuff
self.default_scroll = None
Then by setting default_scroll to hold the default parameters
def setup_view_window(self):
if self.default:
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
# HERE
self.dynamic.stuff.MASTER.default_scroll = [self.canvas, self.on_mousewheel]
else:
self.view_window.bind('<Enter>', self.focus_in)
self.view_window.bind('<Leave>', self.focus_out)
You can then access it in the focus_out.
def focus_out(self, *args):
self.canvas.unbind_all('<MouseWheel>', self.on_mousewheel)
# HERE
if self.dynamic.stuff.MASTER.default_scroll is not None:
self.dynamic.stuff.MASTER.default_scroll[0].bind_all('<MouseWheel>', self.dynamic.stuff.MASTER.default_scroll[1])
Although, I would be interested to know if anyone knows of a way to access it in a more concise way, perhaps something in tkinter that lets you see all bound events in the entire application?
Arguably the simplest solution is to set a variable to be the widget you want to scroll, and then set a global binding to scroll that widget.
For example, your app would initialize the binding like this exactly once:
self.root.bind_all("<MouseWheel>", self.scroller)
Then, in scroller you scroll the default window:
def scroller(self, event):
self.default_window.yview(...)
You can also add the binding to the canvases themselves instead of calling bind_all if you prefer. The basic concept still works without having to adjust the bindings on enter/leave events.

How to loop through subfolders showing jpg in Tkinter?

It's a Python beginner question.
I want to loop through several subfolders of parent folder (subfolders contain jpg and txt files). I want to show images using Tkinter. There should be an image and Next button beside - when button is clicked the next image from the list should be loaded.
How to force an application to stop on each image and wait for user reaction?
In the test code below the image loaded is from the last directory in the loop (images from other directories are not displayed although I can print their names during the loop run).
import Tkinter, os, glob
from PIL import Image, ImageTk
class simpleapp_tk(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid
parentSrcFolder = r"D:\2012\RCIN\test"
srcFoldersLst = os.listdir(parentSrcFolder)
for srcFolder in srcFoldersLst:
jpgFilesPathLst = glob.glob(os.path.join(parentSrcFolder, srcFolder, "*.jpg"))
self.labelVariable = Tkinter.StringVar()
label = Tkinter.Label(self,textvariable=self.labelVariable,anchor="w")
label.grid(column=0,row=0,columnspan=2,sticky='EW')
self.labelVariable.set(jpgFilesPathLst[0])
cardImage = Image.open(jpgFilesPathLst[0])
indexCard = ImageTk.PhotoImage(cardImage)
labelImage = Tkinter.Label(self,image=indexCard)
labelImage.image = indexCard
labelImage.grid(column=0,row=3)
def main():
app = simpleapp_tk(None)
app.title('my application')
app.mainloop()
if __name__ == '__main__':
main()
The easiest way that I can think of doing this :
first, create a method display_next which will increment an index and display the image associated with that index in a list (assume the list is a list of filenames). Enclosing the list inquiry in a try/except clause will let you catch the IndexError that happens when you run out of images to display -- At this point you can reset your index to -1 or whatever you want to happen at that point.
get the list of filenames in __init__ and initialize some index to -1 (e.g. self.index=-1).
create a tk.Button in __init__ like this:
self.Button = Tkinter.Button(self,text="Next",command=self.display_next)
Another side note, you can use a widget's config method to update a widget on the fly (instead of recreating it all the time). In other words, move all the widget creation into __init__ and then in display_next just update the widget using config. Also, it's probably better to inherit from Tkinter.Frame...
class SimpleAppTk(Tkinter.Frame):
def __init__(self,*args,**kwargs):
Tkinter.Frame.__init__(self,*args,**kwargs)
self.filelist=[] #get your files here
#it probably would look like:
#for d in os.listdir(parentDir):
# self.filelist.extend(glob.glob(os.path.join(parentDir,d,'*.jpg'))
self.index=-1
self.setup()
self.display_next()
def setup(self):
self.Label=Tkinter.Label(self)
self.Label.grid(row=0,column=0)
self.Button=Tkinter.Button(self,text="Next",command=self.display_next)
self.Button.grid(row=0,column=1)
def display_next(self):
self.index+=1
try:
f=self.filelist[self.index]
except IndexError:
self.index=-1 #go back to the beginning of the list.
self.display_next()
return
#create PhotoImage here
photoimage=...
self.Label.config(image=photoimage)
self.Label.image=photoimage
if __name__ == "__main__":
root=Tkinter.Tk()
my_app=SimpleAppTk(root)
my_app.grid(row=0,column=0)
root.mainloop()
EDIT
I've given an example of how to actually grid the Frame. In your previous example, you had self.grid in your initialization code. This really did nothing. The only reason you had results was because you were inheriting from Tkinter.Tk which gets gridded automatically. Typically it's best practice to grid after you create the object because if you come back later and decide you want to put that widget someplace else in a different gui, it's trivial to do so. I've also changed the name of the class to use CamelCase in agreement with PEP 8 ... But you can change it back if you want.

Categories

Resources