Drop Down List

OK, how would you go about this: The operators have an order entry screen where they plan orders each day. Since the operators are not exactly data entry specialists, I’m trying to reduce the number of necessary keystrokes to create an order, and give them a drop down list. I bound the list to a “SELECT DISTINT” query, and it displays one of every order name they have ever entered.

So, is there a way that they can add a new name to the drop down list if the one they want has never been entered before? If not, would this be a reasonable feature request? What I’m thinking of is making the display area of the list editable (meaning, the area where “Select One” is displayed) and use only the arrow for displaying the list. Then, the selected string value would be either what they selected from the list or what they typed in the text field. Does this make sense? As always, I’m open for suggestions…

Yes, an editable dropdown box does make sense. It is a little tricky…the Selected Label would be what they typed, but its not immediately clear what we’d use for Selected Value and Selected String Value

I think I remember someone hacking this together with a dynamically generetd popup menu on a text field…hold on, I bet Travis remembers who that was.

In the meantime, I’ve entered it as a feature request - it certainly makes sense.

Thanks,

Hey step7,

I put together something like that before. You can use a UNION query to add a label “Add User” to the top of the list. You just need to make sure you use the same column names in both select queries. Something like:

(SELECT 0 as "id", "Add User" as "user") UNION (SELECT id, DISTINCT user FROM ...)
Also, on the dropdown you need to add a script that fires when the selected label becomes “Add User”. This script will then pop up a small window that asks for a name (assuming that the ID is the auto increment column of your table) and then inserts it into the user table. Then just refresh the dropdown dataset and your new user is in there. For example:

if event.propertyName == "selectedValue": if event.newValue == 0: #the value we gave to the "add user" string name = fpmi.gui.inputBox("Enter User Name: ", "") if name != None: fpmi.db.runUpdateQuery("INSERT INTO users SET name = '%s'" %(name))

1 Like

Thanks, that will definitely get me where I’m going.

I have to tell you, the tech support we get on the site is amazing. Forty five minutes after I ask a question, I have a working code example? This just doesn’t happen so consistently anywhere else.

Is that something you might be willing to post on PLCS.net…?

[quote=“Step7”]Thanks, that will definitely get me where I’m going.

I have to tell you, the tech support we get on the site is amazing. Forty five minutes after I ask a question, I have a working code example? This just doesn’t happen so consistently anywhere else.[/quote]

I have a really good solution that I am currently looking up. I will post the solution soon! Stay tuned.

Haha, this is funny, my operators arent data entry wizards either. I emailed Travis our solution (he made it, i use it), but I will wait for him to post the solution. Works very well. When i cant assign “id” numbers for repetitive entries, we use this code

Alright, here is a good solution. This example uses the Text Field Component. The first thing you need to do is add a dynamic property to the text field called popupShown. After that you need to add a focusLost, keyPressed, and mouseExited action.

focustLost action:

if event.source.popupShown == 1: event.source.requestFocusInWindow()

keyPressed action:

[code]def doPopup(event = event):
import fpmi
textValue = event.source.text
result = fpmi.db.runQuery(“SELECT DISTINCT Name FROM Customers WHERE Name like ‘%s%%’” % (textValue))

func = []
displayNames = []

for row in result:
	if row[0] != "":
		displayNames.append(row[0])
		def loadCustomer(event = event, customer = row[0]):	
			event.source.text = customer
			event.source.setPropertyValue("popupShown", 0)	

		func.append(loadCustomer)

if len(func):
	event.source.popupShown = 1
else:
	event.source.popupShown = 0
popup = fpmi.gui.createPopupMenu(displayNames, func)
popup.show(event, 0, 24)

fpmi.system.invokeLater(doPopup)[/code]

mouseExited action:

event.source.popupShown = 0

The keyPressed action is the most important. It actually goes off and finds the new customers and creates a popup menu. All you need to do is change the query on the 4th line and your set. I attached a component palette to this post which has an example component.
PopupMenu.fpal (2.25 KB)

Ok, since you guys did such a great job on my last dropdown list question, here’s another one:

Let me see if I can explain this: I would like to populate a drop-down list with files in a directory that the operator selects. For instance, let’s say the customer chooses C:\Import as the folder. They then click a button, and the data in my dropdown list dataset is populated. But, I don’t actually see the data until I click on the drop down list data parameter once, at which time it seems to force a refresh on my screen.

import os
a_dir = 'c:/Import/'
mach = []

for f in os.listdir(a_dir):
	mach.append(f)

event.source.parent.getComponent('Dropdown').data.data = [mach]

Also, I’m having another problem when I add some search criteria for the file list. Here is the amended code:

import os
a_dir = 'c:/Import/'
mach = []

for f in os.listdir(a_dir):
	if '.ARC' in f:
		mach.append(f)

event.source.parent.getComponent('Dropdown').data.data = [mach]

I get an error on the line “if ‘.ARC’ in f:” telling me that “string member test needs left char operand”. This works in a Python programming environment, so I must have to do something different within FPMI

You really shouldn't set the "data" property of the dropdown's dataset. You shouldn't even know this exists...

You should do it like this:

[code]import os
a_dir = 'c:/Import/'
mach =

for f in os.listdir(a_dir):
mach.append([f])

dataset = fpmi.db.toDataSet(["Names"], mach)
event.source.parent.getComponent('Dropdown').data = dataset
[/code]

Try this:

[code]import os
a_dir = 'c:/Import/'
mach =

for f in os.listdir(a_dir):
if f.find('.ARC') != -1:
mach.append([f])

dataset = fpmi.db.toDataSet(["Names"], mach)
event.source.parent.getComponent('Dropdown').data = dataset
[/code]

Hope this helps,

I don't even know why I know that. I must have picked it up somewhere, but I'll avoid it at all costs now. :slight_smile:

Just for my own curiousity, why didn't my other code work, since it works in a stand-alone Python script? It's no problem, but I was wondering if there is some way of predicting what will work and what won't if I bring in scripts that I've developed outside of FPMI.

Honestly, I'm not sure. We're running Jython 2.1, and you're probably running CPython (aka "Python") 2.5. Maybe the ability to do "string in string" wasn't present in 2.1, but thats just a guess without doing some digging around...

Yeah, I’m using 2.5, so that must be the issue. I found this in the documentation, which supports that (if I’m reading it right): http://docs.python.org/lib/typesseq.html#foot2611

Ok, I’ve run into another problem. I wrote a python exe (let’s call it rip.exe) that loops through some zip files in a directory, analyzing each one, and copying the useful parts as individual files to new folders. This part works fine. But, so far I’ve used a hard-coded directory for the file locations, which obviously isn’t suitable in the long run.

So, building on the prior questions above, what I would like to do is pass the file name that is selected from my dropdown list into my rip.exe. I’ve scoured the web and found a bunch of ways to do it, but I want your take on it. I’m using “fpmi.system.execute([“C:\Tools\rip.exe”])” to lauch the exe, and I see from your example that I can pass arguments into it. Would I just change my exe to accept these args somehow? Any tips for a novice?

My second question involves modifying my file search code that generates the dropdown list items. I would also like it to find all of the files that match the criteria in sub-directories as well. I tried using the “walk” instruction, but I received various errors. Would this be supported at all? Or should I make another stand-alone exe that does this for me, which would return the list?

I personally wouldn’t use separate executables at all - why bother? You’ve got a full python scripting engine right inside FPMI - you’re going to run into nasty path issues once your client gets launched on more than your development machine, unless this is a single client system.

As for passing params to an externally executed program, I’m not quite sure what advice you’re looking for, but you can do something like…

fpmi.system.execute(["C:\\Tools\\rip.exe", "C:\\data\\myfiles"])

to pass the directory path “C:\data\myfiles” to your app as a command line argument. How to use that argument in your python program? Try this google search.

And for your last question, os.walk is a 2.3 feature, but we can do our own walking with a recursive function. Here is a function that will find all text files below some directory:

[code]results = []

def match(self, startDir, results=results):
import os
for fileName in os.listdir(startDir):
f = startDir + ‘/’ + fileName
if os.path.isdir(f):
self(self, f)
elif fileName.find(’.txt’) != -1:
results.append(f)

