I have a Problem with the JFormattedTextField (I use it as a base class for all of our text fields).
Today I tried to add a document filter to the document of this field which works just fine, but only as long it doesnt have a formatter factory set.
The Problem is, when the formatter factory is set (in my case the default classes) and processFocusEvent is called following happens (JFormattedTextField.java:595):
// if there is a composed text, process it first
if ((ic != null) && composedTextExists) {
ic.endComposition();
EventQueue.invokeLater(focusLostHandler);
} else {
focusLostHandler.run();
}
}
else if (!isEdited()) {
// reformat
setValue(getValue(), true, true);
}
then setValue() is called (JFormattedTextField.java:757):
private void setValue(Object value, boolean createFormat, boolean firePC) {
Object oldValue = this.value;
this.value = value;
if (createFormat) {
AbstractFormatterFactory factory = getFormatterFactory();
AbstractFormatter atf;
if (factory != null) {
atf = factory.getFormatter(this);
}
else {
atf = null;
}
setFormatter(atf);
}
else {
// Assumed to be valid
setEditValid(true);
}
setEdited(false);
if (firePC) {
firePropertyChange("value", oldValue, value);
}
}
As you can see if there is a factory it will try to "refresh" the formatter
(JFormattedTextField.java:439):
protected void setFormatter(AbstractFormatter format) {
AbstractFormatter oldFormat = this.format;
if (oldFormat != null) {
oldFormat.uninstall();
}
setEditValid(true);
this.format = format;
if (format != null) {
format.install(this);
}
setEdited(false);
firePropertyChange("textFormatter", oldFormat, format);
}
And here is the real Problem I have (JFormattedTextField$AbstractFormatter.class:950):
public void uninstall() {
if (this.ftf != null) {
installDocumentFilter(null);
this.ftf.setNavigationFilter(null);
this.ftf.setFormatterActions(null);
}
}
Here it destroys my document filter, I know that the formatter holds the documentFilter normally, but was it really intended to work that way? The document should be the object handling its filter (imho) not the formatter. Is there a way to go around it without the use of a specialized formatter subclass?
EXAMPLE CODE: (as requested :) )
package jftf;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultFormatter;
import javax.swing.text.DocumentFilter;
/**
* @author Pawel Miler
*/
public class JFormattedTextFieldExample {
private Container container;
private JFormattedTextField workingTextField;
private JFormattedTextField brokenTextField;
private DocumentFilter documentFilter;
public static void main(String[] args) {
new JFormattedTextFieldExample();
}
public JFormattedTextFieldExample() {
initializeDocumentFilter();
initializeTextFields();
initializeGui();
}
private void initializeDocumentFilter(){
documentFilter = new UppercaseDocumentFilter();
}
private void initializeTextFields() {
workingTextField = createTextField(false);
addDocumentFilter(workingTextField);
brokenTextField = createTextField(true);
addDocumentFilter(workingTextField);
}
private JFormattedTextField createTextField(boolean createFormatter) {
JFormattedTextField textField;
textField = createFormatter ? new JFormattedTextField(new DefaultFormatter()) : new JFormattedTextField();
return textField;
}
private void addDocumentFilter(JTextField textField) {
((AbstractDocument) textField.getDocument()).setDocumentFilter(documentFilter);
}
private void initializeGui() {
container = createFrame();
container.setLayout(new FlowLayout());
Dimension dimension = new Dimension(80, 20);
brokenTextField.setPreferredSize(dimension);
container.add(brokenTextField);
workingTextField.setPreferredSize(dimension);
container.add(workingTextField);
}
private Container createFrame() {
JFrame frame = new JFrame("JFormattedTextField example");
frame.setSize(200, 70);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
return frame.getContentPane();
}
public class UppercaseDocumentFilter extends DocumentFilter {
public void insertString(FilterBypass filterBypass, int offset, String text, AttributeSet attr) throws BadLocationException {
super.insertString(filterBypass, offset, text.toUpperCase(), attr);
}
public void replace(DocumentFilter.FilterBypass filterBypass, int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
super.replace(filterBypass, offset, length, text.toUpperCase(), attrs);
}
}
}
Both text fields should have the same document filter, but type in one and it always will get capital letters the other one does not.
Current Solution: (the workaround I wrote moments ago implemented in a subclass of JFormattedTextField, I need tu use the flag in the case the formatter has a documentfilter too, you can't use both at the same time, but I'm not really happy needing one at all)
public boolean isPreserveDocumentFilter() {
return preserveDocumentFilter;
}
public void setPreserveDocumentFilter(boolean preserveDocumentFilter) {
this.preserveDocumentFilter = preserveDocumentFilter;
}
/**
* We need to override if we want to use a documentFilter with DefaultFormatter implementation.
* For more info see: <a href="http://stackoverflow.com/questions/20074778/jformattedtextfield-destroys-documentfilter">info</a>
*/
@Override
protected void setFormatter(AbstractFormatter format) {
Document doc = this.getDocument();
DocumentFilter filter = null;
if (preserveDocumentFilter) {
if ( doc instanceof AbstractDocument ) {
filter = ((AbstractDocument) doc).getDocumentFilter();
}
}
super.setFormatter(format);
if ( filter != null ) {
((AbstractDocument) doc).setDocumentFilter(filter);
}
}