Listbox Foreground or Background Dynamic Color change via Python

I would like to add a button on my screen. When pressed, I want to run a python script in "actionPerformed", that loops thru all items in a Listbox and dynamically changes the backgroundcolor and foregroundcolor of each row of my listbox, I will probably come up with a method that determines which color each backgroundcolor and foregroundcolor gets. I know how to iterate/loop the contents of a listbox. I need help on changing each rows colors. Thanks ahead of time.

I did something like this once with a dropdown component. I imagine that the list component works the same way. Your button script will have to override the renderer, and allow you to insert you own colors.

Here is an example that would work in the context you've defined:

from com.inductiveautomation.plaf import ComboListCellRenderer

# Override the cell renderer for custom functionality
class ColorCellRenderer(ComboListCellRenderer):
	def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
		ComboListCellRenderer.getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus)
		
		# Normally the first if condition would be a cellHasFocus condition to indicate the presence of the mouse,
		# ...but it doesn't have an effect on the list component,
		# ...so additional development would be needed to add this functionality
		
		# Give the selected item a destinct color
		if isSelected:
			self.foreground = system.gui.color('black')
			self.background = system.gui.color('white')
		
		# Color the foreground and background of the individual indexes according to the foreground and background color lists
		else:
			self.foreground = system.gui.color(foregroundColors[index])
			self.background = system.gui.color(backgroundColors[index])
		
		return self

# Define a list of colors that corresponds with the indexes in the JList.
foregroundColors = ['red', 'orange', 'yellow', 'lightgreen', 'green', 'lightblue', 'purple', 'grey', 'brown']
backgroundColors = ['brown', 'red', 'orange', 'yellow', 'lightgreen', 'green', 'lightblue', 'purple', 'grey']

# Create an instance of the custom renderer
renderer = ColorCellRenderer()

# Get the JList [view] from the listbox's viewport and set the custom renderer
event.source.parent.getComponent('List').viewport.view.setCellRenderer(renderer)

Result:
image

Edit: Refactored ColorCellRender per the feedback below

2 Likes

It occurs to me that I should probably also include examples of how to evaluate each row of data in some way, to dynamically create the lists of colors.

Using the custom renderer from the previous post...


Here's an example of how to create a list box with alternating colors:

# Define lists of colors that will correspond with the indexes in the JList.
foregroundColors = []
backgroundColors = []

# Get the dataset that the list is rendered from
listData = event.source.parent.getComponent('List').data

# Iterate through the dataset and evaluate each row in some way
for row in xrange(listData.rowCount):
	if not row % 2: # If the row is an even number, color it light blue
		backgroundColors.append('lightblue')
	else: # Odd numbered rows are colored grey
		backgroundColors.append('lightgrey')
	
	# The foreground is always black
	foregroundColors.append('black')

Result:
image



Here is an example of how to change the color of each row in the list box based upon the cell values:

# Define lists of colors that will correspond with the indexes in the JList.
foregroundColors = []
backgroundColors = []

# Get the dataset that the list is rendered from
listData = event.source.parent.getComponent('List').data

# Iterate through the dataset and evaluate each row in some way
for row in xrange(listData.rowCount):
	# Passing results are green
	if listData.getValueAt(row, 0) == 'Passed':
		backgroundColors.append('green')
		foregroundColors.append('white')
	else: # Results that aren't 'Passed' are red
		backgroundColors.append('red')
		foregroundColors.append('yellow')

Result:
image


Finally, if this is just a one off, and there is no need to make the custom renderer generic or universal, then you can just do the color evaluation directly in the renderer itself using the value or index variables:

Example:

from com.inductiveautomation.plaf import ComboListCellRenderer

# Override the cell renderer for custom functionality
class ColorCellRenderer(ComboListCellRenderer):
	def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
		ComboListCellRenderer.getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus)
		# Give the selected item a destinct color
		if isSelected:
			self.foreground = system.gui.color('blue')
			self.background = system.gui.color('white')
		
		# Color the foreground and background of the individual indexes according to the value or index parameters
		elif value == 'Passed':
			self.foreground = system.gui.color('white')
			self.background = system.gui.color('green')
		else:
			self.foreground = system.gui.color('yellow')
			self.background = system.gui.color('red')
		return self

# Create an instance of the custom renderer
renderer = ColorCellRenderer()

