java - 为什么 JTextComponent.setText(String) 不规范化行结尾?

标签 java swing jtextcomponent

最近引起我注意的是,Java 文本组件使用换行符(LF、\n、0x0A)在内部表示和解释换行符。这让我感到非常惊讶,并提出了我的假设,即在问号下使用 System.getProperty('line.separator') everywhere 是一个很好的做法。

似乎无论何时处理文本组件,在使用上述属性时都应该非常小心,因为如果使用 JTextComponent.setText(String),您最终可能会得到一个组件包含不可见的换行符(例如 CR)。这可能看起来并不那么重要,除非文本组件的内容可以保存到文件中。如果您使用所有文本组件提供的方法将文本保存并打开到一个文件中,那么您隐藏的换行符会在重新打开文件时突然出现在该组件中。原因似乎是 JTextComponent.read(...) 方法进行了规范化。

那么为什么 JTextComponent.setText(String) 不规范化行尾呢?或者任何其他允许在文本组件中修改文本的方法?在处理文本组件时使用 System.getProperty('line.separator') 是一种好的做法吗?这是一个好的做法吗?

一些代码可以正确看待这个问题:

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

public class TextAreaTest extends JFrame {

    private JTextArea jtaInput;
    private JScrollPane jscpInput;
    private JButton jbSaveAndReopen;

    public TextAreaTest() {
        super();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setTitle("Text Area Test");
        GridBagLayout layout = new GridBagLayout();
        setLayout(layout);        

        jtaInput = new JTextArea();
        jtaInput.setText("Some text followed by a windows newline\r\n"
                + "and some more text.");
        jscpInput = new JScrollPane(jtaInput);
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0; constraints.gridy = 0;
        constraints.gridwidth = 2;
        constraints.weightx = 1.0; constraints.weighty = 1.0;
        constraints.fill = GridBagConstraints.BOTH;
        add(jscpInput, constraints);

        jbSaveAndReopen = new JButton(new SaveAndReopenAction());
        constraints = new GridBagConstraints();
        constraints.gridx = 1; constraints.gridy = 1;
        constraints.anchor = GridBagConstraints.EAST;
        constraints.insets = new Insets(5, 0, 2, 2);
        add(jbSaveAndReopen, constraints);

        pack();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                TextAreaTest tat = new TextAreaTest();
                tat.setVisible(true);
            }
        });
    }

    private class SaveAndReopenAction extends AbstractAction {

        private File file = new File("text-area-test.txt");

        public SaveAndReopenAction() {
            super("Save and Re-open");
        }

        private void saveToFile() 
                throws UnsupportedEncodingException, FileNotFoundException,
                IOException {

            Writer writer = null;
            try {
                writer = new OutputStreamWriter(
                        new FileOutputStream(file), "UTF-8");
                TextAreaTest.this.jtaInput.write(writer);
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }

        private void openFile() 
                throws UnsupportedEncodingException, IOException {
            Reader reader = null;
            try {
                reader = new InputStreamReader(
                        new FileInputStream(file), "UTF-8");
                TextAreaTest.this.jtaInput.read(reader, file);
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }

        public void actionPerformed(ActionEvent e) {
            Throwable exc = null;
            try {
                saveToFile();
                openFile();
            } catch (UnsupportedEncodingException ex) {
                exc = ex;
            } catch (FileNotFoundException ex) {
                exc = ex;
            } catch (IOException ex) {
                exc = ex;
            }
            if (exc != null) {
                JOptionPane.showConfirmDialog(
                        TextAreaTest.this, exc.getMessage(), "An error occured",
                        JOptionPane.DEFAULT_OPTION, JOptionPane.ERROR_MESSAGE);
            }
        }        
    }
}

添加新文本行后该程序在我的 Windows 计算机上保存的内容的示例(为什么是单个 CR?o_O):

enter image description here

Edit01

我在 Netbeans IDE 中运行/调试了它,它在 Windows 7 上使用 JDK1.7u15 64 位 (C:\Program Files\Java\jdk1.7.0_15)。

最佳答案

首先,真正的答案是设计师认为设计应该如何运作。你真的需要问他们才能得到真正的原因。

话虽如此:

So why doesn't JTextComponent.setText(String) normalize line endings?

我认为最有可能的原因是:

  • 这将是意想不到的行为。大多数程序员会期望1 文本字段上的“get”返回与“set”相同的字符串值……或用户输入的字符串值。

  • 如果文本字段确实规范化,那么程序员将很难在需要的地方保留原始文本的行结尾。

  • 设计者可能在某些时候想要改变他们的想法(参见报告的readwrite 方法的行为) 但出于兼容性原因无法做到这一点。

无论如何,如果您需要规范化,没有什么可以阻止您的代码对 setter 检索到的值执行此操作。

Or any other method that allows text to be modified within a text component for that matter?

据报道(见评论)read 和/或 write 进行规范化。

Is using System.getProperty('line.separator') a good practice when dealing with text components? Is it a good practice at all?

这取决于上下文。如果您知道您正在读写要在“这个”平台上处理的文件,这可能是个好主意。如果文件打算在不同的平台(使用不同的行分隔符)上读取,那么规范化以匹配当前机器的约定可能不是一个好主意。


1 - 事实上,readwrite 等其他方法可能 的行为不同并不影响这一点。他们不是“getter”和“setter”。我说的是人们期望“getters”和“setters”如何表现……而不是其他任何东西。此外,人们不应该期望一切以同样的方式行事,除非指定他们这样做。但显然,这里的部分问题是规范......javadocs......对这些问题保持沉默。

另一种可能性是@predi 报告的规范化行为实际上发生在Reader/Writer 对象中......

关于java - 为什么 JTextComponent.setText(String) 不规范化行结尾?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17882554/

相关文章:

java - 忽略争论

java - 使用 GridBagLayout

java - 从 JTable 的行中计算值,并在按下保存 Mysql 按钮时将其显示在另一行中

java - 键 1-9 的 KeyStroke 不适用于 JTextComponent 的 Keymap

java - 使 JTextArea 的一部分不可编辑(而不是整个 JTextArea!)

Netbean GUI selectall 操作中的 Java 弹出菜单

java - 从用户那里获取输入

java - 更改 MultiKeyMap 的每个值 (Apache Commons)

java - JComponent 的运行时对齐 + 链接到 RowFilters

java - 如何动态更改 JXTreeTable 中特定单元格的颜色