4

I am trying to build a tool in arcmap, which uses radio buttons in its gui. Since I read, that radio buttons are not supported, I figured, it couldn't be too hard changing the behaviour of simple boolean checkboxes, so that they act like actual radio buttons (no mutliple selections allowed).

So I went on and created three test parameters with datatype boolean. Next I added the following code to the tool validation function "def updateParameters" :

def updateParameters(self):
    if self.params[0].value == True:
        self.params[1].value = False
        self.params[2].value = False

    if self.params[1].value == True:
        self.params[0].value = False
        self.params[2].value = False

    if self.params[2].value == True:
        self.params[0].value = False
        self.params[1].value = False
return

but it produces some odd behaviour: When I start out by selecting the third box (resp. set params[2] to true) and continue with clicking on the first checkbox, then the tool acts like it's supposed to (by unchecking params[2] and checking params[0]). But that's pretty much it. If I click the checkboxes in any different chronological order sometimes, two checkboxes can be set to true, sometimes no other than the active one can be clicked and so on.

Can anybody reproduce this error? Or maybe tell me what I do wrong?

I'm guessing when I check the first checkbox in the beginning, the other checkboxes are set to false until I uncheck it (which I could at least comprehend). But following that logic, why can I check the first box right after I checked the third one. Shouldn't the first one be also set to false and therefore be uncheckable?

Does anybody have an idea how to approach this?

  • 1
    I would think the validation runs top to bottom, so if you have CB 1 checked and then check CB 3 it should uncheck CB 3 as CB 1 is already true. It will always check for CB 1 to be true first. – Midavalo Feb 6 '16 at 20:04
  • yes, I think you're right. I tried changing the order of parameters and then the same thing happens with a different CB. But how can I solve this? I would use simple dropdown lists, but this would add one click to each option, which for my tool would severly alter the users clicking effort. I also thought about something like an expanded dropdown list. like this here: databasedev.co.uk/image/listbox.gif what seems strange to me, is that this is doable and pretty easy in JS for example – fry82 Feb 6 '16 at 20:18
  • Can you confirm that you are trying to do this in a Python Script Tool and not a Python Toolbox, please? Either way can you give a more complete description, please? If it is a Python Toolbox then the whole of a *.pyt that just does the radio buttons/boxes. If it is a Python Script Tool then the whole of the tool validation code and the parameter settings for those three checkboxes. – PolyGeo Feb 7 '16 at 0:41
  • I have to admit, that I didn't know the difference between those two. I think it is a python script tool, since I set it up by creating a new toolbox and then adding the python script which for now is not of importance. Right now I am just trying to make the checkbox validation work. I posted the whole validation code in my answer below. Hope this clears it up. otherwise please ask – fry82 Feb 7 '16 at 14:17
  • it seems to be script tool – Andreas Müller Feb 7 '16 at 18:22
3

Try setting it up this way. Define a lastChecked global variable to hold the value of 0 to represent the first checkbox and by default make sure the first check box is checked at tool initialization.

First check if the user set all checkboxes to False, and if they did, reset the lastChecked checkbox back to True (radio buttons cannot be turned off by clicking on them, but checkboxes can be). Then make each of your three original if conditional statements first determine that the lastChecked variable does not contain the number representing the checkbox being validated by that if statement. This way it will only apply settings based on the True checkbox that was not previously checked and uncheck all others. A maximum of two boxes can be simultaneously set to true by the user when the method fires and one of them will be stored in the lastChecked variable and the other won't.

# Set up a global parameter at initialization
lastChecked = 0
# Set the checkbox 0 parameter to True here or set it up in the toolbox definition.

def updateParameters(self):
    if self.params[0].value == False and self.params[1].value == False and self.params[2].value == False: 
        self.params[lastChecked].value = True

    if lastChecked != 0 and self.params[0].value == True:
        self.params[1].value = False
        self.params[2].value = False
        lastChecked = 0

    if lastChecked != 1 and self.params[1].value == True:
        self.params[0].value = False
        self.params[2].value = False
        lastChecked = 1

    if lastChecked != 2 and self.params[2].value == True:
        self.params[0].value = False
        self.params[1].value = False
        lastChecked = 2
return
  • I tried to comment, but since I had to use sample code, I posted an answer – fry82 Feb 7 '16 at 13:57
2

a global variable gives me an error, but i didn't looked further, because i thought of another approach. Given a Script-Tool with three checkboxes (boolean parameter 0,1,2) i added a fouth parameter (long) which stores the users choice. This one you can also evaluate in the Tools Python script. One problem remains: a checkbox can be unchecked, so i decided to turn the first checkbox "on", if i don't have an activated checkbox. Here's my script:

import arcpy

class ToolValidator(object):
  """Class for validating a tool's parameter values and controlling
  the behavior of the tool's dialog."""


  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    self.params = arcpy.GetParameterInfo()

  def initializeParameters(self):
    """Refine the properties of a tool's parameters.  This method is
    called when the tool is opened."""

    # First choice is 0
    self.params[3].value = 0    
    return


  def updateParameters(self):

    # determine wich toggle are true
    toggle = [False,False,False]

    # This stores the index value of the chosen checkbox
    activeRadio = int(self.params[3].value)
    for i in [0,1,2]:
      toggle[i] = self.params[i].value

    # The last active button is to be changed
    if self.params[3].value >=0:
      toggle[activeRadio] = False

    # make sure there is something to switch
    if True in toggle:
      for i in [0,1,2]:
        self.params[i].value = toggle[i]
        if toggle[i]:
          self.params[3].value = i
    else:
      self.params[self.params[3].value].value = True

    return

  def updateMessages(self):
    """Modify the messages created by internal validation for each tool
    parameter.  This method is called after internal validation."""
    return

