JTextField - Part 3

This is quite an advanced lesson, but contains a very useful technique that really lets you control exactly what goes into a JTextField.

Sadly, we first have to look at a bit of theory to understand where this code comes from. A widget in Swing is based on an Model-View-Controller (MVC) design pattern.

The Model is the bit that represents the low-level behaviour and state of the widget.
The View is what is represented on the screen.
The Controller is the bit that handles user interaction with the model.
This is a very simplistic description, but it will do for now.

For this example, we want to limit the amount of characters you can put in the TextField to 8.

To change the fundamental behaviour of a TextField, we need to edit the model. Instead of playing around with the internals of Java and editing the JTextField class, we will create a class that extends the JTextField class. We'll call it KTextField.
1
2
3
4
5
6
7
8
9
class KTextField extends JTextField {
        
	private int length = 0;

	// Creates a TextField with a fixed length of string input.
	public KTextField(int length) {   
		super(new FixedLengthPlainDocument(length), "", length);
	}
}
Now, this KTextField class extends the JTextField class. Normally, the JTextField uses a class called PlainDocument to display the words on screen. Again, we need to make our own version of this. Since the Length is being fixed, we've called that the FixedLengthPlainDocument class.
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class FixedLengthPlainDocument extends PlainDocument {   

	private int maxlength;

	// This creates a Plain Document with a maximum length called maxlength.   
	FixedLengthPlainDocument(int maxlength) {   
		this.maxlength = maxlength;   
	}

	// This is the method used to insert a string to a Plain Document.	
	public void insertString(int offset, String str, AttributeSet a) throws
		BadLocationException {   
				
		// If the current length of the string
		// + the length of the string about to be entered 
		//(through typing or copy and paste)
		// is less than the maximum length passed as an argument..
		// We call the Plain Document method insertString.
		// If it isn't, the string is not entered.
				
		if (!((getLength() + str.length()) > maxlength)) {   
			super.insertString(offset, str, a);   
		}
	}
}
It gets a little crazy from here, but bear with me (and use the comments).

We first create a basic constructor using the maximum length as an argument.
16
17
18
	FixedLengthPlainDocument(int maxlength) {   
		this.maxlength = maxlength;   
	}
By looking at the Java API, we can see that the method used to input data into a PlainDocument is insertString. (See here)
We need the method to check if it goes over the limit first before actually putting any data in.

