Change an attribute from an instance with a tkinter Listbox - python-3.x

I am new here, and I am a beginner in tkinter. I want to change the attribute of a specific instance from a class. To select the instance, I want to use a Listbox. Here is simple version of the code for the creation of the Listbox. Is it possible to change the flag attribute of the selected instance (if apple is cliked on, change apple.flag to 1) ?
Thanks!
import tkinter as tk
class fruits:
all_fruits = []
def __init__(self,name):
self.flag = 0
self.name = name
fruits.all_fruits.append(self.name)
root = tk.Tk()
#Main window
frame = tk.Frame(root)
frame.pack()
#Fruits selection
selectorlist = tk.Listbox(frame)
selectorlist.pack()
#Fruits creation
apple = fruits('apple')
pear = fruits('pear')
#List creation
for item in fruits.all_fruits:
selectorlist.insert(tk.END, item)
#Effect on click
selectorlist.bind("<Button-1>",lambda *a: print("Flag is 1"))
root.mainloop()

With a few changes it can be done, I tried to add as many comments as I could to help explain why.
import tkinter as tk
class fruits:
def __init__(self, name):
self.flag = 0
self.name = name
def clicked(e):
# Function that gets the selected value, then changes the flag property and prints the flag.
selected = e.widget.get(e.widget.curselection()) # Gets the index of the current selection and retrieves it's value.
all_fruits[selected].flag = 1
print(selected, "changed to", all_fruits[selected].flag)
test() # This is a function call to print the entire dictionary so changes can be seen.
def test():
# Function to print the all_fruits dictionary.
for k,v in all_fruits.items():
print(k, v.flag)
print()
root = tk.Tk()
#Main window
frame = tk.Frame(root)
frame.pack()
#Fruits selection
selectorlist = tk.Listbox(frame)
selectorlist.pack()
#Fruits creation
names = ['apple', 'pear', 'grape', 'orange', 'pineapple'] # List of fruit names.
all_fruits = {} # Define a dictionary for all fruits.
for name in names:
# Add each name as a dictionary key with the class as its value.
all_fruits[name] = fruits(name)
#List creation
for item in all_fruits:
# Insert each dictionary key into the listbox
selectorlist.insert(tk.END, item)
#Effect on click
selectorlist.bind("<<ListboxSelect>>", clicked) # Changed this to call a function
root.mainloop()
To implement Add/Remove functions it would almost be better to restructure the class so that there is a separate class managing the all of the fruits.
I added some functions for add/remove that could be helpful later:
import tkinter as tk
class fruit:
"""A Class for each fruit."""
def __init__(self, name):
self.flag = 0
self.name = name
def change(self, value):
self.flag = value
class fruits:
"""A Class for all the fruits."""
def __init__(self):
self.all_fruits = {}
def add(self, listbox, name):
self.all_fruits[name] = fruit(name)
listbox.insert(tk.END, name)
print("Added", name)
def remove(self, listbox, name):
indx = listbox.get(0, tk.END).index(name)
listbox.delete(indx)
del self.all_fruits[name]
print("Deleted", name)
def change(self, name, value):
self.all_fruits[name].change(value)
def clicked(e):
# Function that gets the selected value, then changes the flag property and prints the flag.
selected = e.widget.get(e.widget.curselection()) # Gets the index of the current selection and retrieves it's value.
Fruits.change(selected, 1)
print(selected, "changed to 1")
test() # This is a function call to print the entire dictionary so changes can be seen.
def test():
# Function to print the all_fruits dictionary.
for k,v in Fruits.all_fruits.items():
print(k, v.flag)
print()
root = tk.Tk()
#Main window
frame = tk.Frame(root)
frame.pack()
#Fruits selection
selectorlist = tk.Listbox(frame)
selectorlist.pack()
#Fruits creation
names = ['apple', 'pear', 'grape', 'orange', 'pineapple', 'carrot'] # List of fruit names.
Fruits = fruits() # Creates an instance of fruits
for name in names:
Fruits.add(selectorlist, name) # Add each name with the class's add function.
Fruits.remove(selectorlist, "carrot")
#Effect on click
selectorlist.bind("<<ListboxSelect>>", clicked) # Changed this to call a function
root.mainloop()

