HI guys, as mentioned in my revious post, I'll redo my wordCount application using the GridBagLayout approach. The functionality of the application hasn't changed at all: I've only removed the various Jpanels and, for simplicity, used only the JFrame and attached all the comonents to the JFrame.
I've had a look around to see how the GridBagLayout works etc, and even if I've seen slightly different approaches, I went for the most concise of them all where I create the component, add it to the panel using this constructor (found here https://docs.oracle.com/javase/8/docs/api/java/awt/GridBagConstraints.html)

GridBagConstraints(int gridx, int gridy, int gridwidth, int gridheight, double weightx, double weighty, int anchor, int fill, Insets insets, int ipadx, int ipady)

I started with photoshop first, drawing roughly how I'd like the application to look like:
wordCountGBL.jpg
So I began to position my components in a 4x2 grid, but I run into problems, and quite a few. Here is the code that sets the constraints:

add(instructions, new GridBagConstraints(0,0,2,1,0,1.0,CENTER,BOTH, new Insets(1,1,1,1),1,1));//adding instruction jlabel and constraints
    add(new JScrollPane(textInput), new GridBagConstraints(0,1,2,2,1.0,1.0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding textInput and constraints
    add(processButton, new GridBagConstraints(0,2,1,2,0,0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding processButton and constraints
    add(clearButton, new GridBagConstraints(1,2,1,2,0,0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding clearButton and constraints
    add(new JScrollPane(textOutput), new GridBagConstraints(0,3,2,3,1.0,1.0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding textOutput       

Somehow, only the JLabel and the two JTextAreas are visible, the two buttons aren't there. This is how it looks like:
wordCountGBL_problems.jpg
The code seems to me to be OK, in the sense that I've positioned the buttons one next to the other (pos 0,2 and 1,2) and centered them, but they don't appear.
Also, equally annoying, the java window doesn't seem to be the size I want it to be: I specified wordCount.setSize(900,800); but it doesn't look anywhere near that size, much smaller in fact, and I'm wondering where it is getting its size from...
Here is the code:

/*WordCount.java
    Application determines how many times a word has occurred in the text given
    */
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JTextArea;
    import javax.swing.JButton;
    import javax.swing.JScrollPane;
    import java.awt.GridBagLayout;
    import java.awt.GridBagConstraints;
    import static java.awt.GridBagConstraints.*;
    import java.awt.Insets;
    import java.awt.Color;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Map;
    import java.util.HashMap;
    import java.util.Set;
    import java.util.TreeSet;
    import java.util.StringTokenizer;
    public class WordCount extends JFrame{
        private JTextArea textInput;
        private JTextArea textOutput;
        private JButton processButton;
        private JButton clearButton;
        private JLabel instructions;
        private GridBagLayout gbLayout;
        /* private GridBagConstraints inputConstraints;//declare constraints input textAea
        private GridBagConstraints outputConstraints;//declare constraints output textArea
        private GridBagConstraints CountConstraints;//declare constraints count button
        private GridBagConstraints ClearConstraints;//declare constraints clear button
        private GridBagConstraints instructionsConstraints;//declare constraints JLabel */
        private String stringToTest;//holds string pasted in textarea
        private String[] tokens;//array of tokenized strings
        Map<String,Integer> mp = new HashMap<String, Integer>();//declaring a map object
        public WordCount(){
            super("Word count application");
            gbLayout = new GridBagLayout();//create new GBL object
            setLayout(gbLayout);//set JFrame GBL
            textInput = new JTextArea(10,15);//holds the input text
            textOutput = new JTextArea(10,15);//hold the output text
            textOutput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
            textInput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
            stringToTest = "";//initialize to empty string
            processButton = new JButton("Count");//action abutton
            clearButton = new JButton("Clear");//to clear textArea
            instructions = new JLabel("Copy and paste text in the below text area, click Count to count the words.\n");//holding instructions       
            textInput.setText("");//initialize to empty string
            textOutput.setText("");//initialize to empty string

            add(instructions, new GridBagConstraints(0,0,2,1,0,1.0,CENTER,BOTH, new Insets(1,1,1,1),1,1));//adding instruction jlabel and constraints
            add(new JScrollPane(textInput), new GridBagConstraints(0,1,2,2,1.0,1.0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding textInput and constraints
            add(processButton, new GridBagConstraints(0,2,1,2,0,0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding processButton and constraints
            add(clearButton, new GridBagConstraints(1,2,1,2,0,0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding clearButton and constraints
            add(new JScrollPane(textOutput), new GridBagConstraints(0,3,2,3,1.0,1.0,CENTER,BOTH,new Insets(1,1,1,1),1,1));//adding textOutput       

            textOutput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
            textInput.setMargin(new Insets(5,5,5,5));//sets margins inside the element

            ProcessButtonHandling handler1 = new ProcessButtonHandling();
            ClearButtonHandling handler2 = new ClearButtonHandling();
            processButton.addActionListener(handler1);
            clearButton.addActionListener(handler2);
        }//end of constructor
        public String getString(){
            return stringToTest;
        }
        //inner class for event handlings
        private class ProcessButtonHandling implements ActionListener{
            public void actionPerformed(ActionEvent event){
                stringToTest = textInput.getText();
                processString(getString());//call function to split string
                mapWords();//count words and place results in the hashmap
                printMap();//print the map values and keys in the textArea
            }//end of actionPerformed
        }//end of inner class
        private class ClearButtonHandling implements ActionListener{
            public void actionPerformed(ActionEvent event){
                textOutput.setText("");
                textInput.setText("");
            }//end of actionPerformed
        }//end of inner class
        public void processString(String testingString){        
            //String[] tokens = testingString.split("(?=[,.])|\\s+");
            testingString = testingString.replaceAll("[,.;:]","");//remove the characters in brackets and replace them with nothing
            tokens = testingString.split(" ");//split string using space as delimiter
            /* System.out.printf("Number of elements: %d\nTokens are \n", tokens.length);
            //print to test
            for(String token : tokens){
                System.out.println(token);
            }    */ 
        }
        public void mapWords(){
            for(int i = 0; i < tokens.length; i++){
                if(mp.containsKey(tokens[i])){
                    //increment Integer
                    mp.put(tokens[i], mp.get(tokens[i]) + 1);//add it to the hashmap and increment integer
                } 
                else{
                    mp.put(tokens[i],1);//add it to the hashmap
                }
            }
        }
        public void printMap(){
            for(Map.Entry<String, Integer> entry : mp.entrySet()){//loop thru map to print data
                //System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
                textOutput.append("Key = " + entry.getKey() + ",\t Value = " + entry.getValue() + "\n");
                System.out.println("Key = " + entry.getKey() + ",\t Value = " + entry.getValue()+ "\n");//for debug purposes
            }
        }
    }//end of WordCount



/*WordCountTest.java
        WordCountTest to test the class
    */
    import javax.swing.JFrame;
    public class WordCountTest{
        public static void main(String[] args){
            WordCount wordCount = new WordCount();
            wordCount.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            wordCount.setSize(900,800);
            wordCount.pack();
            wordCount.setVisible(true);
        }//end of main
    }//end of WordCountTest

Recommended Answers

All 16 Replies

I don't have a JDK here, but if I remember right you are setting the weights to 1 for the text areas and 0 for the buttons, so all the available space is allocated to the areas

Thanks. So I played around with the values a little and also made some changes to the insets of the textAreas, just to create a bit more space. For the weightx and weighty instead, I eventually set the ones for the textArea both at 0.5 and the ones for both buttons at 1.00, but still no joy. If I understand things correctly the weight controls the extra space allocated when resized, but when I resize, all the space seems to be going to the JLabel instead (which has weightx set to 0 and weighty set to 1.0.)
The amendments to the insets of the textareas though allowed me to see exactly what goes on in the GUI, even though I don't quite understand it: it looks like the buttons are huge and sitting behind the textArea?! see screenshot. I hadn't found anywhere reference to the fact that a component can sit behind another one.
Also, how do I make the whole windows the size I want it to be? As mentioned previously I have it set at wordCount.setSize(900,800); but the real size of the GUI isn't the one specified, it's much smaller and I'm not quite sure where it's taking its size from.
Here is the GUI window as it comes up when I launch the application
wordCountGBL_error_1.jpg
and here is the resized GUI instead:
wordCountGBL_error_1_resized.jpg

Just an idea, would it make any different if I arrange components in separate panels rather than having them to sit inside the JFrame directly?

Hi, it's me again (still on the road, so no JDK to check things).
There's no need for extra panels to do this in gbl.
Looking at the code again I see you set the height of your buttons to 2, which would imply you want them to overlap with whatever is below. But then you set the height of the bottom text area to 3, so maybe you have misunderstood the use of width/height parameters?

Setting the size of the window is a problem. The whole idea of layout managers is to fit the contents, allowing for the current system's font sizes, screen resolution etc. You may think 900x800 pixels looks good on your screen, but what happens if someone runs it on a smartphone or a retina Mac? And what happens when the user resizes it?
Set the preferred/ min sizes of each component if you don't like the defaults, but then let the layout manger do its job.

HI, sorry for the late reply, I was on the road as well, but no access to internet.
Right, maybe I did misunderstood the whole thing then, I don't know. My understanding is that gridwidth and gridheight set the height and the width of the component. Why would a height of 2 mean overlapping with wathever below? I would have thought it meant only for the component to span 2 rows rather than 1 and whatever is below gets pushed down. There is nothing about overlapping in the API, well not that I could see.
As for the text area, the height is 3 because I would have thought, again, that it would span 3 rows, pushing down whatever is below. This was my itnerpretation of http://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html
What's the real explanation :-)? Meaning, when would you want the height to be 1 and when not?
I've made some changes to the constraints:

add(instructions, new GridBagConstraints(0,0,2,1,0,0,CENTER,HORIZONTAL, new Insets(10,15,10,15),0,0));//adding instruction jlabel and constraints
        //shouldn't grow vertically, where is the padding on the left
        add(new JScrollPane(textInput), new GridBagConstraints(0,1,2,1,0.5,0.5,CENTER,BOTH,new Insets(10,15,10,15),10,10));//adding textInput and constraints
        add(processButton, new GridBagConstraints(0,2,1,1,1.0,1.0,CENTER,HORIZONTAL,new Insets(10,15,10,15),1,1));//adding processButton and constraints
        add(clearButton, new GridBagConstraints(1,2,1,1,1.0,1.0,CENTER,HORIZONTAL,new Insets(10,15,10,15),1,1));//adding clearButton and constraints
        add(new JScrollPane(textOutput), new GridBagConstraints(0,3,2,1,0.5,0.5,CENTER,BOTH,new Insets(10,15,10,15),1,1));//adding textOutput   

and the layout looks a tad better, in the sense that the components are not overlapping anymore (I've reduced the height of the textAreas to 1, the height of the buttons to 1 as well, changed the fill property to horizontal for the buttons, set the width of the textareas to 2 and added proper inset values to both buttons and the jlabel, so that there is a bit more space, screenshot attached ):
wordCountGBL_final.jpg
I'm happy with that. So the two things left for me to be clarified are:
-as above widht and height
-the ipadx and ipady don't seem to take any efect with regards to the textareas: you'll notice in the code that the input has values 10,10 and the output has instead 1,1 - this was for testing purposes to see the differences - but visually there is no difference. Are they being ignored?
-about the size: fair point about the different screen etc. two questions: if I want the default, what do I do (I'd like to try that and see how it looks like). setSize() is used only to set a specific size, if I leave setSize out completely does it mean that the default is instead implemented? (I removed it and everything seems to work still)

Set the preferred/ min sizes of each component

Does it mean that for each component I have to create a Dimension object and then set its preferred size? Also how do I know what's an optimum preferred size( when creating a dimension object, I presume you need to specify a size anyway)
something like this I believe:

In GBL each child has a position row/col and a size (in units of rows & cols), so if you have something at
(1 ,1, 1, 2 ...
thats position 1,1 one column wide, 2 rows high, ie it occupies rows 1 and 2
so if you don't want to clash with the next child you need to start that at row 3
(1, 3, ...

Sizes are best done in rows/cols/characters etc so Swing can scale the components based on the font size. Text components typically have constructors or set methods in chars/lines, or default based on ther content, so they usually work well. You normally only need to specify pixel sizes for non-text components.

Ah OK, crystal clear for the columns/rows now, thanks for that!
For resizing, OK so no need to use dimension then, still a bit confused though, sorry. Let's see i I understood.

Text components typically have constructors or set methods in chars/lines

So technically, I've already done this when I created the text areas:

textInput = new JTextArea(10,12);//holds the input text
        textOutput = new JTextArea(10,15);//hold the output text

JLabel and Jbutton though don't seem to have any constructor in the API that allows to enter any size

or default based on ther content

JLabel and JButton set their (preferred?) size to fit the text that's in them when you pack().
Sometimes you want to start out with them empty, in which case you can initialise them with a suitable number of blanks to get the size right.

OK, so if I leave things as they are and remove this wordCount.setSize(900,800); I get everything I need as the application width is all taken care of. Is that correct?

Should be. Just try it.

Thanks, yes I did and I didn't see anything different from when that line was in there :-). I mean I suppose it is OK, in the sense that when I run the application it opens up in a window that is 464x518. Admittedly it's a bit small for my 22inches monitor, but then again, bearing in mind what you said before, perhaps it isn't the end of the world? Or do you think it is better, if possible of course, to force the window to be full width regardless of the device? What's the best practice in the industry?

Here is the whole code for reference:

/*WordCountTest.java
    WordCountTest to test the class
*/
import javax.swing.JFrame;
public class WordCountTest{
    public static void main(String[] args){
        WordCount wordCount = new WordCount();
        wordCount.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //wordCount.setSize(900,800);
        wordCount.pack();
        wordCount.setVisible(true);
    }//end of main
}//end of WordCountTest



/*WordCount.java
Application determines how many times a word has occurred in the text given
*/
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JScrollPane;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import static java.awt.GridBagConstraints.*;
import java.awt.Insets;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import java.util.TreeSet;
import java.util.StringTokenizer;
import java.awt.Dimension;//to sort specify the dimension
public class WordCount extends JFrame{
    private JTextArea textInput;
    private JTextArea textOutput;
    private JButton processButton;
    private JButton clearButton;
    private JLabel instructions;
    private GridBagLayout gbLayout;
    /* private GridBagConstraints inputConstraints;//declare constraints input textAea
    private GridBagConstraints outputConstraints;//declare constraints output textArea
    private GridBagConstraints CountConstraints;//declare constraints count button
    private GridBagConstraints ClearConstraints;//declare constraints clear button
    private GridBagConstraints instructionsConstraints;//declare constraints JLabel */
    private String stringToTest;//holds string pasted in textarea
    private String[] tokens;//array of tokenized strings
    Map<String,Integer> mp = new HashMap<String, Integer>();//declaring a map object
    public WordCount(){
        super("Word count application");
        gbLayout = new GridBagLayout();//create new GBL object
        setLayout(gbLayout);//set JFrame GBL
        textInput = new JTextArea(10,12);//holds the input text
        textOutput = new JTextArea(10,15);//hold the output text
        textOutput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
        textInput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
        stringToTest = "";//initialize to empty string
        processButton = new JButton("Count");//action abutton
        clearButton = new JButton("Clear");//to clear textArea
        instructions = new JLabel("Copy and paste text in the below text area, click Count to count the words.\n");//holding instructions       
        textInput.setText("");//initialize to empty string
        textOutput.setText("");//initialize to empty string



        add(instructions, new GridBagConstraints(0,0,2,1,0,0,CENTER,HORIZONTAL, new Insets(10,15,10,15),0,0));//adding instruction jlabel and constraints
        //shouldn't grow vertically, where is the padding on the left
        add(new JScrollPane(textInput), new GridBagConstraints(0,1,2,1,0.5,0.5,CENTER,BOTH,new Insets(10,15,10,15),10,10));//adding textInput and constraints
        add(processButton, new GridBagConstraints(0,2,1,1,1.0,1.0,CENTER,HORIZONTAL,new Insets(10,15,10,15),1,1));//adding processButton and constraints
        add(clearButton, new GridBagConstraints(1,2,1,1,1.0,1.0,CENTER,HORIZONTAL,new Insets(10,15,10,15),1,1));//adding clearButton and constraints
        add(new JScrollPane(textOutput), new GridBagConstraints(0,3,2,1,0.5,0.5,CENTER,BOTH,new Insets(10,15,10,15),1,1));//adding textOutput       

        /* textOutput.setMargin(new Insets(5,5,5,5));//sets margins inside the element
        textInput.setMargin(new Insets(5,5,5,5));//sets margins inside the element */

        ProcessButtonHandling handler1 = new ProcessButtonHandling();
        ClearButtonHandling handler2 = new ClearButtonHandling();
        processButton.addActionListener(handler1);
        clearButton.addActionListener(handler2);
    }//end of constructor
    public String getString(){
        return stringToTest;
    }
    //inner class for event handlings
    private class ProcessButtonHandling implements ActionListener{
        public void actionPerformed(ActionEvent event){
            stringToTest = textInput.getText();
            processString(getString());//call function to split string
            mapWords();//count words and place results in the hashmap
            printMap();//print the map values and keys in the textArea
        }//end of actionPerformed
    }//end of inner class
    private class ClearButtonHandling implements ActionListener{
        public void actionPerformed(ActionEvent event){
            textOutput.setText("");
            textInput.setText("");
        }//end of actionPerformed
    }//end of inner class
    public void processString(String testingString){        
        //String[] tokens = testingString.split("(?=[,.])|\\s+");
        testingString = testingString.replaceAll("[,.;:]","");//remove the characters in brackets and replace them with nothing
        tokens = testingString.split(" ");//split string using space as delimiter
        /* System.out.printf("Number of elements: %d\nTokens are \n", tokens.length);
        //print to test
        for(String token : tokens){
            System.out.println(token);
        }    */ 
    }
    public void mapWords(){
        for(int i = 0; i < tokens.length; i++){
            if(mp.containsKey(tokens[i])){
                //increment Integer
                mp.put(tokens[i], mp.get(tokens[i]) + 1);//add it to the hashmap and increment integer
            } 
            else{
                mp.put(tokens[i],1);//add it to the hashmap
            }
        }
    }
    public void printMap(){
        for(Map.Entry<String, Integer> entry : mp.entrySet()){//loop thru map to print data
            //System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
            textOutput.append("Key = " + entry.getKey() + ",\t Value = " + entry.getValue() + "\n");
            System.out.println("Key = " + entry.getKey() + ",\t Value = " + entry.getValue()+ "\n");//for debug purposes
        }
    }
}//end of WordCount

In my opinion you should not make a window any bigger than it needs to be unless it's modal and must be noticed. You cannot know what else the user is doing, and what other windows may be competing for his screen space. Things like "full width" are a real no-no when you realise that Mac users in professional environemnts may have musltiple 4k screens aligned horizontally side-by-side.

Apart from that, re

    public void mapWords(){
        for(int i = 0; i < tokens.length; i++){
            if(mp.containsKey(tokens[i])){
                //increment Integer
                mp.put(tokens[i], mp.get(tokens[i]) + 1);//add it to the hashmap and increment integer
            } 
            else{
                mp.put(tokens[i],1);//add it to the hashmap
            }
        }
    }

what's wrong with

    public void mapWords(){
        for (string token : tokens) {
             mp.put(token, mp.getOrDefault(token, 0) +1)
        }
    }

... and if a method call needs a comment then you probably want a better name for the method.

Fair enough so I won't change anything else as far as the GUI is concerned.
Nothing wrong at all with your suggestion of course, I somehow prefer the classic for loop, that's why I used it, more than happy to change it to yours if it's better :-)!
Perhaps mapWords() could then be countWords() ?

Looks like the compiler doesn't like the amendments:

public void countWords(){       
        for (String token : tokens) {
             mp.put(token, mp.getOrDefault(token, 0) +1);
        }
    }

Produces this:

G:\JAVA\GUI\2015\createFrames\word_counter\gbl\preferredSize>javac *.java
WordCount.java:108: error: cannot find symbol
             mp.put(token, mp.getOrDefault(token, 0) +1);
                             ^
  symbol:   method getOrDefault(String,int)
  location: variable mp of type Map<String,Integer>
1 error

G:\JAVA\GUI\2015\createFrames\word_counter\gbl\preferredSize>

The "enhanced" for loop is generally considered eaier to read and harder to get wrong. but it's up to you.

The thing with method signatures is that ideally a Java programmer should be able to make sense of a call just from by reading it. If there is a comment that should explain why you are making that call now, not what the call does.

Without studying your code in detail I get the feeling that you coculd combine "processString" (what does that mean???) and "mapWords" into a single method maybe something like

void parseAndCountTokens(String tokens) {
    // cleans up tokens, splits on blanks, increments counts in token map mp
    ...
} 

although personally I hate methods that work by hidden side effects (changes mp but mp does not appear in the method signature), so I would probably go the whole hog and make things explicit, eg

    void parseAndCountTokensIntoMap(String tokens, Map<String, Integer> map) {
        // cleans up tokens, splits on blanks, increments counts in token map 
        ...
    } 

Yes, that's a long method name, but it's better than a short name plus a long comment!

(of course, all this is just my opinion)

I see what you mean, thanks for the feedback, much appreciated :-)!

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.