PyQt: removing custom widgets from a QListWidget - python

I am loading in a picture into a QGraphicsScene then when you click on the photo it will place circles in the area that is clicked. Then I am adding a custom widget that will keep a list of the points. This widget has a couple of buttons. one of them will be able to move the circle and the other will be to delete it. I am currently stuck on the delete part.
Problem
I can delete the circle just fine and I can delete the widget from the list. The problem is there is still a space in the list from where the widget once was and since I'm using the button from the widget and not selecting the item I'm not sure how to delete that spot. Also if I delete a bunch and then try and add some Python its self will crash and I have no idea why.
I'm not sure if what I want can be done since there really is no reference or if I'm better off moving the buttons to the main window and removing them from the custom widget. If it is possible I would like to keep it the way I have it.
There are a few files so I will put it on GitHub so I am not to take up a ton of space. Any help is much appreciated.
GitHub Link
Link to GitHub Project
The relevant code (from Main.py):
class MainWindow(QMainWindow, Ui_MainWindow):
...
def Add_History(self,pos):
self.HistoryWidget = HistoryList()
self.HistoryWidget.setObjectName("HistoryWidget_"+ str(Constents.analysisCount))
myQListWidgetItem = QListWidgetItem(self.History_List)
myQListWidgetItem.setSizeHint(self.HistoryWidget.sizeHint())
self.History_List.addItem(myQListWidgetItem)
self.History_List.setItemWidget(myQListWidgetItem, self.HistoryWidget)
self.HistoryWidget.buttonPushed.connect(self.deleteObject)
self.HistoryWidget.setXpoint(str(pos.x()))
self.HistoryWidget.setYpoint(str(pos.y()))
self.HistoryWidget.setHistoryName("Point "+ str(Constents.analysisCount))
Constents.analysisCount = Constents.analysisCount + 1
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
HistoryWidget = self.findChildren(HistoryList, "HistoryWidget_"+number)[0] #find the actual object
HistoryWidget.deleteLater() #delete that object
#Simport pdb; pdb.set_trace()
#self.History_List.takeItem(HistoryWidget)
Pictures

You must delete both the item-widget and the item itself. To do that, a method is required for getting an item from an item-widget (or one of its child widgets). A clean way to do this is to use the list-widget's itemAt method, which can get an item from a point on the screen. The major benefit of doing it this way is that it does not require knowledge of the item's index, which can of course change when other items are deleted. It also means the item-widgets don't need to know anything about the specific list-widget items they are associated with.
Here is a re-write of your deleteObject method, which implements that:
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
# get the viewport coords of the item-widget
pos = self.History_List.viewport().mapFromGlobal(
sender.mapToGlobal(QtCore.QPoint(1, 1)))
if not pos.isNull():
# get the item from the coords
item = self.History_List.itemAt(pos)
if item is not None:
# delete both the widget and the item
widget = self.History_List.itemWidget(item)
self.History_List.removeItemWidget(item)
self.History_List.takeItem(self.History_List.row(item))
widget.deleteLater()

Related

Referencing child widgets (QPushButton) in QScrollArea only refers to the last instance of the button object, regardless of which button is clicked