I think all checkboxes shoul be optional parameters here and the fourth one, I called it RadioChoice, can be an derived output parameter.

  • thx very much. I had a chance to try out your code now. It works fine, I think except for the problem you mentioned before. But this doesn't bother me too much. Just a thought: you said you are turning on cb1 if the user tries to uncheck any checked checkbox. Couldn't I just turn on the one that the user just tried to uncheck? This would solve the problem, I guess....I will try that and get back to you. thanks again! – fry82 Feb 7 '16 at 14:25
  • Unfortunately we did not know, which parameter changed, when entering updateParameters. So we only could use the value strored in the output parameter. In the else-part of the last if-clause you could try: self.params[activeRadio] = True – Andreas Müller Feb 7 '16 at 15:24
  • well I used a similair approach there. stored the i from the last else-clause in another parameter. but it causes further problemes which only compromise the stability...I'll investigate further on that matter. – fry82 Feb 7 '16 at 17:38
  • I edited the code, so it works as expected – Andreas Müller Feb 7 '16 at 18:28
  • 1
    I can see after testing that my proposed code does not work, no matter where the global parameter is initialized. I found ways to avoid generating an error, but the logic always failed for one of the buttons. In any event, the code by Andreas (after his last edit) does work and is easier to revise to work for more than 3 buttons. In any case, I was right that the code needed to deal with the situation where all buttons were unchecked and that the user could only set 2 buttons to checked at the same time and that a way of remembering the previously checked button was needed. – Richard Fairhurst Feb 7 '16 at 22:57
0

okay, since Richard answered frist, I played around with his approach first. Can't get it to work though. I set up the global parameter right at the start of the code (before class ToolValidator) and tried to get it in the function updateParameters by adding "global lastChecked" inside the updateParameters.

import arcpy

lastChecked = 0 # here I set up the global parameter

class ToolValidator(object):
  """Class for validating a tool's parameter values and controlling
  the behavior of the tool's dialog."""

  def __init__(self):
    """Setup arcpy and the list of tool parameters."""
    self.params = arcpy.GetParameterInfo()


  def initializeParameters(self):
    """Refine the properties of a tool's parameters.  This method is
    called when the tool is opened."""
    return 

  def updateParameters(self):
    """Modify the values and properties of parameters before internal
    validation is performed.  This method is called whenever a parameter
    has been changed."""

    global lastChecked #here I try to get it in the function

    #number 1
    if self.params[0].value == False and self.params[1].value == False and self.params[2].value == False: 
        self.params[lastChecked].value = True
    #number 2
    if lastChecked != 0 and self.params[0].value == True:
        self.params[1].value = False
        self.params[2].value = False
        lastChecked = 0
    #number 3
    if lastChecked != 1 and self.params[1].value == True:
        self.params[0].value = False
        self.params[2].value = False
        lastChecked = 1
    #number 4
    if lastChecked != 2 and self.params[2].value == True:
        self.params[0].value = False
        self.params[1].value = False
        lastChecked = 2
    return


  def updateMessages(self):
    """Modify the messages created by internal validation for each tool
    parameter.  This method is called after internal validation."""
    return

First he goes inside the first control structure (number 1 in the code) and sets the checkbox one to True, which is right. Then, I click the second checkbox and he goes into control structure 3 (number 3), also okay. But now, if I try to click either checkbox 1 or 3 he will still enter control structure 3 because lastChecked is back to zero again and two checkboxes are checked (the second and one of the others). I can't seem to save the global parameter. If I could manage that, the code would work just fine. I tested it in an extern python script with slightly modified parameter names.

so:


state 1) entering the script

  • lastChecked: 0
  • cb0 before: False
  • cb1 before: False
  • cb2 before: False

leads to:

  • lastChecked: 0
  • cb0 after: True
  • cb1 after: False
  • cb2 after: False

state 2) the state, when CB 1 was checked and I click CB 2

  • lastChecked: 0
  • cb0 before: True
  • cb1 before: True
  • cb2 before: False

leads to:

  • lastChecked: 1

  • cb0 after: False

  • cb1 after: True
  • cb2 after: False

state 3) the state when CB2 is checked and I click CB 3

  • lastChecked: 0
  • cb0 before: False
  • cb1 before: True
  • cb2 before: True

leads to:

  • lastChecked: 1
  • cb0 after: False
  • cb1 after: True
  • cb2 after: False

which is the same result as from state 2 because lastChecked is 0 again instead of 1 when it enters

I hope I could illustrate the problem. How can I save into the global variable? I thought this was possible by using the global statement? Or did you mean, that I should define the lastChecked parameter somewhere else? If so, how could I get it from f.e. the initializeParameters function into the updateParameters function?

  • Responding to your first question .... instead of a global variable, use an instance attribute: in init put self.lastChecked = 0 . Afterward, just refer to the variable with its fully qualified name self.lastChecked. In initializeParamters() You can initialize it with value different from what it was created with, or you can just manipulate it as you wish in updateParameters(). Global variables seem to work OK in the tool script, but I would avoid them in the ToolValidator() class. – celticflute Feb 17 '16 at 12:49

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.