TclError unknown option when unpacking a dictionary into treeview.insert (tkinter) - python

I have treeview tree built like so:
tree = ttk.Treeview(self.treeview_frame, columns=columns,
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set,
show="headings")
for name in columns:
tree.column(name, anchor=tk.W, width=150, stretch=True)
tree.heading(name, text=name, anchor=tk.CENTER)
Where columns for this example would be ["Annotation Name"].
I have a dictionary input_dict:
{'parent': '',
'iid': 'Shell-local',
'text': 'Shell-local (Ann.)',
'image': None,
'values': ('Shell Annotation',),
'open': True,
'tags': [],
'index': 'end'}
Which matches the required keyword arguments for treeview.insert(),
but when executing the following:
self.tree.insert(**input_dict)
The below error occurs:
File "C:\ProgramData\Miniconda3\envs\aat\lib\tkinter\ttk.py", line 1361, in insert
res = self.tk.call(self._w, "insert", parent, index,
_tkinter.TclError: unknown option "{Shell Annotation}"
I've tries replacing the backspace character with '\b':
new_values = tuple([str(val).replace(" ", "\b") for val in input_dict["values"]])
input_dict["values"] = new_values
But...:
File "C:\ProgramData\Miniconda3\envs\aat\lib\tkinter\ttk.py", line 1361, in insert
res = self.tk.call(self._w, "insert", parent, index,
_tkinter.TclError: unknown option "ShelAnnotation"
The reason I don't just assign the input arguments like this self.tree.insert(index=input_dict["index"],...) is because the input_dict is the result of __dict__ of a dataclass, and I want the code to be flexible with changes in the dataclass.
Anyone knows what causes these errors?

Looks like 'image' = None is responsible.
After popping the key 'image' from input_dict the code works.

Related

Getting none from fields while parsing a pdf file

I am trying to parse a pdf file. I want to get all the values in a list or dictionary of the checkbox values. But I am getting this error.
"return OrderedDict((k, v.get('/V', '')) for k, v in fields.items())
AttributeError: 'NoneType' object has no attribute 'items'"
The code I am trying is this
from collections import OrderedDict
from PyPDF2 import PdfFileWriter, PdfFileReader
def _getFields(obj, tree=None, retval=None, fileobj=None):
fieldAttributes = {'/FT': 'Field Type', '/Parent': 'Parent', '/T': 'Field Name', '/TU': 'Alternate Field Name',
'/TM': 'Mapping Name', '/Ff': 'Field Flags', '/V': 'Value', '/DV': 'Default Value'}
if retval is None:
retval = OrderedDict()
catalog = obj.trailer["/Root"]
# get the AcroForm tree
if "/AcroForm" in catalog:
tree = catalog["/AcroForm"]
else:
return None
if tree is None:
return retval
obj._checkKids(tree, retval, fileobj)
for attr in fieldAttributes:
if attr in tree:
# Tree is a field
obj._buildField(tree, retval, fileobj, fieldAttributes)
break
if "/Fields" in tree:
fields = tree["/Fields"]
for f in fields:
field = f.getObject()
obj._buildField(field, retval, fileobj, fieldAttributes)
return retval
def get_form_fields(infile):
infile = PdfFileReader(open(infile, 'rb'))
fields = _getFields(infile)
return OrderedDict((k, v.get('/V', '')) for k, v in fields.items())
if __name__ == '__main__':
from pprint import pprint
pdf_file_name = 'Guild.pdf'
pprint(get_form_fields(pdf_file_name))
After tracing through your code, on the 10th line it seems that catalog stores the value {'/Metadata': IndirectObject(16, 0), '/Pages': IndirectObject(1, 0), '/Type': '/Catalog'}, meaning /AcroForm is not a key in the dictionary and your function returns None.
Your _getFields explicitly returns None from first if block.
So basically that's where you could get this error from.

default argument in function for creating a dictionary creates an infinete loop of self references

a have a problem with the following with the following code:
from lxml import etree as ET
class test(object):
def __init__(self, **args):
self.tag = []
self.meshfiles = []
self.root = None
def addMesh(self, **args):
if not "filename" in args:
raise KeyError("No filename given")
else:
self.meshfiles.append(args["filename"])
def populateTree(self, tag, text='', attr={}, children={}):
return {'tag': tag, 'text': text, 'attr': attr, 'children': children}
#property
def tree(self):
baum = {'meshes': {}}
if len(self.meshfiles) == 1:
baum['meshes'] = self.populateTree('mesh', text=self.meshfiles[0])
else:
baum['meshes'] = self.populateTree('meshes')
for i, meshfile in enumerate(self.meshfiles):
# works:
baum['meshes']['children'][i] = self.populateTree('mesh', text=meshfile)
# not working:
# baum['meshes']['children'][i] = self.populateTree('mesh', text=meshfile, children={})
return baum
def createXML(self):
self.root = ET.Element("rootelement")
self.dict2xml(self.root, self.tree)
return ET.tostring(self.root, pretty_print=True)
def dict2xml(self, parent, dictionary):
for entry in dictionary:
self.tag.append(ET.SubElement(parent, dictionary[entry]['tag']))
self.tag[-1].text = str(dictionary[entry]['text'])
for attr in dictionary[entry]['attr']:
self.tag[-1].set(attr, dictionary[entry]['attr'][attr])
if len(dictionary[entry]['children']) > 0:
self.dict2xml(self.tag[-1], dictionary[entry]['children'])
if __name__ == "__main__":
t = test()
t.addMesh(filename="a")
t.addMesh(filename="c")
t.addMesh(filename="b")
print(t.tree)
print(t.createXML())
While this example gives a recursion error:
{'meshes': {'tag': 'meshes', 'text': '', 'attr': {}, 'children': {0: {'tag': 'mesh', 'text': 'a', 'attr': {}, 'children': {...}}, 1: {'tag': 'mesh', 'text': 'c', 'attr': {}, 'children': {...}}, 2: {'tag': 'mesh', 'text': 'b', 'attr': {}, 'children': {...}}}}}
Traceback (most recent call last):
File "/home/buchwalj/temp/bug-python/test.py", line 48, in <module>
print(t.createXML())
File "/home/buchwalj/temp/bug-python/test.py", line 32, in createXML
self.dict2xml(self.root, self.tree)
File "/home/buchwalj/temp/bug-python/test.py", line 41, in dict2xml
self.dict2xml(self.tag[-1], dictionary[entry]['children'])
File "/home/buchwalj/temp/bug-python/test.py", line 41, in dict2xml
self.dict2xml(self.tag[-1], dictionary[entry]['children'])
File "/home/buchwalj/temp/bug-python/test.py", line 41, in dict2xml
self.dict2xml(self.tag[-1], dictionary[entry]['children'])
[Previous line repeated 994 more times]
File "/home/buchwalj/temp/bug-python/test.py", line 36, in dict2xml
self.tag.append(ET.SubElement(parent, dictionary[entry]['tag']))
RecursionError: maximum recursion depth exceeded while calling a Python object
Commenting out line 27 instead of 25 prints out the correct dictionary with the corresponding xml. The only difference there is, that the working example uses the same argument as the default argument in the function call instead of the default argument itself.
Is this a bug or am I doing something wrong here?
Using empty dictionary as defaults are actually bad ideas, mainly because dictionaries are mutable objects, meaning if you accidentally changing the content of the dictionary, it is there to stay (cause reference to object stays the same). See: Why is the empty dictionary a dangerous default value in Python?
I would suggest changing your populateTree function to something like:
def populateTree(self, tag, text='', attr=None, children=None):
if attr is None:
attr = {}
if children is None:
children = {}
return {'tag': tag, 'text': text, 'attr': attr, 'children': children}
so it creates a new empty dictionary everything populateTree is called without those arguments.

Delete item from dictionary and Listbox

I have a program that puts the contents of a dictionary in a Tkinter Listbox, but I'm having trouble deleting it from the Listbox and the dictionary.
from tkinter import *
import ast
f = open("orders.txt", "r")
contents = f.read()
f.close()
things = ast.literal_eval(contents)
secondthing = [things, "test"]
root = Tk()
f = Frame(root).pack()
l = Listbox(root)
b = Button(root, text = "delete selection", command = lambda: delete(l))
b.pack()
l.pack()
for i, j in things.items():
oneitem = i + " " + j
l.insert(END, oneitem)
def delete(listbox):
global things
# Delete from Listbox
selection = l.curselection()
l.delete(selection[0])
# Delete from list that provided it
evaluater = l.get(selection[0])
value = eval(evaluater)
ind = things.index(value)
del(things[ind])
print(things)
root.mainloop()
When I try to delete something it gives me:
Exception in Tkinter callback
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/Users/mimmo/black_market/aaa.py", line 12, in <lambda>
b = Button(root, text = "delete selection", command = lambda: delete(l))
File "/Users/mimmo/black_market/aaa.py", line 28, in delete
value = eval(evaluater)
File "<string>", line 1
ohhh ohhhhh
^
SyntaxError: unexpected EOF while parsing
Can someone help me because I can delete it from the Listbox, I just have an error when deleting it from the dictionary.
The contents of orders.txt:
{"ayyy" : "ayyyyyy", "ohhh" : "ohhhhh"}
First of all, I would recommend using json or pickle to store contents of the dictionary - it's the most common practice. I don't really understand what do you want to do so I wrote a function which deletes an element from listbox and things by it's index.
An error you are getting is caused by eval function which tries to intepret your listbox item as python code. Of course, it's getting syntax error.
# Deletes element from listbox and thigs by it's index
def delete(listbox, index: int):
global things
item = listbox.get(index)
key = item.split()[0]
del things[key]
listbox.delete(index)

"can only concatenate str (not "int") to str" when zipping list of strings and list of objects

I get this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Hunter\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1883, in __call__
return self.func(*args)
File "c:\Users\Hunter\Documents\Programming\Python Scripts\Scripts\spoolClient\menuScript.py", line 46, in <lambda>
updateJsonButton = Button(preferences, text="Save Preferences", command=lambda: updateJson())
File "c:\Users\Hunter\Documents\Programming\Python Scripts\Scripts\spoolClient\menuScript.py", line 17, in updateJson
for i, j in zip(entryNames, entry):
File "C:\Users\Hunter\AppData\Local\Programs\Python\Python38-32\lib\tkinter\__init__.py", line 1643, in cget
return self.tk.call(self._w, 'cget', '-' + key)
TypeError: can only concatenate str (not "int") to str
When trying to run my script:
from tkinter import *
from tkinter.ttk import *
from tkinter import messagebox
from tkinter import filedialog
import qrMaker
import qrReader
import json
settings = {}
#define vars
preferencesSkippedRows = [1, 3, 5, 7, 9, 11]
def openPreferences():
def updateJson():
print("here")
for i, j in zip(entryNames, entry):
print("loopdie")
value = str(j.get())
settings[i]=value
settingsjson = json.dumps(settings)
print(settingsjson)
f = open("preferences.json","w")
f.write(settingsjson)
f.close()
preferences = Tk()
preferences.title("Preferences")
preferences.iconbitmap(qrMaker.getCurrentPath()+'icon.ico')
preferences.geometry('400x600')
topText = Label(preferences, width=30, text="Filament Spool Client Preferences")
cameraText = Label(preferences, width=30, text="Select Camera Instance:")
cameraEntry = Combobox(preferences, width=30, values=qrReader.getCameras())
qrWidthText = Label(preferences, width=30, text="QR Output Width (in.)")
qrWidthEntry = Entry(preferences, width=30)
qrHeightText = Label(preferences, width=30, text="QR Output Height (in.)")
qrHeightEntry = Entry(preferences, width=30)
text = [cameraText, qrWidthText, qrHeightText]
entry = [cameraEntry, qrWidthEntry, qrHeightEntry]
entryNames = ['cameraEntry', 'qrWidthEntry', 'qrHeightEntry']
updateJsonButton = Button(preferences, text="Save Preferences", command=lambda: updateJson())
for i in preferencesSkippedRows:
preferences.grid_rowconfigure(i, minsize=10)
topText.grid(column = 0, row = 0)
row=2
for text, entry in zip(text, entry):
text.grid(column = 0, row = row)
entry.grid(column = 1, row = row)
row+=2
updateJsonButton.grid(column=1, row=row+2)
preferences.mainloop()
openPreferences() #I call script.openPreferences() in my main program but I left this here for debugging purposes
I can see from the error message that the error occurs somewhere in the line that my zip function occurs, but I have no idea what causes this. Oddly enough, this error goes away if instead of setting updateJson equal to the command value of my Tkinter button state, I set updateJson, which calls the function right as the button object is initialized. I also know what the error is saying, I just don't know where an integer is coming from, and how I can fix this issue. Any help would be appreciated.
Update: I've just found that the actual zipping of the two lists is not the problem, but when I introduce the for loop, the same error occurs.
Answering to close out this thread, answer from "user2357112 supports Monica".
The issue in this script is that for text, entry in zip(text, entry) literally uses "entry" in the for loop, and is executed after the button instance is created, meaning that if updateJson is called during the button object initialization, then there will be no error thrown as entry is still defined as a list. However, after for text, entry in zip(text, entry) executes at startup, entry is now defined as the last object in the list entry, no longer the list entry itself. When the user presses the button and updateJson is called, an error is thrown as entry is not a list anymore(I'm not 100% sure on the error part).

How would I copy the contents of a Listbox row in Tkinter which the user selects to the windows clipboard

So I want to make a Tkinter Listbox where if the user clicks (preferably double clicks) on a row the contents of that row would be copied to the clipboard.
Here is example code of what I need help with:
from tkinter import *
app = Tk()
def listbox_copy():
app.clipboard_clear()
app.clipboard_append(listbox[1])
listbox = Listbox(app)
list = ['string 1', 'string 2', 'string 3']
for c in list:
listbox.insert(END,c)
listbox.place(relx=0.5, rely=0.55, anchor="center")
listbox.bind('<<ListboxSelect>>', lambda event: listbox_copy())
app.mainloop()
Right now I'm getting this error:
File "C:\Users\elias\Desktop\test.py", line 14, in <lambda>
listbox.bind('<<ListboxSelect>>', lambda event: listbox_copy())
File "C:\Users\elias\Desktop\test.py", line 7, in listbox_copy
app.clipboard_append(listbox[1])
File "C:\Users\elias\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1489, in cget
return self.tk.call(self._w, 'cget', '-' + key)
TypeError: can only concatenate str (not "int") to str
Thank you for any help.
You should get the selected item using listbox.get(ANCHOR) instead of listbox[1]. If you want to copy the selected item to clipboard when user double-clicks the item, bind <Double-Button-1> instead of <<ListboxSelect>>:
def listbox_copy(event):
app.clipboard_clear()
selected = listbox.get(ANCHOR)
app.clipboard_append(selected)
...
listbox.bind('<Doubld-Button-1>', listbox_copy)

Categories

Resources