# Get the JList [view] from the listbox's viewport and set the custom renderer
event.source.parent.getComponent('List').viewport.view.setCellRenderer(renderer)

This code gives the same result as the preceding example.

Edit: Refactored ColorCellRenderer per the feedback below

2 Likes

Some minor golfing on your example, and one improvement:

  1. You should use from com.inductiveautomation.plaf import ComboListCellRenderer as your base class, for proper interaction with our look and feel, as of 8.0.0. Slightly annoying, but c'est la vie.
  2. Since both DefaultListCellRenderer and ComboListCellRenderer extend JLabel and themselves act as the rendering component, you can just use self after you call the super method:
from com.inductiveautomation.plaf import ComboListCellRenderer

# Override the cell renderer for custom functionality
class ColorCellRenderer(ComboListCellRenderer):
    def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
        ComboListCellRenderer.getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus)
        
        # Normally the first if condition would be a cellHasFocus condition to indicate the presence of the mouse,
        # ...but it doesn't have an effect on the list component,
        # ...so additional development would be needed to add this functionality
        
        # Give the selected item a destinct color
        if isSelected:
            self.foreground = system.gui.color('black')
            self.background = system.gui.color('white')
        
        # Color the foreground and background of the individual indexes according to the foreground and background color lists
        else:
            self.foreground = system.gui.color(foregroundColors[index])
            self.background = system.gui.color(backgroundColors[index])

        return self
2 Likes

Justin Thank you for taking the time (and on a weekend) to assist with my problem. I will review your notes and get back to you.

Thank you for the feedback. I've refactored my examples.

@yiotis, I have one other recommendation. I know I gave both semi-reusable examples and one non-generic example, but I imagine the best way to do this is to keep this generic. Stick the custom color renderer logic in a library script, so it can be used for any component that renders a JList anywhere in the project without having to have multiple instances of the same code. For example, if there were a library script called component scripts, the color renderer logic could be nested in a function like this:

from com.inductiveautomation.plaf import ComboListCellRenderer

# Library script function for setting custom colors
def setColoredCellRenderer(jlist, foregroundColors, backgroundColors):
	'''
	Arguments:
		jlist = any javax.swing.JList (ie JLists nested in dropdown or list components)
		foregroundColors, backgroundColors = lists of plain text color names that match the indexes of the list that is being passed in. The length of these lists must be at least as long as the number of indexes in the JList.
	'''
	# Override the cell renderer for custom functionality
	class ColorCellRenderer(ComboListCellRenderer):
		def getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus):
			ComboListCellRenderer.getListCellRendererComponent(self, list, value, index, isSelected, cellHasFocus)
			
			# Give the selected item a destinct color
			if isSelected:
				self.foreground = system.gui.color('black')
				self.background = system.gui.color('white')
			
			# Color the foreground and background of the individual indexes according to the foreground and background color lists
			else:
				self.foreground = system.gui.color(foregroundColors[index])
				self.background = system.gui.color(backgroundColors[index])
			
			return self
			
	jlist.setCellRenderer(ColorCellRenderer())

Then, get the internal JList from any applicable list type component, evaluate the color scheme, and hand it off to the library script like this:

# Define lists of colors that will correspond with the indexes in the JList.
foregroundColors = []
backgroundColors = []

# Get the dataset that the list is rendered from
listData = event.source.parent.getComponent('List').data

# Iterate through the dataset and evaluate each row in some way
for row in xrange(listData.rowCount):
	# Insert color scheme conditions here
	if conditions == met:
		foregroundColors.append('someColor')
		backgroundColors.append('someColor')

# Get the jlist from the primary component
listComponent = event.source.parent.getComponent('List').viewport.view

# Call the library script and pass in the variables
componentScripts.setColoredCellRenderer(listComponent, foregroundColors, backgroundColors)

Generally, this approach reduces project complexity and improves maintainability.

3 Likes

Justin, I appreciate your support and your time. I ended up putting it together just like your final recommendation and it works great. I did test the various examples you provided up above and they all work. I put the script in one of my libraries; I prefer it this way else code gets lost from project to project when you do local instances on random screens. Thanks again.

2 Likes

Paul thank you for assisting. I ended using the final post from Justin which includes all recommendations and it works great. I appreciate your time.