0

I'm attempting to create a function to declare a variable in terms of an item chosen in a QComboBox. It's for a plugin for QGIS 2.0 and 2.2. I'm getting a "list index out of range" error, but cannot see why. I'm wondering if my combobox.currentIndex() isn't giving me what I think it is. If this is the case, I wonder if I should find a way set the combo box's index to something by default before the program runs.

#connecting the combo boxes to function
def initGui(self):
    QObject.connect(self.dlg.ui.indivCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)
    QObject.connect(self.dlg.ui.grosCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)
    QObject.connect(self.dlg.ui.resCombo,SIGNAL("currentIndexChanged(int)"),self.layerChanged)

#function to set my layer parameter to the equal the item at index chosen
def layerChanged(self):
    self.layerMap = QgsMapLayerRegistry.instance().mapLayers().values()
    self.indivLayer = self.layerMap[self.dlg.ui.indivCombo.currentIndex()]
    self.grosLayer = self.layerMap[self.dlg.ui.grosCombo.currentIndex()]
    self.resLayer = self.layerMap[self.dlg.ui.resCombo.currentIndex()]

#populating combo box with layers in stack
def run(self):
    # show the dialog
    self.dlg.show()
    for layer in self.iface.legendInterface().layers():
        if layer.type() == QgsMapLayer.VectorLayer:
            self.dlg.indivCombo.addItem(layer.name())
            self.dlg.grosCombo.addItem(layer.name())
            self.dlg.resCombo.addItem(layer.name())
    # Run the dialog event loop
    result = self.dlg.exec_()
    # See if OK was pressed
    if result == 1:
        pass

I've now made some changes to the code thanks to the near-answer below. layerChanged() now uses an identifiers method and run() adds layers to combo box differently based on ideas from thread http://lists.osgeo.org/pipermail/qgis-developer/2010-November/011505.html. Both areas still give me issues however. "None type object has no attribute mapLayer" for the former and "Syntax error" for the latter.

def layerChanged(self, index):
    #globals previously initialized as None
    global registry, indivID, grosID, resID
    registry = QgsMapLayerRegistry.instance()
    indivID = self.dlg.ui.indivCombo.data(index).toPyObject()
    grosID = self.dlg.ui.grosCombo.data(index).toPyObject()
    resID = self.dlg.ui.resCombo.data(index).toPyObject()
    self.indivLayer = registry.mapLayer(indivID)
    self.grosLayer = registry.mapLayer(grosID)
    self.resLayer = registry.mapLayer(resID)

