tkinter listbox insert error "invalid command name ".50054760.50055432" - python-3.x

I have a database of objects and you can view the items in the database in a listbox and there's a button to remove an item and to create an item. Creating an item opens a dialog window for the item class and then the item's data is stored in the database. I have reproduced the problem with a very simple duplicate of my set-up (see code below).
Every time I add a new item, the addition is successful (it's there the next time I open up the database dialog), but the listbox doesn't insert the item, and when I close the database dialog I get the following error:
Exception in Tkinter callback Traceback (most recent call last):
File "C:\Python33\lib\tkinter__init__.py", line 1442, in call
return self.func(*args) File "", line 21, in addRecord File "C:\Python33\lib\tkinter__init__.py", line 2604, in insert
self.tk.call((self._w, 'insert', index) + elements)
_tkinter.TclError: invalid command name ".50054760.50055432"
The same problem doesn't come up if I just try to create the object and populate its values without invoking its inputs GUI (which is necessary for the process of inserting things into my database). I've seen a similar error in another thread (sorry, but I can't seem to find it again), where the problem was with multithreading. I'm not aware of any threading that I'm doing and don't want to download yet another package to handle tkinter threading. Any ideas? Workarounds? I'm using Python v3.3 and 64-bit Windows 7, if that helps.
Here's my simplified database code:
import tkinter
import traceback
# Test =========================================================================
class Test:
def __init__(self):
"""A database of objects' IDs and values."""
self.data = {1: 'a', 2: 'b', 3: 'c'}
#---------------------------------------------------------------------------
def addRecord(self):
"""Opens up a new item for editing and saves that ability to the
database."""
print('hi0')
newItem = OtherObject()
newItem.create(self.root)
print('hi1')
self.data[newItem.ID] = newItem.value
print('hi2')
self.listbox.insert(tkinter.END, self.formatItem(newItem.ID))
print('hi3')
#---------------------------------------------------------------------------
def delRecord(self):
"""Removes selected item from the database."""
try:
index = self.listbox.curselection()[0]
selection = self.listbox.get(index)
except:
return
ID = int(selection.split(':')[0])
self.data.pop(ID)
self.listbox.delete(index)
#---------------------------------------------------------------------------
def dataframe(self, master):
"""Assembles a tkinter frame with a scrollbar to view database objects.
(Returns Frame, Scrollbar widget, Listbox widget)
master: (Tk or Toplevel) tkinter master widget."""
frame = tkinter.Frame(master)
# scrollbar
scrollbar = tkinter.Scrollbar(frame)
scrollbar.pack(side=tkinter.LEFT, fill=tkinter.Y)
# listbox
listbox = tkinter.Listbox(frame, yscrollcommand=scrollbar.set)
listbox.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
# fill listbox
for ID in self.data:
listbox.insert(tkinter.END, self.formatItem(ID))
return (frame, listbox, scrollbar)
#---------------------------------------------------------------------------
def destroyLB(self, e):
for line in traceback.format_stack():
print(line.strip())
#---------------------------------------------------------------------------
def formatItem(self, ID):
"""Creates a nice string representation of an item in the database."""
return '{0}:{1}'.format(ID, self.data[ID])
#---------------------------------------------------------------------------
def listboxSelect(self, e):
"""Manages events when the selection changes in the database interface.
e: (Event) tkinter event."""
try:
selection = self.listbox.get(self.listbox.curselection()[0])
except:
return
# set description label
ID = int(selection.split(':')[0])
self.lblstr.set(self.data[ID])
#---------------------------------------------------------------------------
def view(self):
"""Displays database interface."""
self.root = tkinter.Tk()
# listbox frame
self.frame, self.listbox, self.scrollbar = self.dataframe(self.root)
self.frame.grid(column=0, row=0)
self.listbox.bind('<<ListboxSelect>>', self.listboxSelect)
self.listbox.bind('<Destroy>', self.destroyLB)
# record display frame
self.lblstr = tkinter.StringVar()
self.lbl = tkinter.Label(self.root, textvariable=self.lblstr)
self.lbl.grid(column=1, row=0, sticky=tkinter.N)
# buttons frame
self.frame_btn = tkinter.Frame(self.root)
self.frame_btn.grid(row=1, columnspan=2, sticky=tkinter.E+tkinter.W)
# 'create new' button
self.btn_new = tkinter.Button(
self.frame_btn, text='+', command=self.addRecord)
self.btn_new.grid(row=0, column=0)
# 'delete record' button
self.btn_del = tkinter.Button(
self.frame_btn, text='-', command=self.delRecord)
self.btn_del.grid(row=0, column=1)
# display
self.root.mainloop()
# Test =========================================================================
# OtherObject ==================================================================
class OtherObject:
"""An object with an ID and value."""
def __init__ (self):
self.ID = 0
self.value = ''
#---------------------------------------------------------------------------
def create(self, master=None):
"""open a dialog for the user to entry a new object ID and value.
master: (Tk or Toplevel) tkinter master widget."""
self.stuff = tkinter.Toplevel(master)
# ID
tkinter.Label(self.stuff, text='ID: ').grid(row=0, column=0)
self.IDvar = tkinter.StringVar(self.stuff)
self.IDvar.set(self.ID)
IDwidget = tkinter.Entry(self.stuff, textvariable=self.IDvar)
IDwidget.grid(row=0, column=1)
# value
tkinter.Label(self.stuff, text='Value: ').grid(row=1, column=0)
self.valueVar = tkinter.StringVar(self.stuff)
self.valueVar.set(self.value)
valueWidget = tkinter.Entry(self.stuff, textvariable=self.valueVar)
valueWidget.grid(row=1, column=1)
# OK button
tkinter.Button(self.stuff, text='OK', command=self.OK).grid(row=2)
self.stuff.mainloop()
#---------------------------------------------------------------------------
def OK(self):
try: self.ID = int(self.IDvar.get())
except: self.ID = 0
self.value = self.valueVar.get()
self.stuff.destroy()
# OtherObject ==================================================================
Thanks in advance

You are creating more than one instance of Tk. Tkinter is not designed to work like that and you will get unexpected behavior. You need to refactor your code so that you create an instance of Tk only once. If you need multiple windows, create instances of Toplevel.
... time passes ... the code in the question gets updated ...
In the updated version of your question you now are creating one instance of Tk, and then instances of Toplevel. This is good. However, you are also calling mainloop more than once which is a problem. Worse, you're redefining self.root which no doubt is part of the problem. You must call mainloop exactly once over the entirety of your program.

Bryan Oakley guided me to the exact problem that I was encountering:
(1) I created a second Tk object in my OtherObject class.
(2) I called mainloop in my OtherObject class.
Only one Tk object should exist and mainloop should only ever be called once, no matter how many windows are to be displayed.

Related

Integrating class into my existing code or not? Tkinter, python 3 [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I have this GUI application that I have been obsessing about for over a week now and I think it is slightly harder than what I can manage with the limited programming/python knowledge I have gained so far - however, I just can't stop thinking about it and it is driving me insane that I can't figure it out.
I have created a GUI with Tkinter which is a Todo-list application. However, every task in the application must have some information associated with it.
So let's say we create a task called "Homework".
I want to associate some attributes with Homework.
So, among others, some attributes would be "Impact" which represents the impact of completing the task on a scale from 0-10 (e.g. 10: I will fail my class if I don't complete my Homework task.), and deadline - which is self-explanatory. Those are just 2 of the attributes I want (there will be more though).
So as far as I have come to understand, this would be a great time to utilize classes.
If I understand it correctly, I would have to create a class Task: and then set the attributes for every instance of Task to something.
I created the GUI before I became familiar with classes and some of the attribute information I want to associate with a given task can already be specified in the GUI (without any functionality) after creating a task, but I don't know how to use that information and connect it to the given task so that it has the functionality that I want.
I want to be able to enter a task, specify some attributes associated with that task, and then I want to be able to 'summon' that task and its' associated attributes for some simple math and/or sorting.
I only want the user to see that task itself - the calculations/sorting will just happen behind the scenes.
My question:
Should I do this with classes or is there a more beginner-level approach to this which would be easier to implement into my already existing code?
I really, really want to figure out how to go about this so any qualified pointers or in the right direction or examples, explanations, etc. will be truly appreciated.
This is my code:
from tkcalendar import *
import tkinter.messagebox # Import the messagebox module
import pickle # Module to save to .dat
import tkinter as tk
TASKS_FILE = "tasks.dat" ##Where the task strings are saved
task_list = [] ##Don't know what I am doing yet
impact_number = [] ##Don't know if this could be useful
root = tk.Tk() # ?????
root.title('SmaToDo') # Name of the program/window
# ROOT WINDOW
# Gets the requested values of the height and widht.
windowWidth = root.winfo_reqwidth()
windowHeight = root.winfo_reqheight()
# Gets both half the screen width/height and window width/height
positionRight = int(root.winfo_screenwidth()/2 - windowWidth/2)
positionDown = int(root.winfo_screenheight()/2 - windowHeight/2)
# Positions the window in the center of the page.
root.geometry("+{}+{}".format(positionRight, positionDown))
def new_task():
def add_task():
global task
global impact_window
task = entry_task.get() # we get the task from entry_task and we get the input from the entry_task type-field with .get()
if task != '': # If textbox inputfield is NOT empty do this:
listbox_tasks.insert(tkinter.END, task)
entry_task.delete(0, tkinter.END) #
task_window.destroy()
else:
tkinter.messagebox.showwarning(title='Whoops', message='You must enter a task')
task_window.destroy()
task_window = tk.Toplevel(root)
task_window.title('Add a new task')
task_label = tk.Label(task_window, text = 'Title your task concisely:', justify='center')
task_label.pack()
# Entry for tasks in new window
entry_task = tk.Entry(task_window, width=50, justify='center')
entry_task.pack()
# Add task button in new window
button_add_task = tk.Button(task_window, text='Add task', width=42, command=lambda: [add_task(), impact()])
button_add_task.pack()
def impact():
global impact_next_button
global impact_window
global impact_label
global impact_drop
global options
global select
impact_window = Toplevel(root)
impact_window.title('Impact')
options = StringVar()
options.trace_add
task_label = tk.Label(impact_window, text=str(task), font='bold')
task_label.pack()
impact_label = tk.Label(impact_window, text = 'Specify below the impact of completing this task \n (10 being the highest possible impact)', justify='center')
impact_label.pack()
impact_drop = OptionMenu(impact_window, options, '0','1','2','3','4','5','6','7','8','9','10')
impact_drop.pack()
impact_next_button = tk.Button(impact_window, text='Next', command=connectivity)
impact_next_button.pack(side=tkinter.RIGHT)
def select():
global impact_next_button
global impact_window
global impact_label
global impact_drop
global options
impact_number.append(str(task) + options.trace_add('write', lambda *args: print(str(task)+' '+ 'impact' + ' ' + options.get())))
options.set(options.get())
def connectivity():
impact_window.destroy()
clicked = StringVar()
clicked.set('Select')
global connectivity_next_button
global connectivity_window
global connectivity_label
global connectivity_drop
connectivity_window = Toplevel(root)
connectivity_window.title('Connectivity')
task_label = tk.Label(connectivity_window, text=str(task), font='bold')
task_label.pack()
connectivity_label = tk.Label(connectivity_window, text = 'Specify below the connectivity this task has to other tasks\n (e.g. tasks you can not complete unless you have completed this task)', justify='center')
connectivity_label.pack()
# var1 = IntVar()
Checkbutton(connectivity_window, text="Each task from list must be 'checkable' in this window").pack() # variable=var1).pack()
connectivity_next_button = tk.Button(connectivity_window, text='Next', command=lambda: [Proximity(), select()])
connectivity_next_button.pack(side=tkinter.RIGHT)
def Proximity():
connectivity_window.destroy()
global proximity_button
global proximity_window
global proximity_label
global proximity_drop
global cal
global proximity_output_date
proximity_window = Toplevel(root)
proximity_window.title('Proxmity')
task_label = tk.Label(proximity_window, text=str(task), font='bold')
task_label.pack()
proximity_label = tk.Label(proximity_window, text = 'Specify a deadline for when this task must be completed', justify='center')
proximity_label.pack()
cal = Calendar(proximity_window, selectmode='day', year=2021, month=4, day=27)
cal.pack()
def get_date():
proximity_output_date.config(text=cal.get_date())
proximity_date_button = tk.Button(proximity_window, text='Pick date', command=get_date)
proximity_date_button.pack()
proximity_output_date = tk.Label(proximity_window, text='')
proximity_output_date.pack()
proximity_button = tk.Button(proximity_window, text='Next', command=manageability)
proximity_button.pack(side=tkinter.RIGHT)
def manageability():
print('Deadline:'+' '+cal.get_date())
proximity_window.destroy()
clicked = StringVar()
clicked.set('Select')
global manageability_next_button
global manageability_window
global manageability_label
global manageability_drop
manageability_window = Toplevel(root)
manageability_window.title('Manageability')
task_label = tk.Label(manageability_window, text=str(task), font='bold')
task_label.pack()
manageability_label = tk.Label(manageability_window, text = 'Specify how difficult this task is to complete \n (0 being extremely difficult and 10 being extremely easy)', justify='center')
manageability_label.pack()
manageability_drop = OptionMenu(manageability_window, clicked, '0','1','2','3','4','5','6','7','8','9','10')
manageability_drop.pack()
manageability_next_button = tk.Button(manageability_window, text='Next', command=urgency)
manageability_next_button.pack(side=tkinter.RIGHT)
def urgency():
pass
def delete_task():
try:
task_index = listbox_tasks.curselection()[0]
listbox_tasks.delete(task_index)
except:
tkinter.messagebox.showwarning(title='Oops', message='You must select a task to delete')
def save_tasks():
tasks = listbox_tasks.get(0, listbox_tasks.size())
pickle.dump(tasks, open('tasks.dat', 'wb'))
# Create UI
your_tasks_label = tk.Label(root, text='THESE ARE YOUR TASKS:', font=('Roboto',10, 'bold'), justify='center')
your_tasks_label.pack()
frame_tasks = tkinter.Frame(root)
frame_tasks.pack()
scrollbar_tasks = tkinter.Scrollbar(frame_tasks)
scrollbar_tasks.pack(side=tkinter.RIGHT, fill=tkinter.Y)
listbox_tasks = tkinter.Listbox(frame_tasks, height=10, width=50, font=('Roboto',10), justify='center') # tkinter.Listbox(where it should go, height=x, width=xx)
listbox_tasks.pack()
listbox_tasks.config(yscrollcommand=scrollbar_tasks.set)
scrollbar_tasks.config(command=listbox_tasks.yview)
try:
tasks = pickle.load(open('tasks.dat', 'rb'))
listbox_tasks.delete(0, tkinter.END)
for task in tasks:
listbox_tasks.insert(tkinter.END, task)
except:
tkinter.messagebox.showwarning(title='Phew', message='You have no tasks')
# Add task button
button_new_task = tkinter.Button(root, text='New task', width=42, command=new_task)
button_new_task.pack()
button_delete_task = tkinter.Button(root, text='Delete task', width=42, command=delete_task)
button_delete_task.pack()
button_save_tasks = tkinter.Button(root, text='Save tasks', width=42, command=save_tasks)
button_save_tasks.pack()
root.mainloop() # Runs the program - must be at the very buttom of the code
Some people on here have already helped me out a tremendous amount which I truly appreciate.
So I made some sample code:
from tkinter import Tk, Frame, Button, Entry, Label, Canvas, Scrollbar, OptionMenu, Toplevel, StringVar
import pickle
class Task:
def __init__(self, name, type_, importance):
self.name = name
self.type = type_
self.importance = importance
try:
with open('saved_tasks.dat', 'rb') as file:
task_list = pickle.load(file)
except FileNotFoundError and EOFError:
with open('saved_tasks.dat', 'w') as file:
pass
print('Could not locate the file or the file was empty. A new file was created.')
task_list = []
task_types = ['Misc', 'Math', 'Science', 'Economics', 'Biology', 'Homework']
def show_tasks():
for widget in task_frame.winfo_children():
widget.destroy()
for task in task_list:
Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
def open_add_task():
win = Toplevel(root)
win.focus_force()
Label(win, text='Write task name').grid(column=0, row=0)
Label(win, text='Choose type of task').grid(column=1, row=0)
Label(win, text='Choose importance of task').grid(column=2, row=0)
entry = Entry(win)
entry.grid(column=0, row=1, sticky='ew')
type_var = StringVar(value=task_types[0])
OptionMenu(win, type_var, *task_types).grid(column=1, row=1, sticky='nsew')
imp_var = StringVar(value=1)
OptionMenu(win, imp_var, *range(1, 10+1)).grid(column=2, row=1, sticky='nsew')
def add_task():
task_list.append(Task(entry.get(), type_var.get(), imp_var.get()))
show_tasks()
Button(win, text='Add Task', command=add_task).grid(column=0, row=2, columnspan=3, sticky='nsew')
def sort_tasks():
type_ = sort_type.get()
importance = sort_imp.get()
order = asc_desc_var.get()
for widget in task_frame.winfo_children():
widget.destroy()
for task in task_list if order == 'Ascending' else task_list[::-1]:
if type_ == 'All' and importance == 'Any':
Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
elif type_ == 'All':
if importance == task.importance:
Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
elif type_ != 'All':
if type_ == task.type and importance == 'Any':
Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
elif type_ == task.type and importance == task.importance:
Label(task_frame, text=f'{task.name} | Type: {task.type} | Importance: {task.importance}').pack(fill='x')
root = Tk()
btn_frame = Frame(root)
btn_frame.pack(fill='x')
sort_type = StringVar(value='All')
OptionMenu(btn_frame, sort_type, 'All', *task_types).grid(column=0, row=0, sticky='nsew')
sort_imp = StringVar(value='Any')
OptionMenu(btn_frame, sort_imp,'Any', *range(1, 10+1)).grid(column=1, row=0, sticky='nsew')
asc_desc_var = StringVar(value='Ascending')
OptionMenu(btn_frame, asc_desc_var, 'Ascending', 'Descending').grid(column=2, row=0, sticky='nsew')
Button(btn_frame, text='Sort', command=sort_tasks).grid(column=0, row=1, columnspan=3, sticky='nsew')
Button(btn_frame, text='Add New', command=open_add_task).grid(column=3, row=0, rowspan=2, sticky='nsew')
task_frame_main = Frame(root)
task_frame_main.pack()
task_frame = Frame()
canvas = Canvas(task_frame_main)
canvas.pack(side='left', expand=True, fill='both')
canvas.create_window((0, 0), window=task_frame, tag='task_frame', anchor='nw')
scrollbar = Scrollbar(task_frame_main, orient='vertical', command=canvas.yview)
scrollbar.pack(side='right', fill='y')
canvas.config(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', lambda e: canvas.config(scrollregion=canvas.bbox('task_frame')))
show_tasks()
root.mainloop()
try:
with open('saved_tasks.dat', 'wb') as file:
pickle.dump(task_list, file)
print('File saved.')
except Exception as e:
print(f'Exception was raised:\n{e}')
The main part here is the Task class, as You can see it is pretty small and that was the whole point, it doesn't have to be anything large. And the task_list can be easily serialized using pickle and easily loadable and it will keep those classes with their given attributes.
If You have any other questions, ask.
Explaining everything (or most at least):
First import everything You need and I like to do it like this when working with tkinter.
Then I defined task_list which will be used to store added tasks, and this can and probably should be replace by a file so You would read a file and then append the saved tasks to this list or just use json or pickle to import the file as a list which makes it easier. Anyways, then I just created a list to store all the types of tasks, it can be modified and again this is not as necessary but the list can be loaded from a file for example if there is an option to add another type (which requires quite some coding) it would be great to save them to a file so the info is saved.
Now about class Task:
this class doesn't have a lot of attributes, in fact just three to store basic stuff like name of the task, type of the task and how important it is.
There is not much to this but to mention it is possible to use for example pickle module and apply it to the task_list (which will store instances of Task) and it will serialize this object while keeping its attributes so when loading from pickle it will still be possible to access the object the same way.
Now moving on to the show_tasks() function.
So this is a general function for just showing everything that is appended to the task_list a.k.a. all tasks. This function puts Labels on the frame specified below but first it deletes everything off that frame because otherwise it will append all the items each time which means that it will grow exponentaly (You can test if You want by removing the first loop and adding a few tasks).
Next is open_add_task() function which is just for adding tasks.
It starts by creating another window Toplevel and puts focus on it. (sometimes I like to bind such windows so that when user clicks out of the window and this window loses focus close it so that there are not multiple such windows laying around but that is up to You).
Then I created a few labels that help users indicate what is asked from them.
Then I placed entry so that user can type in the task name and then so to avoid user typing error I gave them a choice to choose which category and importance they want so that they don't mistype something.
Then I defined a function (add_task()) for collecting data from user inputs, creating the class instance and appending it to the task_list. And it finishes with creating a Button that calls this function.
Onto sort_tasks.
This function is linked with widgets on root so there is the reference. so first get what the user has inputed and store in variables for easier reuse. Again user input is made easier by just giving choices. Again all widgets are cleared and then comes the sorting/logical part which probably can be improved and I didn't add the option to sort by importance number (so how high is it) so it sorts by time added (again there is no exact time but just order in the list is the time added). The logic could be improved but it mostly does what it is supposed to do (it was late and I couldn't properly think) but yeah there is not much to explain just step through it yourself.
Next bit is the main window which is not inside any functions as to not use global. first I initiated Tk() which is the basic and at the end there is .mainloop() used - the basic stuff. Then there is a lot of frames used to help organize the widgets and then all the widgets are added in this case I'm talking about the widgets that contribute to sort_tasks() function. So added all the choice menus and the button to execute sort_tasks. And a button to call open_add_task().
Next bit is the sort of main frame where all the tasks are shown. There is canvas which is important, because a Frame cannot be scrolled itself, whereas canvas can be scrolled so essentially what happens, is that a frame gets added to the canvas and the scrollregion is set to the Frame size. Then added the scrollbar (will add link to this).
And that is pretty much it.
EDIT:
Added functionality for saving and reading file so that tasks can be saved and loaded. For some reason the file has to be loaded after Task because apparently otherwise it throws an error which I don't completely understand but I dealt with it by moving class above the whole thing. I also found an issue where when new tasks are added and they exceed the visible frame limit, scrollbar does not work, but then again opening the file again it works fine, so that could be dealt with. Also as You can see I added save function after mainloop() so that whenever user closes the window it saves tasks to file. (added to sources)
Sources:
General tkinter info about widgets (I mostly use this)
Very good documentation of tkinter
Info about OptionMenu (1st source does not have this and 2nd source is a bit limited)
"Scrolling Frame" (there are other sources too)
Pickle module (as it is noticeable it is not entirely safe but...)

Python3, difficulty with classes and tkinter

First of all to kick it off,
I'm not great at programming,
I have difficulty with understanding most basics,
I always try doing my uterly best to solve things like this myself.
I'm trying to create a simple gui that makes json files. Everything works fine. Fine in the sense that I'm able to create the files.
Now I wanted to get my code cleaned up and to the next level. I have added tabs to the tkinter screen and that is where the troubles starts. Because when I'm, on a differend tab, the function doesn't get the current selected items, so I added buttons to save that list and then move to different tab.
I have a function(Save_List_t), which looks at the selected items from the listbox(a_lsb1) and saves them to a list(choice_list_t). This function runs when I press button(a_button).
After doing that I got a problem, I don't want to use "global" but I need the list in a other function(Mitre_Gen_Techs) to generate the files. This function runs when I press a button on the third tab.(c.button1)
To tackel this problem, I saw a post where someone uses a class to fix it. However even after reading to the documentation about classes I still don't truely get it.
Now I'm stuck and get the error. Which I don't find that strange, it makes sense to me why it gives the error but what am I doing wrong or how do I solve this issue.
The error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\thans\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
TypeError: Save_List_t() missing 1 required positional argument: 'self'
The code I wrote:
from tkinter import *
from attackcti import attack_client
from mitretemplategen import *
from tkinter import ttk
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mitre ATT&Ck
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
ac = attack_client()
groups = ac.get_groups()
groups = ac.remove_revoked(groups)
techs = ac.get_enterprise_techniques()
techs = ac.remove_revoked(techs)
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Tkinter screen setup
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
root = Tk()
root.title("Mitre Att&ck")
root.minsize(900, 800)
root.wm_iconbitmap('')
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Functions / classes
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Screen(object):
def __init__(self):
self.choice_list_t = []
self.choice_list_g = []
def Save_List_t(self):
for item in a_lsb2.curselection():
self.choice_list_t.append(a_lsb2.get(item))
print(self.choice_list_t)
def Save_List_g(self):
choice_list_g = []
for item in b_lsb1.curselection():
self.choice_list_g.append(b_lsb1.get(item))
print(self.choice_list_g)
def Mitre_Gen_Techs(self):
# Gen the json file
# mitre_gen_techs(self.choice_list_t, techs)
#testing class
print(self.choice_list_t)
def Mitre_Gen_Groups(self):
# Gen the json file
# mitre_gen_groups(self.choice_list_g, groups)
#testing class
print(self.choice_list_g)
def main():
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# First Tkinter tab
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
rows = 0
while rows < 50:
root.rowconfigure(rows, weight=1)
root.columnconfigure(rows, weight=1)
rows += 1
# Notebook creating tabs
nb = ttk.Notebook(root)
nb.grid(row=1, column=0, columnspan=50, rowspan=50, sticky='NESW')
# Create the differend tabs on the notebook
tab_one = Frame(nb)
nb.add(tab_one, text='APG')
tab_two = Frame(nb)
nb.add(tab_two, text='Actors')
tab_gen = Frame(nb)
nb.add(tab_gen, text='test')
# =-=- First Tab -=-=
# List box 1
a_lsb1 = Listbox(tab_one, height=30, width=30, selectmode=MULTIPLE)
# List with techs
a_lsb2 = Listbox(tab_one, height=30, width=30, selectmode=MULTIPLE)
for t in techs:
a_lsb2.insert(END, t['name'])
# Save list, to later use in Screen.Mitre_Gen_Techs
a_button = Button(tab_one, text="Save selected", command=Screen.Save_List_t)
# =-=- Second Tab -=-=
# List with TA's
b_lsb1 = Listbox(tab_two, height=30, width=30, selectmode=MULTIPLE)
for g in groups:
b_lsb1.insert(END, g['name'])
# Save list, to later use in Screen.Mitre_Gen_Groups
b_button = Button(tab_two, text="Save selected", command=Screen.Save_List_g)
# =-=- Third tab -=-=
c_button = Button(tab_gen, text="Print group json", command=Screen.Mitre_Gen_Groups)
c_button1 = Button(tab_gen, text="Print techs json", command=Screen.Mitre_Gen_Techs)
# Placing the items on the grid
a_lsb1.grid(row=1, column=1)
a_lsb2.grid(row=1, column=2)
b_lsb1.grid(row=1, column=1)
a_button.grid(row=2, column=1)
b_button.grid(row=2, column=1)
c_button.grid(row=2, column=1)
c_button1.grid(row=2, column=2)
root.mainloop()
# If main file then run: main()
if __name__ == "__main__":
main()
The application:
Image
I found someone who explained what was wrong.
Credits to Scriptman ( ^ , ^ ) /
simply adding:
sc = Screen()
And changing:
Button(tab_apg, text="Save selected", command=sc.Save_List_t)
Resolved the issue.

PyQt - QCombobox in QTableview

I am displaying data from an SQLite database in a QTableView using a QSqlTableModel. Letting the user edit this data works fine. However, for some columns I want to use QComboboxes instead of free text cells, to restrict the list of possible answers.
I have found this SO answer and am trying to implement it on my model/view setting, but I'm running into problems (so this is a follow-up).
Here's a full mini-example:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QTableView, QApplication, QHBoxLayout,
QItemDelegate, QComboBox)
from PyQt5.QtCore import pyqtSlot
import sys
class ComboDelegate(QItemDelegate):
"""
A delegate that places a fully functioning QComboBox in every
cell of the column to which it's applied
source: https://gist.github.com/Riateche/5984815
"""
def __init__(self, parent, items):
self.items = items
QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
combo = QComboBox(parent)
li = []
for item in self.items:
li.append(item)
combo.addItems(li)
combo.currentIndexChanged.connect(self.currentIndexChanged)
return combo
def setEditorData(self, editor, index):
editor.blockSignals(True)
# editor.setCurrentIndex(int(index.model().data(index))) #from original code
editor.setCurrentIndex(index.row()) # replacement
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentIndex())
#pyqtSlot()
def currentIndexChanged(self):
self.commitData.emit(self.sender())
class Example(QWidget):
def __init__(self):
super().__init__()
self.resize(400, 150)
self.createConnection()
self.fillTable() # comment out to skip re-creating the SQL table
self.createModel()
self.initUI()
def createConnection(self):
self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
self.db.setDatabaseName("test.db")
if not self.db.open():
print("Cannot establish a database connection")
return False
def fillTable(self):
self.db.transaction()
q = QtSql.QSqlQuery()
q.exec_("DROP TABLE IF EXISTS Cars;")
q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year NUMBER);")
q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")
self.db.commit()
def createModel(self):
self.model = QtSql.QSqlTableModel()
self.model.setTable("Cars")
self.model.select()
def initUI(self):
layout = QHBoxLayout()
self.setLayout(layout)
view = QTableView()
layout.addWidget(view)
view.setModel(self.model)
view.setItemDelegateForColumn(0, ComboDelegate(self, ["VW", "Honda"]))
for row in range(0, self.model.rowCount()):
view.openPersistentEditor(self.model.index(row, 0))
def closeEvent(self, e):
for row in range(self.model.rowCount()):
print("row {}: company = {}".format(row, self.model.data(self.model.index(row, 0))))
if (self.db.open()):
self.db.close()
def main():
app = QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
In this case, I want to use a QCombobox on the "Company" column. It should be displayed all the time, so I'm calling openPersistentEditor.
Problem 1: default values I would expect that this shows the non-edited field's content when not edited (i.e. the company as it is listed in the model), but instead it apparently shows the ith element of the combobox's choices.
How can I make each combobox show the model's actual content for this field by default?
Problem 2: editing When you comment out "self.fill_table()" you can check whether the edits arrive in the SQL database. I would expect that choosing any field in the dropdown list would replace the original value. But (a) I have to make every choice twice (the first time, the value displayed in the cell remains the same), and (b) the data appears in the model weirdly (changing the first column to 'VW', 'Honda', 'Honda' results in ('1', 'VW', '1' in the model). I think this is because the code uses editor.currentIndex() in the delegate's setModelData, but I have not found a way to use the editor's content instead. How can I make the code report the user's choices correctly back to the model? (And how do I make this work on first click, instead of needing 2 clicks?)
Any help greatly appreciated. (I have read the documentation on QAbstractItemDelegate, but I don't find it particularly helpful.)
Found the solution with the help of the book Rapid GUI Programming with Python and Qt:
createEditor and setEditorData do not work as I expected (I was misguided because the example code looked like it was using the text content but instead was dealing with index numbers). Instead, they should look like this:
def setEditorData(self, editor, index):
editor.blockSignals(True)
text = index.model().data(index, Qt.DisplayRole)
try:
i = self.items.index(text)
except ValueError:
i = 0
editor.setCurrentIndex(i)
editor.blockSignals(False)
def setModelData(self, editor, model, index):
model.setData(index, editor.currentText())
I hope this helps someone down the line.

Bug appears when writing file from tkinter module

import tkinter as tk
class Application(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.pack()
self.create_widgets()
def create_widgets(self):
self.hi_there = tk.Button(self)
self.hi_there["text"] = "Done!\n(click me)"
self.hi_there["command"] = self.say_hi
self.hi_there.pack(side="top")
self.entrythingy = tk.Entry()
self.entrythingy2 = tk.Entry()
self.entrythingy.pack()
self.entrythingy2.pack()
# here is the application variable
self.contents = tk.StringVar()
self.contents2 = tk.StringVar()
# set it to some value
self.contents.set("stdio")
self.contents2.set("script name")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
self.entrythingy2["textvariable"] = self.contents2
self.text = tk.Text()
self.text.pack()
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
self.quit = tk.Button(self, text="QUIT", fg="red",
command=root.destroy)
self.quit.pack(side="bottom")
def say_hi(self):
#print("hi there, everyone!")
self.fn = self.contents2.get()
self.body = self.text.get(1.0, tk.END).split('\n')
#print('Self.body:\n',self.body)
self.libs = self.contents.get().split(' ')
self.make_c()
def make_c(self):
lib_text = ''
for i in self.libs:
lib_text += "#include <lib.h>\n".replace('lib', i)
body_text = "int main() {\n\t"+"\n\t".join(self.body)+"return 0\n}"
print(lib_text+body_text)
with open(self.fn+'.c', 'w+') as f:
f.write(lib_text+body_text)
print('File written!')
from subprocess import call
call(['gcc',self.fn+'.c', '-o', self.fn])
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
#self.contents.set("")
#def
root = tk.Tk()
app = Application(master=root)
app.mainloop()
Those are the my code, which tries to make a c file and convert it. The problem is, when I convert it once, it is working fine, but when I change the content of the text box, the file doesn't change, and I don't understand why. I am sure that I put in the new file content, because it prints before it writes. Also, it appears that when I try to write files independent from tkinter, it works just the way I want it to.
I think there is some mechanism that I am not aware of in TK, or there is a bug. Please help me out, thanks.
I solved it. It doesn't compile again due to the error in it when I added return 0, without semicolon. So, when I click the executable file, it shows the old program. I added the semicolon, and now it is fine. Thx everyone!

Binding method to dynamically created labels

I have a Tkinter frame that is essentially showing a group of thumbnails, displayed in Label widgets. I need the Labels to be created dynamically to accommodate differing numbers of thumbnails to generate. I have a generated list of file names, and can create the thumbnails as needed, but when I try to bind a function to each of the created labels, it seems to be over ridden by the last created Label/Binding. The result is that only the final label has the method bound to it.
import tkinter as tk
class test(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args)
self.shell = tk.Frame(self)
self.shell.pack()
self.create_widgets()
def create_widgets(self):
'''
Two rows of labels
'''
for row in range(2):
for i in range(5):
text = 'Thumbnail\nrow{}\ncolumn{}'.format(row,i)
self.thumb = tk.Label(self.shell,
text = text)
self.thumb.grid(row = row, column = i, sticky = 'news')
self.thumb.bind("<Button-1>",lambda x: self.click(self.thumb))
def click(self, *args):
#This should affect only the Label that was clicked
print('CLICK!')
app = test()
root = app
root.mainloop()
The method being called will always be the same, but how do I identify the Label to be effected?
There are at least three solutions.
The first is the simplest: the function is passed an event object that contains a reference to the widget:
label = tk.Label(...)
label.bind("<Button-1>", self.click)
...
def click(self, event):
print("you clicked:", event.widget)
If you prefer to use lambda, you can pass the label itself to the function:
label = tk.Label(...)
label.grid(...)
label.bind("<Button-1>",lambda event, label=label: self.click(label))
Another solution is to keep a reference to every label in a list, and pass the index to the function:
self.labels = []
for row in range(2):
for i in range(5):
label = tk.Label(...)
label.grid(...)
self.labels.append(label)
index = len(self.labels)
label.bind("<Button-1>",lambda event, i=index: self.click(i))
...
def click(self, index):
print("the label is ", self.labels[index])
When you click label then tk runs function with event object which you skip using lambda x.
You need
lambda event:self.click(event, ...)
and then in click you can use event.widget to get clicked widget.
def click(event, ...);
print('Widget text:', event.widget['text'])
You had problem with self.thumb in self.click(self.thumb) because you don't know how works lambda in for loop. (and it is very popular problem :) )
lambda is "lazy". It doesn't get value from self.thumb when you declare lambda x: self.click(self.thumb) but when you click button (and you execute lambda. But when you click label then for loop is finished and self.thumb keeps last value.
You have to use this method to get correct value when you declare lambda
labda event, t=self.thumb: self,clikc(t)

Resources