3
\$\begingroup\$

I've created a class to help resize Swing components while maintaining their aspect ratio within a container.

The class works by creating an instance and adding a container's components to it, then the SwingResizer's resizeComponents() method can be invoked whenever the container's size is changed.

At present the class only accepts a few Swing components.

  1. Please critique this class' design and usability.
  2. Is there a simpler solution to this already that I missed? What is the standard solution?

Any other feedback welcome!

/*
 * A class for resizing Swing components within a container
 * to maintain aspect ratio with the container.
 */

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * @author Patrick Wallace
 */
public class SwingResizer {

/**
 * Map holding each component's name as key, and the component's
 * size requirements as value.
 */
private Map<String,float[]> componentResizeMap; 

private Collection<JButton> buttonList;
private Collection<JLabel> labelList;
private Collection<JTextField> textList;
private Collection<JPanel> panelList;
private Collection<JLayeredPane> lPaneList;

public SwingResizer()
{
  componentResizeMap = new HashMap<>();
  buttonList = new HashSet<>();
  labelList = new HashSet<>();
  textList = new HashSet<>();
  panelList = new HashSet<>();
  lPaneList = new HashSet<>();
}          

/**
 * Returns a scale value of a container size's axis value against 
 * it's contained 
 * component's axis value. For example, if a container size's x-value 
 * was 100, and it's component's x-value was 50, a scale value of 2
 * would be returned.
 * 
 * @param compVec - Component vector, ie an axis value
 * @param contSize - a Container size axis value
 * @return - scale value
 */
 private float convertVectorToScale(int compVec, int contSize)  
 {
   float result = (float) contSize/compVec;
   return result;
 }        

 /**
 * Adds a JButton's name and size requirement values to a Mapped collection. 
 * 
 * @param comp - a Swing component 
 * @param x - a Swing component's location x-axis value
 * @param y - a Swing component's location y-axis value
 * @param xCompSize -  a Swing component's size x-axis value
 * @param yCompSize -  a Swing component's size y-axis value
 * @param xContSize - a Swing components's container size's x-value
 * @param yContSize - a Swing components's container size's y-value
 */
 public void addJButton(JButton comp,int x,int y,int xCompSize,
                          int yCompSize,int xContSize, int yContSize)
 {
  String name = comp.getName();
  System.out.println(comp);
  float[] scales = new float[]{convertVectorToScale(x,xContSize),
                           convertVectorToScale(y,yContSize),
                           convertVectorToScale(xCompSize,xContSize),
                           convertVectorToScale(yCompSize,yContSize)};
  buttonList.add(comp);
  float[] duplicateKey = componentResizeMap.put(name, scales);
  if (duplicateKey != null)
  {
    System.out.println("Error - Duplicate component name");
  }   
}  