So, we change the method to check the current length of the String + the added string does not go over the maximum length.
31
		if (!((getLength() + str.length()) > maxlength)) {   
If it doesn't, we use the PlainDocument insertString as normal (super calls the class that you have extended).
If it does go over the limit, the insertion is ignored.
16
17
18
	FixedLengthPlainDocument(int maxlength) {   
		this.maxlength = maxlength;   
	}
We could do this for all sorts. If you wanted the String to be all upper-case, or for them all to be the letter L.

So let's use this example and put it into place in the previous example. In this we shall only limit the TextField...you could create an extended class for the PasswordField if you wanted to, but I won't here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import java.awt.*;
import java.awt.event.*;
import java.util.Arrays;
import javax.swing.*;
import javax.swing.text.*;

public class TextFieldExample_Extended implements  ActionListener{

    JPanel textPanel, panelForTextFields, completionPanel;
    JLabel titleLabel, usernameLabel, passwordLabel, userLabel, passLabel;
    KTextField usernameField;
    JPasswordField loginField;
    JButton loginButton;

    public JPanel createContentPane (){

        // We create a bottom JPanel to place everything on.
        JPanel totalGUI = new JPanel();
        totalGUI.setLayout(null);

        titleLabel = new JLabel("Login Screen");
        titleLabel.setLocation(0,0);
        titleLabel.setSize(290, 30);
        titleLabel.setHorizontalAlignment(0);
        totalGUI.add(titleLabel);

        // Creation of a Panel to contain the JLabels
        textPanel = new JPanel();
        textPanel.setLayout(null);
        textPanel.setLocation(10, 35);
        textPanel.setSize(70, 80);
        totalGUI.add(textPanel);

        // Username Label
        usernameLabel = new JLabel("Username");
        usernameLabel.setLocation(0, 0);
        usernameLabel.setSize(70, 40);
        usernameLabel.setHorizontalAlignment(4);
        textPanel.add(usernameLabel);

        // Login Label
        passwordLabel = new JLabel("Password");
        passwordLabel.setLocation(0, 40);
        passwordLabel.setSize(70, 40);
        passwordLabel.setHorizontalAlignment(4);
        textPanel.add(passwordLabel);

        // TextFields Panel Container
        panelForTextFields = new JPanel();
        panelForTextFields.setLayout(null);
        panelForTextFields.setLocation(110, 40);
        panelForTextFields.setSize(100, 70);
        totalGUI.add(panelForTextFields);

        // Username Textfield
        usernameField = new KTextField(8);
        usernameField.setLocation(0, 0);
        usernameField.setSize(100, 30);
        panelForTextFields.add(usernameField);

        // Login Textfield
        loginField = new JPasswordField(8);
        loginField.setEchoChar('&');
        loginField.setLocation(0, 40);
        loginField.setSize(100, 30);
        panelForTextFields.add(loginField);

        // Creation of a Panel to contain the completion JLabels
        completionPanel = new JPanel();
        completionPanel.setLayout(null);
        completionPanel.setLocation(240, 35);
        completionPanel.setSize(70, 80);
        totalGUI.add(completionPanel);

        // Username Label
        userLabel = new JLabel("Wrong");
        userLabel.setForeground(Color.red);
        userLabel.setLocation(0, 0);
        userLabel.setSize(70, 40);
        completionPanel.add(userLabel);

        // Login Label
        passLabel = new JLabel("Wrong");
        passLabel.setForeground(Color.red);
        passLabel.setLocation(0, 40);
        passLabel.setSize(70, 40);
        completionPanel.add(passLabel);

        // Button for Logging in
        loginButton = new JButton("Login");
        loginButton.setLocation(130, 120);
        loginButton.setSize(80, 30);
        loginButton.addActionListener(this);
        totalGUI.add(loginButton);

        totalGUI.setOpaque(true);    
        return totalGUI;
    }

    // With this action performed, we simply check to see if the username and
    // password match "Bob" as the username and "Robert" as the password.
    // If they do, we set the labels ajacent to them to "Correct!" and color
    // them green.
    // At the end, we check if both labels are green. If they are, we set the
    // screen to be 'Logging In'.

    public void actionPerformed(ActionEvent e) {

        if(e.getSource() == loginButton)
        {
            if(usernameField.getText().trim().compareTo("Bob") == 0)
            {
                userLabel.setForeground(Color.green);
                userLabel.setText("Correct!");
            }
            else
            {
                userLabel.setForeground(Color.red);
                userLabel.setText("Wrong!");
            }

            // Here, because we use a password field, we use the getPassword
            // command. This is more secure.
            // Once we are finished with the char array with the correct answer
            // we change it back to blank.
            // You may ask why we do this when the char array we compare it to
            // is in clear text one line above.
            // Obviously we'd store this in an encrypted database. (or something
            // along those lines!)

            char[] answer = {'R', 'o', 'b', 'e', 'r', 't'};
            char[] input = loginField.getPassword();

            if(Arrays.equals(input, answer))
            {
                passLabel.setForeground(Color.green);
                passLabel.setText("Correct!");
                for(int i = 0; i < input.length; i++)
                {
                    input[i] = ' ';
                }
            }
            else
            {
                passLabel.setForeground(Color.red);
                passLabel.setText("Wrong!");
            }

            if((userLabel.getForeground() == Color.green) 
                && (passLabel.getForeground() == Color.green))
            {
                titleLabel.setText("Logging in....");
                loginButton.setEnabled(false);
            }
        }
    }

    private static void createAndShowGUI() {

        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("[=] KTextField of MVC [=]");

        TextFieldExample_Extended demo = new TextFieldExample_Extended();
        frame.setContentPane(demo.createContentPane());
        
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(310, 200);
        frame.setVisible(true);
    }


    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    class KTextField extends JTextField {

        private int length = 0;

        // Creates a TextField with a fixed length of string input.
        public KTextField(int length) {
            super(new FixedLengthPlainDocument(length), "", length);
        }
    }

    class FixedLengthPlainDocument extends PlainDocument {

        private int maxlength;

        // This creates a Plain Document with a maximum length
        // called maxlength.
        FixedLengthPlainDocument(int maxlength) {
            this.maxlength = maxlength;
        }

        // This is the method used to insert a string to a Plain Document.
        public void insertString(int offset, String str, AttributeSet a) throws
                BadLocationException {

            // If the current length of the string
            // + the length of the string about to be entered
            // (through typing or copy and paste)
            // is less than the maximum length passed as an argument..
            // We call the Plain Document method insertString.
            // If it isn't, the string is not entered.

            if (!((getLength() + str.length()) > maxlength)) {
                super.insertString(offset, str, a);
            }
        }
    }
}
Things to point out are...

1) The importing of java.swing.text.*; on line 5. This lets you play around with the internals of PlainDocument.
2) Changing the JTextField to a KTextField (line 11 and line 56).
3) The insertion of the two modified classes at the end of the code. (lines 182-218).

Good luck with all that! By getting an insight to how widgets are designed, you can see that you do have a HUGE amount of flexibility with the widgets in your GUI.

Exercises

To see if you actually understand this, stop the username box from using any letters apart from B, b and o.

Hint for Exercise
Exercise Example Code

Questions

Ooh, that was tricky. Can you answer these questions?

Question 1. MVC?

  • a) Mobile Vehicle Carrier.
  • b) Movies, Video Games and Cheap CDs.
  • c) Model - View - Controller.

Question 2. What class does the JTextField call normally?

  • a) PlainDocument
  • b) NormalDocument
  • c) BlankDocument

Question 3. Where do we make the changes to restrict what goes into the KTextField?

  • a) insertString in our modified PlainDocument.
  • b) modifyChanges in our modified PlainDocument.
  • c) printAll in our modified PlainDocument.




Back Top Next
Email Me
Code Style


Required Lessons

External Links

Created and Edited by Stuart Davidson
All Rights Reserved ©

Valid XHTML 1.0 Strict