I am setting up a large scale GUI (larger than anything I have done before) using Java's Swing toolkit and I would like to set up my own custom color scheme to draw colors from so that all color definitions are in one place. To do this, I have decided to make a pseudo-static top-level class called ColorPalette (applied from https://stackoverflow.com/a/7486111/4547020 post) that contains a SchemeEnum where the programmer sets a color scheme for the entire GUI.
I would like the color selection to be independent to knowledge of color scheme. Does anyone know a design pattern or an efficient way to do this? I'm not entirely confident that my current setup is the best way to implement this, but I would like to set up a modular design where it would not be intrusive to add more ColorEnums or SchemeEnums (at compile time, not runtime).
For clarification sake, I want the programmer to be able to simply select a ColorEnum and get returned a java.awt.Color object based on the ColorEnum and the defined SchemeEnum.
For instance:
// Use the BASIC color scheme
ColorPalette.setCurrentScheme(ColorPalette.SchemeEnum.BASIC);
// Set button backgrounds
testButton.setBackground(ColorPalette.ColorEnum.DARK_RED.getColor());
testButton2.setBackground(ColorPalette.ColorEnum.BLUE.getColor());
should return different Color objects than
// Use the DARK color scheme
ColorPalette.setCurrentScheme(ColorPalette.SchemeEnum.DARK);
// Set button backgrounds
testButton.setBackground(ColorPalette.ColorEnum.DARK_RED.getColor());
testButton2.setBackground(ColorPalette.ColorEnum.BLUE.getColor());
because they have different SchemeEnums even though they are requesting the same color from ColorPalette. This way, changing the SchemeEnum changes every color in the GUI with a one line code change (or Colors could even be changed at runtime).
I've heard of HashTables being used for large data storage such as this, but I don't know how they work. Might that apply here?
Here is my code thus far. Thanks in advance!
package common.lookandfeel;
import java.awt.Color;
/**
* Class which contains the members for the color scheme used throughout the project.
* <p>This class is essentially static (no constructor, class is final, all members static) and
* should not be instantiated.
*/
public final class ColorPalette
{
/**
* The list of color schemes to choose from.
*/
public static enum SchemeEnum
{
BASIC, DARK, METALLIC
}
/**
* The list of color descriptions to choose from.
*/
public static enum ColorEnum
{
LIGHT_RED(256,0,0), RED(192,0,0), DARK_RED(128,0,0),
LIGHT_GREEN(0,256,0), GREEN(0,192,0), DARK_GREEN(0,128,0),
LIGHT_BLUE(0,0,256), BLUE(0,0,192), DARK_BLUE(0,0,128),
LIGHT_ORANGE(256,102,0), ORANGE(256,102,0), DARK_ORANGE(192,88,0),
LIGHT_YELLOW(256,204,0), YELLOW(256,204,0), DARK_YELLOW(192,150,0),
LIGHT_PURPLE(136,0,182), PURPLE(102,0,153), DARK_PURPLE(78,0,124);
private int red;
private int green;
private int blue;
private ColorEnum(int r, int g, int b)
{
this.red = r;
this.green = g;
this.blue = b;
}
/**
* Get the selected color object for this Enum.
* #return The color description as a Color object.
*/
public Color getColor()
{
// WANT TO RETURN A COLOR BASED ON currentScheme
return new Color(red, green, blue);
}
}
private static SchemeEnum currentScheme = SchemeEnum.BASIC;
/**
* Default constructor is private to prevent instantiation of this makeshift 'static' class.
*/
private ColorPalette()
{
}
/**
* Get the color scheme being used on this project.
* #return The current color scheme in use on this project.
*/
public static SchemeEnum getCurrentScheme()
{
return currentScheme;
}
/**
* Set the overall color scheme of this project.
* #param currentPalette The color scheme to set for use on this project.
*/
public static void setCurrentScheme(SchemeEnum cp)
{
currentScheme = cp;
}
/**
* Main method for test purposes only. Unpredictable results.
* #param args Command line arguments. Should not be present.
*/
public static void main(String[] args)
{
// Declare and define swing data members
JFrame frame = new JFrame("Test Environment");
CustomButton testButton = new CustomButton ("Hello World");
CustomButton testButton2 = new CustomButton ("I am a button!");
// Use a particular color scheme
ColorPalette.setCurrentScheme(ColorPalette.SchemeEnum.BASIC);
// Set button backgrounds
testButton.setBackground(ColorPalette.ColorEnum.DARK_RED.getColor());
testButton2.setBackground(ColorPalette.ColorEnum.BLUE.getColor());
// Place swing components in Frame
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(testButton, BorderLayout.NORTH);
frame.getContentPane().add(testButton2, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
// Set allocated memory to null
frame = null;
testButton = null;
testButton2 = null;
// Suggest garbage collecting to deallocate memory
System.gc();
}
}
It looks and sounds like you just need to compose SchemeEnum to be made up of ColorEnums just like how you have the ColorEnum made up of rgb values.
public static enum SchemeEnum
{
// Don't really know what colors you actually want
BASIC(ColorEnum.RED, ColorEnum.GREEN, ColorEnum.ORANGE),
DARK(ColorEnum.DARK_RED, ColorEnum.DARK_GREEN, ColorEnum.DARK_ORANGE),
METALLIC(ColorEnum.LIGHT_RED, ColorEnum.LIGHT_GREEN, ColorEnum.LIGHT_ORANGE);
// nor know how many colors make up a scheme
public ColorEnum mainColor;
public ColorEnum secondaryColor;
public ColorEnum borderColor;
private SchemeEnum(ColorEnum mainColor, ColorEnum secondaryColor,
ColorEnum borderColor)
{
this.mainColor = mainColor;
this.secondaryColor = secondaryColor;
this.borderColor = borderColor;
}
}
Then, use code like the following, where the colors are based on the selected scheme:
testButton.setBackground(ColorPalette.getCurrentScheme().mainColor.getColor());
Before you go reinventing the wheel, Swing is based on a pluggable look and feel API, see Modifying the Look and Feel.
The right way would be to define your own look and feel and load this. Because you want to provide a variable number of changes, it would probably be better to use something like Synth. This allows you to define cascading properties for objects and allows you to inherit from other properties as well (so you could devise a base set of properties and then only change the properties you need in each subsequent look and feel).
The cheat way would be to modify the UIManager directly, changing the various properties used by the current look and feel. This is sometimes easier if you want to perform small tweaks.
Either way, this will effect ALL components created by your application without you needing to do anything more then changing the look and feel at startup
I was asking about the right way to make a component that holds some state. Like a Jbutton that saves a color in it, or a list item that saves a certain object. So when those GUI components fire an event I can use the saved states to do something with it.
My way was like that:
1- Make a subclass of the required component, like a subclass from Jbutton.
2- Make a Listener for this new subclass : in the listener check if the event source is the subclass, convert it then use the stored data.
Example:
class ColorButton extends JButton
{
static class Listener implements ActionListener{
#Override
public void actionPerformed(ActionEvent actionEvent) {
Object source = actionEvent.getSource();
if( source.getClass() == ColorButton.class)
{
ColorButton t = (ColorButton) source;
t.getComponent().setBackground(t.getColor());
}
}
}
//states i want to be saved
private Color c;
private Component comp;
ColorButton(Component comp, Color c) {
setColorChanger(comp, c);
}
/* ......
......
rest of constructors added with those additions
......
*/
private void setColorChanger(Component comp, Color c)
{
this.comp = comp;
this.c = c;
}
Color getColor() {
return c;
}
Component getComponent() {
return comp;
}
}
And I use it this way:
JPanel panel = new JPanel();
ColorButton.Listener l = new ColorButton.Listener();
JButton b = new ColorButton("Blue", panel, Color.BLUE);
JButton r = new ColorButton("Red", panel, Color.RED);
r.addActionListener(l);
b.addActionListener(l);
panel.add(b);
panel.add(r);
add(panel);
So I was wondering if this way is okay or what, I feel it is very boring to make this for every component that should hold a certain states, is there a better way?
Yes, there is a better way. Every single component object should have its own separate ActionListener, so that you don't have to check if( source.getClass() == ColorButton.class), and you can directly access the fields of the component by name, without having to go through the source at all. For that to work, you have to use a non-static inner class, or an anonymous inner class. That if statement is a very old-fashioned and non-OOP way of doing things.
In fact, the component object itself can be its own ActionListener - but that style only allows you to have one ActionListener, and is a bit less well-organised.
The better way is dependent on what kind of state you want to hold and what use you want to make of it. Without thinking that through so that you can state it, it isn't possible to make an overall plan for a better way to do it. Is setting color the only thing you want to do? Do you need to mix regular JButtons with ColorButtons in your application?
Im using a JPanel with propertyChangeListener and want it to rerender itself based on whenever a particular variable model changes. My code for the same is as follows --
public class LabelMacroEditor extends JPanel implements PropertyChangeListener {
private static final long serialVersionUID = 1L;
private LabelMacroModel model;
public LabelMacroEditor(LabelMacroModel bean) {
this.model = bean;
model.addPropertyChangeListener(this);
setupComponents();
validate();
setVisible(true);
}
public void setupComponents()
{
Box allButtons = Box.createVerticalBox();
JScrollPane macroModelScroller = new JScrollPane(allButtons);
macroModelScroller.setPreferredSize(new Dimension(300, 200));
for(MacroModel macroModel : model.getMacroModelList())
{
LabelMacroEditorEditableEntity macroEditorEntity = new LabelMacroEditorEditableEntity(macroModel);
Box entityBox = Box.createHorizontalBox();
entityBox.add(macroEditorEntity.getUpButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getMacroDetailsButton());
entityBox.add(Box.createHorizontalStrut(15));
entityBox.add(macroEditorEntity.getDownButton());
allButtons.add(entityBox);
}
add(macroModelScroller);
}
#Override
public void propertyChange(PropertyChangeEvent arg0) {
revalidate();
repaint();
}
}
When i use the debug mode in eclipse i can see that whenever there is a change to model it triggers off the call propertyChange and it also runs over revalidate and repaint but only the JPanel display remains the same. It does not seem to be rerendering itself.
Anything fundamental that I'm missing here ?
EDIT :
An example snippet of a property im changing is as follows --
labelMacroModel.addMacroModel(addedMacroModel);
where labelMacroModel is of the type LabelMacroModel and addedMacroModel is of the type Macro
Now the relevant part of LabelMacroModel class that fires off the property change is as follows --
private List<MacroModel> macroModelList;// this is the list of all MacroModels
public void addMacroModel(MacroModel macroModel) {
macroModelList.add(macroModel);
pcs.fireIndexedPropertyChange("LabelMacroModel", macroModelList.size(), null, macroModel);
}
Its not clear how you are changing the components in the panel. If panel is not updated then repaint/revalidate will have no effect. I think you should not need revalidate/repaint to be called explicitly if you are not modifying the way components are laid out. JButton.setText should for example change the label of the button without need of calling repaint.
To expand on the answer by AKJ above, I think you should be reconstructing your components on property change. So doing a remove all then readding is one way to do this. Once you get this working you could be more selective about pushing the model update into the GUI eg if a new entry has been added then just add a new component to reflect this. The remove all / readd is fine for a lot of cases though. HTH.
I am working on a custom JSlider that has a custom Track Rectangle. I want the ability to set the color of the track rectangle when first declaring the slider.
Here's a snippet of what I have (The classes are in separate files in the same package):
public class NewSlider extends JSlider {
Color kolor;
public NewSlider (Color k) {
kolor = k;
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
public class NewSliderUI extends BasicSliderUI {
Color sliderColor = Color.BLACK;
public NewSliderUI (JSlider b, Color k) {
super(b);
sliderColor = k;
}
}
In the above code, "kolor" is initially null and leads to and error when NewSliderUI tries to use it. It appears that the updateUI() method is called before anything else. Then the NewSlider constructor is called. I have tried a variety of things, but because updateUI() appears to run before anything else, nothing I add to the NewSlider class seems to matter.
If I hardcode a Color (ie. setUI(new NewSliderUI(this, Color.BLACK);), then it works, but having a different class for each color seems silly.
Thanks.
I don't see how kolor could be null unless one of the following are happening:
You're passing a null value to the constructor
You're not instantiating NewSlider in the Swing EDT and are having some strange cache issues
NewSlider is being constructed via reflection/deserialization and kolor is not being set.
Have you tried running this in the debugger with some breakpoints? I'd be curious to ensure that the NewSlider constructor is being called (and before the NewSliderUI constructor).
Edit: I see what you mean below. I forgot that the no args constructor for JSlider was being called implicitly. What about doing the following:
public class NewSlider extends JSlider {
Color kolor = Color.BLACK;
public NewSlider (Color k) {
kolor = k;
updateUI();
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
You end up calling updateUI() twice, but the end result should be what you want.
I'd like to add scrolling capability to a javax.swing.JDesktopPane. But wrapping in a javax.swing.JScrollPane does not produce the desired behavior.
Searching the web shows that this has been an issue for quite some time. There are some solutions out there, but they seem to be pretty old, and I'm not not completely satisfied with them.
What actively maintained solutions do you know?
I've used JavaWorld's solution by creating my own JScrollableDesktopPane.
Javaworld's JScrollableDesktopPane is no longer available on their website. I managed to scrounge up some copies of it but none of them work.
A simple solution I've derived can be achieved doing something like the following. It's not the prettiest but it certainly works better than the default behavior.
public class Window extends Frame {
JScrollPane scrollContainer = new JScrollPane();
JDesktopPane mainWorkingPane = new JDesktopPane();
public Window() {
scrollContainer.setViewportView(mainWorkingPane);
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent evt) {
revalidateDesktopPane();
}
});
}
private void revalidateDesktopPane() {
Dimension dim = new Dimension(0,0);
Component[] com = mainWorkingPane.getComponents();
for (int i=0 ; i<com.length ; i++) {
int w = (int) dim.getWidth()+com[i].getWidth();
int h = (int) dim.getHeight()+com[i].getHeight();
dim.setSize(new Dimension(w,h));
}
mainWorkingPane.setPreferredSize(dim);
mainWorkingPane.revalidate();
revalidate();
repaint();
}
}
The idea being to wrap JDesktopPane in a JScrollPane, add a resize listener on the main Frame and then evaluate the contents of the JDesktopPane on resize (or adding new elements).
Hope this helps someone out there.
I've found this : http://www.javaworld.com/javaworld/jw-11-2001/jw-1130-jscroll.html?page=1
It's a nice tutorial with lots of explanations and infos on Swing & so, which permits to create a JscrollableDesktopPane with lots of stuff.
You will need to modify a bit some parts of code to fulfill your requirements.
Enjoy !