 /**
 * Adds a JLabels's name and size requirement values to a Mapped collection. 
 * 
 * @param comp - a Swing component 
 * @param x - a Swing component's location x-axis value
 * @param y - a Swing component's location y-axis value
 * @param xCompSize -  a Swing component's size x-axis value
 * @param yCompSize -  a Swing component's size y-axis value
 * @param xContSize - a Swing components's container size's x-value
 * @param yContSize - a Swing components's container size's y-value
 */
public void addJLabel(JLabel comp,int x,int y,int xCompSize,int yCompSize,
                                           int xContSize, int yContSize)
{   
  String name = comp.getName();
  float[] scales = new float[]{convertVectorToScale(x,xContSize),
                            convertVectorToScale(y,yContSize),
                            convertVectorToScale(xCompSize,xContSize),
                            convertVectorToScale(yCompSize,yContSize)};
  labelList.add(comp);
  float[] duplicateKey = componentResizeMap.put(name, scales);
  if (duplicateKey != null)
  {
     System.out.println("Error - Duplicate component name");
  }  
} 

/**
 * Adds a JTextField's name and size requirement values to a 
 * Mapped collection. 
 * 
 * @param comp - a Swing component 
 * @param x - a Swing component's location x-axis value
 * @param y - a Swing component's location y-axis value
 * @param xCompSize -  a Swing component's size x-axis value
 * @param yCompSize -  a Swing component's size y-axis value
 * @param xContSize - a Swing components's container size's x-value
 * @param yContSize - a Swing components's container size's y-value
 */
public void addJTextField(JTextField comp,int x,int y,int xCompSize,
    int yCompSize, int xContSize, int yContSize)
{
  String name = comp.getName();
  float[] scales = new float[]{convertVectorToScale(x,xContSize),
                           convertVectorToScale(y,yContSize),
                           convertVectorToScale(xCompSize,xContSize),
                           convertVectorToScale(yCompSize,yContSize)};
 textList.add(comp);
 float[] duplicateKey = componentResizeMap.put(name, scales);
 if (duplicateKey != null)
 {
   System.out.println("Error - Duplicate component name");
 }  
} 

/**
 * Adds a JPanel's name and size requirement values to a Mapped collection. 
 * 
 * @param comp - a Swing component 
 * @param x - a Swing component's location x-axis value
 * @param y - a Swing component's location y-axis value
 * @param xCompSize -  a Swing component's size x-axis value
 * @param yCompSize -  a Swing component's size y-axis value
 * @param xContSize - a Swing components's container size's x-value
 * @param yContSize - a Swing components's container size's y-value
 */
 public void addJPanel(JPanel comp,int x,int y,int xCompSize,int yCompSize,
                                           int xContSize, int yContSize)
 {
   String name = comp.getName();
   float[] scales = new float[]{convertVectorToScale(x,xContSize),
                           convertVectorToScale(y,yContSize),
                           convertVectorToScale(xCompSize,xContSize),
                           convertVectorToScale(yCompSize,yContSize)};
   panelList.add(comp);
   float[] duplicateKey = componentResizeMap.put(name, scales);
   if (duplicateKey != null)
   {
     System.out.println("Error - Duplicate component name");
   }   
  } 

/**
 * Adds a JLayeredPane's name and size requirement values to a 
 * Mapped collection. 
 * 
 *@param comp - a Swing component 
 * @param x - a Swing component's location x-axis value
 * @param y - a Swing component's location y-axis value
 * @param xCompSize -  a Swing component's size x-axis value
 * @param yCompSize -  a Swing component's size y-axis value
 * @param xContSize - a Swing components's container size's x-value
 * @param yContSize - a Swing components's container size's y-value
 */
 public void addJLayeredPane(JLayeredPane comp,int x,int y,int xCompSize,
    int yCompSize, int xContSize, int yContSize)
{
  String name = comp.getName();
  float[] scales = new float[]{convertVectorToScale(x,xContSize),
                           convertVectorToScale(y,yContSize),
                           convertVectorToScale(xCompSize,xContSize),
                           convertVectorToScale(yCompSize,yContSize)};
  lPaneList.add(comp);
  float[] duplicateKey = componentResizeMap.put(name, scales);
  if (duplicateKey != null)
  {
    System.out.println("Error - Duplicate component name");
  }  
} 

/**
 * Resizes all components named in the component map to maintain 
 * aspect ration with the container's current size.
 * 
 * @param x - container's current size, x-axis value
 * @param y - container's current size, y-axis value
 */
public void resizeComponents(int x,int y)
{
  try
  {     
   SwingUtilities.invokeLater(new Runnable(){

    public void run()
    {    

     for (JButton button : buttonList)
     {
      try
      {      
         float[] aspectRatios = componentResizeMap.get(button.getName());
         button.setBounds(Math.round(x/aspectRatios[0]), 
                          Math.round(y/aspectRatios[1]), 
                          Math.round(x/aspectRatios[2]), 
                          Math.round(y/aspectRatios[3]));
      }
      catch (Exception e)
      {
        System.out.println("Error - " + e.getMessage());
      }
  }  

     for (JLabel label : labelList)
     {
       try
       {      
        float[] aspectRatios = componentResizeMap.get(label.getName());
        label.setBounds(Math.round(x/aspectRatios[0]), 
                        Math.round(y/aspectRatios[1]), 
                        Math.round(x/aspectRatios[2]), 
                        Math.round(y/aspectRatios[3]));
       }
       catch (Exception e)
       {
         System.out.println("Error - " + e.getMessage());
       }
     } 

     for (JTextField tField : textList)
     {
       try
       {      
         float[] aspectRatios = componentResizeMap.get(tField.getName());
         tField.setBounds(Math.round(x/aspectRatios[0]), 
                          Math.round(y/aspectRatios[1]), 
                          Math.round(x/aspectRatios[2]), 
                          Math.round(y/aspectRatios[3]));
       }
       catch (Exception e)
       {
         System.out.println("Error - " + e.getMessage());
       }
     }

      for (JPanel panel : panelList)
      {
       try
       {      
         float[] aspectRatios = componentResizeMap.get(panel.getName());
         panel.setBounds(Math.round(x/aspectRatios[0]), 
                         Math.round(y/aspectRatios[1]), 
                         Math.round(x/aspectRatios[2]), 
                         Math.round(y/aspectRatios[3]));
       }
       catch (Exception e)
       {
         System.out.println("Error - " + e.getMessage());
       }
     }

     for (JLayeredPane lPane : lPaneList)
     {
       try
       {      
        float[] aspectRatios = componentResizeMap.get(lPane.getName());
        lPane.setBounds(Math.round(x/aspectRatios[0]), 
                        Math.round(y/aspectRatios[1]), 
                        Math.round(x/aspectRatios[2]), 
                        Math.round(y/aspectRatios[3]));
       } 
       catch (Exception e)
       {
        System.out.println("Error - " + e.getMessage());
       }
     }
   }        
  });
 }
 catch (Exception e)
 {
  System.out.println("Error - " + e.getMessage());   
 }    
 }
}      
\$\endgroup\$
4
  • 1
    \$\begingroup\$ It would have more appropriate to start with a LayoutManager based class. You would then need to determine the "base" (or normal) ratio, maybe based on the preferredSize of the component, then from this you could calculate the required size need to facilitate the requirements of the actual physical layout (as the ratio would only be a small part). If you're after a review, it would better to post the code on codereview instead \$\endgroup\$ Commented Sep 23, 2015 at 11:18
  • 1
    \$\begingroup\$ As a "crude" "conceptional" example \$\endgroup\$ Commented Sep 23, 2015 at 11:23
  • \$\begingroup\$ There are many similar configs already built in the existing layout managers. For example, GridBagConstraints weightx and weighty. \$\endgroup\$
    – Dragan Bozanovic
    Commented Sep 23, 2015 at 11:23
  • \$\begingroup\$ Also, if you want a LayoutManager that respects the sizes of the child components and supports auto resizing, you want GroupLayout \$\endgroup\$
    – durron597
    Commented Sep 23, 2015 at 18:04

