3

I need to display a GUI including two checkboxes (referring to the two fields of a polyline shapefile representing the roads of an OSM data). The first checkbox ("sidewalk"), has three radio buttons (i.e. left, right and both as a sidewalk could take one of these values). The second checkbox ("highway") has two radio buttons (i.e. residential and secondary).

My requirements:

  • The radio buttons do belong to the relevant checkbox. Therefore, they have to be grouped.
  • Initially, no check box or radio button is selected. Once the user checks one of the checkboxes, the relevant radio buttons will be enabled. If the user deselects the checkbox, then the radio buttons will get back to their initial state - all of them are disabled and none is selected.
  • Each click will cause to select some of the roads belonging to OSM data. For instance, once the user checks the "sidewalk" checkbox, the roads having the value "left", "right" or "both" would be selected. If the user then selects one of these radio buttons, the selection would be refined accordingly. Similarly, the user could check the "highway", and the selection would again be refined so that only those roads having the values "residential" or "secondary" will be selected.
  • These actions are reversible, and once we deselect everything, we will not have any selection.

Once the attached code is executed on QGIS, we will obtain the following outcome: Possible Configurations of a Single Run.

For example, going from the default configuration (a) to (b), there will be two actions in the map (first we have to check the "sidewalk" check box and then the "right" radio button). However, if we first uncheck the checkbox when we are at (b), we should get the default configuration (one action). Similarly, if we want to move from (c) to (d), there should be two updates on the map. The following code print the updates:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