Alright, so I'm pretty deep into an app I'm making using PyQt5 and I have a section (QScrollArea) where I essentially have multiple lines of "accounts" which are QWidgets. The user can add an account and delete an account, but I would like to give them the ability to edit an account. On the same row as the account is a QPushButton, which is basically the edit button. If I have 3 accounts, then I can see 3 buttons. However, when I click the button for any account, all of the buttons refer to the exact same button object. I am storing the objects in a list and using findChildren() to get the objects. Both of these will give me three different button objects, but using clicked.connect() with the buttons reference only the latest, or bottom-most, button.
I am calling the clicked.connect() function after the part of my code that adds the account. I have also tried moving this to right below where I instantiate the button, but it didn't work.
Here is what I have:
btns = self.accountsWidget.findChildren(QPushButton)
for btn in btns:
btn.clicked.connect(lambda: self.editAccount(btn))
The method I call to try to edit the account is here:
def editAccount(self, btn):
print(btn)
self.editAccountWindow = EditAccountsWindow()
self.editAccountWindow.show()
The result of what's printed is always the last QPushButton object, and I'm only printing to see if I referenced the button I wanted to. That method just opens up a window, but what's shown on the window depends on calling the correct object. I want to use the object to reference which account I want to edit.
I have looked on many SO threads and could not find anything similar to this. I think I'm missing something, so please help a brother out :)
def editAccount(self, objects):
sender = self.sender()
index = 0
for object in objects:
if sender == object:
account = account_handler.get_account(index)
...
widgets = self.accountsScrollView.findChildren(QWidget, "accountsWidget")
for widget in widgets:
editButtons = widget.findChildren(QPushButton, 'editButtonAccounts')
for e in range(0, len(editButtons)):
editButtons[e].clicked.connect(lambda: self.editAccount(edit_objects_list))
...

Try to correctly show image in tkinter into a method of a class

I am writing a class that builds a canvas and positionate elements containing Title, Description and an Icon.
Everything works well except the part of showing the icon.
The class itself build the canvas and it have a method to add elements.
The problem comes when this function into the class.
def addElement(self, title='Element title', description='Element description' icon='*')
if self.elementnumber != 10:
self.elementnumber = self.elementnumber+1
else:
raise Exception("The maximum number of elements permitted is 10.")
element=Frame(self.iconframe.interior, name=str(self.elementnumber))
if icon != '*':
self.iconimage = PhotoImage(file=icon)
elementicon = Label(element, image=self.iconimage,name='icon')
else:
elementicon = Label(element, name='icon')
elementtitle = Label(element, text=title, name='title')
elementdescription = Label(element, text=description, name='description', wraplength=155,justify=LEFT)
So, when I call the function and put an icon, if I put self. to iconimage, I keep the reference and the image show correctly, but only the image for the last element.
I know that this is because self. is the reference for the main class and not for the element of the function, so this element keeps updating everytime I call the function, leaving only the last icon.
But, if I call the PhotoImage function without self., anyone of the images shows it correctly.
So i don't know how to dow it to show the images correctly.
self.iconimage has to be a list and for each new element, you have to manage the Index of that list.
Updating the self.iconimage to a list, try to use the append mehtod when adding New elements. Then, in elementicon, inform the new item appended of self.iconimage list.

PyQt need to throw custom widgets into a list and be able to move them around

I have been fighting with custom widgets and lists for weeks now.
I have the ability to add a custom widget to a QListWidget. My issue is the insertItem seems to be buggy as it always drops it to the bottom of the list no matter what row I tell it to go to.
def AddToInitiative(self):
creature = self.comboBoxSelectCharacter.currentText()
if(creature):
widget = self.NewItemWidget()
customWidgetItem = QtGui.QListWidgetItem(self.initiativeList)
customWidgetItem.setSizeHint(QtCore.QSize(400,50))
self.initiativeList.addItem(customWidgetItem)
self.initiativeList.setItemWidget(customWidgetItem, widget)
widget.lineName.setText(creature)
return
def NewItemWidget(self):
customWidget = creature_initiative_object.InitCreatureObject()
customWidget.btnRemove.clicked.connect(self.ItemButtonClicked)
customWidget.btnInfo.clicked.connect(self.ItemButtonClicked)
customWidget.btnIncreaseInitOrder.clicked.connect(self.ItemButtonClicked)
customWidget.btnDecreaseInitOrder.clicked.connect(self.ItemButtonClicked)
return customWidget
def ChangeInit(self, row, direction, oldwidget):
item = self.initiativeList.takeItem(row)
widget = self.NewItemWidget()
widget.lineName.setText(oldwidget.lineName.text())
customWidgetItem = QtGui.QListWidgetItem(self.initiativeList)
customWidgetItem.setSizeHint(QtCore.QSize(400,50))
self.initiativeList.insertItem((row + direction), customWidgetItem)
self.initiativeList.setItemWidget(customWidgetItem, widget)
self.initiativeList.setCurrentRow(row+direction)
If I try to move the item up or down the list it always just goes straight to the bottom despite assigning the row number. I've output the row number and count to verify it should be saying the right row.
Do you know a way to do this with QListWidget for custom widgets that need to preserve data? Is there a better module to use?
I've looked at QListView, QBoxLayout, and a few others without being able to get it to work as well as QListWidget.

