System.gui.transform works in designer but not client

Hi all.

The title says it all: I wrote a script that changes the widths of some text boxes to match the width of the table columns above it using system.gui.transform. It works in the designer, but in the vision client it does not. I created a test screen to demonstrate:
testwindow.zip (14.2 KB)

The script I'm using is below, in case you don't want to download the screen:

if event.propertyName == 'defaultColumnView':
		
		# When column widths are re-sized, re-size header textbox widths with them
		def resizeHeaderFooterWithColumns():
		
			# Header 0 = column 0 header, etc ...
			header0 = event.source.parent.getComponent('myTableHeader0')
			header1 = event.source.parent.getComponent('myTableHeader1')
			header2 = event.source.parent.getComponent('myTableHeader2')
			header3 = event.source.parent.getComponent('myTableHeader3')
			header4 = event.source.parent.getComponent('myTableHeader4')
			
			# Footer 0 = column 0 footer, etc ...
			footer0 = event.source.parent.getComponent('myTableFooter0')
			footer1 = event.source.parent.getComponent('myTableFooter1')
			footer2 = event.source.parent.getComponent('myTableFooter2')
			footer3 = event.source.parent.getComponent('myTableFooter3')
			footer4 = event.source.parent.getComponent('myTableFooter4')
			
			# CSV of column properties for table. Converted to array by split('\t')
			columnSizing = event.source.defaultColumnView.split('\t')
			
			# Get column widths from columnSizing array.
			# Column widths begin at index 7 with column 0 and increment by 6 ([13] for col1, [19] for col2, etc...)
			columnWidths = [
				int(columnSizing[7]) + 1,    # Column 0
				int(columnSizing[13]) + 1,   # Column 1
				int(columnSizing[19]) + 1,   # Column 2
				int(columnSizing[25]) + 1,   # Column 3
				int(columnSizing[31]) + 1    # Column 4
			]
			
			# New X positions of headers.
			# Header0 is origin so add header0.x to all values.
			# Then add sum of previous columns' widths minus sum of previous columns' border thickness (1 pixel by default)
			columnPositionsX = [
				0, # dummy value, not used in transforms below because header0.x is origin
				columnWidths[0] + header0.x - 1,
				sum(columnWidths[i] for i in range(2)) + header0.x - 2,
				sum(columnWidths[i] for i in range(3)) + header0.x - 3,
				sum(columnWidths[i] for i in range(4)) + header0.x - 4
			]
			
			# Set header widths to column widths and header X position to column X position
			system.gui.transform(component=header0, newWidth=columnWidths[0])
			system.gui.transform(component=header1, newWidth=columnWidths[1], newX=columnPositionsX[1], coordSpace=1)
			system.gui.transform(component=header2, newWidth=columnWidths[2], newX=columnPositionsX[2], coordSpace=1)
			system.gui.transform(component=header3, newWidth=columnWidths[3], newX=columnPositionsX[3], coordSpace=1)
			system.gui.transform(component=header4, newWidth=columnWidths[4], newX=columnPositionsX[4], coordSpace=1)
			
			# Set footer widths to column widths and footer X position to column X position
			system.gui.transform(component=footer0, newWidth=columnWidths[0])
			system.gui.transform(component=footer1, newWidth=columnWidths[1], newX=columnPositionsX[1], coordSpace=1)
			system.gui.transform(component=footer2, newWidth=columnWidths[2], newX=columnPositionsX[2], coordSpace=1)
			system.gui.transform(component=footer3, newWidth=columnWidths[3], newX=columnPositionsX[3], coordSpace=1)
			system.gui.transform(component=footer4, newWidth=columnWidths[4], newX=columnPositionsX[4], coordSpace=1)
		
		resizeHeaderFooterWithColumns()

I've tried playing with the "coordSpace" argument as other posts have suggested but that did not change anything. Could someone help me get this working in my vision client? Thanks!

The problem is that the defaultColumnView event you're listening for doesn't get fired at runtime, only in the designer.

Unfortunately, that makes this...tricky to fix.

Let's step back a bit - what's the end goal for these text fields above and below the headers? There might be a better way to achieve something close to what you want by decorating the headers or footers directly, instead of adding additional components.

1 Like

The problem is that the defaultColumnView event only fires in the designer to set how the table is displayed at startup. You will probably have to add a listener to the table in the initialize extension function, and fire your changes from that.

1 Like

Hi PGriffith.

The goal is to have the text boxes re-size to match the column widths if the columns are re-sized by the user in the vision client. The text field "headers" are where users type in keywords to filter data in the column below. I'd like them to have the appearance of being part of the table.

EDIT: I should mention I'm the same user with the question from a few days ago here:

I switched to the text boxes so that I could have the "deferUpdate disabled" functionality with my column headers.

Is it feasible to train your users to use the power table's built in column filtering?
If you set your column filterable in the column attributes, you get this icon on hover:
image

Clicking it (for a string column) brings up a list of values in the column:
image

You can immediately start typing in this field and get a filtered list of results:
image

Or you can click (Custom...) and you get a dialog with a bunch of filtering options:
image

As much as I like the challenge of what you're trying to do, by far and away the simplest option would be to embrace what's provided out of the box.

