【问题标题】:StyledEditorKit text align not working properlyStyledEditorKit 文本对齐无法正常工作
【发布时间】:2016-05-27 13:33:25
【问题描述】:

我正在尝试在用 Java 生成的 html 文档中实现用户可选择的文本对齐方式。我试过了:

JMenuItem leftAlignMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
JMenuItem centerMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
JMenuItem rightAlignMenuItem = 
  new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));

以及这个主题的各种变体。选择菜单项会使文本在文本窗格中正确对齐,并将适当的 html 标记添加到保存的文档中。问题是,一旦添加了标签,单击另一个对齐菜单项并不会更改它,因此无法多次更改默认(左)的文本对齐方式并保存更改。

我知道我不是第一个遇到此问题的人,但到目前为止我还没有找到任何解决方案,因此我们将不胜感激。

这是我的“M”CVE,不幸的是它仍然很大,但我无法删除更多代码,否则它不会显示问题:

package aligntest;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.JFrame;

public class AlignTest extends JFrame implements ActionListener {

    private HTMLDocument doc; // Stores the formatted text.
    private JTextPane textPane = new JTextPane(); // The Pane itself.
    String FilePath = "";  // Stores the file path.

        public AlignTest() { // This method is called automatically when the app is launched.
            HTMLEditorKit editorKit = new HTMLEditorKit();
            doc = (HTMLDocument)editorKit.createDefaultDocument();  
            init(); // Calls interface method below.
    }

    public static void main(String[] args) {
        AlignTest editor = new AlignTest();
    }
        public void init(){

            JMenuBar menuBar = new JMenuBar();
            getContentPane().add(menuBar, BorderLayout.NORTH);
            JMenu fileMenu = new JMenu("File"); 
            JMenu alignMenu = new JMenu("Text Align");

            menuBar.add(fileMenu);
            menuBar.add(alignMenu);

            JMenuItem openItem = new JMenuItem("Open"); //
            JMenuItem saveItem = new JMenuItem("Save"); //

            openItem.addActionListener(this);
            saveItem.addActionListener(this);

            fileMenu.add(openItem);
            fileMenu.add(saveItem);

            JMenuItem leftAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Left Align", StyleConstants.ALIGN_LEFT));
            JMenuItem centerMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Center", StyleConstants.ALIGN_CENTER));
            JMenuItem rightAlignMenuItem = new JMenuItem(new StyledEditorKit.AlignmentAction("Right Align", StyleConstants.ALIGN_RIGHT));

            leftAlignMenuItem.setText("Left");
            centerMenuItem.setText("Center");
            rightAlignMenuItem.setText("Right");

            alignMenu.add(leftAlignMenuItem);
            alignMenu.add(centerMenuItem);
            alignMenu.add(rightAlignMenuItem);

            textPane = new JTextPane(doc); // Create object from doc and set this as value of textPane.
            textPane.setContentType("text/html"); // textPane holds html.
            JScrollPane scrollPane = new JScrollPane(textPane); // textPane in JScrollPane to allow scrolling if more text than space.
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // Get screen size to use below.
            Dimension scrollPaneSize = new Dimension(1*screenSize.width/2,1*screenSize.height/2); // Together with next line, sets dimensions of textPane relative to screen size.
            scrollPane.setPreferredSize(scrollPaneSize);
            getContentPane().add(scrollPane, BorderLayout.SOUTH);

            pack();
            setLocationRelativeTo(null);        
            show(); // Actually displays the interface.

        }

        public void actionPerformed(ActionEvent ae) { // Method called with action commands from interface objects above.  Which action depends on the text of the interface element.
            String actionCommand = ae.getActionCommand();           
        if (actionCommand.compareTo("Open") == 0){ // Calls method when action command received.
            openDocument();
        } else if (actionCommand.compareTo("Save") == 0){
            saveDocument();
                }
        }

