如何在不使用JavaFX更改其值的情况下格式化TextField中的字符串 [英] How to format a string in a TextField without changing it's value with JavaFX
问题描述
我试图更改TextField
的值,仅用于显示.即-当用户尝试输入电话号码时,他们只输入数字,而当他们离开该字段时,它将显示格式,而不会更改该字段中的数据.
假设我有一个电话号码TextField
,并且只能输入数字,最多10个字符:
2085551212
我可以使用UnaryOperator
通过TextFormatter
处理.
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
int maxlength = 14;
if(change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
System.out.println(change);
if (change.getControlText().length() + change.getText().length() >= maxlength) {
int maxPos = maxlength - change.getControlText().length();
change.setText(change.getText().substring(0, maxPos));
}
String text = change.getText();
for (int i = 0; i < text.length(); i++)
if (!Character.isDigit(text.charAt(i)))
return null;
return change;
}
};
但是我想将值格式化为10个字符长(当!= 10时可能未格式化):
(208)555-1212
当我使用TextFormatter
对其进行格式化时,它会将字符串的值更改为(208)555-1212.我们仅将数字存储在数据库2085551212中.
我尝试使用StringConverter
进行此操作.但是我无法使其工作.在toString()
方法中,我去除了格式,但是当我这样做时,我的TextField
不显示.
StringConverter<String> formatter = new StringConverter<String>() {
@Override
public String fromString(String string) {
System.out.println("fromString(): before = " + string);
if (string.length() == 14) {
System.out.println("fromString(): after = " + string);
return string;
} else if (string.length() == 10 && string.indexOf('-') == -1) {
String result = String.format("(%s) %s-%s", string.substring(0, 3), string.substring(3, 6),
string.substring(6, 10));
System.out.println("fromString(): after = " + result);
return result;
} else {
return null;
}
}
@Override
public String toString(String object) {
System.out.println("toString(): before = " + object);
if(object == null) {
return "";
}
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(object);
object = m.replaceAll("");
System.out.println("toString(): after = " + object);
return object;
}
};
我绑定到了一个我认为会起作用的TextField
:
txtPhone.setTextFormatter(new TextFormatter<String>(formatter, null, filter));
t2.textProperty().bindBidirectional(foo.fooPropertyProperty(), formatter); //I was just testing to see the results in another textfield to see if it would work.
所以我很茫然.我本质上只想允许数字,然后当用户离开字段时以格式化的方式显示值-而不实际更改进入数据库的字符串值.
您在转换器中混淆了toString()
和fromString()
方法的用途. toString()
会将文本编辑器的value属性转换为显示的文本,而不是相反.尝试用这些方法切换代码,它应该可以工作.
失去焦点后,文本字段不显示任何内容的原因是因为fromString()
方法被调用并返回null(来自else
子句).这会将null
提交给编辑器的value属性.值更改属性通过调用toString(null)
来更新显示的文本(textProperty),这会将编辑器的text属性更改为空字符串.
编辑
下面是我的测试代码,它是注释中讨论的后续内容.我重复使用了大量原始代码.我创建了一个FXML JavaFX项目,并在FXML文件中定义了TextField
和Label
. TextField
接受用户输入并设置其格式. Label
显示应该进入数据库的文本格式化程序的值(仅数字).可通过调用formatter.valueProperty().get()
来访问该值.希望对您有帮助.
import java.net.URL;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
public class FXMLDocumentController implements Initializable {
// label displays phone number containing only digits (for database)
@FXML private Label label;
/* field displays formatted text (XXX)-XXX-XXXX after user types
10 digits and presses Enter or if the field looses focus */
@FXML private TextField field;
@Override
public void initialize(URL url, ResourceBundle rb) {
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
if (!change.isContentChange()) {
/* nothing is added or deleted but change must be returned
* as it contains selection info and caret position
*/
return change;
}
int maxlength = 14;
if (change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
if (change.getControlNewText().length() > maxlength
|| change.getText().matches("\\D+")) {
// invalid input. Cancel the change
return null;
}
return change;
}
};
StringConverter<String> converter = new StringConverter<String>() {
// updates displayed text from commited value
@Override
public String toString(String commitedText) {
if (commitedText == null) {
// don't change displayed text
return field.getText();
}
if (commitedText.length() == 10 && !commitedText.matches("\\D+")) {
return String.format("(%s) %s-%s", commitedText.substring(0, 3), commitedText.substring(3, 6),
commitedText.substring(6, 10));
} else {
/* Commited text can be either null or 10 digits.
* Nothing else is allowed by fromString() method unless changed directly
*/
throw new IllegalStateException(
"Unexpected or incomplete phone number value: " + commitedText);
}
}
// commits displayed text to value
@Override
public String fromString(String displayedText) {
// remove formatting characters
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(displayedText);
displayedText = m.replaceAll("");
if (displayedText.length() != 10) {
// user is not done typing the number. Don't commit
return null;
}
return displayedText;
}
};
TextFormatter<String> formatter = new TextFormatter<String>(converter, "1234567890", filter);
field.setTextFormatter(formatter);
label.textProperty().bind(formatter.valueProperty());
}
}
I am trying to change the value of a TextField
for display only. Ie - when users attempt to enter phone number, they only enter the digits and when they leave the field, it displays formatted without changing the data in the field.
Let's say I have a TextField
for a phone number and it should allow digits only, maximum of 10 characters:
2085551212
I can handle that with a TextFormatter
using a UnaryOperator
.
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
int maxlength = 14;
if(change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
System.out.println(change);
if (change.getControlText().length() + change.getText().length() >= maxlength) {
int maxPos = maxlength - change.getControlText().length();
change.setText(change.getText().substring(0, maxPos));
}
String text = change.getText();
for (int i = 0; i < text.length(); i++)
if (!Character.isDigit(text.charAt(i)))
return null;
return change;
}
};
However I would like to format the value to be when it's 10 characters long (likely unformatted when != 10):
(208) 555-1212
When I use a TextFormatter
to format it, it changes the value of the string to (208) 555-1212. We store only the digits in the database 2085551212.
I attempted this with a StringConverter
. But I couldn't make it work. In the toString()
method I strip out the formatting, however when I do that my TextField
doesn't display.
StringConverter<String> formatter = new StringConverter<String>() {
@Override
public String fromString(String string) {
System.out.println("fromString(): before = " + string);
if (string.length() == 14) {
System.out.println("fromString(): after = " + string);
return string;
} else if (string.length() == 10 && string.indexOf('-') == -1) {
String result = String.format("(%s) %s-%s", string.substring(0, 3), string.substring(3, 6),
string.substring(6, 10));
System.out.println("fromString(): after = " + result);
return result;
} else {
return null;
}
}
@Override
public String toString(String object) {
System.out.println("toString(): before = " + object);
if(object == null) {
return "";
}
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(object);
object = m.replaceAll("");
System.out.println("toString(): after = " + object);
return object;
}
};
I bound to a TextField
like this which I assumed would work:
txtPhone.setTextFormatter(new TextFormatter<String>(formatter, null, filter));
t2.textProperty().bindBidirectional(foo.fooPropertyProperty(), formatter); //I was just testing to see the results in another textfield to see if it would work.
So I am at a loss. I essentially want to allow only digits and then when the user leaves the field present the value in a formatted way - without actually changing the string value that goes to the database.
You are confusing the purpose of toString()
and fromString()
methods with each other in your converter. toString()
converts the value property of your text editor to the displayed text, not the other way around. Try switching the code in these methods and it should work.
The reason why your text field does not display anything after loosing focus is because fromString()
method is called and returns null (from the else
clause). This commits null
to the value property of your editor. The change in value property updates the displayed text (textProperty) by calling toString(null)
which changes the text property of your editor to an empty string.
EDIT
Below is my test code that is a follow-up to the discussion in the comments. I reused a large amount of your original code. I created an FXML JavaFX project and defined TextField
and Label
in FXML file. The TextField
accepts user's input and formats it. The Label
displays value of the text formatter (only digits) that should go to the database. The value is accessible by calling formatter.valueProperty().get()
. I hope it helps.
import java.net.URL;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TextFormatter;
import javafx.util.StringConverter;
public class FXMLDocumentController implements Initializable {
// label displays phone number containing only digits (for database)
@FXML private Label label;
/* field displays formatted text (XXX)-XXX-XXXX after user types
10 digits and presses Enter or if the field looses focus */
@FXML private TextField field;
@Override
public void initialize(URL url, ResourceBundle rb) {
UnaryOperator<TextFormatter.Change> filter = new UnaryOperator<TextFormatter.Change>() {
@Override
public TextFormatter.Change apply(TextFormatter.Change change) {
if (!change.isContentChange()) {
/* nothing is added or deleted but change must be returned
* as it contains selection info and caret position
*/
return change;
}
int maxlength = 14;
if (change.getControlText().indexOf('(') == -1) {
maxlength = 10;
}
if (change.getControlNewText().length() > maxlength
|| change.getText().matches("\\D+")) {
// invalid input. Cancel the change
return null;
}
return change;
}
};
StringConverter<String> converter = new StringConverter<String>() {
// updates displayed text from commited value
@Override
public String toString(String commitedText) {
if (commitedText == null) {
// don't change displayed text
return field.getText();
}
if (commitedText.length() == 10 && !commitedText.matches("\\D+")) {
return String.format("(%s) %s-%s", commitedText.substring(0, 3), commitedText.substring(3, 6),
commitedText.substring(6, 10));
} else {
/* Commited text can be either null or 10 digits.
* Nothing else is allowed by fromString() method unless changed directly
*/
throw new IllegalStateException(
"Unexpected or incomplete phone number value: " + commitedText);
}
}
// commits displayed text to value
@Override
public String fromString(String displayedText) {
// remove formatting characters
Pattern p = Pattern.compile("[\\p{Punct}\\p{Blank}]", Pattern.UNICODE_CHARACTER_CLASS);
Matcher m = p.matcher(displayedText);
displayedText = m.replaceAll("");
if (displayedText.length() != 10) {
// user is not done typing the number. Don't commit
return null;
}
return displayedText;
}
};
TextFormatter<String> formatter = new TextFormatter<String>(converter, "1234567890", filter);
field.setTextFormatter(formatter);
label.textProperty().bind(formatter.valueProperty());
}
}
这篇关于如何在不使用JavaFX更改其值的情况下格式化TextField中的字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!