def calculatelength(self):
    global registry, resID
    self.resLayer = registry.mapLayer(resID)
    idx = self.resLayer.fieldNameIndex('Length')
    #code continues

 def run(self):

    # show the dialog
    self.dlg.show()
    for layer in self.iface.legendInterface().layers():
        if layer.type() == QgsMapLayer.VectorLayer:
            self.dlg.ui.indivCombo.addItem(layer.name(),QVariant(layer.id())
            self.dlg.ui.grosCombo.addItem(layer.name(),QVariant(layer.id())
            self.dlg.ui.resCombo.addItem(layer.name(),QVariant(layer.id())
    # Run the dialog event loop
    result = self.dlg.exec_()
    # See if OK was pressed
    if result == 1:
        pass
        #AEPStats()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
user25976
  • 1,005
  • 4
  • 18
  • 39
  • Surely it's much more likely that the list (presumably the misnamed `self.layerMap`) isn't giving you what you think it does. Have you tried the simple sanity check of printing the list and the relevant indexes to see if they correspond with your expectations? – ekhumoro Apr 09 '14 at 20:52
  • yes, I have done print tests to check for layerMap. it gives me a list of values of a dictionary of all map layers loaded into the QGIS project. I'm not sure how to print test the relevant currentIndex(), as the combo box is not yet "shown" at this point (thus item isn't chosen). Maybe i'm missing something else--what do you mean by "misnamed self.layerMap"? – user25976 Apr 09 '14 at 22:08
  • Since you have a traceback, you know the line where the error occurs - so print the list and the relevant index immediately before that line. There _must_ be a discrepancy of some kind, so what, exactly, is it? (BTW: I was being slightly facetious about `layerMap` - I described it as "misnamed" because it is actually a _list_ of layers rather than a _map_). – ekhumoro Apr 09 '14 at 22:58
  • unfortunately, i don't have a traceback and don't know where the error occurs. After installing my plugin, I simply get a message in the plugin window "This plugin is broken: list index out of range." I'm looking into setting up a debug environment with eclipse and pydev plugin, but am PREtty sure this snippet of the code is causing my error. – user25976 Apr 09 '14 at 23:25
  • I have rolled back your recent changes, because they undermine the original question. If you want to show changes made to your code, please add them below your original example code. – ekhumoro Apr 10 '14 at 03:44

2 Answers2

1

Taking example code you posted at face-value, I can see several problems.

Firstly, judging by the differences between the initGui and run methods, there may be two sets of combo-boxes in use. The signals are connected to self.dlg.ui.*Combo, whereas the items are added to self.dlg.*Combo.

Secondly, you seem to be populating the combo-boxes over and over again without clearing them beforehand.

Thirdly, you do not seem to be preserving a one-to-one relationship between the combo-box indexes and the list, because you are filtering the layers based on type.

And finally, the list of layers comes from the values of a map, so surely there is no guarantee that they will come out in the same order.

I would suggest you associate a layer id with each combo item, and then retrieve the layer via the mapLayer method. That is, add the combo items like this:

    self.dlg.indivCombo.addItem(layer.name(), layer.id())

and then retrieve the layer like this:

def layerChanged(self, index):
    registry = QgsMapLayerRegistry.instance()
    identifier = self.dlg.ui.indivCombo.itemData(index)
    self.indivLayer = registry.mapLayer(identifier)

NB: if you're using Python2, the combo data will be stored as a QVariant so you would need to extract the identifier like this:

    identifier = self.dlg.ui.indivCombo.itemData(index).toString()

or this:

    identifier = self.dlg.ui.indivCombo.itemData(index).toPyObject()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Yeah, I had tried a similar identifier approach based on this thread: http://lists.osgeo.org/pipermail/qgis-developer/2010-November/011503.html, and have now tried with toPyObject(), but continue to get "This plugin is broken: 'NoneType' object has no attribute 'mapLayer'". Thank you for your insight. – user25976 Apr 10 '14 at 00:59
  • @user25976. That makes no sense, because it would mean that `QgsMapLayerRegistry.instance()` returns `None` (since `mapLayer` is one of its methods). So it definitely looks like you're doing something wrong, somewhere. – ekhumoro Apr 10 '14 at 01:33
  • I just updated my code. I initialized registry and identifiers as globally as none, since I use them in following functions--perhaps this is why QgsMapLayerRegistry.instance() returns None. Alternatively, I wonder if it would be better to initialize as self.registry = None in __init__. Using the QGIS python console, QgsMapLayerRegistry.instance().mapLayers() gives me the expected dictionary. – user25976 Apr 10 '14 at 03:05
  • @user25976. You need to use `itemData()` not `data()` to get the identifier from the combos (my original answer had some copy/paste errors). Also, there is absolutely no reason to use global variables in `layerChanged`. Please verify that `print(repr(QgsMapLayerRegistry.instance()))` in your `layerChanged` method outputs `None`, because I'm finding it really hard to believe that at the moment. – ekhumoro Apr 10 '14 at 03:40
  • No, it returns as expected. Thanks for rolling back changes, I've added some changes I made to run() as how combo boxes are filled. – user25976 Apr 11 '14 at 19:56
  • @user25976. In that case, the `NoneType` error cannot be coming from the `layerChanged` method - it's actually coming from `calculatelength`. Why are you recalculating `self.resLayer` in there? And why are you using all those nasty globals? The `SyntaxError` is coming from the `run` method, and is caused by three missing closing parentheses. A simple way to fix this is to remove `QVariant(`, which is redundant. – ekhumoro Apr 11 '14 at 21:09
  • Originally, i tried recalculating self.resLayer as I got 'NoneType' object has no attribute 'fieldNameIndex', but now I'm starting to think my issue comes from higher up. Thanks for your help and patience. – user25976 Apr 12 '14 at 00:44
  • I've accepted your answer, because it helps improve/correct my code in the area you've addressed. I think i've got bigger problems than this will solve, though. – user25976 Apr 14 '14 at 17:22
  • maybe issues in this thread are at the root of issues I had here? http://stackoverflow.com/questions/23117765/simple-pyqt-combobox-task-populate-with-layers – user25976 Apr 16 '14 at 19:35
1

Thanks to help from @ekhumoro, this now works. Only changes made to answer's suggestions were in layerChanged():

def layerChanged(self):
    registry = QgsMapLayerRegistry.instance()
    identifier = str(self.dlg.ui.indivCombo.itemData(self.dlg.ui.indivCombo.currentIndex()))
    self.indivLayer = registry.mapLayer(identifier)

This solves an issue of the index chosen getting mixed up and incorrect for the multiple combo boxes i have.

user25976
  • 1,005
  • 4
  • 18
  • 39