        public void saveDocument(){

            String FP = FilePath;  // This paragraph calls Save As instead of Save if file not already saved.
            String unsaved = "";
            int saved = FP.compareTo(unsaved);
            if (saved == 0) {
                saveDocumentAs();
            } else {
                save();
            }
        }

        public void saveDocumentAs(){                
            JFileChooser SaveDialog = new javax.swing.JFileChooser();
            int returnVal = SaveDialog.showSaveDialog(this);

            if (returnVal == JFileChooser.APPROVE_OPTION) {
                java.io.File saved_file = SaveDialog.getSelectedFile();
                FilePath = saved_file.toString();

                save();
            }
        }

        public void save(){
            try {
                WriteFile objPane = new WriteFile(FilePath, false);
                String PaneText = textPane.getText();  // Gets text from Title Pane.
                objPane.writeToFile(PaneText);
            }
            catch (Exception ex) {
            }
        }

        public void openDocument(){

            JFileChooser OpenDialog = new javax.swing.JFileChooser(); // Creates file chooser object.
            int returnVal = OpenDialog.showOpenDialog(this);  // Defines 'returnVal' according to what user clicks in file chooser.

            if (returnVal == JFileChooser.APPROVE_OPTION) { // Returns value depending on whether user clicks 'yes' or 'OK' etc.
                java.io.File file = OpenDialog.getSelectedFile(); // Gets path of selected file.
                FilePath = file.toString( ); // Converts path of selected file to String.

// The problem seems to be related to the code that starts here...
                try {
                    ReadFile readPane = new ReadFile(FilePath);  // Creates "readPane" object from "FilePath" string, using my ReadFile class.
                    String[] aryPane = readPane.OpenFile();  // Creates string array "aryPane" from "readPane" object.

                    int i;  // Creates integer variable "i".
                    String PaneText = "";

                    for (i=0; i < aryPane.length; i++) {  //  Creates a for loop with starting "i" value of 0, adding 1 to i each time round and ending when i = the number of lines in the aryLines array.
                        PaneText = PaneText + aryPane[i];  //  Add present line to "PaneText".
                    }
                    textPane.setText(PaneText);  // Displays "PaneText" in "TextPane".

                } catch (Exception ex) {
// and ends here.  This code also calls ReadFile, so code in that class may be at fault.

                }
                }
            }
}

它还必须调用以下两个类中的方法才能工作:

package aligntest;

import java.io.IOException;
import java.io.FileReader;
import java.io.BufferedReader;

public class ReadFile {

    private String path;

    public ReadFile(String file_path) {
        path = file_path;
    }

    public String[] OpenFile() throws IOException {

        FileReader fr = new FileReader(path);
        BufferedReader textReader = new BufferedReader(fr);

        int numberOfLines = readLines( );
        String[] textData = new String[numberOfLines];

        int i;

        for (i=0; i < numberOfLines; i++) {
            textData[i] = textReader.readLine();
        }

        textReader.close( );
            return textData;
    }


    int readLines() throws IOException {

        FileReader file_to_read = new FileReader(path);
        BufferedReader bf = new BufferedReader(file_to_read);

        String aLine;
        int numberOfLines = 0;

        while ((aLine = bf.readLine()) != null) {
            numberOfLines++;
        }
        bf.close();

        return numberOfLines;
    }

}

&

package aligntest;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;

public class WriteFile {

    private String path;
    private boolean append_to_file = false;

    public WriteFile(String file_path) {
        path = file_path;
    }

    public WriteFile(String file_path, boolean append_value) {
        path = file_path;
    }

