I have a button in a template, and I want to resize it to a perfect square at runtime regardless of how the template has been sized.
To develop this, I have a Multi-State button inside a test template, and a test button that is in the same container as the template:
My desired result is this: button @ (x = 100, y = 100, width = 100, height = 100)
The obvious approach is this:
# Retrieve the button from the template
button = event.source.parent.getComponent('testTemplate').getComponent(0).getComponent(0)
# Transform the button in the normal and natural way using system.gui.transform
system.gui.transform(button, newX = 100, newY = 100, newWidth = 100, newHeight = 100)
...but this produces a radically incorrect result where the y and height dimensions seem to be exponentially larger than they should be, and the height dimension walks about 4 pixels every time the transform is applied.
It should also be noted that switching this to a relative coordinate space has no perceivable effect on the transform
The x dimension is the ONLY exception in that it always transforms in the expected way.
# Behaves as expected, but is obviously short three dimensions
button = system.gui.transform(button, newX = 100)
Trying to set the dimensions directly on the component actually works for the height and the width, but a second later, the component snaps back to its original size:
from java.awt import Dimension
button = event.source.parent.getComponent('testTemplate').getComponent(0).getComponent(0)
button.setMinimumSize(Dimension(100, 100))
button.setMaximumSize(Dimension(100, 100))
button.setPreferredSize(Dimension(100, 100))
button.setSize(Dimension(100, 100))
Believing this is a layout issue, I looked the layout up in the documentation, and it says this about the constraints:
Vision Layout constraints object, used with FPMILayout to hold the "layout constraints" (LC) of a component. This is a bit of a mess because of backwards compatibility, here's the explanation
[...]
Conveniently, the functionsFPMILayout.getPreferredBounds(javax.swing.JComponent)
andFPMILayout.getBounds(javax.swing.JComponent)
make neat work of this confusion. these methods (and their setters) should be the only way that bounds get accessed.
Trying to directly adjust the size with FPMILayout actually works for BOTH size and location, but a second later, the component snaps back to its original position as if nothing had been changed:
from com.inductiveautomation.factorypmi.application.components.util import FPMILayout
from java.awt.geom import Rectangle2D
button = event.source.parent.getComponent('testTemplate').getComponent(0).getComponent(0)
layout = FPMILayout.getOffsettingParent(button).layout
layout.setBounds(button, Rectangle2D.Double(100, 100, 100, 100))
Setting the preferredBounds
has a permanent effect, but just like the transform, the result is way off. However, I discovered that if I created a ratio between the widths and heights of the preferredBounds
to the actual bounds, and then used that to transform the component, I got the size and position I wanted:
from com.inductiveautomation.factorypmi.application.components.util import FPMILayout
#Retrieve the button
button = event.source.parent.getComponent('testTemplate').getComponent(0).getComponent(0)
# Get the FPMILayout
layout = FPMILayout.getOffsettingParent(button).layout
# Make sure there is no possiblility of dividing by zero
widthDivisor, heightDivisor = max(1, layout.getBounds(button).width), max(1, layout.getBounds(button).height)
# Define ratios to get sizes that are relative to the original template
widthRatio = float(layout.getPreferredBounds(button).width) / widthDivisor
heightRatio = float(layout.getPreferredBounds(button).height) / heightDivisor
# Define the desired dimensions
desiredX = 100
desiredY = 100
desiredWidth = 100
desiredHeight = 100
# Calculate the dimensions that will be needed to get the transform to work using ratios
newX = desiredX
newY = desiredY * heightRatio
newWidth = desiredWidth * widthRatio
newHeight = desiredHeight * heightRatio
# Execute the transform
system.gui.transform(button, newX, newY, newWidth, newHeight)
However, I assume the button text is still rendering at its original coordinates because the word 'Off' is no longer visible:
Edit: It's possible that I messed something up when I was probing the component. After restarting the designer, the label renders as expected.
At this point, I'm assuming that I've somehow wandered off course or lost track of some key concept, so before I travel any further into left field, I have to ask: does anybody know a better way to go about this?