Python Tkinter - Multiple entry box with same validation

I have created around 5 Entry boxes and binded them as well. Take this as model:
def makeEntry(self, master, data):
self.entry = Entry(master, width=8, textvariable=data)
self.entry.bind("<Leave>", lambda event, value=data: self.validate(event, value))
Now, I did also a validate method that check if the input was a string (and if so, the highlight background of the entry would change to red). The problem which is still taking me a lot of time is that I would need that the method should be able to check every entries, and if at least one of them has got a red background, then a final button should be disabled (button.configure(state=DISABLED)).
With just one entry it would be much easier, I would simply check if the background was red (status = str(self.myOneEntry.cget("highlightbackground"))), but what about with more entries?
If you want to check all of your entries, keep them in a list. Then, write a function that iterates over the list and sets the button state to disabled if any widget has a red background. You can then call this whenever something changes, such as within your validation function for each widget.
Example:
class Example(...):
def __init__(...):
self.entries = []
for data in ("one","two","three"):
entry = makeEntry(...)
self.entries.append(entry)
def _update_button(self):
for entry in self.entries:
if entry.cget("background") == "red":
self.button.configure(state="disabled")
return
self.button.configure(state="normal")
Note: you'll need to make sure that makeEntry(...) returns a reference to the created widget.
Also, you don't have to use makeEntry. You can create your widgets however you want. The point is to save references to the widgets in a data structure that you can iterate over. It doesn't matter how the widgets are created.

Updating cell widgets changes in a Qtable without selecting the cell

well I'm using Pyqt4 in Maya2012 to make a reference editor alike ui. I working with a QtableWidget to make the reference list and i have subwidgets in each cell. One of the widgets is a checkbox that unload or reload the reference.
The problem i have is if a click directly in the checkbox without have the cell selected it doesn't do anything
this is my code:
def listConnections(self):
self.pos=self.sender().currentRow()
wid = self.list.ref_l.cellWidget(self.pos, 0).children()
self.text = self.list.list[self.pos]
self.ref()
for wt in wid:
if type(wt)== type(QCheckBox()):
wt.stateChanged.connect(self.changeState)
if type(wt)== type(QComboBox()):
wt.currentIndexChanged.connect(self.changeType)
I'm calling the function with a "itemSlectionChanged" signal because is the only way i knew i could detect the subwidgets.
All the subwidgets are made in the moment i fill the list.
Is there a way to make what i want?
Edit:
This is how i called the function
self.list.ref_l.itemSelectionChanged.connect(self.listConnections)
and this is how i create all the subwidgets in the cells
def fillList(self):
mayaRef = self.findRef()
if len(mayaRef)>0:
for count in range(0,len(mayaRef)):
self.ref_l.insertRow(count)
wid=QWidget()
cLayout=QHBoxLayout()
wid.setLayout(cLayout)
checkWid=QCheckBox()
nameWid=QLabel()
cLayout.addWidget(nameWid)
nameWid2=QLabel()
cLayout.addWidget(nameWid2)
comWid=QComboBox()
cLayout.addWidget(comWid)
self.ref_l.setCellWidget(count,0,wid)
self.ref_l is my QTable Widget, this is in another code that i'm calling with self.list in the original
You should set up all your connections right after you create the check boxes in fillList. Each item is associated with the path of the reference. You can use a QSignalMapper to map each check box to the path, then connect the signal mapper to your changeState slot. The signal mapper then calls that slot with the path you specified.

Categories

Resources