Related

PYQT QTableView Delegate can not show createEditor when applied with Proxy

I'm having problem to show the Editor Widget when Delegate is applied with the Proxy situation.
-> self.table.setModel(self.proxy)
If the Delegate is applied to the View/Model structure, then there is no problem at all.
-> #self.table.setModel(self.model)
Refer to: https://www.pythonfixing.com/2021/10/fixed-adding-row-to-qtableview-with.html
See the code below:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys
class Delegate(QItemDelegate):
def __init__(self):
QItemDelegate.__init__(self)
self.type_items = ["1", "2", "3"]
def createEditor(self, parent, option, index):
if index.column() == 0:
comboBox = QComboBox(parent)
comboBox.addItems(self.type_items)
return comboBox
# no need to check for the other columns, as Qt automatically creates a
# QLineEdit for string values and QTimeEdit for QTime values;
return super().createEditor(parent, option, index)
class TableModel(QAbstractTableModel):
def __init__(self, data):
super(TableModel, self).__init__()
self._data = data
def appendRowData(self, data):
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self._data.append(data)
self.endInsertRows()
def data(self, index, role=Qt.DisplayRole):
if role in (Qt.DisplayRole, Qt.EditRole):
return self._data[index.row()][index.column()]
def setData(self, index, value, role=Qt.EditRole):
if role == Qt.EditRole:
self._data[index.row()][index.column()] = value
self.dataChanged.emit(index, index)
return True
return False
def rowCount(self, index=None):
return len(self._data)
def columnCount(self, index=None):
return len(self._data[0])
def flags(self, index):
# allow editing of the index
return super().flags(index) | Qt.ItemIsEditable
class CustomProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self._filters = dict()
#property
def filters(self):
return self._filters
def setFilter(self, expresion, column):
if expresion:
self.filters[column] = expresion
elif column in self.filters:
del self.filters[column]
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for column, expresion in self.filters.items():
text = self.sourceModel().index(source_row, column, source_parent).data()
regex = QRegExp(
expresion, Qt.CaseInsensitive, QRegExp.RegExp
)
if regex.indexIn(text) == -1:
return False
return True
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
localWidget = QWidget()
self.table = QTableView(localWidget)
data = [["1", "Hi", QTime(2, 1)], ["2", "Hello", QTime(3, 0)]]
self.model = TableModel(data)
self.proxy = CustomProxyModel() # Customized Filter
self.proxy.setSourceModel(self.model)
#self.table.setModel(self.model) # Original code, for View/Model
self.table.setModel(self.proxy) # Revised code, for View/Proxy/Model
self.table.setItemDelegate(Delegate())
self.add_row = QPushButton("Add Row", localWidget)
self.add_row.clicked.connect(self.addRow)
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
index = self.model.index(row, column)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
layout_v = QVBoxLayout()
layout_v.addWidget(self.table)
layout_v.addWidget(self.add_row)
localWidget.setLayout(layout_v)
self.setCentralWidget(localWidget)
self.show()
def addRow(self):
row = self.model.rowCount()
new_row_data = ["3", "Howdy", QTime(9, 0)]
self.model.appendRowData(new_row_data)
for i in range(self.model.columnCount()):
index = self.model.index(row, i)
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
Test with View/Model, Widget Editor display.
Test with View/Proxy/Model, Widget Editor not display.
Any attempt to access the view indexes must use the view's model.
Your code doesn't work because the index you are providing belongs to another model, so the editor cannot be created because the view doesn't recognize the model of the index as its own: the view uses the proxy model, while you're trying to open an editor for the source model.
While in this case the simplest solution would be to use self.proxy.index(), the proper solution is to always refer to the view's model.
Change both self.model.index(...) to self.table.model().index(...).
Yes, thanks you very much.
Revised the code as below, now the Widget Editor display accordingly.
for row in range(self.model.rowCount()):
for column in range(self.model.columnCount()):
#index = self.model.index(row, column) # original code, which get the wrong index from the model
index = self.proxy.index(row, column) # revised code, get the correct index from the proxy
self.table.openPersistentEditor(index) # openPersistentEditor for createEditor

