John
John

Reputation: 113

Java Elegantly access property passed to component, e.g. JComponent a & b use Font 1 Color 2. Access 1 & 2 dynamically, & show they used in a & b

Summary: When Building JComponent you often declare property variables (fonts, colors, dimensions etc.). Many Components in a GUI share the same format (e.g. sets of buttons, textfields). So you define a subclass of Font font. If you want to implement a groupwide change for every Component that only uses font, how do you go about this? My current solution is to create an ArrayList for every property and then add the Component which uses it. This is tedious.

In other words I want to be able to dynamically identify my named variables used in building a component(i.e. the properties), so that I can implement changes throughout the programme.

I have tried using the Java reflection utility but had problems with this method arise and are described later on.

first things first: Lets define an example to show you what I am doing. Build a class, define some Components, define property variables, add the properties to the components.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class CLASSNAME{
//First define all variables to be accessed within the class      //so they can be accessed within the whole class.

JPanel panel;
JButton button;
JLabel label;

String stringButton;
String stringLabel;
int[] setCol={125,125,125};
Font font;
Color color;
Dimension dimension;
Dimension dimension2;
FlowLayout flowlayout;

//Then build the UI class
classUI extends JFrame{
panel =new JPanel();
button =new JButton();
label =new JLabel();

//Then define spec variables to pass into these components.

stringButton="Button"; 
stringLabel="Label";

font =new Font("Arial",font.PLAIN,18);
color =new Color(setCol[1],setCol[2],setCol[3]);
dimension =new Dimension(150,150);
dimension2=new Dimension(100,100);
flowlayout =new FlowLayout();

//Pass these spec variables into the components as required.

panel.setLayout(flowlayout);
panel.setPreferredSize(dimension);
panel.setBackground(color);


label.setFont(font);
label.setText(stringLabel);
label.setBackground(color);
label.setPreferredSize(dimension2);

button.setText(stringButton);
button.setFont(font);
button.setPreferredSize(dimension2);

Must predefine each variable ahead of time as the first input into the class to access all the components and component property variables from anywhere within the class including in functions or outside the class. (but see below answer by fdif you don't want to predefine each Component.)

So specific example : to change property of JComponent, you need new instance of the property variable, and then need to repaint that component, and the panel if its visible.

font=new Font("Arial",font.PLAIN,12);
//Camickr  correctly commented I had left these two lines out.    
label.setFont(font);
button.setFont(font);

// I think this is redundant - namely when setting a property 
// the component is repainted automatically.
label.repaint();    
button.repaint();

/* does the panel the component is displayed on need a repaint()?
 * from what I can tell usually not.
 */

This is fine if you have 5 components, and you don't need changes. However if you start to have about 30 - 50 components in a GUI, and you want to implement a change... e.g. just change the fonts of the components which use font, you have a list of 50 components, so I want to dynamically access these.

Therefore: 1) I want to dynamically find all components by name and component type. I have a part way solution using java.reflect.util

2) I want to dynamically find which property (spec) variable was used when defining a component. java.reflect.util does not keep a record of which components pass which variable.

There is a solution to the problem which is not dynamic: Declare an ArrayList for every spec, and manually add to it each time it is used. Example: //for each property used in a JComponent create an arraylist. //so for the example you should define the ArrayLists as follows

ArrayList<Object>     spec_stringButton=new ArrayList();
ArrayList<Object>     spec_arraystringLabel=new ArrayList();
ArrayList<Object>     spec_arrayfont =new ArrayList();
ArrayList<Object>     spec_color =new ArrayList();
ArrayList<Object>     spec_dimension =new ArrayList();
ArrayList<Object>     spec_dimension2=new ArrayList();
ArrayList<Object>     spec_flowlayout =new ArrayList();

and then : //manually add which components used each variable. This sucks.
spec_stringButton.add(button);

 spec_arraystringLabel.add(label);

 spec_arrayfont.add(button);
 spec_arrayfont.add(label);

 spec_color.add(label);
 spec_color.add(panel);

 spec_dimension.add(panel);

 spec_dimension2.add(button);
 spec_dimension2.add(label);

 spec_flowlayout.add(panel);

This solution does work. But it is not an elegant solution. It is very labour intensive, you've got to remember to add each component that uses a property.

Whenever you need to make a groupwide change you can do this easily enough :

 //spec=new spec;
 dimension2=new Dimension(80,40);

 int i=0;
 for (i=0;i<spec_dimension2.size();i++){
 JComponent a=(JComponent)spec_dimension2.get(i);
 //component.setSpec(new spec);
 a.setPreferredSize(dimension2);
//component.repaint();
 a.repaint();
//proof we have made the change.
 System.out.println(a.getPreferredSize());
 }

Is there no way to dynamically access which property variable was used by a component?

What tools are available? After much searching the Java.lang. reflect utility comes to mind.

From the tutorial:

//Field comes from java.lang.reflect.field
try {
int i=0;
Class cls = Class.forName("CLASSNAME");

Field fieldlist[] = cls.getDeclaredFields();
for ( i= 0; i < fieldlist.length; i++) {                       
    Field fld = fieldlist[i];



System.out.println("    name = " + fld.getName());
System.out.println("    decl class = " +fld.getDeclaringClass());
System.out.println("    type= " + fld.getType());
System.out.println("    declaredannotations="+fld.getDeclaredAnnotations());
System.out.println("    generic type="+fld.getGenericType());
System.out.println("    get type ="+fld.getType());
System.out.println("    hashcode="+fld.hashCode());
System.out.println("    to generic string="+fld.toGenericString());
int mod = fld.getModifiers();
System.out.println("    modifiers = " +Modifier.toString(mod));
                           System.out.println("    -----");
}                      

I went to reflect utility and tried all the methods above. The println strings outputs the following and shows that you can dynamically gain access to declared variables in a class, including the class type (e.g. the component type):

name = panel
decl class = class CLASSNAME
type= class javax.swing.JPanel
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class javax.swing.JPanel
get type =class javax.swing.JPanel
hashcode=-1546142233
to generic string=javax.swing.JPanel CLASSNAME.panel
modifiers = 
-----
name = button
decl class = class CLASSNAME
type= class javax.swing.JButton
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class javax.swing.JButton
get type =class javax.swing.JButton
hashcode=141398225
to generic string=javax.swing.JButton CLASSNAME.button
modifiers = 
-----
name = label
decl class = class CLASSNAME
type= class javax.swing.JLabel
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class javax.swing.JLabel
get type =class javax.swing.JLabel
hashcode=-1550792425
to generic string=javax.swing.JLabel CLASSNAME.label
modifiers = 
-----
name = stringButton
decl class = class CLASSNAME
type= class java.lang.String
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.lang.String
get type =class java.lang.String
hashcode=1112659264
to generic string=java.lang.String CLASSNAME.stringButton
modifiers = 
-----
name = stringLabel
decl class = class CLASSNAME
type= class java.lang.String
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.lang.String
get type =class java.lang.String
hashcode=23733888
to generic string=java.lang.String CLASSNAME.stringLabel
modifiers = 
-----
name = font
decl class = class CLASSNAME
type= class java.awt.Font
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.awt.Font
get type =class java.awt.Font
hashcode=-1514161236
to generic string=java.awt.Font CLASSNAME.font
modifiers = 
-----
name = color
decl class = class CLASSNAME
type= class java.awt.Color
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.awt.Color
get type =class java.awt.Color
hashcode=-1607952256
to generic string=java.awt.Color CLASSNAME.color
modifiers = 
-----
name = dimension
decl class = class CLASSNAME
type= class java.awt.Dimension
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.awt.Dimension
get type =class java.awt.Dimension
hashcode=456448645
to generic string=java.awt.Dimension CLASSNAME.dimension
modifiers = 
-----
name = dimension2
decl class = class CLASSNAME
type= class java.awt.Dimension
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.awt.Dimension
get type =class java.awt.Dimension
hashcode=-1120040849
to generic string=java.awt.Dimension CLASSNAME.dimension2
modifiers = 
-----
name = flowlayout
decl class = class CLASSNAME
type= class java.awt.FlowLayout
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class java.awt.FlowLayout
get type =class java.awt.FlowLayout
hashcode=-466921925
to generic string=java.awt.FlowLayout CLASSNAME.flowlayout
modifiers = 
-----
name = setCol
decl class = class CLASSNAME
type= class [I
declaredannotations=[Ljava.lang.annotation.Annotation;@15dfd77
generic type=class [I
get type =class [I
hashcode=1871572573
to generic string=int[] CLASSNAME.setCol
modifiers = 
-----

So here we have a dynamic method to access every declared variable be it a component or spec. (This feature does not exist in c++!)

fld.getName() will list all declared variable names in a class.

fld.getType() will provide the Component types and even the properties (font, color etc) we want but they need to be extracted.

So it should be possible to dynamically extract / create a list of all Components, and all property variables.

Great. But still no method to show which variable passed to which component. There does not seem to be a direct way of accessing the variable property name used. Bother.

So... one solution: its possible to go through each components, and if it matches the current one (e.g. if Dimension is 100,100 then it must be variable dimension). But if you define two variables with same current parameters? It would be much more effective to be able to access variable name. So that's no good.

Can Java show which property (what I have called spec variable) was used when defining a component? in plain English=if I defined Font font can I find components which use font.

if not... can someone think of a way to dynamically add the variable to the ArrayList without the manual schlep of "spec_stringButton.add(button)";

The second problem is the Reflect utility in Java. The println string shows you can get access to each Component and what type it is, e.g. JPanel, JLabel, JButton etc. But how on earth do you use the string name and pass that into an object?

QUESTION 2 to be answered: how do I change access Field and pass it into something I can use? I have managed to pass the Field into a String. But to get the Object name from a string to a component is producing problems.

 // this String objectname works.

String type= fld.getType().getName();

The output of this string should allow you to access and limit your search to JComponent types.

However getting access to the object and then casting it to a JComponent object from the String name was a fumble.

first:

  String name=fld.getName(); 

name gives you your component names.

Using arraylist method

ArrayList<Object> a=new ArrayList();
a.add(name);
JComponent b=(JComponent)a.get(0);

This produces the following beautiful error: java.lang.ClassCastException: java.lang.String cannot be cast to
javax.swing.JComponent

I am struggling to convert the output of the String variable name and cast it into object. Advice please.

So Ideally Elegant solution would be able to say something like:

variable spec =new spec.

1) for every use of variable property get the component name and type.
(I know this is backwards type of logic).

2) using component type, reset variable spec appropriately e.g. component.setSpec(new spec);

(e.g. for a font TitledBorder would have to be setTitleFont(),
otherwise setFont();)

3) component.repaint();

Other potential solutions might be try to dynamically add the spec as an arraylist. Has anyone written a class which can track the variable names used to add properties to all components? If not could someone work on a solution.

I am currently trying addPropertyListener to each Component. This triggers an event when you change a property (but not a Layout). Perhaps it might be possible to identify the variable used (the spec) and use the fact that a property change has been registered to dynamically add to the ArrayList solution.

For convenience: the code I have written here is complete in one section. Please use to make changes to implement your solution. Or to play around with an example.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


 class CLASSNAME extends JFrame{
JPanel panel;
JButton button;
JLabel label;

String stringButton;
String stringLabel;

Font font;
Color color;
Color color2;
Dimension dimension;
Dimension dimension2;
FlowLayout flowlayout;

int[] setCol={125,125,125};
int[] setCol2={100,100,100};

public CLASSNAME(){
panel =new JPanel();
button =new JButton();
label =new JLabel();

stringButton="Button";
stringLabel="Label";
font =new Font("Arial",font.PLAIN,18);
color =new Color(setCol[0],setCol[1],setCol[2]);
color2=new Color(setCol2[0],setCol2[1],setCol2[2]);

dimension =new Dimension(150,150);
dimension2=new Dimension(150,150);
flowlayout =new FlowLayout();



panel.setLayout(flowlayout);
panel.setPreferredSize(dimension);
panel.setBackground(color);

label.setFont(font);
label.setText(stringLabel);
label.setBackground(color2);

button.setText(stringButton);
button.setFont(font);

/* This is the reflect utility. Allows you dynamic access to the list of  
 * components in a class.
 */

try {
int i=0;
Class cls = Class.forName("CLASSNAME");

Field fieldlist[] = cls.getDeclaredFields();
for ( i= 0; i < fieldlist.length; i++) {                       
    Field fld = fieldlist[i];
try{               
String objectname= fld.getType().getName();
Object a=objectname;                    
System.out.println("preferredsize="+((JComponent) a).getPreferredSize());
}catch(Exception e){}
System.out.println("    name = " + fld.getName());
System.out.println("    decl class = " +fld.getDeclaringClass());
System.out.println("    type= " + fld.getType());
System.out.println(" declaredannotations="+fld.getDeclaredAnnotations());
System.out.println("    generic type="+fld.getGenericType());
System.out.println("    get type ="+fld.getType());
System.out.println("    hashcode="+fld.hashCode());
System.out.println("    to generic string="+fld.toGenericString());
int mod = fld.getModifiers();
System.out.println("    modifiers = " +Modifier.toString(mod));
       System.out.println("    -----");

}
}catch (Throwable a) {System.err.println(a);}

//This is my current solution. Create an array list for each property,
// and add to it every time you add a property to a Component.

ArrayList<Object>     spec_stringButton=new ArrayList();
ArrayList<Object>     spec_arraystringLabel=new ArrayList();
ArrayList<Object>     spec_arrayfont =new ArrayList();
ArrayList<Object>     spec_color =new ArrayList();
ArrayList<Object>     spec_dimension =new ArrayList();
ArrayList<Object>     spec_dimension2=new ArrayList();
ArrayList<Object>     spec_flowlayout =new ArrayList();

 //and then:

 spec_stringButton.add(button);

 spec_arraystringLabel.add(label);

 spec_arrayfont.add(button);
 spec_arrayfont.add(label);

 spec_color.add(label);
 spec_color.add(panel);

 spec_dimension.add(panel);

 spec_dimension2.add(button);
 spec_dimension2.add(label);

 spec_flowlayout.add(panel);


//so then whenever you need to make a groupwide change you can do 
//this with just 7 lines:


 dimension2=new Dimension(80,40);
 int i=0;
 for (i=0;i<spec_dimension2.size();i++){
 JComponent a=(JComponent)spec_dimension2.get(i);
 a.setPreferredSize(dimension2);
 a.repaint();
 //shows that the change has been implemented.
 System.out.println(a.getPreferredSize());
 }

}
}

Upvotes: 2

Views: 3023

Answers (4)

John
John

Reputation: 346

It seems Ethan Nicholas' work on JavaCSS is not dead.

Read here for good introduction: today.java.net/pub/a/today/2006/03/30/introducing-jaxx.html

JavaCSS is found in the JAXX project. JAXX is a xml format style implementation of the swing gui interface. You write an xml document, and a css style document, and are able to bind the inputs and outputs of the GUI to your java implementation engine. The css style document allows for rapid sophisticated gui development. Using the jaxx jar engine, the xml code is converted into java code that runs as rapidly as if deployed in a .java class file.


The project has been continued by a French group of programmers and is now to be found here:

http://www.nuiton.org/projects/jaxx/files

The latest release is JAXX 2.4.2. The latest update was May or June 2011. Whilst in French, the documentation is still comprehensible in English. Just translate.


The original ethan nicholas files of jaxx were last updated on 17-07-2009

http://sourceforge.net/projects/jaxx/

Please note: www.jaxxframework.org/wiki/Main_Page is a dead link, the site is discontinued. instead a mirror has been made and can be found at: buix.labs.libre-entreprise.org/original-jaxx/www.jaxxframework.org/wiki/Main_Page.html

This documentation is essential to understand the meaning and purpose of jaxx and its use.


Upvotes: 1

Geoffrey Zheng
Geoffrey Zheng

Reputation: 6640

I do not pretend to having read 1/10 of the question and understand 1/100 of it, but if you need to apply display properties (font and color) to many JComponents, consider using something like java-css.

The site is currently down for maintenance, so here's a blog describing it.

Upvotes: 0

Mike Tunnicliffe
Mike Tunnicliffe

Reputation: 10772

I'm not 100% sure what you're trying to do, but I had a bit of time to kill and wrote this:

class ComponentGroup {
  private JComponent components;

  public ComponentGroup(JComponent... components) {
    this.components = components;
  }

  // For any method that is defined in JComponent
  public void setPreferredSize(Dimension newSize) {
    for (JComponent component : components) {
      component.setPreferredSize(newSize);
    }
  }

  // For methods that only apply to some subclasses of JComponent
  public void setText(String newText) {
    for (JComponent component : components) {
      if (component instanceof JLabel) {
        ((JLabel)component).setText(newText);
      }
    }
  }
}

You could use reflection for setText if you don't want to know up-front what classes you need to support.

  // For methods that only apply to some subclasses of JComponent
  // using reflection
  public void setText(String newText) {
    for (JComponent component : components) {
      try {
        Method setTextMethod = component.getClass().getMethod("setText", String.class)
        setTextMethod.invoke(component, newText);
      } catch (NoSuchMethodException e) {
        // Failed to find setText(), ignore
      } catch (InvocationTargetException e) {
        // setText() found, but invocation failed
        // Handle this!
      }
    }
  }

Upvotes: 0

camickr
camickr

Reputation: 324128

I also do not understand the pont of this and I see problems. For example:

font=new Font("Arial",font.PLAIN,12); 
label.repaint();     

That code won't do anything. When you invoke a method:

label.setFont( someFont );

The font value is stored in a class variable in the label class. Changing your local variable to contain a new Font will not cause the Font of the label to change even if you invoke repaint.

Upvotes: 0

Related Questions