PyQt4 - "RuntimeError: underlying C/C object has been deleted" - pyqt4

I keep getting this RuntimeError which I'm not sure how to fix. Here's what I'm trying to accomplish. I want to update this QTableWidget with values dynamically as I'm clicking on different items in my QTreeView. On the most part, my code works except when I click on my second item and I need to update my QTableWidgt which is when I run into this "RuntimeError: underlying C/C object has been deleted". Here's a snippet of my code:
def BuildTable( self ):
...
for label in listOfLabels :
attr = self.refAttr[label]
self.table.setItem(row, 0, QtGui.QTableWidgetItem( label ) )
tableItem = QtGui.QTableWidgetItem( str(attr.GetValue()) )
self.table.setItem(row, 1, tableItem )
someFunc = functools.partial( self.UpdateValues, tableItem, label )
QtCore.QObject.connect(self.table, QtCore.SIGNAL('itemChanged(QTableWidgetItem*)'), someFunc)
def UpdateValues(self, tableItem, label):
print '--------------------------------'
print 'UPDATING TEXT PROPERTY VALUE!!!'
print tableItem.text()
print label
The compiler complains errors on the line, "print tableItem.text()"
Thx!

I believe the issue is that you are binding up a callback with a QTableWidget item and making many many connections (bad). Items can change. Thus, they can be deleted making your callback dead.
What you want is to just let the itemChanged signal tell you what item changed, the moment it happens.
self.table = QtGui.QTableWidget()
...
# only do this once...ever...on the init of the table object
QtCore.QObject.connect(
self.table,
QtCore.SIGNAL('itemChanged(QTableWidgetItem*)'),
self.UpdateValues
)
And then in your SLOT, it will receive the item:
def UpdateValues(self, tableItem):
print '--------------------------------'
print 'UPDATING TEXT PROPERTY VALUE!!!'
print tableItem.text()

Related

ttk Treeview, get item's bbox after scrolling into view

I am working on an editable tkinter.ttk.Treeview subclass. For editing I need to place the edit widget on top of a choosen "cell" (list row/column). To get the proper coordinates, there is the Treeview.bbox() method.
If the row to be edited is not in view (collapsed or scrolled away), I cannot get its bbox obviously. Per the docs, the see() method is meant to bring an item into view in such a case.
Example Code:
from tkinter import Tk, Button
from tkinter.ttk import Treeview
root = Tk()
tv = Treeview(root)
tv.pack()
iids = [tv.insert("", "end", text=f"item {n}") for n in range(20)]
# can only get bbox once everything is on screen.
n = [0]
def show_bbox():
n[0] += 1
iid = iids[n[0]]
b = tv.bbox(iid)
if not b:
# If not visible, scroll into view and try again
tv.see(iid)
# ... but this still doesn't return a valid bbox!?
b = tv.bbox(iid)
print(f"bbox of item {n}", b)
btn = Button(root, text="bbox", command=show_bbox)
btn.pack(side="bottom")
root.mainloop()
(start, then click the button until you reach an invisible item)
The second tv.bbox() call ought to return a valid bbox, but still returns empty string. Apparently see doesnt work immediately, but enqeues the viewport change into the event queue somehow. So my code cannot just proceed synchronously as it seems.
How to solve this? Can see() be made to work immediately? If not, is there another workaround?
The problem is that even after calling see, the item isn't visible (and thus, doesn't have a bounding box) until it is literally drawn on the screen.
A simple solution is to call tv.update_idletasks() immediately after calling tv.see(), which should cause the display to refresh.
Another solution is to use tv.after to schedule the display of the box (or the overlaying of an entry widget) to happen after mainloop has a chance to refresh the window.
def print_bbox(iid):
bbox = tv.bbox(iid)
print(f"bbox of item {iid}", bbox)
def show_bbox():
n[0] += 1
iid = iids[n[0]]
tv.see(iid)
tv.after_idle(print_bbox, iid)

Python 3.7 + tkInter : How can I make sure a button is assigned an individual function from a file?

