Time-inputfield for quick entering - java

I want a quick-to-use way for entering times down to centiseconds. Entering digits should push them in from the right and automatically add leading zeros, "." and ":" as needed, so for example:
"" => "0.03" => "0.31" => "3.14" => "31.41" => "3:14.15"
Also, entering "f" should result in "DNF" ("did not finish"):
"3:14.15" => "DNF" => "0.04"
My code below "works", except it always puts the cursor at the end (because I full-replace), which is not nice when someone tries to edit in the middle. Is there an easy way to keep the cursor where it should be? And more importantly, is my whole approach proper at all or I should really do this differently? I've tried many ways before (JFormattedTextField and Formatters, DocumentFilter, DocumentListener, etc), some because they were suggested here for similar things, but couldn't get them to work.
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
class TimeField extends JTextField {
public static void main ( String[] args ) {
JFrame frame = new JFrame();
frame.add( new TimeField() );
frame.add( new TimeField(), BorderLayout.SOUTH );
frame.pack();
frame.setDefaultCloseOperation( frame.EXIT_ON_CLOSE );
frame.setVisible( true );
}
TimeField () {
super( 10 );
setHorizontalAlignment( RIGHT );
setFont( new Font( "MONOSPACED", Font.BOLD, 32 ));
((AbstractDocument) getDocument()).setDocumentFilter( new DocumentFilter() {
public void insertString ( DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr ) throws BadLocationException {
replace( fb, offset, 0, string, attr );
}
public void remove ( DocumentFilter.FilterBypass fb, int offset, int length ) throws BadLocationException {
replace( fb, offset, length, "", null );
}
public void replace ( DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs ) throws BadLocationException {
fb.replace( offset,length, text, attrs );
if ( text.contains("f") ) {
fb.replace( 0, getText().length(), "DNF", null );
return;
}
text = getText(); // get current text
text = text.replaceAll( "\\D", "" ); // remove non-digits
text = text.replaceAll( "^0+", "" ); // remove leading zeros
while ( text.length() < 3 ) text = "0" + text; // make at least three digits
text = text.replaceAll( "(.)(..)$", "$1.$2" ); // add point if necessary
text = text.replaceAll( "(.)(..\\.)", "$1:$2" ); // add colon if necessary
fb.replace( 0, getText().length(), text, null ); // replace original with formatted
}
} );
}
}
Edit:
I tried JFormattedTextField before, here's my best attempt. I can enter digits and when the focus leaves the field, valueToString gets called and adds point+colon which is then displayed. But I'd like this to be done during editing, not after. I must be missing something, as MaskFormatter can apparently do that.
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
class TimeField extends JFormattedTextField {
public static void main(String[] args) {
//Create Event Disptach Thread for swing UI and its components to run on
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
JFormattedTextField ftf = new JFormattedTextField(new TimeFormatter());
ftf.setHorizontalAlignment(JTextField.RIGHT);
ftf.setFont(new Font(Font.MONOSPACED, Font.BOLD, 32));
ftf.setColumns(10);
frame.getContentPane().add(ftf);
frame.getContentPane().add(new JButton("foo"), BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
static class TimeFormatter extends DefaultFormatter {
public Object stringToValue(String text) {
System.out.println("stringToValue(" + text + ")");
return text == null ? "" : text.replaceAll("\\D", "");
}
public String valueToString(Object value) {
System.out.println("valueToString(" + value + ")");
return value == null ? null : format((String)value);
}
private String format(String text) {
text = text.replaceAll("\\D", ""); // remove non-digits
text = text.replaceAll("^0+", ""); // remove leading zeros
while (text.length() < 3) text = "0" + text; // make at least three digits
text = text.replaceAll("(.)(..)$", "$1.$2"); // add point
text = text.replaceAll("(.)(..\\.)", "$1:$2"); // add colon if necessary
return text;
}
}
}

Related

How to make a jtextfield type from right to left

first time posting here, i hope i didnt violate any rules.. ok here goes the question.
Question:
I want to make a jtextfield that writes from right to left with the default value of "0.00", when user input a number from keyboard it will change the value from the right, if the user input another number, it will push the previous input to the left and place the new input the to right side of the previous input. For example if a user types "1" on the keyboard, the jtextfield should display as "0.01", then if the user types "5", the jtextfield should display as "0.15", then if the user types "0", the jtextfield should display as "1.50" and so on..
Problem statement:
The problem is my code only can input the first number, and will keep changing the 2nd decimal place number if i input more numbers. Im using Eclipse IDE and also new to Java and i apologize in advance
for my lack of knowledge. Anyway, heres my code snippet.
txtp = new JTextField();
txtp.setText("0.00");
String gp = txtp.getText();
txtp.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
String[] sn1 = {"1", "2", "3", "4", "5", "6", "7", "8", "9","0"};
//String sn1 = String.valueOf(e);
char C = e.getKeyChar();
if(Character.isDigit(C) ||Character.isISOControl(C)) {
txtp.setText("0.0");
}else {
txtp.setEditable(false);
}
/*if((gp=="0.0"+sn1&&(Character.isDigit(C) ||Character.isISOControl(C)))){
txtp.setText("0."+sn1);
//String gp = txtp.getText();
}else {
txtp.setEditable(false);
}
else if((gp=="0.0"+sn1) && (Character.isDigit(C) ||Character.isISOControl(C))) {
txtp.setText("0."+sn1);
if(Character.isDigit(C) ||Character.isISOControl(C)) {
txtp.setEditable(true);
}else{
txtp.setEditable(false);
}
}
else{
txtp.setEditable(false);
}
switch (gp) {
case "":
txtp.setText("0.0");
if(Character.isDigit(C) ||Character.isISOControl(C)) {
txtp.setText("0.0");
}else{
txtp.setEditable(false);
}
break;
case "0.01":
txtp.setText("0.1");
if(Character.isDigit(C) ||Character.isISOControl(C)) {
txtp.setText("0.1");
}else{
txtp.setEditable(false);
}
break;
}*/
}
});
I did it using a DocumentFilter as suggested. I had to make it only take in or remove one character at a time.
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
public class TextFielding{
static class EndFilter extends DocumentFilter{
#Override
public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException{
replace(fb, offset, 0, string, attr);
}
public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException{
if(length > 1) return;
Document d = fb.getDocument();
int docLength = d.getLength();
String left = docLength == 4 ? "0" : d.getText(0, docLength - 4);
String shiftl = d.getText(docLength -4, 1);
String shiftr = d.getText(docLength - 2, 1);
String result = left + "." + shiftl + shiftr;
super.replace(fb, 0, docLength, result, null);
}
#Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException{
if(length > 0 || text.length() != 1 || !Character.isDigit( text.charAt(0) ) ){
return;
}
Document d = fb.getDocument();
int docLength = d.getLength();
//build the string.
String left = d.getText( 0, docLength - 3);
if( left.equals("0") ) left = "";
String crossing = d.getText( docLength - 2, 1);
String shift = d.getText( docLength -1, 1);
String result = left + crossing + "." + shift + text;
super.replace(fb, 0, docLength, result, attrs);
}
}
public static void main(String[] args){
JFrame frame = new JFrame("text field");
JTextField field = new JTextField(10);
field.setHorizontalAlignment( JTextField.RIGHT );
field.setText("0.00");
((AbstractDocument)field.getDocument()).setDocumentFilter(new EndFilter());
frame.add(field);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
I assumed that if more than 3 numbers are entered, then it should always keep 2 decimal places.
When a character is removed it shifts all of the characters to the right across the decimal point. If the last character on the left is empty, it a 0 is shifted in.
When a character is typed it needs to be one character, and it cannot be replacing any characters, and it needs to be a digit. Then it is appended on the right shifting all of the characters to the left across the decimal point.
This should serve as a template because if it doesn't correcting guess the format, it shows how to receive the input, modify it and modify the corresponding document.

show warning if users enter letter instead number in java applet

I am writing an tip calculator app in java applet with GUI, my question is how I make sure the error message will pop up if users enter letter instead of number
it is my first time asking question, please be easy on me! Thanks!!!
import objectdraw.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
// Typing in the text field and hitting return adds text to text area.
// Clicking on button erases the text area.
public class TextApplet extends Controller implements ActionListener
{
private static final int ROWS = 1; // rows in TextArea
private static final int COLS = 10; // cols in text field & area
private String amount;
private float number;
private JTextField inField, output; // Input field
private JButton clear, calc;
// button to clear output
public void begin()
{
Container contentPane = getContentPane();
JPanel topPanel = new JPanel(); // prepare text field & label
JLabel inLabel = new JLabel("Bill Cost: ");
inField = new JTextField(COLS);
inField.addActionListener(this);
JLabel topTitle = new JLabel("Tip Calculator", JLabel.CENTER);
JPanel combinePanel = new JPanel();
combinePanel.add ( inLabel );
combinePanel.add ( inField );
JPanel combinePanel1 = new JPanel();
combinePanel1.add ( topTitle );
topPanel.add ( combinePanel1 );
topPanel.add ( combinePanel );
topPanel.setLayout ( new GridLayout ( 3,1) );
contentPane.add(topPanel,BorderLayout.NORTH);
JPanel centerPanel = new JPanel(); // prepare text area & label
JLabel outLabel = new JLabel("Bill + Tip:");
output = new JTextField(COLS);
output.setEditable(false); // Prevent user from wrting in output
centerPanel.add(outLabel);
centerPanel.add(output);
contentPane.add(centerPanel,BorderLayout.CENTER);
JPanel bottomPanel = new JPanel();
// create button
clear = new JButton(" Clear ");
calc = new JButton("Calculate");
calc.addActionListener(this);
clear.addActionListener(this);
bottomPanel.add(calc);
bottomPanel.add(clear);
contentPane.add(bottomPanel,BorderLayout.SOUTH);
validate();
}
// add text to area if user hits return, else erase text area
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == calc )
{
amount = inField.getText();
number = ( Float.parseFloat( amount ) );
number = (15*number/100);
output.setText ( Float.toString ( number ) + '$' );
}
else if (evt.getSource() == clear )
{
output.setText("$");
inField.setText("");
}
}
}
There are any number of ways you might achieve this, you could use
An InputVerifier
A JFormattedTextField
A JSpinner
Or a DocumentFilter and examples
Take a look at javax.swing.InputVerifier. That can be easily attached to a JTextField
JTextField inputField = new JTextField();
inputField.setInputVerifier(new NumericInputVerifier());
private class NumericInputVerifier extends InputVerifier
{
#Override
public boolean verify(JComponent input)
{
if (((JTextField) input).getText().matches("[0-9]+"))
{
return true;
}
else
{
JOptionPane.showMessageDialog(input, "Only numbers are allowed", "Warning", JOptionPane.WARNING_MESSAGE);
return false;
}
}
}
A complete example can be found here.
Edit Added an example of how to use InputVerifier to limit to numeric input. You'll want to double check the regex, but the basic idea is there...
Use a JFormattedTextField or a DocumentFilter. Then the user won't even be able to enter a non-numeric digit. See:
How to Use Formatted Text Fields
Implementing a Document Filter
For the document filter you will need to check each chraacter as it is entered to make sure it is a digit.
It is always better to do simple edits like that as the user types, rather than wait until you click on a button to do processing.
Hello Friend I will give a suggestion
please add validation when call actionPerformed method
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == calc )
{
if(validate()){
amount = inField.getText();
number = ( Float.parseFloat( amount ) );
number = (15*number/100);
output.setText ( Float.toString ( number ) + '$' );
}
else{
// show message for inter valid number or any other
}
}
else if (evt.getSource() == clear )
{
output.setText("$");
inField.setText("");
}
}
boolean validate(){
try{
amount = inField.getText();
number = ( Float.parseFloat( amount ) );
return true;
}
catch(Exception e){
return false;
}
}
If you try to call Float.parseFloat on a String that cannot be converted to a float, it will throw a NumberFormatException. You need to catch this exception.
try {
number = ( Float.parseFloat( amount ) );
number = (15*number/100);
output.setText ( Float.toString ( number ) + '$' );
} catch(NumberFormatException e) {
//code to show error message here
}
Well considering, you'd have to turn the string into an integer to do the math, you could do this:
try {
int number = Ineger.parseInt(inField.getText());
} catch (NumberFormatException e) {
//SHOW WARNING
}
if (Label1.getText().matches("[0-9]+"))
// does Label1 containts numbers.
{
// do math
}
else
{
// warning
Lavbel1.setText("");
}