#Create our custom class for the checkboxes and textboxes:
class CheckBox(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.layout = QVBoxLayout()

        # Add the check box and radio buttons for the first field -sidewalk
        self.cb1 = QCheckBox('sidewalk')
        self.cb1_rb1 = QRadioButton('left')
        self.cb1_rb2 = QRadioButton('right')
        self.cb1_rb3 = QRadioButton('both')

        # Add the check box and radio button for the second field -highway
        self.cb2 = QCheckBox('highway')
        self.cb2_rb1 = QRadioButton('residential')
        self.cb2_rb2 = QRadioButton('secondary')

        # Initially, all radio buttons are inactive
        self.cb1_rb1.setEnabled(False)
        self.cb1_rb2.setEnabled(False)
        self.cb1_rb3.setEnabled(False)

        self.cb2_rb1.setEnabled(False)
        self.cb2_rb2.setEnabled(False)


        # Connect the status signals of the checkboxes to the check box methods:
        self.cb1.toggled.connect(self.cb1_active)
        self.cb2.toggled.connect(self.cb2_active)
        # Select the according roads.. One slot for both of the checkboxes
        self.cb1.toggled.connect(self.selectRoads)
        self.cb2.toggled.connect(self.selectRoads)

        #Add the widgets to the layout:
        self.layout.addWidget(self.cb1)
        self.layout.addWidget(self.cb1_rb1)
        self.layout.addWidget(self.cb1_rb2)
        self.layout.addWidget(self.cb1_rb3)
        # Checkbox 2 (highway) 
        self.layout.addWidget(self.cb2)
        self.layout.addWidget(self.cb2_rb1)
        self.layout.addWidget(self.cb2_rb2)

        # Group the radio buttons based on the field
        self.sidewalk=QButtonGroup()
        self.highway=QButtonGroup()
        # Add the radio buttons to the groups accordingly
        self.sidewalk.addButton(self.cb1_rb1)
        self.sidewalk.addButton(self.cb1_rb2)
        self.sidewalk.addButton(self.cb1_rb3)

        self.highway.addButton(self.cb2_rb1)
        self.highway.addButton(self.cb2_rb2)

        #Set the layout:
        self.setLayout(self.layout)

    # First checkbox
    def cb1_active(self, on):
        if on:
            # Enable the radio buttons
            self.cb1_rb1.setEnabled(True)
            self.cb1_rb2.setEnabled(True)
            self.cb1_rb3.setEnabled(True)

            # Once a radio button is toggled, select the roads accordingly
            self.cb1_rb1.toggled.connect(self.selectRoads)
            self.cb1_rb2.toggled.connect(self.selectRoads)
            self.cb1_rb3.toggled.connect(self.selectRoads)
        else:
            # Radio buttons should be inactive: 
            self.cb1_rb1.setEnabled(False)
            self.cb1_rb2.setEnabled(False)
            self.cb1_rb3.setEnabled(False)

            # Uncheck the checked radio button in order to provide a better display:
            #https://stackoverflow.com/questions/8689909/uncheck-radiobutton-pyqt4
            self.sidewalk.setExclusive(False)        
            # Return to the default
            self.cb1_rb1.setChecked(False)
            self.cb1_rb2.setChecked(False)
            self.cb1_rb3.setChecked(False)

            self.sidewalk.setExclusive(True)
            # ----------------------------------------------------------------------------------


    # Second checkbox    
    def cb2_active(self, on):
        if on:
            self.cb2_rb1.setEnabled(True)
            self.cb2_rb2.setEnabled(True)

            self.cb2_rb1.toggled.connect(self.selectRoads)
            self.cb2_rb2.toggled.connect(self.selectRoads)
        else:
            self.cb2_rb1.setEnabled(False)
            self.cb2_rb2.setEnabled(False)

            # Uncheck the radio buttons:
            self.highway.setExclusive(False)        
            self.cb2_rb1.setChecked(False)
            self.cb2_rb2.setChecked(False)
            self.highway.setExclusive(True)

    def selectRoads(self):
        # Obtain the configuration of the buttons:
        cb1 = self.cb1.isChecked()
        cb2 = self.cb2.isChecked()

        cb1_rb1 = self.cb1_rb1.isChecked()
        cb1_rb2 = self.cb1_rb2.isChecked()
        cb1_rb3 = self.cb1_rb3.isChecked()

        cb2_rb1 = self.cb2_rb1.isChecked()
        cb2_rb2 = self.cb2_rb2.isChecked()

        print cb1, cb1_rb1, cb1_rb2, cb1_rb3, cb2, cb2_rb1, cb2_rb2

buttons = CheckBox()
buttons.show()

With this, I could get more than one print statement for an update. Each click different from the previous configuration should produce only a single print statement; because we'll be updating the selection once even if we uncheck a checkbox whose radio button is selected.

I would like to satisfy my requirements as I am learning PyQGIS, but I am also happy to hear different designs to achieve the same purpose.

1
  • I've moved all the selectRoads triggers under the __init__ but still I am getting more than one printed statement for some of the updates. For example, let's assume the order of clicking is as follows: (highway-secondary-residential); then we'll receive 1-1-2 print statements.
    – banbar
    Jul 27, 2017 at 18:31

1 Answer 1

1

The first thing I notice is that you are not disconnecting the signals inside your checkbox functions when they are untoggled. So each time this checkbox is clicked, the selectRoads function is again being connected which may explain why you are getting multiple print statements.

For example, in your cb1_active(self, on) function, it should be probably be :

def cb1_active(self, on):
    if on:
        # Enable the radio buttons
        self.cb1_rb1.setEnabled(True)
        self.cb1_rb2.setEnabled(True)
        self.cb1_rb3.setEnabled(True)

        # Once a radio button is toggled, select the roads accordingly
        self.cb1_rb1.toggled.connect(self.selectRoads)
        self.cb1_rb2.toggled.connect(self.selectRoads)
        self.cb1_rb3.toggled.connect(self.selectRoads)
    else:
        # Radio buttons should be inactive: 
        self.cb1_rb1.setEnabled(False)
        self.cb1_rb2.setEnabled(False)
        self.cb1_rb3.setEnabled(False)

        # Once a radio button is untoggled, disconnect the signal
        self.cb1_rb1.toggled.disconnect(self.selectRoads)
        self.cb1_rb2.toggled.disconnect(self.selectRoads)
        self.cb1_rb3.toggled.disconnect(self.selectRoads)

Depending on how your plugin is set up, you could catch any unsuspecting errors when disconnecting a signal using the try, except method inside your function. I.e.:

try:
    self.cb1_rb1.toggled.disconnect(self.selectRoads)
except TypeError:
    pass
3
  • I will have a look at this in more detail, but my first impression is that it does not satisfy the second requirement. Even though the radio buttons are disabled, the previous selection is still visible.
    – banbar
    Aug 14, 2017 at 13:04
  • @banbar - To remove the selection, you could use self.cb1_rb1.setChecked(False) and repeat for the others.
    – Joseph
    Aug 14, 2017 at 13:08
  • I think the problem persists. Here is the code, which I am running on a Windows 10 machine with QGIS 2.18.3. For example, the sequence 1) sidewalk, 2) one of the radio buttons under sidewalk, 3) clicking on another radio button would start causing to print two lines.
    – banbar
    Aug 14, 2017 at 20:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.