Python: Sorting based on class attribute

I am a rookie making a to-do list-GUI in Tkinter. Every task in the to-do list has a class attribute associated with it.
So a task is an object of Task: and it has a value attribute self.value.
I want to sort all the tasks displayed in the GUI based on the attribute self.value. So for instance the task with the highest value is displayed on top of the list and the task with the lowest value is displayed at the bottom of the list.
Note that it is not the value that must be displayed in the gui but the attribute self.name but it has to be displayed in an order based on the self.value.
I only understand conceptually what to do (i think). Somethng like, i need to make a list based on the self.value, then sort the "items" in the list from highest to lowest value, and then display the task.namein the gui based on the ordering of self.value.
Maybe I am retarded?
What would be the norm to do here?
EDIT
Code:
from tkinter import Tk, Frame, Button, Entry, Label, Toplevel
import tkinter.messagebox # Import the messagebox module
task_list = []
#value_list = []
class Task:
def __init__(self, n, h):
self.name = n
self.hours = h
def show_tasks():
task = task_list[-1]
print('\n')
print('_______________________')
print('\n')
print('Task:')
print(task.name)
print('\n')
print('Hours')
print(task.hours)
def open_add_task():
taskwin = Toplevel(root)
taskwin.focus_force()
#NAME
titlelabel = Label(taskwin, text='Title task concisely:', font=('Roboto',11,'bold')).grid(column=1, row=0)
name_entry = Entry(taskwin, width=40, justify='center')
name_entry.grid(column=1, row=1)
#HOURS
hourlabel = Label(taskwin, text='Whole hours \n required', font=('Roboto',10)).grid(column=1, row=2)
hour_entry = Entry(taskwin, width=4, justify='center')
hour_entry.grid(column=1, row=3)
def add_task():
if name_entry.get() != '':
task_list.append(Task(name_entry.get(), hour_entry.get()))
show_tasks()
listbox_tasks.insert(tkinter.END, name_entry.get())
name_entry.delete(0, tkinter.END)
taskwin.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
Add_button = Button(taskwin, text='Add', font=('Roboto',10), command=add_task).grid(column=1, row=4)
def sort_tasks():
pass
root = Tk()
task_frame = Frame()
# Create UI
your_tasks_label = Label(root, text='THESE ARE YOUR TASKS:', font=('Roboto',10, 'bold'), justify='center')
your_tasks_label.pack()
listbox_tasks = tkinter.Listbox(root, height=10, width=50, font=('Roboto',10), justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
listbox_tasks.pack()
#BUTTONS
New_Task_Button = Button(root, text='New Task', width=42, command=open_add_task)
New_Task_Button.pack()
root.mainloop()
There are multiple ways to do what you are trying to achieve.
The simplest way of all is to use the list.sort method and set reverse=True, which will sort in descending order. Assuming all your list values will be Task instances, In your case, you could do
todo_list.sort(key=lambda a: a.value, reverse=True)
Assuming your value attributes are integers. I made a small example.
names_values={"name1": 5, "name2": 10, "name3": 2}
todo_list = []
class Task:
def __init__(self, name: str, value: int):
self.name = name
self.value = value
for key, value in names_values.items():
task = Task(key, value)
todo_list.append(task)
print("Before sorting: ", todo_list)
print("\nBefore sorting values: ", [(x.name, x.value) for x in todo_list])
todo_list.sort(key=lambda a: a.value, reverse=True)
print("\nAfter sorting: ", todo_list)
print("\nAfter sorting values: ", [(x.name, x.value) for x in todo_list])
Now you have a sorted list. You can now, iterate through list and use object.name to insert it to your list view.
updating since you added MRE:
Since you also need to show the sorted list in the GUI. you need to reload your list view. One way would to clear your list view and insert it again is as shown below.
...
def add_task():
if name_entry.get() != '':
task_list.append(Task(name_entry.get(), int(hour_entry.get())))
show_tasks()
reload()
taskwin.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
Add_button = Button(taskwin, text='Add', font=('Roboto',10), command=add_task).grid(column=1, row=4)
def reload():
task_list.sort(key=lambda a: a.hours, reverse=True)
listbox_tasks.delete(0, tkinter.END)
for x in task_list:
listbox_tasks.insert(tkinter.END, x.name)
...

How to retrieve value from selected radiobutton after root.mainloop()?

I am looking to write a pop-up window which asks the user to select a specific option, and if the option does not exist, to add it. However, I am having trouble retrieving the value of the selected option (that is, the key from the dict). My code --summarized-- so far:
import tkinter as tk
class Category():
def __init__(self):
self.categories = {1:"Coffee",2: "Tesco"}
def index(...):
# code ... #
root = tk.Tk()
v = tk.IntVar()
# I was thinking this would help:
def quit():
global irow
irow = v.get()
print("Irow is",irow)
root.quit()
tk.Label(root, text="Choose category:").pack()
for key, cat in self.categories.items():
tk.Radiobutton(root, text=cat, variable=v, value=key).pack()
tk.Radiobutton(root, text="Other", variable=v, value=key+1).pack()
# I want to add a text box here so user can add the "Other"
tk.Button(root, text="Close", command=quit)
irow = v.get()
print(irow)
root.mainloop()
print(irow)
# code which uses irow #
Executing this code yields:
0
Irow is 0
0
regardless of what button I select. I expect irow to be 2 is I were to select Tesco or 1 if I selected coffee (or 3 if I selected other). Any guidance would be very much appreciated.
Typically, mainloop only exits after all of the widgets have been destroyed. Therefore, you can't directly get values from the widgets at this point. The simplest solution is to save the value to a global variable, or an instance variable if you're using classes.
For example, in your case you could do this:
def quit():
self.irow = v.get()
root.quit()
Then, after mainloop exists you can access self.irow
...
root.mainloop()
print(self.irow)
Thank you to Bryan Oakley for the answer. I have made 4 changes so it runs on the Linux terminal: 1) I changed the class from Tk() to Toplevel();
2) I put the dialog into a class for cleanliness' sake; 3) I changed self.quit() to self.destroy(); & 4) I changed mainloop() to wait_window().
class AddCategory(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self)
self.v = tk.IntVar()
self.parent = parent
tk.Label(self, text="Choose category:").pack()
for key, cat in self.parent.categories.items():
tk.Radiobutton(self, text=cat, variable=self.v, value=key).pack()
tk.Radiobutton(self, text="Other", variable=self.v, value=key+1).pack()
self.e = tk.Entry(self,text="")
self.e.pack()
tk.Button(self, text="Close", command=self.quit).pack()
def quit(self):
self.parent.key = self.v.get()
self.parent.cat = self.e.get()
print(self.v.get())
print(self.e.get())
self.destroy()
Note that parent is the class from which I am executing the "AddCategory" dialogue. I invoke it as follows:
class Category():
def __init__(self):
self.cat = self.key = self.desc = []
self.categories = {1:"Coffee",2: "Tesco"}
self.descriptions = {}
def index(...):
# code ... #
dialog = AddCategory(self) # I pass the parent onto the dialog
dialog.wait_window()
print(self.key)
print(self.cat)
This works because self.parent inside of AddCategory is a soft copy of parent.

