Column Widths of Dropdown List (table mode)

Is there a way to dictate the width of the dropdown list column(s) in table mode. They are excessively wide, often causing the horizontal scrollbar to appear.

What version of Ignition are you using?
In 8.1.12 or greater, you can set the ‘max table width’ property to -1, and the table will only be as large as necessary to fit the given columns. Similar for the height.

https://docs.inductiveautomation.com/display/DOC81/Vision+-+Dropdown+List

I am using version 8.1.13.

I still get the horizontal scrollbar with a -1 for “max table width” Is there no way to define the column width for each column? Through scripting perhaps?

Also, docs.inductiveautomation.com seems to be down right now.

It looks like the problem is the increased scrollbar width you have, it’s breaking the auto-sizing calculation. I’m not sure there’s any perfect solution…

Hello
I have the same problem:
image
I would like to resize the columns to my liking in order to obtain:
DropDownCorrect
is there any possibility to achieve this?
Thankyou

I imagine that this could probably be done by extending the DataSetListCellRenderer class in some way. This looks like an interesting problem, so if you figure it out, please let me know; I'm interested to see how you do it.

Edit: After looking at this, I believe the crossed out statement above to be wrong. The JTable is accessible directly, so modifying the column model in some way is probably more appropriate.

@Micah_Carr, your problem could probably be solved by simply disabling the horizontal scroll bar. Here is a script I've developed that accomplishes this:

from com.inductiveautomation.factorypmi.application.components.combobox import TableComboBoxPopup
from javax.swing import JTable, JScrollPane, JViewport
#Can be launched from a button for testing but belongs in the parent window's internalFrameOpened event handler
dropdown = system.gui.getParentWindow(event).getComponentForPath('Root Container.Dropdown')
def getPopup():
	ui = dropdown.getUI()
	if ui is not None:
		for child in range(ui.getAccessibleChildrenCount(dropdown)):
			component = ui.getAccessibleChild(dropdown, child)
			if isinstance(component, TableComboBoxPopup):
				return component
def getScrollPane():
	popup = getPopup()
	if popup is not None:
		for component in popup.getComponents():
			if isinstance(component, JScrollPane):
				return component
def getViewport():
	scrollPane = getScrollPane()
	if scrollPane is not None:
		for component in scrollPane.getComponents():
			if isinstance(component, JViewport):
				return component
def getTable():
	viewport = getViewport()
	if viewport is not None:
		for component in viewport.getComponents():
			if isinstance(component, JTable):
				return component
scrollPane = getScrollPane()
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
table = getTable()
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN)

The above code also sets the sets the auto resize policy for the table.

Edit: Further experimentation has revealed that changes to the table and scrollpane are remembered by the component for the duration of the session, so it is not necessary for a listener to reapply the scrollbar policy or auto resize policy every time the dropdown is opened. I've refactored the above script to be applied once and only once when the window is initially opened, and I've eliminated the listener entirely.

2 Likes

I played around with this some more, and I was able to accomplish this with a custom listener. If the following code is added to the script I have posted above, it will generate the following result:

Here is the script:

from java.awt.event import MouseAdapter
class CustomMouseListener(MouseAdapter):
	def mouseClicked(self, event):
		setColumnWidths()
def setColumnWidths():
	if dropdown.isPopupVisible():
		dropdown.setPopupVisible(False)
	else:
		dropdown.setPopupVisible(True)
		column = getTable().getColumnModel().getColumn(0)
		if column is not None:
			column.setMaxWidth(70)
			column.setPreferredWidth(70)
		else:
			print 'Column could not be found!'
for mouseListener in dropdown.getMouseListeners():
	dropdown.removeMouseListener(mouseListener)
dropdown.addMouseListener(CustomMouseListener())

The code gets the column model from the JTable and sets the width of the value column in pixels to whatever width is specified in the setMaxWidth or setPrefferedWidth methods. Since the previous script sets the auto resize policy to last column, the label column will automatically resize accordingly.

1 Like

Hi Justine,
I don't know how to thank you, this is just what I wanted! :clap:

1 Like

This is great! I appreciate all the work you put into this, it works perfectly.

I altered the code a bit to support multiple dropdowns on a page. Now all that's needed to format additional dropdowns, is an addition of a dictionary with the appropriate keys to the "dropdowns" list.

I also added support for configuring the width of multiple columns (In the case where more than 2 columns might be displayed).

# list of dictionaries with path to dropdown and max/preffered width parameters
dropdowns = [
				{
					"dropdown": system.gui.getParentWindow(event).getComponentForPath('Root Container.Dropdown'),	# Path to dropdown component
					"max_width"      : [70],																		# list of max collumn widths
					"preffered_width": [70]																			# list of preffered collumn widths
				},
				
				{
					"dropdown": system.gui.getParentWindow(event).getComponentForPath('Root Container.Dropdown 1'),	# Path to dropdown component
					"max_width"      : [100, 70],																	# list of max collumn widths
					"preffered_width": [100, 70]																	# list of preffered collumn widths
				},
			]