If that's truly not an option, then your next best bet would probably be to create custom column headers that are an enriched component, with the text field as the top portion and the actual header display on the bottom.

1 Like

Hi PGriffith.

I actually did not know about that feature. It looks really nice! Thank you for bringing it to my attention. I will look into it, but at first glance it seems like the built-in column filters only filter the data they're passed, and I can't always pass it all at once. For cases where I need to filter through 500,000 rows in a SQL table but I only return the top 100 matches to keep memory free I would still need something closer to my setup wouldn't I?

Hi Justin.

Your suggestion is attractive, but I don't know where I would start. Do you have any references to an example of something similar?

Sure; I can show you how to set up a listener on your initialize extension function. The problem you will run into with this approach is the concept of relative width verses original width in your transformations, and looking at the offsets in your code, it looks like you've realized this already. Since I have no way of predicting rendering ratios, I typically compensate for this problem by embedding a shape which has built in relative properties. Using an "anchorShape," I can compensate for the different screen or rendering resolutions with a dynamic offset.

Here is your code adapted with such an anchor behind the first textbox:

#def initialize(self):
	from javax.swing.event import TableColumnModelListener
	source = self
	anchorShape = self.parent.getComponent('anchorShape')
	origin = anchorShape.relX
	baselineWidth = self.parent.getComponent('myTableHeader0').width
	widthRatio = anchorShape.relWidth/baselineWidth
	class ColumnWidthListener(TableColumnModelListener):
		def __init__(self, table):
			self.table = table
		def columnSelectionChanged(self, event):
			do = 'Whatever'
		def columnMoved(self, event):
			do = 'Whatever'
		def columnAdded(self, event):
			do = 'Whatever'
		def columnRemoved(self, event):
			do = 'Whatever'
		def columnMarginChanged(self, event):
			# Header 0 = column 0 header, etc ...
			currentX = origin
			for index, column in enumerate(event.source.getColumns()):
				header = source.parent.getComponent('myTableHeader' + str(index))
				footer = source.parent.getComponent('myTableFooter' + str(index))
				width = column.getWidth() * widthRatio
				system.gui.transform(component=header, newWidth=width, newX=currentX, coordSpace=1)
				system.gui.transform(component=footer, newWidth=width, newX=currentX, coordSpace=1)
				currentX += width
	table = self.table
	listener = ColumnWidthListener(table)
	table.getColumnModel().addColumnModelListener(listener)

Here is your window already adapted with the shape and code:
testWindow.zip (13.5 KB)

NOTE: This can only be tested in an actual session. The initialize extension function does not fire from the designer outside of a template.

That's true, this approach only works for the in-memory data. I still wouldn't necessarily stick to your exact 1:1 column header approach though, even with a broader task. I would use a template repeater or something similar, that can take a set of column names and automatically lay out all the filter text fields you want. I would abandon trying to have them 1:1 with the presentation layer, and just have an area on the screen dedicated to the query vs the presentation of the data.

Hi Justin.

Wow, thanks! I've been playing around with this and the more columns the table has the poorer the alignment is. I tried with 29 columns (the largest table I've ever dealt with) and the last "header" text box was completely to the right of the table. But this is really close to what I want!

I am going to try a different approach tomorrow: combining your script and mine. My propertyChange script will "initialize" the table in the designer so that the text boxes are guaranteed to start precisely aligned with the table columns, and then using your script I can add or subtract the change in column width to all of them when any one column changes width instead of setting them each according to the width of the column they're above. I'll let you know how it goes.

1 Like

I didn't spend any time putting the code through the ringer, so I'm sure there is a lot of room for further development. From what your describing, it sounds like the textboxes are incrementally misaligning, probably by the same amount each column, so there is probably some cell spacing attribute that hasn't been accounted for.

It's an interesting approach you're taking, so please keep me posted. I want to know how this turns out.

For comparison, this is how it looked on my system with the original five columns:

The table does have a getIntercellSpacing method that could be applicable. I imagine that the width parameter inside the for loop would need to be changed in the following way to implement it:

width = (column.getWidth() * widthRatio)  + .5*(source.table.getIntercellSpacing().width) * float(index)
1 Like

Hi Justin.

My table with test data (5 columns) looked like yours too, but here's the same code with 29 columns:

I ran it again with your width correction, but the addition was too much:

If you're interested in testing with the abnormally large table too here's a copy:
testwindow2.zip (14.5 KB)

As for me, after playing around some more I'm going to throw in the towel here. It seems with this method, even if the final alignment were made more precise, the text boxes will always be at least marginally out-of-sync with the columns while the columns are being re-sized. Although the "defer update disabled" functionality is desirable, the most important outcome for me is to create a row that blends seamlessly with the table below it, and that's achieved best with a single-row power table with its column model initialized to be the same as the table below.

1 Like

I played around with this a bit more, and I was able to get it working by changing the coordinate spacing to absolute and doing away with the width ratio I originally recommended. Here is the result:

Here is the refactored component:
testwindow2.zip (14.2 KB)

1 Like

Very nice work! Thank you very much for solving this! It seems coordSpace was the culprit? Other than adding 2 to the last width it's very similar to what you had before.

EDIT: I made a small improvement: by adding 1 to the width (except the last) and subtracting 1 from currentX the borders of the text boxes overlap and make it appear more like a uniform table.

1 Like