I am having some issues wrapping my head around something I encountered in python recently.
So, basically, I want to allow for a user to load several json files, all listed in a python list. These files contain parameters used to create buttons with, namely, the color the button should have, the text that should be displayed in it and the command that it needs to execute once clicked.
def createTags(self):
for items in self.LoadedInstallProfiles:
with open(items, "r") as jsonfiles:
self.loadeddata = json.load(jsonfiles)
self.tag = Button(self.tagmenu, text=self.loadeddata.get("profilename"), background=
self.loadeddata.get("profilecolor"), command=print(self.loadeddata.get("profilename")))
self.tag.pack(side="top",fill="x")
The problem is: the buttons show up with their individual color and text, but all seem to print out the same profilename when clicked, which is that in the last json file in the list.
I common way is to store the created button widgets in a list. I have modified your method. See below.
def createTags(self):
# First create the widget and append to list variable
self.tags = [] #List to store button widgets
for items in self.LoadedInstallProfiles:
with open(items, "r") as jsonfiles:
loadeddata = json.load(jsonfiles)
text = loadeddata.get("profilename")
bg = loadeddata.get("profilecolor")
tag = Button( self.tagmenu, text=text, background=bg, command=print(text) )
self.tag.append( tag )
# Then display the widgets
for tag in self.tags:
tag.pack(side="top",fill="x")
I imagine the problem with command=print(self.loadeddata.get("profilename")) is similar to the problem with lambda statements (that said I am surprised your buttons work at all They should print once at init and then never work after that because you are calling print at button creation instead of saving a reference to print).
Due to the nature of how lambda works here in a loop like this you end up only printing the last value in the loop for all commands. Instead you need to use a lambda statement and also define the value in the lambda for each loop to accurately record the correct data for the print statement.\
I created 3 test files for this:
test.json:
{"profilename":"test", "profilecolor": "green"}
test2.json:
{"profilename":"test2", "profilecolor": "blue"}
test3.json:
{"profilename":"test3", "profilecolor": "orange"}
Example code:
import tkinter as tk
import json
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.btn_list = []
for file in ['test.json', 'test2.json', 'test3.json']:
with open(file, 'r') as f:
self.btn_list.append(json.load(f))
self.create_tags()
def create_tags(self):
for item in self.btn_list:
tk.Button(self, text=item.get("profilename"), background=item.get("profilecolor"),
command=lambda x=item.get("profilename"): print(x)).pack(side="top", fill="x")
if __name__ == '__main__':
Window().mainloop()
Results:

wxPython listctrl insertitem and SetItem all at once

I have a listctrl,
self.list = wx.ListCtrl(panel, -1, style=wx.LC_REPORT | wx.LC_NO_HEADER)
self.list.InsertColumn(col=0, heading='', format=wx.LIST_FORMAT_CENTER, width=150)
self.list.InsertColumn(col=1, heading='', format=wx.LIST_FORMAT_CENTER, width=450)
for person in people:
#this is the issue right here
index = self.list.InsertItem(0, person.age) #one line to insert an item and get the index, and the next line to update the item at that index... two lines to actually put a full entry in.
self.list.SetItem(index=index, column=1, label=person.name)
This works fine setting up the listctrl initially in the constructor, but what if I want to dynamically add/remove items in the listctrl at runtime?
I've come across wx.CallAfter, wx.CallLater, startWorker (from wx.lib.delayedresult), and wx.Timer
If you look at the example above the issue is that I've got one line that inserts the item, and another line that updates the item to have the correct name on the item that was just inserted. So if I've got threads that are taking turns removing and adding items to the listctrl, if I insert an item and another thread inserts an item at the same time, the index that I just got won't be relevant for updating. i.e. I need an atomic operation for inserting an item that includes inserting both the person's age and the person's name. So my first question is, is there a way to insert all information of a list item in one line?
If I cannot do that, then my next question is how could I accomplish the prescribed behavior? For example, suppose there are threads randomly coloring the top row, adding, and deleting:
self.color_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_colorizer, self.color_timer)
self.red_shown = True
self.color_timer.Start(500)
self.delete_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_deleter, self.delete_timer)
self.delete_timer.Start(500)
self.adder_timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.item_queuer, self.adder_timer)
self.adder_timer.Start(400)
Then here are my methods that are used to add people, delete people, and color the top row:
def item_queuer(self, event):
startWorker(consumer=self.item_adder,
workerFn=self.person_generator)
def person_generator(self):
return {'person':random.choice(people)}
def item_adder(self, result):
res = result.get()
person = res['person']
wx.CallAfter(self.list.InsertItem, 0, person.name) # this is the problem right here. If another thread does a color swap in between this line and the next, then this won't work.
wx.CallAfter(self.list.SetItem, index=0, column=1, label=person.name)
def item_deleter(self, event):
wx.CallAfter(self.list.DeleteItem, 0)
def item_colorizer(self, event):
if self.color_timer.IsRunning():
if not self.red_shown:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.RED)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.WHITE)
self.red_shown = True
else:
wx.CallAfter(self.list.SetItemBackgroundColour, 0, wx.BLUE)
wx.CallAfter(self.list.SetItemTextColour, 0, wx.BLACK)
self.red_shown = False
What actually happens when I run this is that I end up having rows where the person is partially inserted (just the age), and the color changes before the name is inserted. I've noticed that the InsertItem method on the listctrl is overloaded and offers one signature where I can insert a ListItem, but I cannot get that to work either.
item1 = wx.ListItem()
item1.SetBackgroundColour(wx.GREEN)
item1.SetColumn(0)
item1.SetText(32)
item1.SetColumn(1)
item1.SetText('John')
self.list.InsertItem(item1)
wx._core.wxAssertionError: C++ assertion "rv != -1" failed at ....\src\msw\listctrl.cpp(1730) in wxListCtrl::InsertItem(): failed to insert an item in wxListCtrl