Tkinter Labels self update

I have a linkedlist implemented in Python and for every node I need a label in a Tkinter window. The problem I have is that I don't know how to update the label when the corresponding node data changes.
currently I have this code for the window that displays all the nodes and the data within the nodes in its default "no data" state.
def interface(self):
Window = Tk()
Row = 0
current_node = self.get_top_of_list()
while current_node is not None:
Label(Window, text = current_node).grid(row = Row, column = 0)
Label(Window, text = current_node.data).grid(row = Row, column = 1)
current_node = current_node.next_node
Row +=1
mainloop()
As furas also points out in the comment, Label widget will show its textvariable at all times, so you can modify the object that is assigned to textvariable, without referencing the label object again.
I'd add an attribute, dataLabel which later will hold my variable class object.
class Node:
def __init__(self, data):
self.data = data
self.next_node = None
self.dataLabel = None
then assign that variable class object, self.dataLabel to my label's textvariable in my interface method:
def interface(self):
Window = Tk()
Row = 0
current_node = self.get_top_of_list()
while current_node is not None:
current_node.dataLabel = StringVar() #assuming from Window tkinter is imported as wildcard
current.node.dataLabel.set(current_node.data)
Label(Window, textvariable = current_node.dataLabel).grid(row = Row, column = 0)
current_node = current_node.next_node
Row +=1
mainloop()
A similar object can be created for next_node attribute as well.
Label changes text automatically if it uses StringVar with textvariable= in Label and you change text in StringVar.
Linkedlist can't update labels automatically - you have to write function for it.
You could keep access to Label or StringVar inside node and node could change value in Label or StringVar when you change value in node.
See Observer Pattern. Node could be "subject" (class Observable) and Label or StringVar could be "observer" (class Observer)
BTW: StringVar/IntVar/otherVar use pattern Observer. StringVar is "subject" and you can use StringVar.trace() to assign function to StringVar (it will be "observer") and StringVar will execute this function automatically when you change value in StringVar.
More on effbot.org: The Variable Classes (BooleanVar, DoubleVar, IntVar, StringVar)
Other method is to use root.after to execute function which will get all data from nodes and updates Labels
Simple example which use after() to display current time
import tkinter as tk
from datetime import datetime
# --- functions ---
def update_time():
# get current date & time as text
current = datetime.now().strftime('%Y.%m.%d %H:%M:%S')
# update label with current date and time
label['text'] = current
# run update_time again after 1000ms (1s)
root.after(1000, update_time)
# --- main ---
root = tk.Tk()
# create Label without text - update_time will set text
label = tk.Label(root)
label.pack()
# run update_time first time
update_time()
root.mainloop()

