Search code examples
javajsonswingcommand-promptnotepad

Java - Swing - Can't seem to get Incremental Painting to work (Runned through Executable Jar File)


I'm trying to build a periodic table of the elements program. I have a couple of classes, and a JSON file acting as a database. I am using an executable jar file for a couple of reasons, one of the biggest is because Applet security restrictions doesn't apply me to read files like JSON files.

The periodic table image is going to be partially static. The current plan is to draw it out, and use a 2d array and the location of the cursor to determine other actions. (Such actions includes a popup-menu that shows extra information on the element)

My issue: What I have, I believe should be working, but only one of the element blocks is being shown in the end. (And it isn't even being drawn correctingly.... That little part is probably some minor bug that I can find later)

Here is what is shown: (It's the Periodic Table of the Elements... There is post to be 118 elements) enter image description here

Here is my source files: (Main Source File)

package PeriodicTable;

class PeriodicTable {
public static void main (String[] args) {
    Table table = new Table();
}
}

My overall json structure is as following:

JsonObject holds JsonArray (Elements)

Elements holds 118 JsonObjects (1 object per element)

JsonObjects hold information for their corresponding element

(As of right now, basically everything that is not shown on the screen)

package PeriodicTable;

import javax.swing.JFrame;
import javax.swing.JPanel;

import javax.json.JsonArray;
import javax.json.JsonObject;

import java.awt.Toolkit;
import java.awt.Dimension;

import java.io.IOException;

class Table {
private JFrame frmMain;
private Element[][] aryElements;
Dimension dmsScreenSize;
int intScreenHeight, intScreenWidth;
DataBaseReader dbReader;
private int intElementsColumns = 18, intElementsRows = 9;//18 columns in the periodic table, 7 rows + the 2 F block rows, 6 and 7. Totals 9 rows

public Table(){
    dmsScreenSize = Toolkit.getDefaultToolkit().getScreenSize();
    intScreenWidth = (int)dmsScreenSize.getWidth();
    intScreenHeight = (int)dmsScreenSize.getHeight();
    frmMain = new JFrame("Periodic Table of the Elements");
    frmMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frmMain.setMinimumSize(dmsScreenSize);
    dbReader = new DataBaseReader();
    layout("Periodic Table");
    frmMain.setVisible(true);
}
void layout(String strLayout){ //Creates the specified table, at the time there is only one.
    switch(strLayout){
        case "Periodic Table":
            periodicTable();
            break;
    }
}
private void periodicTable() {
    aryElements = new Element[intElementsRows][intElementsColumns]; //creates a two-dimensional array of the class Element, hold 9 by 18 blocks
    try{
        JsonArray jAryElements = dbReader.readDataBase(); //obtains the Element array from Elements.JSON
        Element clsElement; //Define an element instance, but not create
        for(int index = 0; index < jAryElements.size(); index++){   //go through the Element array (called jAryElements), for every json object in this array...
            clsElement = new Element(jAryElements.getJsonObject(index));//create an instance of class Element with the current selected JsonObject
            frmMain.add(clsElement);                /*add that instance to the JFrame frmMain (Element extends JPanel)
                                        *(For below) Store that instance in the 2d array
                                        *storage location is dependant on the element's row and column in the periodic table*/
            aryElements[Integer.parseInt(element.getString("row"))-1][Integer.parseInt(element.getString("columnNumber"))-1] = clsElement;
        }
    }catch(IOException ioe){
        ioe.printStackTrace();
    }

}
}

(Currently handling next to all graphics)

package PeriodicTable;

import javax.json.JsonObject;

import javax.swing.JPanel;

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Font;

class Element extends JPanel {
JsonObject element;
int intX, intY, intWidth, intHeight;
Font aFont, bFont, cFont;
Element(JsonObject elementForeign){
    element = elementForeign; //Store the json object from the database here
    intWidth = 50;  //width of an element
    intHeight = 71; //height of an element
    intX = 10+(intWidth*(Integer.parseInt(element.getString("columnNumber"))));
    /*<10+> add some space to the left of the entire grid of elements
    *<intWidth*(<some code>(<some code>("columnNumber")> 
    *   position would be equal to the element width multiplied by it's column. */
    intY = 50+(intHeight*(Integer.parseInt(element.getString("row")))); //Same as intX, but vertically
    aFont = new Font("TimesRoman", Font.BOLD, 10);//some fonts for text inside each element
    bFont = new Font("TimesRoman", Font.PLAIN, 9);
    cFont = new Font("TimesRoman", Font.BOLD, 17);
}
public void update(Graphics gr){ //Post to stop the program from clearing the screen
    paintComponent(gr);
}
public void paintComponent(Graphics gr){/*painting method. I've read someone describing the activation
                    *of this method to occur "magically" when ever something happens to the container
                    *such as resizing the main window.*/
    gr.setColor(getColor(""));              //The specifics for every piece of information to be drawn inside a element block
    gr.drawRect(intX, intY, intWidth, intHeight);
    gr.setColor(getColor(element.getString("group")));
    gr.fillRect(intX+1, intY+1, intWidth-2, intHeight-2);
    gr.setColor(getColor(""));
    gr.setFont(aFont);
    gr.drawString(element.getString("atomicNumber"), intX+4, intY+10);
    gr.drawString(element.getString("molarMass"), intX+4, intY+29);
    gr.setFont(bFont);
    gr.drawString(element.getString("electronegativity"), intX+4, intY+20);
    gr.drawString(element.getString("ionCharge1"), intX+4, intY+29);
    gr.drawString(element.getString("ionCharge2"), intX+4, intY+38);
    gr.drawString(element.getString("name"), intX+(intWidth/2)-((gr.getFontMetrics().stringWidth(element.getString("name")))/2), intY+56);
    gr.setColor(getColor(element.getString("naturalState")));
    gr.setFont(cFont);
    gr.drawString(element.getString("symbol"), intX+((intWidth/2)-((gr.getFontMetrics().stringWidth(element.getString("symbol")))/2)), intY+45);
    gr.setColor(getColor(element.getString("synthetic")));
    gr.drawRect(intX+(intWidth-(2*(intWidth/5)))-1, intY+1, intWidth/5, intWidth/5);
    gr.fillRect(intX+(intWidth-(2*(intWidth/5)))-1, intY+2, intWidth/5, intWidth/5);
    gr.setColor(getColor(element.getString("diatomic")));
    gr.drawRect(intX+(intWidth-(intWidth/5))-1, intY+1, intWidth/5, intWidth/5);
    gr.fillRect(intX+(intWidth-(intWidth/5))-1, intY+2, intWidth/5, intWidth/5);
}
private Color getColor(String strType){ //Returns a color depending on the situation (the string that is passed in)
    switch(strType){
        case "Hydrogen":
            return new Color(255,229,204);
        case "Alkali Metal":
            return new Color(255,102,102);
        case "Alkali Earth Metal":
            return new Color(255,204,204);
        case "Transition Metal":
            return new Color(153,255,153);
        case "Inner-Transition Metal":
            return new Color(0,255,0);
        case "Metalloid":
            return new Color(255,0,255);
        case "Post-Transition Metal":
            return new Color(255, 153,51);
        case "Halogen":
            return new Color(255,128,0);
        case "Noble Gas":
            return new Color(204,229,255);
        case "Unknown-Post-Transition Metal":
        case "Unknown-Halogen":
        case "Unknown-Noble Gas":
        case "false":
            return new Color(255,255,255);
        case "true":
            return new Color(255,255,0);
        case "Gas":
            return new Color(255,0,0);
        case "Liquid":
            return new Color(0,0,255);
        case "Solid":
        default:
            return new Color(0,0,0);
    }
}
}

What versions/programs/etc am I using?

Notepad

Command Prompt

Java 8

javax.json-1.0.jar

The program will javac, jar and java -jar fine, the database reads fine and I can use the information. For that reason, I will not include additional information about those (unless someone asks).

Before this, I've tried using layouts and such, but I had a ridiculous amount of trouble with this... Eventually I had thought of using a 2d array to hold the information.

I've searched many sites, including:

How does paintComponent work?

http://www.oracle.com/technetwork/java/painting-140037.html

http://www.slideshare.net/martyhall/java-7-programming-tutorial-multithreaded-graphics-and-animation

Incremental graphics in Swing

Java clears screen when calling paint method - how to avoid that?

And many more. Some of the things I didn't understand, but I did learn a fair amount.

Note, I'm still rather new to java, my actual education goes as far as the end of a highschool online computer science course. Though while working on this extra project I've learned many things outside of the course.

To clearly state my question...

Any clue as to why the incremental painting is not working? How would I fix this?


Solution

  • Lots of problems going on here, but the biggest 3 that immediately come to mind:

    1. You're overriding the update(Graphics g) method, and calling paintComponent within it. You're not supposed to override this method in Swing GUI's, that's an AWT sort of thing, and you certainly shouldn't change its innate behavior as your code does.
    2. You're not calling the super's paintComponent method within your override.
    3. Most importantly, you're adding element components directly to the JFrame without changing the JFrame's default BorderLayout layout manager -- what do you think this will do? Perhaps make it so that only the last item entered is displayed!

    Consider creating a JPanel that uses a better layout, a GridLayout would likely work best, and add your element components to this JPanel, and then add that JPanel to your GUI.

    Note that the periodic table will have spots for empty elements, and for those, add blank JLabels to your GridLayout-using JPanel.

    Another problem: If you're going to use layouts properly, then you will want each element to draw it's images in the coordinate system relative to the element, not the table. In other words each element will place its images and text in the same relative location. The absolute location will be different because each element will occupy its own different absolute location on the JPanel.



    Just as an edit to my own question, I was playing with periodic table data an display from another question, and tried to create a simplified periodic table, one without the lanthanide and actinide elements, and using a simple Swing GUI.

    I created a simple Element.java class that only holds the most basic information within an element:

    public class Element {
        private int atomicNumber;
        private String name;
        private String symbol;
        private int group;
        private int period;
    
        public Element(int atomicNumber, String name, String symbol, int group, int period) {
            this.atomicNumber = atomicNumber;
            this.name = name;
            this.symbol = symbol;
            this.group = group;
            this.period = period;
        }
    
        public int getAtomicNumber() {
            return atomicNumber;
        }
    
        public String getName() {
            return name;
        }
    
        public String getSymbol() {
            return symbol;
        }
    
        public int getGroup() {
            return group;
        }
    
        public int getPeriod() {
            return period;
        }
    
        @Override
        public String toString() {
            return "Element [atomicNumber=" + atomicNumber + ", name=" + name + ", symbol=" + symbol
                    + ", group=" + group + ", period=" + period + "]";
        }
    
    }
    

    I then created this data table, Elements.txt, located this file in the same directory as the class files:

    1, Hydrogen, H, 1, 1
    2, Helium, He, 18, 1
    3, Lithium, Li, 1, 2
    4, Beryllium, Be, 2, 2
    5, Boron, B, 13, 2
    6, Carbon, C, 14, 2
    7, Nitrogen, N, 15, 2
    8, Oxygen, O, 16, 2
    9, Fluorine, F, 17, 2
    10, Neon, Ne, 18, 2
    11, Sodium, Na, 1, 3
    12, Magnesium, Mg, 2, 3
    13, Aluminium, Al, 13, 3
    14, Silicon, Si, 14, 3
    15, Phosphorus, P, 15, 3
    16, Sulfur, S, 16, 3
    17, Chlorine, Cl, 17, 3
    18, Argon, Ar, 18, 3
    19, Potassium, K, 1, 4
    20, Calcium, Ca, 2, 4
    21, Scandium, Sc, 3, 4
    22, Titanium, Ti, 4, 4
    23, Vanadium, V, 5, 4
    24, Chromium, Cr, 6, 4
    25, Manganese, Mn, 7, 4
    26, Iron, Fe, 8, 4
    27, Cobalt, Co, 9, 4
    28, Nickel, Ni, 10, 4
    29, Copper, Cu, 11, 4
    30, Zinc, Zn, 12, 4
    31, Gallium, Ga, 13, 4
    32, Germanium, Ge, 14, 4
    33, Arsenic, As, 15, 4
    34, Selenium, Se, 16, 4
    35, Bromine, Br, 17, 4
    36, Krypton, Kr, 18, 4
    37, Rubidium, Rb, 1, 5
    38, Strontium, Sr, 2, 5
    39, Yttrium, Y, 3, 5
    40, Zirconium, Zr, 4, 5
    41, Niobium, Nb, 5, 5
    42, Molybdenum, Mo, 6, 5
    43, Technetium, Tc, 7, 5
    44, Ruthenium, Ru, 8, 5
    45, Rhodium, Rh, 9, 5
    46, Palladium, Pd, 10, 5
    47, Silver, Ag, 11, 5
    48, Cadmium, Cd, 12, 5
    49, Indium, In, 13, 5
    50, Tin, Sn, 14, 5
    51, Antimony, Sb, 15, 5
    52, Tellurium, Te, 16, 5
    53, Iodine, I, 17, 5
    54, Xenon, Xe, 18, 5
    55, Caesium, Cs, 1, 6
    56, Barium, Ba, 2, 6
    57, Lanthanum, La, 3, 6
    72, Hafnium, Hf, 4, 6
    73, Tantalum, Ta, 5, 6
    74, Tungsten, W, 6, 6
    75, Rhenium, Re, 7, 6
    76, Osmium, Os, 8, 6
    77, Iridium, Ir, 9, 6
    78, Platinum, Pt, 10, 6
    79, Gold, Au, 11, 6
    80, Mercury, Hg, 12, 6
    81, Thallium, Tl, 13, 6
    82, Lead, Pb, 14, 6
    83, Bismuth, Bi, 15, 6
    84, Polonium, Po, 16, 6
    85, Astatine, At, 17, 6
    86, Radon, Rn, 18, 6
    87, Francium, Fr, 1, 7
    88, Radium, Ra, 2, 7
    89, Actinium, Ac, 3, 7
    104, Rutherfordium, Rf, 4, 7
    105, Dubnium, Db, 5, 7
    106, Seaborgium, Sg, 6, 7
    107, Bohrium, Bh, 7, 7
    108, Hassium, Hs, 8, 7
    109, Meitnerium, Mt, 9, 7
    110, Darmstadtium, Ds, 10, 7
    111, Roentgenium, Rg, 11, 7
    112, Copernicium, Cn, 12, 7
    113, Nihonium, Nh, 13, 7
    114, Flerovium, Fl, 14, 7
    115, Moscovium, Mc, 15, 7
    116, Livermorium, Lv, 16, 7
    117, Tennessine, Ts, 17, 7
    118, Oganesson, Og, 18, 7
    

    Then the GUI would use BoxLayout to create each element cell for the table, and would use GridLayout to hold and display all the element cells:

    import java.awt.Color;
    import java.awt.Font;
    import java.awt.GridLayout;
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    import javax.swing.*;
    
    @SuppressWarnings("serial")
    public class ElementsGui extends JPanel {
    
        private static final int MAX_GROUPS = 7;
        private static final int MAX_PERIODS = 18;
    
        public ElementsGui(List<Element> elements) {
            setLayout(new GridLayout(MAX_GROUPS, MAX_PERIODS));
            int prevGroup = 1;
            for (Element element : elements) {
                ElementPanel elementPanel = new ElementPanel(element);
                for (int i = prevGroup; i < element.getGroup() - 1; i++) {
                    add(new JLabel());
                }
                add(elementPanel);
                prevGroup = element.getGroup();
            }
        }
    
        // a utility method for downloading the elements from the text file
        public static List<Element> extractElements(InputStream sourceStream) {
            List<Element> elements = new ArrayList<>();
            Scanner scan = new Scanner(sourceStream);
            while (scan.hasNextLine()) {
                String line = scan.nextLine();
                String[] tokens = line.split(", ");
    
                int atomicNumber = Integer.parseInt(tokens[0]);
                String name = tokens[1];
                String symbol = tokens[2];
                int group = Integer.parseInt(tokens[3]);
                int period = Integer.parseInt(tokens[4]);
                elements.add(new Element(atomicNumber, name, symbol, group, period));
            }
            scan.close();
    
            return elements;
        }
    
        private class ElementPanel extends JPanel {
            Element element;
    
            public ElementPanel(Element element) {
                this.element = element;
                JLabel nameLabel = new JLabel(element.getName(), SwingConstants.CENTER);
                JLabel symbolLabel = new JLabel(element.getSymbol(), SwingConstants.CENTER);
                JLabel atomNumbLabel = new JLabel("" + element.getAtomicNumber(), SwingConstants.CENTER);
    
                symbolLabel.setFont(symbolLabel.getFont().deriveFont(Font.BOLD, 32f));
    
                JPanel namePanel = new JPanel();
                JPanel symbolPanel = new JPanel();
                JPanel atomNumbPanel = new JPanel();
    
                namePanel.add(nameLabel);
                symbolPanel.add(symbolLabel);
                atomNumbPanel.add(atomNumbLabel);
    
                setBorder(BorderFactory.createLineBorder(Color.BLACK));
                setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
                add(namePanel);
                add(symbolPanel);
                add(atomNumbPanel);
            }
    
            public Element getElement() {
                return element;
            }
    
        }
    
        private static void createAndShowGui() {
            String path = "Elements.txt";
            InputStream sourceStream = ElementUtilities.class.getResourceAsStream(path);
            if (sourceStream == null) {
                String message = "\"" + path + "\"" + " not found. Program will abort";
                String title = "Path Error";
                JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
                System.exit(-1);
            }
            List<Element> elements = extractElements(sourceStream);
    
            ElementsGui mainPanel = new ElementsGui(elements);
    
            JFrame frame = new JFrame("Elements Gui");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.getContentPane().add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> createAndShowGui());
        }
    }
    

    This displays as:

    enter image description here