    public WriteFile(File SectionPath, boolean success) {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    public void writeToFile( String textLine ) throws IOException {
        FileWriter write = new FileWriter(path, append_to_file);
        PrintWriter print_line = new PrintWriter(write);

        print_line.printf( "%s" + "%n" , textLine);

        print_line.close();
}
}

问题似乎与打开文档(第 126 - 138 行)或“ReadFile”类有关:在另一个程序中查看保存的文件时,我可以看到标签正在更改,直到文档关闭,然后再次使用“AlignTest”打开。在此之后,任何对齐更改都不会反映在 html 中。

希望有人能提供帮助。

编辑:这是由“AlignTest”生成的一些 html。如果将其粘贴到文本文件中,然后在“AlignTest”中打开,它应该会重现问题:“AlignTest”无法更改对齐标签。

<html>
  <head>
    <meta id="_moz_html_fragment">

  </head>
  <body>
    <p align="right" style="margin-top: 0pt">
      Another
    </p>
  </body>
</html>

【问题讨论】:

  • 发布MCVE。请务必将您的代码复制粘贴到新项目,并确保在将其发布到此处之前编译并运行。
  • 感谢 user1803551。我现在已经发布了我希望通过 MCVE 的内容。
  • "第 126 - 138 行" 哪些是?
  • aLine in readLines 未使用,debug in AlignTest 也未使用。他们有什么需要吗?
  • 回应你的其他cmets:我会马上去标记我认为可能有问题的行(我匆忙中没有注意到这里没有行号!)。 aLine 用于获取行数的循环中,在我的实际程序中的其他地方需要调试,但这里不需要 - 我应该有,现在将删除它。

标签: java html swing jtextpane text-alignment


【解决方案1】:

事实证明,这比我想象的要困难。让我解释一下幕后发生的事情,然后我会给出几个场景。

AlignmentAction 的操作调用文档的setParagraphAttributes,并将boolean replace 设置为false。在setParagraphAttributes 中,给定属性(Alignment.XXX)通过MutableAttributeSet.addAttributes 添加到段落标签的当前属性列表中。效果如下:

  1. 如果段落标签没有任何对齐指令,则添加 HTML align="xxx"。该文件使用这个新属性保存。
  2. 如果段落标签只有一个 HTML 属性,则会添加一个内联 CSS 属性:text-align=xxx。文件只保存了HTML属性(CSS属性被舍弃了,不知道为什么,可能是"需要替换成')。
  3. 如果段落标签具有 HTML 和 CSS 属性,则修改 CSS 属性。这些文件仅保存为 HTML 文件。
  4. 如果段落标签只有一个 CSS 属性,它会被修改。该文件具有一个从 CSS 转换而来的新 HTML 属性。

总结是,由于某种原因,无论运行时存在哪些属性,都只能保存 HTML 属性。由于没有修改,所以必须先将其删除,然后再添加新属性。可能需要使用不同的编写器。

解决方案的一种尝试是创建您自己的对齐操作并将替换值设置为true。问题是它替换了整个段落元素:

<html>
  <head>
    <meta id="_moz_html_fragment">

  </head>
  <body>
    <body align="center">
      Another
    </body>
  </body>
</html>

您需要做的是访问元素并“手动”替换属性。创建一个扩展 HTMLDocument@overridesetParagraphAttributes 的类,使其包含该行

// attr is the current attribute set of the paragraph element
attr.removeAttribute(HTML.Attribute.ALIGN); // remove the HTML attribute

之前

attr.addAttributes(s); // s is the given attributes containing the Alignment.XXX style.

然后保存文件将按照上述1-4个场景保持正确的对齐方式。

最终你会想要使用一个 HTML 解析器,比如jsoup;只是 Google for Java HTML 解析器。另见Which HTML Parser is the best?

【讨论】:

  • 哇,感谢 user1803551,你已经付出了很多努力!我不禁想到将布尔替换设置为false的问题,以及未保存的CSS只是彻头彻尾的错误,我的意思是谁不希望它替换属性?即使您想锁定它以便用户无法更改它,也肯定会有其他方法来实现它。但我是谁来评判它,我只是一个新手!我肯定会在某个时候研究解析器。您对我提出的短期解决方案有任何想法吗?这似乎太简单了,但似乎工作正常,与
  • 一两个明显的限制。随意说它是否会在第一关失败!再次感谢您的帮助!
  • @user5399283 我不知道这些错误,因为无论如何您都应该在 CSS 文件中定义对齐方式。替换布尔值看起来不像是一个错误,因为它替换了整个元素。可能是我没有正确地做到这一点。我可以再研究一下。
  • 我知道这是一个非常古老的条目,但我今天正在与这种奇怪的属性行为作斗争。我已经尝试过建议的解决方案,扩展 HTMLDocument,覆盖 setParagraphAttributes。但当前段落属性是只读值。我可以通过调用 AttributeSet attr = this.getParagraphElement(offset).getAttributes(); 来获得集合但我无法改变它。并且尝试将 attr 强制转换为 MutableAttributeSet 将导致 Illegal cas 异常。
  • @shaman74 你应该用minimal reproducible example 提出一个新问题,并用你在评论中给出的解释指出这个问题。
【解决方案2】:

这是我想出的在 JTextPane 中更改文本对齐方式的方法(也可以在其他 Swing 组件中使用):

public void alignLeft() {
        String text = textPane.getText();
        text = text.replace("<p style=\"margin-top: 0\">", "<p align=left style=\"margin-top: 0\">");
        text = text.replace("align=\"center\"", "align=\"left\"");
        text = text.replace("align=\"right\"", "align=\"left\"");
        textPane.setText(text);

以及居中和右对齐的等价物。

如果其他人正在考虑使用,请注意:

  • 我没有彻底测试过
  • 它将改变 JTextPane 中所有文本的对齐方式 - 用户无法定义对齐的文本。

【讨论】:

  • 这不是健壮的并且会导致过多的代码。您将需要为每个对齐操作创建一个方法,并且您正在弄乱对齐以外的属性。您最好使用正则表达式来捕获 align 的值并仅替换它。是的,它将替换文档中的所有对齐属性。
  • 感谢您的 cmets。我很欣赏你所说的,并将优先考虑用更强大的东西替换它。谢谢大家的建议!
【解决方案3】:

我已尝试实施@user1803551 建议的解决方案,但就像我在上面的评论中所说的那样,我找不到在只读段落 AttributeSet 上使用 removeAttribute() 的方法。

我已经实现了建议解决方案的不同版本,克隆了除对齐相关属性之外的所有段落的 AttributeSet。然后我使用 setParagraphAttributes 和 replace=true 覆盖当前属性,并应用请求的修改,使用 setParagraphAttributes 和 replace=false。而且它似乎工作得很好。

public class ExtendedHTMLDocument extends HTMLDocument {

    @Override
    public void setParagraphAttributes(int offset, int length, AttributeSet attr, boolean replace) {
        AttributeSet paragraphAttributes = this.getParagraphElement(offset).getAttributes();
        MutableAttributeSet to =  new SimpleAttributeSet();
        Enumeration<?> keys = paragraphAttributes.getAttributeNames();
        String value = "";
        while (keys.hasMoreElements()) {
            Object key = keys.nextElement();
            if (key instanceof CSS.Attribute) {
                if (!key.equals(CSS.Attribute.TEXT_ALIGN)) {
                    value = value + " " + key + "=" + paragraphAttributes.getAttribute(key) + ";";
                }
            }
            else {
                if (!key.equals(HTML.Attribute.ALIGN)) {
                    to.addAttribute(key, paragraphAttributes.getAttribute(key));
                }
            }
        }
        if (value.length() > 0) {
            to.addAttribute(HTML.Attribute.STYLE, value);
        }
        super.setParagraphAttributes(offset, length, to, true);
        super.setParagraphAttributes(offset, length, attr, replace);
    }

}

【讨论】:

    猜你喜欢
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    • 2020-11-01
    • 2015-12-13
    • 1970-01-01
    • 2017-01-13
    • 1970-01-01
    • 2014-08-18
    相关资源
    最近更新 更多