match(match, ‘c:/mydir’)

for f in results:
print f[/code]

Hope this helps,

Thanks for the help.

I guess my only concern was the difference between 2.1 and 2.5. I did all of the dev in Wing IDE (this saved a lot of time), but wasn't sure how porting it back to FPMI would do in the long term. But I'll try to keep the script in FPMI if that will keep things simpler.

That said, I just gave it a try, and after changing the couple of instances of "find string" already mentioned above, it almost works. But then it breaks when I try to clean up the temporary files. This is the code:

for f in os.listdir(a_dir):
    if f.find('.t') != -1:
        outFile = open(a_dir + f, 'r')
      
        outFile.close()
        os.remove(a_dir + f)

This is the error (line 90 is the last line above):

Traceback (innermost last):
File "event:mouseClicked", line 90, in ?
File "C:\Documents and Settings\Com2A\Application Data\Sun\Java\Deployment\cache\FPMI\script_lib\javaos.py", line 74, in remove
OSError: [Errno 0] couldn't delete file: c:/Import/test.t1

at org.python.core.Py.makeException(Py.java)
at javaos$py.remove$7(C:\Documents and Settings\Com2A\Application Data\Sun\Java\Deployment\cache\FPMI\script_lib\javaos.py:74)
at javaos$py.call_function(C:\Documents and Settings\Com2A\Application Data\Sun\Java\Deployment\cache\FPMI\script_lib\javaos.py)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyFunction.__call__(PyFunction.java)
at org.python.core.PyMethod.__call__(PyMethod.java)
at org.python.core.PyObject.__call__(PyObject.java)
at org.python.core.PyObject.invoke(PyObject.java)
at org.python.pycode._pyx5.f$0(<event:mouseClicked>:90)
at org.python.pycode._pyx5.call_function(<event:mouseClicked>)
at org.python.core.PyTableCode.call(PyTableCode.java)
at org.python.core.PyCode.call(PyCode.java)
at org.python.core.Py.runCode(Py.java)
at com.inductiveautomation.factorypmi.application.script.ScriptManager.runCode(ScriptManager.java:228)
at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.runActions(ActionAdapter.java:144)
at com.inductiveautomation.factorypmi.application.binding.action.ActionAdapter.invoke(ActionAdapter.java:285)
at com.inductiveautomation.factorypmi.application.binding.action.RelayInvocationHandler.invoke(RelayInvocationHandler.java:54)
at $Proxy0.mouseClicked(Unknown Source)
at java.awt.AWTEventMulticaster.mouseClicked(Unknown Source)
at java.awt.Component.processMouseEvent(Unknown Source)
at javax.swing.JComponent.processMouseEvent(Unknown Source)
at java.awt.Component.processEvent(Unknown Source)
at java.awt.Container.processEvent(Unknown Source)
at java.awt.Component.dispatchEventImpl(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
at java.awt.Container.dispatchEventImpl(Unknown Source)
at java.awt.Window.dispatchEventImpl(Unknown Source)
at java.awt.Component.dispatchEvent(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)

I looked it up, and that error is raised when the underlying call to java.io.File.delete() returns false, which happens when (not suprisingly) the file cannot be deleted.

My guess is that someone or something has a lock on that file (meaning that they have it opened).

Yes, FPMI has it locked. If I run the rip.exe, it deletes the temp files just fine. But if I run the script in the designer, the only way I can delete the files is by shutting the designer down first and deleting the files manually. Ideas?

hrm…you said its a temp file, who creates it? I see you’re opening it for read access here, but do you open it for writing somewhere else and forget to close it there?

Also, you might try the reading/writing functions in fpmi.file

I am the one who creates the temp files (it is further up in the script) . As I interate through the zip files, there are two temp files created for each (I can see them appear in the folder as the function is executed). After all of my new files and folders are created, I then just loop through the list of files to remove those that end with a .t*, as you can see in the code. If I run the rip.exe, these files are deleted immediately.

I will go through my code line by line and see if I am leaving anything open, but I would think that if I explicitly open and close each one at the end, then I should be able to remove them. I’ll see what I can find though.