在 JFormattedTextField 中删除掩码时遇到问题

Posted

技术标签:

【中文标题】在 JFormattedTextField 中删除掩码时遇到问题【英文标题】:trouble with deleting in masks in a JFormattedTextField 【发布时间】:2012-07-29 23:24:34 【问题描述】:

我在使用 JFormattedTextField 中的掩码时遇到问题

我知道它用空格替换无效字符, 或您通过 setPlaceholderCharacter 定义的任何内容, 但我需要它做的是允许删除或退格, 并且不要插入空格来代替我删除的字符 只要掩码中允许字符串的其余部分。

例如,使用掩码:*#*****,则字符串 "12 abc" 有效。 如果将光标放在 b 和 c 字符之间,然后按退格键,我需要它来删除 b,从而产生"12 ac"。相反,它会删除它并添加一个空格,变成:"12 a c"

下面是一个简单的代码示例来演示。

如果有解决此问题的任何想法或示例,我将不胜感激。


public class testFrame extends javax.swing.JFrame 

    public testFrame() 

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        getContentPane().setLayout(new java.awt.FlowLayout());

        setMinimumSize(new Dimension(300,150));

        java.awt.Button closeButton = new java.awt.Button();
        JFormattedTextField maskTextField = new JFormattedTextField();
        maskTextField.setMinimumSize(new Dimension(100,30));

        getContentPane().add(maskTextField);

        closeButton.setLabel("close");
        closeButton.addActionListener(new java.awt.event.ActionListener() 
            public void actionPerformed(java.awt.event.ActionEvent evt) 
                System.exit(0);
            
        );

        getContentPane().add(closeButton);

        try 
            MaskFormatter someMask = new MaskFormatter("*#****");
            DefaultFormatterFactory formatterFactory 
                = new DefaultFormatterFactory(someMask);
            maskTextField.setFormatterFactory(formatterFactory);
         catch (ParseException ex) 
            ex.printStackTrace();
        
        maskTextField.setText("12 abc");

        pack();

    

    public static void main(String args[]) 
        java.awt.EventQueue.invokeLater(new Runnable() 

            public void run() 
                new testFrame().setVisible(true);
            
        );
    


更新代码以反映下面的答案。我添加了第二个字段,以便您可以查看有无修复的行为。也是一个小修复,我调整了窗口的大小并将其在屏幕中居中以使其更友好。

公共类 testFrame 扩展 javax.swing.JFrame

public testFrame() 
    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setMinimumSize(new java.awt.Dimension(300, 200));
    getContentPane().setLayout(new java.awt.FlowLayout());


    JFormattedTextField maskTextField = new JFormattedTextField();
    maskTextField.setMinimumSize(new Dimension(100,30));
    getContentPane().add(maskTextField);


    JFormattedTextField maskTextField2 = new JFormattedTextField();
    maskTextField2.setMinimumSize(new Dimension(100,30));
    getContentPane().add(maskTextField2);

    java.awt.Button closeButton = new java.awt.Button();
    closeButton.setLabel("close");
    closeButton.addActionListener(new java.awt.event.ActionListener() 

        public void actionPerformed(java.awt.event.ActionEvent evt) 
            System.exit(0);
        
    );

    getContentPane().add(closeButton);

    try 

        MaskFormatter someMask = new MaskFormatter("*#****");
        DefaultFormatterFactory formatterFactory = 
            new DefaultFormatterFactory(someMask);
        maskTextField.setFormatterFactory(formatterFactory);

        MaskFormatter someMask2 = new MaskFormatter("*#****");
        DefaultFormatterFactory formatterFactory2 = 
            new DefaultFormatterFactory(someMask2);
        maskTextField2.setFormatterFactory(formatterFactory2);

     catch (ParseException ex) 
        ex.printStackTrace();
    

    maskTextField.setText("12 abc");
    maskTextField2.setText("12 abc");

    // added per suggestion below
    if (maskTextField.getFormatter() instanceof DefaultFormatter) 
         DefaultFormatter f = (DefaultFormatter) maskTextField.getFormatter();
         f.setAllowsInvalid(true);

         // options are: 
         // JFormattedTextField.COMMIT
         // JFormattedTextField.COMMIT_OR_REVERT  --> default
         // JFormattedTextField.REVERT
         // JFormattedTextField.PERSIST
         maskTextField.setFocusLostBehavior(JFormattedTextField.PERSIST);
     
    pack();
    this.setLocationRelativeTo(null);



public static void main(String args[]) 
    java.awt.EventQueue.invokeLater(new Runnable() 

        public void run() 
            new testFrame().setVisible(true);
        
    );

【问题讨论】:

听起来有点误解:掩码是具有固定长度的东西,每个位置由有效字符或占位符填充。在删除字符时,它被 placeHolder 替换,在您的示例中,这是默认的空格。因此,第一个空间与第二个空间在语义上是不同的 :-) 将 placeHolder 更改为其他内容,以查看差异。由于您的要求似乎类似于任何字符到一个最大值后的 4 个字符,因此您必须按照@DuncanJones 的建议实现自定义格式化程序 我不介意它是固定长度的,我想我只是有一个他们没有满足的特殊用例。例如,如果您的掩码是 (###) ###-### 并且您尝试输入 (123) 456-7890 并意外输入了两个 2,即 (122) 345-6789,则标准掩码行为似乎意味着如果您删除前 2 个,它将替换一个空格(占位符)给您(12)345-6789。我需要它来删除字符并在字符串末尾添加占位符。但我想这不是其他人需要的,所以我必须制作一个自定义格式化程序:( 啊...我明白了,感谢您的澄清 【参考方案1】:

首先,感谢您发布一个体面的工作示例。

似乎DefaultFormatter 是您的屏蔽文本字段使用的格式化程序。我发现我可以通过以下方式允许临时无效编辑:

if (maskTextField.getFormatter() instanceof DefaultFormatter) 
  DefaultFormatter f = (DefaultFormatter) maskTextField.getFormatter();
  f.setAllowsInvalid(true);          

希望这足以让您入门。尽管请注意,如果您在字段中存在无效值时更改焦点,则此快速修复具有完全擦除文本字段内容的有趣行为。这似乎与 JFormattedTextField 的 JavaDoc 相反,后者表明默认行为是 COMMIT_OR_REVERT

【讨论】:

我认为这绝对是一个很好的提示。只需添加一件事,如果您放入 maskTextField.setFocusLostBehavior(JFormattedTextField.PERSIST);它似乎使结果不清楚。谢谢! 测试了一些,我意识到这并不能真正解决问题。添加 setAllowsInvalid(true) 使其忽略掩码,这不是我想要的。我希望的是,如果删除操作不会使掩码无效(通过移动已删除字符右侧的字符),那么删除或退格应该只是这样做,而不是添加空格。我猜如果删除操作确实使掩码无效,那么它应该不起作用。 如果掩码是固定长度的(大概大多数/全部都是?),那么删除 always 不会使掩码无效吗?要么您暂时允许违反掩码规则,要么您将始终以这种奇怪的空间行为告终。也许您可以考虑编写自己的 AbstractFormatter 子类? 好问题。不总是。如果您的掩码是###-***** 并且您在连字符后删除了一个字符,则掩码仍然有效,因为右侧的空字符(由移位导致)将是一个空格(或其他您定义为占位符)并且 *.但是如果字符在前 3 个位置,那么是的,它会使它无效,所以我可以看到在这种情况下使用占位符会更好。这就是为什么我想让验证代码在盲目地添加占位符之前检查删除对整体掩码的影响。 (逻辑似乎在 MaskFormatter 中的私有 canReplace(ReplaceHolder rh) 方法中进行了深入的硬编码,该方法本身由 DefaultFormatter 中的私有 DefaultDocumentFilter 调用)。【参考方案2】:

只是一个想法 - 绝对不适合生产,并且在一般情况下很可能不可能:您可以尝试包装默认 documentFilter 并在调用委托之前/之后调用自定义检查/操作。

这是一个似乎适用于您问题中的特定示例的 sn-p:

public static class MyMaskFormatter extends MaskFormatter 

    DocumentFilter filter;

    /**
     * @param string
     * @throws ParseException
     */
    public MyMaskFormatter(String string) throws ParseException 
        super(string);
    

    @Override
    protected DocumentFilter getDocumentFilter() 
        if (filter == null) 
            filter = new MyDocumentFilter(super.getDocumentFilter());
        
        return filter;
    

    public class MyDocumentFilter extends DocumentFilter 

        DocumentFilter delegate;

        MyDocumentFilter(DocumentFilter delegate) 
            this.delegate = delegate;
        

        @Override
        public void remove(FilterBypass fb, int offset, int length)
                throws BadLocationException 
            String toRemove = fb.getDocument().getText(offset, length);
            delegate.remove(fb, offset, length);
            String replaced = fb.getDocument().getText(offset, length);
            if (replaced.charAt(0) == getPlaceholderCharacter() && 
                toRemove.charAt(0) != getPlaceholderCharacter()    ) 
                int sublength = fb.getDocument().getLength() - offset;
                String text = fb.getDocument().getText(offset, sublength);
                text = text.substring(1) + text.charAt(0);
                replace(fb, offset, sublength, text, null);
                getFormattedTextField().setCaretPosition(offset);
                //getNavigationFilter().setDot(fb, offset, null);
            
        

        @Override
        public void insertString(FilterBypass fb, int offset,
                String string, AttributeSet attr)
                throws BadLocationException 
            delegate.insertString(fb, offset, string, attr);
        

        @Override
        public void replace(FilterBypass fb, int offset, int length,
                String text, AttributeSet attrs)
                throws BadLocationException 
            delegate.replace(fb, offset, length, text, attrs);
        

    


【讨论】:

以上是关于在 JFormattedTextField 中删除掩码时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

在 JFormattedTextField 中禁用哔声

JFormattedTextField 仅返回默认值

具有任意小数位数的 JFormattedTextfield

如何从 JformattedTextfield 检索货币格式值

JFormattedTextField 问题

Double的JFormattedTextField仍然需要字符[重复]