###################################################################################################

from com.inductiveautomation.factorypmi.application.components.combobox import TableComboBoxPopup
from javax.swing import JTable, JScrollPane, JViewport
from java.awt.event import MouseAdapter

#Can be launched from a button for testing but belongs in the parent window's internalFrameOpened event handler
def getPopup(dropdown):
	ui = dropdown.getUI()
	if ui is not None:
		for child in range(ui.getAccessibleChildrenCount(dropdown)):
			component = ui.getAccessibleChild(dropdown, child)
			if isinstance(component, TableComboBoxPopup):
				return component

def getScrollPane(dropdown):
	popup = getPopup(dropdown)
	if popup is not None:
		for component in popup.getComponents():
			if isinstance(component, JScrollPane):
				return component

def getViewport(dropdown):
	scrollPane = getScrollPane(dropdown)
	if scrollPane is not None:
		for component in scrollPane.getComponents():
			if isinstance(component, JViewport):
				return component

def getTable(dropdown):
	viewport = getViewport(dropdown)
	if viewport is not None:
		for component in viewport.getComponents():
			if isinstance(component, JTable):
				return component

class CustomMouseListener(MouseAdapter):
	def __init__(self, dropdown, maxWidth, preferredWidth):
		self.dropdown = dropdown
		self.maxWidth = maxWidth
		self.preferredWidth = preferredWidth
	
	def mouseClicked(self, event):
		setColumnWidths(self.dropdown, self.maxWidth, self.preferredWidth)

def setColumnWidths(dropdown, maxWidth, preferredWidth):
	if dropdown.isPopupVisible():
		dropdown.setPopupVisible(False)
	else:
		dropdown.setPopupVisible(True)
		
		for i in range(min(len(maxWidth), len(preferredWidth), dropdown.data.getColumnCount())):
			column = getTable(dropdown).getColumnModel().getColumn(i)
			if column is not None:
				column.setMaxWidth(maxWidth[i])
				column.setPreferredWidth(preferredWidth[i])
			else:
				print 'Column could not be found!'


for item in dropdowns:
	scrollPane = getScrollPane(item["dropdown"])
	scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
	table = getTable(item["dropdown"])
	table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN)
	
	for mouseListener in item["dropdown"].getMouseListeners():
		item["dropdown"].removeMouseListener(mouseListener)
	item["dropdown"].addMouseListener(CustomMouseListener(item["dropdown"], item["max_width"], item["preffered_width"]))


( @Alessandro_Sanson )

2 Likes

@Micah_Carr @justinedwards.jle Thanks for the solution. This solution was helpful for me and worked perfectly.

2 Likes

Here's the newbie question of the day: Where is all this scripting taking place?

This was originally developed as a one off for the internalFrameOpened event handler, and @Micah_Carr developed the script further for multiple dropdowns with variable column widths. In retrospect, if the project is going to have more than one of these dropdowns, this should be done in the project library, so that any dropdown from any window could call the script from the componentRunning property change event handler. This approach would be much more maintainable because the script call would intuitively be with the component that is affected by it instead of the window, and the needed column widths could just be passed into the library function when it's called. If there were going to be many of these custom "Table Dropdowns", it also wouldn't hurt to create a template with a custom property to specify the desired column widths. That way, you could drag and drop the template with a default desired width already specified without having to add the property change script each time.

Example Project Library Script:
image

from java.awt.event import MouseAdapter
from javax.swing import JScrollPane, JTable

# This is a custom mouse listener class that extends MouseAdapter.
# This is needed because the popup will revert to its default widths every time it opens
class CustomMouseListener(MouseAdapter):

	# The constructor initializes the listener with a reference to a table and a specified column width.
	def __init__(self, table, columnWidth):
		self.columnWidth = columnWidth
		self.table = table
	
	# This method is triggered when a mouse click event occurs.
	# It sets the width of the columns of the table based on whether the dropdown's popup is visible or not.
	def mouseClicked(self, event):
		setColumnWidths(event.source, self.table, self.columnWidth)
		
# This function adjusts the column widths of the table.
# If the dropdown's popup is visible, it hides it. If it's hidden, it shows it and then adjusts the column widths.
def setColumnWidths(dropdown, table, columnWidth):

	# Checking if the dropdown's popup is currently visible
	if dropdown.isPopupVisible():
		
		# Hiding the popup if it's visible
		dropdown.setPopupVisible(False)
	else:
	
		# Displaying the popup if it's not visible
		dropdown.setPopupVisible(True)
		
		# Iterating through the columns in the dropdown's data and adjusting their width
		# The minus one omits the last column, so it can fill all available space in the dropdown
		for columnIndex in range(table.columnModel.columnCount - 1):
			column = table.columnModel.getColumn(columnIndex)
			column.setMaxWidth(columnWidth)
			column.setPreferredWidth(columnWidth)
			
