Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Getting Row from ListWidget



  • I'm trying to figure out how to get the selected row from a list widget. Currently I have a method that will get me the text of the row but I need to find the actual position. (Basically I have a dictionary with duplicate values but unique keys the list widget is displaying the values but I need to know which unique key which is handled by position) Here's what I've got so far:

    import sys
    from PySide2 import QtCore, QtGui, QtWidgets
    
    # Create Test List/Dictionary
    list_dict = {1: "First Label", 2: "Second Label", 3: "First Label"}
    
    list_list = [1, 2, 3]
    
    #Create Widgets/Layout
    class Widget(QtWidgets.QWidget):
        def __init__(self, parent = None):
            super(Widget, self).__init__(parent)
            self.listwidget = QtWidgets.QListWidget()
            self.lineedit = QtWidgets.QLineEdit("Nothing clicked")
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(self.listwidget)
            lay.addWidget(self.lineedit)
    
            # Populate List with Values from Dictionary
            for e in list_list:
                value = list_dict.get(e)
                it = QtWidgets.QListWidgetItem()
                it.setData(QtCore.Qt.DisplayRole, value)
                self.listwidget.addItem(it)
            
            self.listwidget.itemClicked.connect(self.on_item_clicked)
        
        @QtCore.Slot(QtWidgets.QListWidgetItem)
        def on_item_clicked(self, item):
            # This gets the text from the list box
            key = item.data(QtCore.Qt.DisplayRole)
            self.lineedit.setText(key)
            
    def main():
        app = QtWidgets.QApplication(sys.argv)
        window = Widget()
        window.show()
        sys.exit(app.exec_())
    
    if __name__ == "__main__":
        main()
    

    I've tried changing key = to be row.data, self.row, self.currentrow. I've also tried changing the def on_item_clicked to contain self and row instead of self and item, but I haven't figured out any way to get the row passed from the listwidget into my function.

    Note: Currently I'm using the lineedit box to report out what is passed into key just to help me check the code.


  • Banned

    Okay a simple tweak and you now have the Text and the Row Id I am assuming that is what you needed. Keep in mind (as the code shows) your Slot is receiving the Item and the Row Id is located in the List Widget -- I also removed all the comments since those were in the first rendition and thus not needed here

    from PySide2.QtCore    import Qt, Slot
    #from PySide2.QtGui     import 
    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout
    from PySide2.QtWidgets import QLineEdit, QListWidget, QListWidgetItem
    
    from sys import exit as sysExit
    
    #Create Widgets/Layout
    class Widget(QWidget):
        def __init__(self):
            QWidget.__init__(self)
    
            self.ListDict = {1: 'First Label', 2: 'Second Label', 3: 'Third Label'}
            self.ListList = [1, 2, 3]
    
            self.lswList = QListWidget()
            self.lswList.itemClicked.connect(self.on_item_clicked)
    
            self.lneId = QLineEdit('-1')
            self.lneDsply = QLineEdit('Nothing clicked')
    
            HBox = QHBoxLayout()
            HBox.addWidget(self.lneId)
            HBox.addWidget(self.lneDsply)
    
            VBox = QVBoxLayout()
            VBox.addWidget(self.lswList)
            VBox.addLayout(HBox)
            VBox.addStretch(1)
    
            self.setLayout(VBox)
    
          # Populate List with Values from Dictionary
            for Item in self.ListList:
                value = self.ListDict[Item]
    
                ViewItem = QListWidgetItem()
                ViewItem.setData(Qt.DisplayRole, value)
    
                self.lswList.addItem(ViewItem)
            
        
        @Slot(QListWidgetItem)
        def on_item_clicked(self, Item):
          # This gets the Row Id from list box
            RowId = self.lswList.currentRow()
            self.lneId.setText(str(RowId))
          # This gets the Text from the list box
            ListData = Item.text()
            self.lneDsply.setText(ListData)
    
    if __name__ == "__main__":
        MainEventHandler = QApplication([])
    
        application = Widget()
        application.show()
        
        sysExit(MainEventHandler.exec_())
    
      # If anyone wants more extensive free help I run an online lab-like classroom-like 
      # message server feel free and drop by you will not be able to post until I clear 
      # you as a student as this prevents spammers so if interested here is the invite
      # https://discord.gg/3D8huKC
    

  • Qt Champions 2019

    The currently selected rows can be retrieved through the selection model



  • That definitely got me closer. I was able to find this site that gives me the python syntax for the selection model: https://doc.qt.io/qtforpython/PySide2/QtCore/QItemSelectionModel.html, but I'm still having trouble getting it to work in the code.

    I edited the on_item_clicked(self,item) function to now have

            active_item = QtCore.QItemSelectionModel.selectedRows
            self.lineedit.setText(str(active_item))
    

    But the value returned by active item is:

    <method 'selectedRows' of 'PySide2.QtCore.QItemSelectionModel' objects>
    

    How do I actually get the value? I tried wrapping it in item.data like I had previously with the display role but that gives me an error.



  • @BD4L
    You need to get the selection model of the QListWidget instance you have. So:

    self.listwidget.selectionModel().selectedRows()
    

    would give you a list of QModelIndexes into your model.

    Depending on what you want, see e.g. https://doc.qt.io/qt-5/qlistwidget.html#selectedItems which gives you a list of QListWidgetItems without going via indexes.

    Your "but I need to know which unique key which is handled by position" sounds a bit dodgy, but up to you. Another way of dealing with such an issue is to store the necessary discriminatory item (your unique key) in the list item data, so that the value you get back includes whatever is needed to distinguish, rather than relying on looking at row numbers. Which is then more flexible, e.g. if rows get reordered.


  • Banned

    Wow okay here is a simple straight forward answer to your question created using your MRE please pay attention to the comments I added as they will help you correct things you are doing that are wrong or dangerous. Note I tested this using PyQt5 but I am pretty sure this will work as is within PySide2

    from PySide2.QtCore    import Qt, Slot
    #from PySide2.QtGui     import 
    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout
    from PySide2.QtWidgets import QLineEdit, QListWidget, QListWidgetItem
    
    # Always declare additional imports after you delcare your Qt imports
    from sys import exit as sysExit
    
    #Create Widgets/Layout
    class Widget(QWidget):
      # ", parents" not needed nor does it make any sense due to where its being called from
        def __init__(self):
          # First do not use super( ) unless you are fully aware of what 3 major issues 
          # you must code for in conjunction with using it and the actual rare issue 
          # that it is meant to solve. Note this rare issue is rare and unless you are 
          # doing some complicated inheritance you will most likely never run into that
          # issue -- however the 3 major issues it creates by using it you are much more 
          # likely to run into than the rare issue its meant for
          #
          #  super(Widget, self).__init__(parent)
            QWidget.__init__(self)
    
          # Do not create Global variables unless you absolutely most definitely need to
          # which is next to never
          # Create Test List/Dictionary
            self.ListDict = {1: 'First Label', 2: 'Second Label', 3: 'Third Label'}
            self.ListList = [1, 2, 3]
    
          # Keep like things grouped when at all possible
            self.lswList = QListWidget()
            self.lswList.itemClicked.connect(self.on_item_clicked)
    
            self.lneEdit = QLineEdit("Nothing clicked")
    
            VBox = QVBoxLayout(self)
            VBox.addWidget(self.lswList)
            VBox.addWidget(self.lneEdit)
            VBox.addStretch(1)
    
            self.setLayout(VBox)
    
          # Populate List with Values from Dictionary
            for Item in self.ListList:
                value = self.ListDict[Item]
    
                ViewItem = QListWidgetItem()
                ViewItem.setData(Qt.DisplayRole, value)
    
                self.lswList.addItem(ViewItem)
            
        @Slot(QListWidgetItem)
        def on_item_clicked(self, Item):
          # This gets the text from the list box
            ListData = Item.text()
            self.lneEdit.setText(ListData)
    
    if __name__ == "__main__":
      # Do not use sys.argv unless you are using Command Line arguments
      # If you are using Command Line arguments look into the argparser
      # library as it handles them much cleaner and more efficiently
        MainEventHandler = QApplication([])
    
        application = Widget()
        application.show()
        
      # Eventually PySide2 will catch up to Qt5 as this is still Qt4 format  
        sysExit(MainEventHandler.exec_())
    
      # If anyone wants more extensive free help I run an online lab-like classroom-like 
      # message server feel free and drop by you will not be able to post until I clear 
      # you as a student as this prevents spammers so if interested here is the invite
      # https://discord.gg/3D8huKC
    


  • @Denni-0 That code worked fine with Pyside2, but it ran into the same issue I had earlier where it gives me the text from the row but doesn't give me the row ID. I am grateful for a lot of your suggestions there, a lot of the code I'm using is pulled together from tutorials online etc. and I'm still trying to pick apart what it all means. So some clarification on when not to do things is great.

    @JonB I tried using that line of code but got the same value I did before of <method 'selectedRows' of 'PySide2.QtCore.QItemSelectionModel' objects>. I'd be interested in learning another way to handle the keys but I need to give you a better description of the full data I'm dealing with. I have a dictionary with the following structure:

    countries_dict = {1 : {"name" :  "United Kingdom", "abbrev"  : "UK", "pop" :  66.44, "cities" : 
                                  { 1 : {"name" : "London", "pop" : 8.9}, 2 : {"name" : "York",  "pop" : 0.04}} , 
                      2 : {"name" :  "United States", "abbrev"  : "US", "pop" :  327.2, "cities" :
                                  { 1 : {"name" : "Springfield,", "pop" : 0.16, "state" : "MO"}, 2 : {"name" : "Springfield",  "pop" : 0.11, "state" : "IL"}}}
    

    Sorry I know the formatting isn't great. I also can't use the actual data from my dictionary and this was the closest idea I could come up with to replicate it. Basically I'm dealing with nested dictionaries that are identified by unique keys because the name for each item can be duplicated (see US example above where both cities are named Springfield.

    I'm still very new to QT so I've been trying to build up my document slowly but what I will need to have eventually is a list box containing all of the first level items (in this case it would be United Kingdom and United States). When one of those is selected from the list box I will need other fields to populate (in this example it would be a text box for population, and another list box for cities - including only the cities from that country). Then when they select a city from the second list box it would populate another text box (city population) and a combo box (state).

    Users will need to be able to add countries and cities dynamically so I can't really make each country its own dictionary, but the order that they are doesn't matter to the users so I have it as a fixed order based on when each item was added to the dictionary (the list boxes don't need to allow for items to be reordered). Which is why my current plan is to just extract the row index from the selected item match that to the list index used to populate the list and then look up the matching dictionary item by the ID stored in the list at that location.

    The current dictionary structure is working really well for the program from a command line standpoint (I can easily display, manipulate, add, delete and modify data) but now we need to make it accessible to users which means a GUI which is something I'm not very familiar with unfortunately.

    So if there's a way to store the unique id in the list element but have it not be visible to the user, that would be amazing and get me closer to how the command line version functions, but this is the closest idea I've had so far to dealing with the data structure I have.

    Thanks again to everyone for their help so far.


  • Banned

    Okay I will look at this bit closer a bit later and get back here to help you with this specific issue



  • That would be awesome, thanks. Just so you're aware, my current plan was to figure out how to get the selected row from the listbox and then compare that to the the index of the list list that I'm using to drive loading the list box (ListList in the code). Which would then return the unique key which I can store and use to access the correct section of the dictionary both at that point and when a selection is made in the child listbox.

    The way I'm currently handling this is to print the unique id and name of each country to the command line and ask users to enter the ID key which I then use to retrieve the data. I was hoping, as part of moving to the gui version) to make those unique keys invisible so users just have to select the name of what they're looking for. I have gotten it to work by concatenating the key and value from the dictionary displaying that as a string in the list box, then getting the text from the list box removing the unique ID and accessing the dictionary directly, but that's not ideal.

    Thanks again for all your help.



  • @BD4L said in Getting Row from ListWidget:

    @JonB I tried using that line of code but got the same value I did before of <method 'selectedRows' of 'PySide2.QtCore.QItemSelectionModel' objects>.

    I don't understand, and you should show the actual code you tried. What I wrote should work. The message you quote should --- as in your original code --- result from trying to output

    self.listwidget.selectionModel().selectedRows
    

    rather than what I wrote, which is:

    self.listwidget.selectionModel().selectedRows()
    

    Did you copy what I wrote, or did you do your own thing as per the first example above instead of the second?


  • Banned

    Okay a simple tweak and you now have the Text and the Row Id I am assuming that is what you needed. Keep in mind (as the code shows) your Slot is receiving the Item and the Row Id is located in the List Widget -- I also removed all the comments since those were in the first rendition and thus not needed here

    from PySide2.QtCore    import Qt, Slot
    #from PySide2.QtGui     import 
    from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QHBoxLayout
    from PySide2.QtWidgets import QLineEdit, QListWidget, QListWidgetItem
    
    from sys import exit as sysExit
    
    #Create Widgets/Layout
    class Widget(QWidget):
        def __init__(self):
            QWidget.__init__(self)
    
            self.ListDict = {1: 'First Label', 2: 'Second Label', 3: 'Third Label'}
            self.ListList = [1, 2, 3]
    
            self.lswList = QListWidget()
            self.lswList.itemClicked.connect(self.on_item_clicked)
    
            self.lneId = QLineEdit('-1')
            self.lneDsply = QLineEdit('Nothing clicked')
    
            HBox = QHBoxLayout()
            HBox.addWidget(self.lneId)
            HBox.addWidget(self.lneDsply)
    
            VBox = QVBoxLayout()
            VBox.addWidget(self.lswList)
            VBox.addLayout(HBox)
            VBox.addStretch(1)
    
            self.setLayout(VBox)
    
          # Populate List with Values from Dictionary
            for Item in self.ListList:
                value = self.ListDict[Item]
    
                ViewItem = QListWidgetItem()
                ViewItem.setData(Qt.DisplayRole, value)
    
                self.lswList.addItem(ViewItem)
            
        
        @Slot(QListWidgetItem)
        def on_item_clicked(self, Item):
          # This gets the Row Id from list box
            RowId = self.lswList.currentRow()
            self.lneId.setText(str(RowId))
          # This gets the Text from the list box
            ListData = Item.text()
            self.lneDsply.setText(ListData)
    
    if __name__ == "__main__":
        MainEventHandler = QApplication([])
    
        application = Widget()
        application.show()
        
        sysExit(MainEventHandler.exec_())
    
      # If anyone wants more extensive free help I run an online lab-like classroom-like 
      # message server feel free and drop by you will not be able to post until I clear 
      # you as a student as this prevents spammers so if interested here is the invite
      # https://discord.gg/3D8huKC
    


  • Hey friends, you guys are awesome thanks. Here's my replies

    @JonB I copied the text directly into my slot so:

    @QtCore.Slot(QtWidgets.QListWidgetItem)
        def on_item_clicked(self, item):
            active_item = QtCore.QItemSelectionModel.selectedIndexes
            self.lineedit.setText(str(active_item))
    

    became:

    @QtCore.Slot(QtWidgets.QListWidgetItem)
        def on_item_clicked(self, item):
            active_item = self.listwidget.selectionModel().selectedRows()
            self.lineedit.setText(str(active_item))
    

    I'm not sure if I put that in the wrong spot or why it didn't work, but it still gave me the same error as before. However the code from @Denni-0 worked so I now have the info I need.

    Denni-O, I just had a question about why you did something in your changes. I get adding the second line display to show the second piece of information but why did you put both of the line edit items into an HBox and then wrap that inside the VBox. Would it have worked just to add both of them into the VBox layout?



  • @BD4L

    but it still gave me the same error as before

    So to quote you from before:

    active_item = QtCore.QItemSelectionModel.selectedRows
    self.lineedit.setText(str(active_item))
    But the value returned by active item is:

    <method 'selectedRows' of 'PySide2.QtCore.QItemSelectionModel' objects>

    You are saying that it shows the same string, with method 'selectedRows' in it, when you replace the first line with:

    active_item = self.listwidget.selectionModel().selectedRows()
    

    You get the same str(active_item) result string with "method" in it in the second case, do you?



  • @JonB Yeah I do. But if I use self.listwidget.currentRow() (from Denni-O's code) it works.


  • Banned

    @BD4L said in Getting Row from ListWidget:

    Denni-O, I just had a question about why you did something in your changes. I get adding the second line display to show the second piece of information but why did you put both of the line edit items into an HBox and then wrap that inside the VBox. Would it have worked just to add both of them into the VBox layout?

    Yes they could have both been put into the VBox and whereupon they would have been vertically stacked -- which by the way you can do with that MUC I supplied just to see what it would look like had it been done pure VBox versus HBox into VBox

    So mostly it was done for aesthetics -- I felt putting them on the same line for this MUC made sense and looked good further it showed you and others how that might look in case any of you were not familiar with that piece and it did not take me more than a couple minutes at most to do it that way versus not doing that way

    Also keep in mind that self.lswList.currentRow() as I have presented can potentially give you funky results if you are not aware of everything you have going on because Event driven programs do not always execute the things in the order you think they should. It would be much better to get that information directly from the source via a Signal/Slot -- I did not dig too deeply into the that List object to determine how to get the selected row as a Signal but I believe there is a way to do that and that would be safer than getting it post selection. However if that is not possible it might be a good idea to double check that the row id you get contains the item that you got.



  • Awesome, thanks. I'll keep that in mind and put in some checks just to make sure.