Cntrl-Click to Select Multiple Images

Hi All: We have a screen that shows a tank farm, and the user will select one or more tanks, then click a button to cause them to be added to a schedule of actions. At the moment we have the user highlight a tank, then click a button, repeating that as often as necessary. Is it possible to control-click to select multiple tanks (images), then click the button to execute the db call. (By db call I mean that the button click sends the tank number to a sproc, which then updates a table. The control-click version would send a string of values to a sproc, which would parse it and update multiple rows in the table).
Hope that was clear. Thanks. David

Sure. I assume you’re rolling your own notion of when a tank is ‘selected’ probably with a boolean (lets call it selected) dynamic property on the tank. If this is the case, then you just need to write mouse code to:

on click: toggle selection on the clicked tank, and un-select any other tanks.
on ctrl-click: just toggle selection on the clicked tank.

You can iterate through a container’s objects pretty easilly. The following code would work if each tank’s name started with “Tank”.

[code]if event.clickCount==1:

Select this tank

event.source.selected = not event.source.selected

If not a ctrl-click, deselect all other tanks.

if not event.isControlDown():
parent = event.source.parent
for tank in parent.components:
if tank.name.startswith(‘Tank’) and tank != event.source:
tank.selected=0[/code]

Hope this helps,

David,

I would just like to make a general comment on GUI design/style.

It’s likely not obvious to the user/operator that they can select multiple tanks by pressing and holding down and clicking on a tank, unless you have some label or tooltip that indicates the fact. clicking is also often a two-handed operation and takes a bit of coordination, and assumes a keyboard is available, which is not always a good assumption if you’re developing an HMI.

As an alternative, I would suggest you place a checkbox next to each tank that the users/operators can toggle to select a tank. Then your code can iterate over all the checkboxes and generate your list of tanks on which to perform your action(s). This graphically communicates that multiple tanks can be selected and it only takes one hand on a mouse (and not a keyboard). It also reduces user/operator frustration because it avoids the times when an operator clicks 10 tanks to only not properly hold down the key on the last one and has to start tank selection all over again.

If you want to get fancy, you could allow the user to simply click on the tank to toggle a respective “selected” SQLTag or dynamic property. You can visually indicate tank selection by changing its color/appearance with properties linked to the SQLTag or dynamic property. Again, you would have to communicate the fact that a tank can be selected with a label, a tooltip, or a good training program.

If you use SQLTags stored in a “selected tank” DB table, you might not have to send your tank list as a parameter to the stored procedure. Your stored procedure can simply reference/query the “selected tank” table for the info it needs.

I like where you’re going with that MickeyBob. Operators are users and users aren’t always the sharpest tools in the shed.

I was going to suggest an alternative method of “Toggling” tanks with colored/altered images. Since you already mentioned it, I’ll second the idea. I’m sure operators will quickly realize that you can select multiple objects after noticing that clicking on a tank doesn’t de-select the current one. That said - training, training, training. MickeyBob’s spot on!

Good points MickeyBob.
I think we’ll explore the sqltags angle, because that will greatly simplify our db writes, etc. In this particular case we don’t have room for checkboxes, but changing the outline color or something along those lines is very do-able. Thanks for the suggestions. D

Yeah, I’ve seen this screen - no room for checkboxes. But, if you basically made it so that it acted like CTRL was always pressed down (clicks toggle selection of that tank, plain and simple), that would simplify it.

Careful putting transient information like tank selection in the database - if you do that, all running client instances will share the same info without some tricky gymnastics, so I’d keep it on the screen.

Out of interest I created a screen with a few tank objects, gave them a Boolean ‘selected’ property and typed in Carl’s code for their mouseClicked event. When I click on a tank I get an error that says “can’t set arbitrary attribute in java instance: selected”.

Does anyone know what’s going wrong?

I think I know whats causing this - I think iterating through a container’s components its one of the last remaining bastions of the scripting architecture where we don’t wrap things up so that you can reference dynamic properties the same as standard properties.

Change the last line to this and see if it works:

tank.setPropertyValue('selected', 0)

Hi everyone: I sent in an error message via the designer regarding the code snippet Carl posted, but it is not actually an error because I later scrolled down and found the new last line he posted, and after that all is good with the cntrl click request.

The next step is I would like to grab a piece of data from each of the selected tanks and do some stuff with that. To begin I would like to be very simple and just concatenate them all into a string and display them in a label. My attempt, hacked from the snippet above, was:

for tank in parent.components: if tank.name.startswith('cnt') and tank.bolSelected=1 lbl= tank.lbl801.text

So, the idea being scroll through all the containers (they are named beginning ‘cnt…’), if they are selected, add the text from one of the labels in that container to the variable lbl. I would then display the contents of variable lbl in a label on my form. Actually I want to do other things to, like for instance run a sproc using as an input parameter the label text from the selected tanks… TIA
ps, I haven’t done much development on pmi for quite a while, and this new version is light years ahead of what I was working on about a year ago. Fine work, all.

I think you’re looking for something like this:

[code]allTanks = ‘’
for tank in parent.components:
if tank.name.startswith(‘cnt’) and tank.getPropertyValue(‘bolSelected’):
allTanks += tank.getComponent(‘lbl801’).text

now do something with the variable allTanks[/code]

Hope this helps,

Hi All: Based on the suggestions above, I was pretty certain this would work, but threw the following error:

Traceback (innermost last):
File “”, line 2, in ?
NameError: parent

Here is the code I have on the mouseClicked event of the button:

allTanks = '' for tank in parent.components: if tank.name.startswith('cnt') and tank.getPropertyValue('bolSelected'): allTanks += tank.getComponent('lblTank').text lblTank = event.source.parent.getComponent("lblTank") lblTank.text=allTanks

I just jumped in this thread, but David- looks like you’re missing the declaration of “parent” (which isn’t a keyword on its own).

Try throwing this in before your “for” loop:

parent = event.source.parent 

For a more complete context, a post a few up in this thread has a code snippet with that line in it.

Regards,

the error message is:

Traceback (innermost last):
File “”, line 7, in ?
TypeError: readonly class or attribute: text

Below is the code:

allTanks = '' 
parent = event.source.parent 
for tank in parent.components: 
   if tank.name.startswith('cnt') and tank.getPropertyValue('bolSelected'): 
      allTanks += tank.getComponent('lblTank').text 
lblTank = event.source.parent.getComponent("lblTank") 
lblTank.text=str(allTanks)

In case it needs to be explicitly in the code somewhere, lblTank is on the root container, not within any of the ‘cnt801, cnt802…’ etc. containers (which are also on the root). Hope this is all clear.
It must be something simple, because I have done this on another screen with essentially the same code… TIA

Hmm… you’re getting a “read only attribute” error while trying to set the tank’s text. Without experimenting on my own, I might suggest you try:

lblTank.setPropertyValue('text',str(allTanks))

Just a shot in the dark, I would expect it to be possible to set the .text property directly.

Regards,

Here is an example of button-click code from another app that does work:

table = event.source.parent.getComponent("tblTTrack") 
selectedRows = table.getSelectedRows() 
if len(selectedRows)>0: 
   ogTotal = 0 
   bblTotal = 0
   for rowNum in selectedRows: 
      bblValue = table.data.getValueAt(rowNum,30)
      ogValue = table.data.getValueAt(rowNum, 28) * bblValue
      #print ("The value at row %d column 28 is: "%rowNum), ogValue 
      ogTotal += ogValue 
      bblTotal += bblValue 
   #average = ogTotal / float(len(selectedRows)) 
   lbln_Avg_OG = event.source.parent.getComponent("lbln_Avg_OG") 
   lbln_Avg_OG.text=str(round(ogTotal/bblTotal,1)) + " P"

Your suggestion throws the following error:

Traceback (innermost last):
File “”, line 7, in ?
AttributeError: ‘None’ object has no attribute ‘setPropertyValue’

TIA David

David,

I don't want to complicate things, but I'm confused by the line

As far as I can make out this line is looking for a component 'lblTank' in each tank, which I don't think is what you are wanting. Do you have a separate label for each tank? What are they called? It may be easier to construct the final string by taking part of the tank name, which you already have available in tank.name.

In regards to David’s post before Al’s…
The message “‘None’ object has no attribute ‘setPropertyValue’” indicates that ‘lblTank’ is currently None at that line, meaning your “getComponent” call isn’t working correctly. You mentioned that lblTank is on the root container… try this:

lblTank = fpmi.gui.getParentWindow(event).rootContainer.getComponent("lblTank")

You could probably also get it to work by doing something like event.source.parent.parent.getComponent.

Hope that helps,

Al (and anyone else following the thread…)

We are working on a app that will help the brewmaster schedule his production. There is a screen showing all the tanks in the brewery, and below a ‘calendar’ strip showing a few weeks’ worth of time. He can scroll forward and backwards in time, and the calendar updates (just like in outlook). The ‘calendar’ is really just a strip of labels.
As he highlights one of the calendar days, the tanks update (color coding, etc.) to show which, at that point in the future, will be full of product or empty.
He will model different production schedules by control clicking to highlight a few tanks, then click a button, and they will be moved to packaging – on the day of the calendar that happens to be highlighted. If he were to move to the next day in the calendar, the tanks would be indicated as being empty; moving a day earlier and they show product. Hopefully you get the idea with this brief description.
All of this will rely on reading and writing to a variety of tables in our production db, but we haven’t gotten that far yet.
Right now I am just trying to get the ‘highlight tank(s) and move them to the calendar’ gui functionality. The db reads/writes and all the other logic will come later…

Traceback (innermost last):
File “”, line 7, in ?
AttributeError: ‘None’ object has no attribute ‘setPropertyValue’

Hi Colby:

lblTank = event.source.parent.parent.getComponent("lblTank")

gave a different error:

Traceback (innermost last):
File “”, line 6, in ?
TypeError: getComponent(): 1st arg can’t be coerced to int

Line 6 is the line above…