# This function initializes the dropdown's columns with the specified width and attaches our custom mouse listener.
def initializeColumns(dropdown, columnWidth):

	# Fetching the scroll pane, which is the only subcomponent of the popup. The popup is the first accessible child of the dropdown
	scrollPane = dropdown.UI.getAccessibleChild(dropdown, 0).getComponent(0)
	
	# Ensuring that the horizontal scrollbar never appears for the dropdown
	scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
	
	# Getting the table from the viewport of the scroll pane
	table = scrollPane.viewport.getComponent(0)
	
	#Ensure that the last column will fill all available space in the dropdown
	table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN)
	
	#Remove any existing listeners
	for mouseListener in dropdown.getMouseListeners():
		dropdown.removeMouseListener(mouseListener)
	
	# Adding the custom mouse listener to the dropdown
	dropdown.addMouseListener(CustomMouseListener(table, columnWidth))

Example dropdown propertyChange event script

# Only runs once when the component first starts
if event.propertyName == 'componentRunning':

	# The desired width of the columns or the custom property where the desired width is specified
	columnWidth = 125
	
	#Call the library script that adds a listener to control the column widths of the dropdown
	DropDownColumnWidth.initializeColumns(event.source, columnWidth)
4 Likes

Thank you, Justin, for the explanation. So many places to put scripts, I was a little confused.

The issue I'm running into now is that, with this script in place, my dropdown list is not staying dropped down. I click the dropdown box and the list (table, actually) drops down, but once I release the mouse, the dropped down portion goes away. No way to select anything from the list (table).

Also, I have the last script applied to my PropertyChange event for the dropdown box, as shown below:

#Only runs once when the component first starts
if event.propertyName == 'componentRunning':
	# The desired width of the columns or the custom property where the desired width is specified
	columnWidth = 20
	DropDownColumnWidth.initializeColumns(event.source, columnWidth)

(I called my project script, DropDownColumnWidth)
No matter what I apply to columnWidth, I still don't see a change in any of my column widths. If I'm using a table dropdown list with 4 columns, is columnWidth only applying to the first column? If so, how do I define column widths for each column?

Roger, please see

There's a pencil icon below your post so that you can fix it.

As an addendum, when i operate the dropdown box in "Preview" Mode, it works fine:
image

But when it goes to Runtime, then I can't keep the dropdown dropped down.

I probably created a bug when I converted the internalFrameOpened script to a library script. I should have mentioned that the script was untested.

A couple of things:

  • The width parameter is in pixels, so 20 is probably too small a number

  • The componentRunning event won't run in preview mode unless the window is opened while in preview mode, so to recreate the problem in the designer for debugging purposes, you will need to start the preview mode before you open the window with the dropdown.

1 Like

I found the bug. I had omitted this code from the old version when I created the new one:

#Remove any existing listeners
for mouseListener in dropdown.getMouseListeners():
	dropdown.removeMouseListener(mouseListener)

Without it, there must be a competing listener that is creating the unexpected behavior.

I have updated the above code. Also when I tested this, I noticed an unsightly gap between the end of the last column and the right side of the scroll pane. To correct this, I added a line of code to set the auto resize mode to last column, and I removed the last column's size adjustment from the listener. This way the last column will automatically adjust to fill the space.

Here is the result:

I was able to recreate that with this code on my mouseEntered event:

# Only runs once when the mouse enters the dropdown
# The desired width of the columns or the custom property where the desired width is specified
columnWidth = 150
DropDownColumnWidth.initializeColumns(event.source, columnWidth)
# event.source.parent.Debug = 10

And that produces:
image

My question is: How do I provide unique widths for each column? What's the column identifier syntax?

This is a simple matter of further development. I can think of many ways to go about this. The simplest way would be to modify your component running event handler to pass a list into the initializeColumns script:

columnWidths = [100, 200]
DropdownScripts.initializeColumns(event.source, columnWidths)

If you are using a custom property, you could make it a dataset property with a columnWidths column. Then just get the column as a list and pass it using the getColumnAsList method.

Then, change your setColumnWidths function, so it sets the column widths like this:

for columnIndex in range(table.columnModel.columnCount - 1):
	column = table.columnModel.getColumn(columnIndex)
	column.setMaxWidth(columnWidths[columnIndex])
	column.setPreferredWidth(columnWidths[columnIndex])

It also wouldn't be a bad idea to add way to handle the columnWidths list if it doesn't have the correct number of elements.

...another idea:

If you wanted to autosize them, there are several examples in forum for setting sizes based on text width.

Edit: @Micah_Carr @Priyanka.Khandge @Alessandro_Sanson

2 Likes