1 Answer 1

3
\$\begingroup\$

Just going through this from top to bottom:

/*
 * A class for resizing Swing components within a container
 * to maintain aspect ratio with the container.
 */

I'd expect this to be your class-level javadoc. Why is it not? It's effectively dead, because no IDE or javadoc-tool will pick this up. Integrate it into the already existing javadoc.


public SwingResizer()
{
  componentResizeMap = new HashMap<>();
  buttonList = new HashSet<>();
  labelList = new HashSet<>();
  textList = new HashSet<>();
  panelList = new HashSet<>();
  lPaneList = new HashSet<>();
}

Why not initialize these when you're declaring them? It's basically faster ... Also I'm not quite sure I want my layout-manager to differentiate between different Component types. We'll see :)

Also: Java convention usually entails "Egyptian braces". Get the first curly one line higher.


 private float convertVectorToScale(int compVec, int contSize)  
 {

Argument names (just like variable names) don't need to be shorter than 8 characters anymore. componentVector and containerSize are clearer in your intent.

     float result = (float) contSize/compVec;

Binary operators are usually surrounded by spaces to ease spotting and processing them:

float result = (float) containerSize / componentVector;

{
  String name = comp.getName();
  System.out.println(comp);

okay... you're grabbing the component's name. At this point I was getting curious, or rather confused. Why would a layout manager need a component's name?

Also, why are you using System.out.println() in a production class?

And why in gods name do you rely on names?. This also explains why you'd split up your components by type. Actually... it doesn't. That step is breaking things for you. Because you're doing the exact same steps for any given component added:

public void addComponent(Component comp, int x, int y, 
      int xComponentSize, int yComponentSize,
      int xContainerSize, int yContainerSize) {
    float[] scales = new float[] { convertVectorToScale(x, xContainerSize),
        convertVectorToScale(y, yContainerSize),
        convertVectorToScale(xComponentSize, xContainerSize),
        convertVectorToScale(yComponentSize, yContainerSize) };
    componentResizeMap.put(component, scales);
}

^^ this is all the code you need to properly resize the components you get. But for the sake of argument, let's keep reviewing, because there's some things that you still need to hear lest you make the same mistakes in a team:


 if (duplicateKey != null)
 {
   System.out.println("Error - Duplicate component name");
 }

THIS IS DANGEROUS!
Your code recognized there is an error and then proceeds to write a meaningless string out into the console (which nobody ever checks, trust me) and then does business as usual.

The caller of the method has no indication that the method failed in any way. And that's dangerous. Really extremely dangerous. Because someday someone (maybe future you) will want to use this class, oblivious of the fact that the components need distinct names to work right. And stuff will break and it will take hours if not days to find out why?


public void resizeComponents(int x,int y)
{
  try
  {     
   SwingUtilities.invokeLater(new Runnable(){

    public void run()
    {    

     for (JButton button : buttonList)
     {
      try
      {

^^ this ... I ... no. Just no. Following steps:

  1. Get a proper IDE
  2. Use the "indent" feature of your IDE
  3. Look at the lipstick on a pig and then think hard about why you need 5 levels of indentation in one method.

But hey. Aren't we lucky that this can all be much simpler with the rewrite for addComponent?
Look:

public void resizeComponents(int x, int y) {
    SwingUtilities.invokeLater(() -> {
        for (Map.Entry<Component, float[]> componentData : componentResizeMap.entrySet()) {
            float[] aspectRatios = entry.getValue();
            entry.getKey().setBounds(Math.round(x / aspectRatios[0]), 
                      Math.round(y / aspectRatios[1]), 
                      Math.round(x / aspectRatios[2]), 
                      Math.round(y / aspectRatios[3]));
        }
    };
}

Note that this uses a lambda-expression from Java 8 as Runnable instance and it could be simplified quite a bit by using further Java 8 features, such as streaming the entry set.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.