which checkbutton has been checked

I have created a checkbox. I want the program prints 'python' when I tick it in created checkbox.
but it doesn't work...I don't get error but it doesn't print 'python'
please help.
this is my code
#!/usr/bin/python3
from tkinter import *
class Checkbar(Frame):
def __init__(self, parent=None, picks=[], side=LEFT, anchor=W):
Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = IntVar()
chk = Checkbutton(self, text=pick, variable=var)
chk.pack(side=side, anchor=anchor, expand=YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
def __getitem__(self,key):
return self.vars[key]
if __name__ == '__main__':
root = Tk()
lng = Checkbar(root, ['Python', 'Ruby', 'Perl', 'C++'])
tgl = Checkbar(root, ['English','German'])
lng.pack(side=TOP, fill=X)
tgl.pack(side=LEFT)
lng.config(relief=GROOVE, bd=2)
def allstates():
## print(lng[0])
if lng[0] == 1:
print ('python')
Button(root, text='Quit', command=root.quit).pack(side=RIGHT)
Button(root, text='Peek', command=allstates).pack(side=RIGHT)
root.mainloop()
lng[0] is an IntVar, not an int, so it's never equal to one.
You need to use the get method to compare the value of the variable to 1:
def allstates():
if lng[0].get() == 1:
print ('python')
Another solution is to change the __getitem__ method of the Checkbar so that it returns the value of the variable instead of the variable itself as I proposed in answer to Python Form: Using TKinter --> run script based on checked checkboxes in GUI
def __getitem__(self, key):
return self.vars[key].get()
In this case, you don't have to change the allstates function.

Resources