4

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();
    }
}
Community
  • 1
  • 1
FallDownT
  • 55
  • 1
  • 8
  • It would be simpler to either define the colors directly into the UIManager or create your own look and feel, maybe using [Synth](http://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/synth.html) – MadProgrammer Feb 09 '15 at 20:43
  • most of the JComponents has arrays of properties, e.g. as JButton, those properties are different for all possible event (selected, pressed, armed, ....), use custom L&F, in some cases is possible to set Color scheme (to avoiding reinvent the wheel) – mKorbel Feb 10 '15 at 08:30
  • Java Swing modular color scheme == loop inside keys from UIManager – mKorbel Feb 10 '15 at 08:31

2 Answers2

4

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());
NESPowerGlove
  • 5,496
  • 17
  • 28
  • This approach seems very modular and would not require a lot of change! I also like the idea of labeling the *purposes* for the colors instead of naming labeling descriptions (ie `mainColor`, `borderColor`, etc instead of `YELLOW`, `DARK_GREEN`, etc.). My original concept was to just tweak what each color looked like (ie the `DARK` scheme would just be the `BASIC` scheme but shaded a bit darker). I'd offer a +1 if I weren't a new member :) – FallDownT Feb 09 '15 at 19:30
  • @FallDownT New users can get a chunk of rep points by looking and scrolling through the Help -> Tour page. – NESPowerGlove Feb 09 '15 at 19:36
  • @FallDownT Also, it may be of interest of you to check out the Nimbus look and feel which has the ability to set color schemes that look pretty good. http://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/color.html – NESPowerGlove Feb 09 '15 at 19:39
  • I had taken a look at the Numbus earlier and did not think it offered enough versatility. Right now though, I'm running into the issue where I have about 50 `Color` objects per **`SchemeEnum`**, and a 50 parameter constructor is not necessarily great practice. Any ideas on how to set each `Color` for each **`SchemeEnum`** without doing it in the constructor? – FallDownT Feb 09 '15 at 20:18
  • @FallDownT I might, I have to go for a while though. That seems strange that you have 50 unique colors that make up a color scheme for a UI... that does not seem right, 50 unique colors seems right, but having a scheme made up of 50 colors does not. – NESPowerGlove Feb 09 '15 at 20:24
  • I think I may be going about my strategy of color definitions incorrectly. My goal is to have a catalogue of `Color` objects predefined so that I may choose from them without needing to define new colors throughout my code. This includes *everything* from the main window's background color to the color of the text in various buttons. I am seeing now that this is probably not the best strategy, but setting a few reusable colors via your method may work just fine for common colors such as the title and background colors. – FallDownT Feb 09 '15 at 21:16
  • @FallDownT You can always do more than a few colors per scheme as well. Some UI components may not need certain colors in a scheme, and they can ignore those certain colors. If there's a `textColor` field within `SchemeEnum`, a `JButton` and `JTextField` can use it, but a `JPanel` can be free to ignore it. You could also make it more specific, like a buttonTextColor, textFieldTextColor, warningTextFieldTextColor, and so on. If there's similarities between the schemes you can default a lot of the ColorEnums in the SchemeEnum constructor. – NESPowerGlove Feb 09 '15 at 21:39
3

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

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I just read the Oracle tutorials on Synth and it looks very promising. I've only touched XML a few times when writing simple Android apps, so I'm relatively new to it. However, from what I've read, this seems like a much more efficient way to change the overall look of the GUI without the need to overload `JComponents` for the sole purpose of changing their appearance. – FallDownT Feb 09 '15 at 21:09
  • I did a prototype for my work place late last year, which use a base look and feel and built 4 different look and feels off it. I won't say it's easy to start with, but the end result is pretty awesome, especially when you can provide customisation for an individual component (like `JButton`) simply based on the components `name` property, so you can have a base button look and feel and some custom look and feels, for the "Ok" and "Cancel" buttons for example... – MadProgrammer Feb 09 '15 at 21:34
  • I've been trying to play around with the xml file but I'm not sure of which options are available/ how to fully make use of the xml file. I have read the tutorial on oracle and am using the sample xmls as resources, but would you happen to know of any other resources to help me quickly learn how to develop a Synth xml file? I really like the way this technique isolates the laf from the code. – FallDownT Feb 10 '15 at 16:27
  • Yeah, it isolates the laf from the code, that's kind of the point, think of it like css. You could have a look at some of the other implementations. – MadProgrammer Feb 10 '15 at 20:12