insert data into text widget in a For loop, tkinter python3

i am new to Python + tkinter
I am trying to enter values into a text widget using FOR loop
The problem is, text widget not showing anything during For loop execution. When for loop finishes, it shows all values.
How can I show inserted values during for loop.
See last line of code
for item in liList:
listDict = {}
# get a tag href
listATag = item.find_all("a", attrs={"class": "product-image"})[0]
listATagHref = listATag['href']
listDict["purchaseLink"] = listATagHref
imgPageRequest = requests.get(listATagHref)
imgPageData = imgPageRequest.text
imgPageSoup = BeautifulSoup(imgPageData, 'lxml')
try:
productImgDiv = imgPageSoup.find_all('div', attrs={"class": "product-image"})[0]
imgATag = productImgDiv.find_all('a')[0]['href']
largeThumbFileName = (imgATag.split('/')[-1])
tempImgNameList.append(largeThumbFileName)
print(listATagHref)
textBox.insert(END,listATagHref+'\n')
etc...
You need to call update on the widget you are adding new data to, for it to refresh and show the new values after each iteration. This is as the Tk mainloop will normally catch the new information on your widget and update it, but when you are in a loop such as this, it cannot check until after the loop is finished.
If root is what you define to be Tk(), then you can call root.update(), or on the actual widget itself. This would go at the end of your for loop.
aside from updating the window, or the text widget you can change the first argument of your textbox from END to INSERT.
import tkinter as tk
# inside the try block change the first argument to INSERT
textBox.insert(tk.INSERT,listATagHref+'\n')

How to get object name of the widget in focus?

Very similiar to
print("focus object class:", window2.focus_get().__class__)
taken from here:
Python get focused entry name
, but I need the exact name of the object.
Something like: self.entrywidget_1
OR:
What to fill the place holder to make if true ?
print(self.focus_get().__class__)
if self.focus_get().__class__ == "placeholder":
print("I work")
The first print returns < class 'tkinter.Entry' >
You can't. Any given object can have many names. For example, given the following code, which name would you expect to get?
self.foo = tk.Button(...)
self.bar = self.foo
You rarely, if ever, need the name of the widget. Having a reference to the widget -- like that is returned by focus_get() -- is all you need. With that yo can insert data, delete data, destroy the widget, get the widget contents, etc.
If you really do need some predictable, unique identifier in the case where the same function must work for multiple widgets, you can give each widget a custom attribute that is a symbolic name. You can then get that name at runtime. For example:
e = tk.Entry(...)
e.name = "placeholder"
...
focused_widget = root.focus_get()
print (the name of the focused widget is %s" % focused_widget.name)
Again, you likely won't ever need that. For example, if you want to delete the text in the focused widget, just use the reference to the widget:
focused_widget.delete(0, "end")
If all you really need is to check if the focused widget is a specific widget, just do a direct comparison:
...
self.placeholder = tk.Entry(...)
...
def whatever(self):
focused_widget = root.focus_get()
if focused_widget == self.placeholder:
print("I work")

Resources