how to validate a jtextfield to accept only integer numbers [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Restricting JTextField input to Integers
Detecting JTextField “deselect” event
i need to validate a JTextField by allowing the user to input only integer values in it if user enters any char other than numbers a JOptionPane.show messagebox should appear showing that the value entered are incorrect and only integer numbers are allowed. I have coded it for a digit values but i also need to discard the alphabets
public void keyPressed(KeyEvent EVT) {
String value = text.getText();
int l = value.length();
if (EVT.getKeyChar() >= '0' && EVT.getKeyChar() <= '9') {
text.setEditable(true);
label.setText("");
} else {
text.setEditable(false);
label.setText("* Enter only numeric digits(0-9)");
}
}
Instead of using a JFormattedTextField, you may write a custom JTextField with a document that allows only integers. I like formatted fields only for more complex masks...
Take a look.
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;
/**
* A JTextField that accepts only integers.
*
* #author David Buzatto
*/
public class IntegerField extends JTextField {
public IntegerField() {
super();
}
public IntegerField( int cols ) {
super( cols );
}
#Override
protected Document createDefaultModel() {
return new UpperCaseDocument();
}
static class UpperCaseDocument extends PlainDocument {
#Override
public void insertString( int offs, String str, AttributeSet a )
throws BadLocationException {
if ( str == null ) {
return;
}
char[] chars = str.toCharArray();
boolean ok = true;
for ( int i = 0; i < chars.length; i++ ) {
try {
Integer.parseInt( String.valueOf( chars[i] ) );
} catch ( NumberFormatException exc ) {
ok = false;
break;
}
}
if ( ok )
super.insertString( offs, new String( chars ), a );
}
}
}
If you are using NetBeans to build your GUI, you just need to put regular JTextFields in your GUI and in the creation code, you will specify the constructor of IntegerField.
There is a componant for that: formatted textfield:
http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html
Use JFormattedTextField capabilities. Have a look at example.

Robot class java , typing a string issue

I m using the following loop , but its only typing the first charecter and the rest as numbers, any idea ?
import java.awt.*;
import javax.swing.KeyStroke;
public class test {
public static void main(String[] args) throws AWTException
{
Robot r = new Robot();
String s = "Face";
for (int i = 0; i < s.length(); i++)
{
char res = s.charAt(i);
r.keyPress(res);
r.keyRelease(res);
r.delay(1000);
}
}
}
OUTPUT typing : F135
The keyPress/Release methods need an int value that represents the character you want to type. These value are the key code for each character as determined by the KeyEvent.VK_??? variables.
Try:
import java.awt.*;
import java.util.*;
import java.lang.reflect.Field;
import java.awt.event.*;
import javax.swing.*;
public class RobotCharacter
{
public static void main(String[] args)
throws Exception
{
JTextField textField = new JTextField(10);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( textField );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
Robot robot = new Robot();
typeCharacter(robot, "a");
typeCharacter(robot, "b");
typeCharacter(robot, "C");
typeCharacter(robot, "D");
}
public static void typeCharacter(Robot robot, String letter)
{
try
{
boolean upperCase = Character.isUpperCase( letter.charAt(0) );
String variableName = "VK_" + letter.toUpperCase();
Class clazz = KeyEvent.class;
Field field = clazz.getField( variableName );
int keyCode = field.getInt(null);
robot.delay(1000);
if (upperCase) robot.keyPress( KeyEvent.VK_SHIFT );
robot.keyPress( keyCode );
robot.keyRelease( keyCode );
if (upperCase) robot.keyRelease( KeyEvent.VK_SHIFT );
}
catch(Exception e)
{
System.out.println(e);
}
}
}
However, even this won't work for all characters. For example on my keyboard the "%" is above the "5". You can't use VK_PERCENT. The key stroke needed is VK_5 along with a shift. There is no way to know the actual mapping of your keyboard to do this automatically.
So a Robot is not a good way to do this.
The Robot class uses key codes which are defined here: http://docs.oracle.com/javase/1.5.0/docs/api/java/awt/event/KeyEvent.html, not raw characters. You need to call it like this:
r.keyPress(KeyEvent.VK_A);
r.keyRelease(KeyEvent.VK_A);
# camickr I made a small edit to your scipt to accomodate a string rather than a letter . Please find below . It helped me Thanks :) function call : typeCharacter(robot, "thanks");
public static void typeCharacter(Robot robot, String letter)
{
for(int i=0;i<letter.length();i++){
try
{
boolean upperCase = Character.isUpperCase( letter.charAt(i) );
String KeyVal=Character.toString(letter.charAt(i));
String variableName = "VK_" + KeyVal.toUpperCase();
Class clazz = KeyEvent.class;
Field field = clazz.getField( variableName );
int keyCode = field.getInt(null);
robot.delay(1000);
if (upperCase) robot.keyPress( KeyEvent.VK_SHIFT );
robot.keyPress( keyCode );
robot.keyRelease( keyCode );
if (upperCase) robot.keyRelease( KeyEvent.VK_SHIFT );
}
catch(Exception e)
{
System.out.println(e);
}
}
}
You could also load your String to the clipboard and just paste it where ever you want to.
StringSelection selection = new StringSelection("Hello World");
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(selection, selection);
robo.keyPress(KeyEvent.VK_CONTROL);
robo.keyPress(KeyEvent.VK_V);
robo.keyRelease(KeyEvent.VK_V);
robo.keyRelease(KeyEvent.VK_CONTROL);
You can make your life easy if you create a function receiving a string to be typed:
public void keyboardString(final String text) {
if (text != null) {
try {
final Robot robot = new Robot();
for (int i = 0; i < text.length(); i++) {
final char ch = text.charAt(i);
final boolean upperCase = Character.isUpperCase(ch);
final int keyCode = KeyEvent.getExtendedKeyCodeForChar(ch);
robot.delay(10);
if (upperCase) {
robot.keyPress(KeyEvent.VK_SHIFT);
}
robot.keyPress(keyCode);
robot.keyRelease(keyCode);
if (upperCase) {
robot.keyRelease(KeyEvent.VK_SHIFT);
}
}
} catch (final Exception e) {
System.out.println(e);
}
}
}

How to calculate the number of rows (and columns in each row) a text takes in a JTextArea?

After getting interested in the problem presented in the question
I tried to approach it few times and failed, and I do not like that :)
I think if the problem was split into sub issues it might help to solve it.
For simplicity lets assume the JTextArea will not change its size, so we do not need to worry about re-evaluation etc. I think the important issues are:
1.How to calculate the number of rows a certain text takes in a JTextArea?
2.What is the relation between the number of columns in a JTextArea and a number of characters it can fit in a row? So we can calculate row length.
Please find included below the sample code presenting the text area to process:
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TextAreaLines
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel p = new JPanel();
JFrame f = new JFrame();
JTextArea ta = new JTextArea("dadsad sasdasdasdasdasd");
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
ta.setRows(5);
ta.setColumns(5);
p.add(ta);
f.setContentPane(p);
f.setSize(400, 300);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
//BTW the code below prints 1
System.out.println("ta.getLineCount()="+ta.getLineCount());
}
});
}
}
EDIT1: So I have come up with the following code but the problem is that the output is not what you see, i.e
//for input
//JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");
//we have output
//s=alfred abcdefghijk
//s=lmnoprstuwvxyz a
//s=bcdefg
FontMetrics fm = ta.getFontMetrics(ta.getFont());
String text = ta.getText();
List<String> texts = new ArrayList<String>();
String line = "";
//no word wrap
for(int i = 0;i < text.length(); i++)
{
char c = text.charAt(i);
if(fm.stringWidth(line +c) <= ta.getPreferredSize().width)
{
//System.out.println("in; line+c ="+(line + c));
line += c;
}
else
{
texts.add(line);//store the text
line = ""+c;//empty the line, add the last char
}
}
texts.add(line);
for(String s: texts)
System.out.println("s="+s);
What am I doing wrong, what am I forgetting about? There is no word wrap on the text area.
EDIT2: #trashgod This is the output I am getting. Apparent from this is that we have different default fonts. And the problem in fact might be either font or even system dependent. (PS: I am on Win7).
line: Twas brillig and the slithy tovesD
line: id gyre and gimble in the wabe;
line count: 2
preferred: java.awt.Dimension[width=179,height=48]
bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=170.0,h=15.09375]
layout1: java.awt.geom.Rectangle2D$Float[x=0.28125,y=-8.59375,w=168.25,h=11.125]
bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.064453,w=179.0,h=15.09375]
layout2: java.awt.geom.Rectangle2D$Float[x=0.921875,y=-8.59375,w=177.34375,h=11.125]
Compiling in my head what all of you guys are saying I think that the possibly reliable solution might be to hack the way in which the text area sets its text, and take a full control over it. By running the algorithm (above one, please notice, as suggested by #trashgod the '<' was changed to '<=') in the setText of the area.
What got me to think like this... for example in the sample I have provided if you
change text of the textarea to JTextArea ta = new JTextArea("alfred abcdefghijkl\nmnoprstuwvxyz ab\ncdefg"); as it is calculated in my case then it will fit perfectly into the textarea.
EDIT3: This is a kind of solution I quickly hacked, at least now the shown characters and calculated are exactly the same. Can someone else please check it out and let me know, possibly how it works on other machine then Win7? The example below is ready to use you should be able to resize the window and get the printout of lines the same as you see.
import java.awt.BorderLayout;
import java.awt.FontMetrics;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class TextAreaLines
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
JPanel p = new JPanel(new BorderLayout());
JFrame f = new JFrame();
final JTextArea ta = new JTextArea("alfred abcdefghijklmnoprstuwvxyz abcdefg");
ta.addComponentListener(new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
super.componentResized(e);
System.out.println("ta componentResized");
reformatTextAreaText(ta);
}
});
//ta.setWrapStyleWord(true);
ta.setLineWrap(true);
p.add(ta);
f.setContentPane(p);
f.setSize(200, 100);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
private void reformatTextAreaText(JTextArea ta)
{
String text = ta.getText();
//remove all new line characters since we want to control line braking
text = text.replaceAll("\n", "");
FontMetrics fm = ta.getFontMetrics(ta.getFont());
List<String> texts = new ArrayList<String>();
String line = "";
//no word wrap
for(int i = 0; i < text.length(); i++)
{
char c = text.charAt(i);
if(fm.stringWidth(line + c) <= ta.getPreferredSize().width)
{
//System.out.println("in; line+c ="+(line + c));
line += c;
}
else
{
texts.add(line);//store the text
line = "" + c;//empty the line, add the last char
}
}
texts.add(line);
//print out of the lines
for(String s : texts)
System.out.println("s=" + s);
//build newText for the
String newText = "";
for(String s : texts)
newText += s + "\n";
ta.setText(newText);
}
});
}
}
Thanks in advance.
What am I doing wrong, what am I forgetting about?
Nothing, really. I modified your example to use "<=" on the width and to highlight a few features:
FontMetrics notes, "the advance of a String is not necessarily the sum of the advances of its characters measured in isolation…"
The preferred size of the text component matches the metric bounds pretty well for the widest line. This varies by font due to proportional spacing.
TextLayout shows even tighter bounds, but note the "baseline-relative coordinates."
The getLineCount() method counts line.separator delimited lines, not wrapped lines.
line: Twas brillig and the slithy toves
line: Did gyre and gimble in the wabe;
line count: 2
preferred: java.awt.Dimension[width=207,height=48]
bounds1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=205.0,h=15.310547]
layout1: java.awt.geom.Rectangle2D$Float[x=0.0,y=-10.0,w=200.0,h=13.0]
bounds2: java.awt.geom.Rectangle2D$Float[x=0.0,y=-12.568359,w=207.0,h=15.310547]
layout2: java.awt.geom.Rectangle2D$Float[x=1.0,y=-10.0,w=205.0,h=13.0]
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
/** #see http://stackoverflow.com/questions/5979795 */
public class TextAreaLine {
private static final String text1 =
"Twas brillig and the slithy toves\n";
private static final String text2 =
"Did gyre and gimble in the wabe;";
private static final JTextArea ta = new JTextArea(text1 + text2);
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
display();
}
});
}
static void display() {
JFrame f = new JFrame();
ta.setWrapStyleWord(false);
ta.setLineWrap(false);
ta.setRows(3);
f.add(ta);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.setVisible(true);
FontMetrics fm = ta.getFontMetrics(ta.getFont());
List<String> texts = new ArrayList<String>();
Dimension d = ta.getPreferredSize();
String text = ta.getText();
String line = "";
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c != '\n') {
if (fm.stringWidth(line + c) <= d.width) {
line += c;
} else {
texts.add(line);
line = "" + c;
}
}
}
texts.add(line);
for (String s : texts) {
System.out.println("line: " + s);
}
System.out.println("line count: " + ta.getLineCount());
System.out.println("preferred: " + d);
System.out.println("bounds1: " + fm.getStringBounds(text1, null));
FontRenderContext frc = new FontRenderContext(null, false, false);
TextLayout layout = new TextLayout(text1, ta.getFont(), frc);
System.out.println("layout1: " + layout.getBounds());
System.out.println("bounds2: " + fm.getStringBounds(text2, null));
layout = new TextLayout(text2, ta.getFont(), frc);
System.out.println("layout2: " + layout.getBounds());
}
}
One thing you can do is use FontMetrics. I wrote some code for splitting JTextAreas up at certain line numbers. The setup code looked like:
Graphics2D g = (Graphics2D) g2;
FontMetrics m = g.getFontMetrics();
int lineHeight = m.getHeight();
This will tell you how tall a line of text is.
Unfortunately, letters have different widths in most fonts. But, you can use the following code to determine the width of a String.
int width = m.getStringBounds("Some String", g).getWidth();
I know this doesn't fully answer your question, but I hope it helps.
If you aren't using word wrap, here is the general algorithm you could use: (in the paint component method)
String text[] = getText().split("\n");
String newText = "";
for (String line: text) {
newText = line + "| " + line.length() + "\n";
}
setText(newText);
That's the general idea. Not sure how well it would work out. Let me know if you try it.
Not sure if this helps but you need to set the width of the text area so that the view knows when to wrap the text. Once you set the size you can determine the preferred height. When you know the preferred height you can use the font metrice line height to determine the total number of lines including the wrapped lines if any.
import java.awt.*;
import javax.swing.*;
public class TextAreaPreferredHeight extends JFrame
{
public TextAreaPreferredHeight()
{
JTextArea textArea = new JTextArea();
textArea.setText("one two three four five six seven eight nine ten");
textArea.setLineWrap( true );
textArea.setWrapStyleWord( true );
FontMetrics fm = textArea.getFontMetrics( textArea.getFont() );
int height = fm.getHeight();
System.out.println("000: " + textArea.getPreferredSize());
textArea.setSize(100, 1);
System.out.println("100: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
textArea.setSize(200, 1);
System.out.println("200: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
textArea.setSize(300, 1);
System.out.println("300: " + textArea.getPreferredSize());
System.out.println("lines : " + textArea.getPreferredSize().height / height);
add(textArea);
pack();
setVisible(true);
}
public static void main(String[] args)
{
new TextAreaPreferredHeight();
}
}
I've seen people using TextLayout for something like this.
Okay, I had written a program that you could load in an image, and it would convert it to ascii art. I wanted it to automatically make the text area the right aspect ration based on the image that was input.
However, I could never get it to work quite right. I gave up and remarked out my attempts, here is the snippet of what I tried. What I ended up doing was just having a textarea that sometimes didn't fill all the way.
//Graphics fontG = this.textBox4.CreateGraphics();
//fontG.PageUnit = GraphicsUnit.Point;
//SizeF fontSize = fontG.MeasureString(RowData, this.textBox4.Font,(SizeF) this.textBox4.ClientSize);
sb.AppendLine();
RowData = "";
//fontH += fontSize.Height + 1.2F;
//fontW = (int) fontSize.Width;
There was a similar question for android yesterday. The way I see it need to be solved is by an iterative approach:
Get JTextArea width
Use the FontMetrics to get the width of a string, as jjnguy suggested
split your string in words.
Start measuring the witdh of a string adding one word at a time until you reach the area width. Save that number.
Once you reached it, start a new iteration, adding one word at a time (beginning with the number saved).
The numbers of lines will be the number of iterations.
Unfortunately, row length will depend on the font and the particular string (not every character as the same width). You can count the number of characters in the words in each iteration, and return an array of lengths or a length average.
This is the related android question: How to find android TextView number of characters per line?
I found that counting the number of lines by text analysis only led to nothing. Instead my solution was calculating it from the preferred size of the text area ...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
public class LineNumbering extends JFrame {
private static final long serialVersionUID = 1L;
private static final Font fixedFont = new Font("Monospaced", Font.PLAIN, 12);
private static JTextArea jta;
private static JTextArea lines;
private static String lineSeparator = "\n";
private static int numRows = 10;
private static int numCols = 30;
public LineNumbering() {
super("Line Numbering Example");
}
public static void createAndShowGUI() {
JFrame frame = new LineNumbering();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane jsp = new JScrollPane();
jta = new JTextArea(numRows, numCols);
jta.setFont(fixedFont);
jta.setLineWrap(true);
jta.setWrapStyleWord(true);
lines = new JTextArea(numRows, 3);
lines.setEditable(false);
lines.setFocusable(false);
lines.setEnabled(false);
lines.setFont(fixedFont);
lines.setBackground(Color.LIGHT_GRAY);
lines.setDisabledTextColor(Color.BLACK);
// do initial line numbering
for (int i = 1; i <= lines.getRows(); i++) {
lines.append(i + System.getProperty("line.separator"));
}
final class DebugCaretListener implements CaretListener {
int rowHeight = jta.getFontMetrics(jta.getFont()).getHeight();
/**
* #return total of lines showed in the text area
*/
private int getTotalLinesInView() {
int insetsTotalHeight = jta.getInsets().top + jta.getInsets().bottom;
return (jta.getPreferredSize().height - insetsTotalHeight) / rowHeight;
}
/**
* #return text with line numbers
*/
public String getText() {
StringBuffer text = new StringBuffer();
int totalLines = getTotalLinesInView();
System.out.println("totalLines : " + totalLines);
for (int i = 1; i <= totalLines; i++) {
text.append(i);
if (i < totalLines) {
text.append(lineSeparator);
}
}
return text.toString();
}
/**
* <p>
* Reset line numbers on caret event. Since the total number of
* lines is calculated from preferred size of text area, we do this
* on an event that occurred after repainting of the text area.
* </p>
* (non-Javadoc)
*
* #see javax.swing.event.CaretListener#caretUpdate(javax.swing.event.CaretEvent)
*/
#Override
public void caretUpdate(CaretEvent e) {
int totalLines = getTotalLinesInView();
System.out.println("totalLines : " + totalLines);
if (totalLines >= numRows) {
lines.setText(getText());
}
}
}
jta.addCaretListener(new DebugCaretListener());
jsp.getViewport().add(jta);
jsp.setRowHeaderView(lines);
jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JPanel textPanel = new JPanel();
textPanel.add(jsp);
JPanel contentPanel = new JPanel();
contentPanel.add(textPanel);
frame.setContentPane(contentPanel);
contentPanel.setOpaque(true);
frame.pack();
frame.setPreferredSize(new Dimension(500, 500));
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}

Categories