【问题标题】:Collapsable sentence within paragraph in java swing jeditorpanejava swing jeditorpane中段落内的可折叠句子
【发布时间】:2013-04-22 02:54:21
【问题描述】:

我想创建collapsable(或foldable)句子。重要的是,它们应该嵌入到段落中并与文本的其余部分一起流动。我正在使用 Java / Swing,在 JEditorpaneDefaultStyledDocument 的扩展中。 例如:

[-] 这是句子
一。 [-] 这是
第二句。 [-] 这是
第三句。

第二句折叠后变成:

[-] 这是句子
一。 [+] [-] 这是
第三句。

webpage 有一个如何折叠文档区域的示例,但该区域没有嵌入到段落中。我的问题是试图决定基于哪个视图。

  • 创建 SentenceView 作为 LabelView 的扩展:问题是 LabelView 不支持具有不同样式属性的部分。也许我可以添加这个,但是大多数具有多个子视图的视图都是基于 CompositeView 的。
  • 将 SentenceView 创建为 BoxView 的扩展:也许可以,但我不知道如何将它嵌入到 ParagraphView 中,使其与文本正常流动。
  • 扩展ParagraphView:我可以创建某种超级感知的ParagraphView,它可以理解句子的开始和结束位置并处理所有的折叠。我或许可以让它发挥作用,但这几乎肯定是错误的做法。

如果有人能给我一个指针(或直觉),我可以将 SentenceView 最好地建立在什么基础上(或其他一些可以帮助我长久的技巧),我将不胜感激。

【问题讨论】:

    标签: java swing jeditorpane collapsable


    【解决方案1】:

    我会尝试回答我自己的问题。在对标准 Java Swing 类进行了一些代码审查之后,我决定我需要的大部分功能都在 FlowViewParagraphView 中实现。值得注意的是,FlowStrategylayoutRow 方法负责查找断点并传递文本行。在我的SentenceView 中,我必须或多或少完全相同。唯一的区别是我不想自己布局行。相反,我的SentenceView需要实现breakView,这样包含句子的段落就可以将句子分割成碎片并进行布局。不幸的是,我找不到可以重用函数layoutRow 的方法,最后我复制/粘贴了相当多的代码。

    在执行我的(可折叠的)SentenceView 期间,我遇到了许多 Java 7 错误。例如,请参阅Java 7 line wrapping bug。此外,在 Java 中,minimumSize 是基于空格处的断点(这是很好的断点权重)还是字符处的断点(这是很好的断点权重)似乎是模棱两可的。 GlyphViewLabelView 在出色的断点权重的基础上给出了它们的 minimumSize,但 ParagraphView 将其与任何比 BadBreakWeight 更好的断点混合在一起(参见 calculateMinorAxisRequirements 的代码)。这相当令人困惑。

    无论如何,让我强调一下我实现SentenceView 的最重要部分。我省略了实际的可折叠部分,因为它有点繁琐,而不是我正在努力解决的部分。我将专注于breakView 功能。

    public class SentenceView extends BoxView {
        boolean containsStart;
        boolean containsEnd;
        public SentenceView(Element elem) {
            super(elem, View.X_AXIS);
            containsStart = true;
            containsEnd = true;
        }
        ...
    

    我还有一个吸气剂isStart 用于containsStartisEnd 用于containsEndbreakView的方法大多是抄袭的,就像我说的:

    public View breakView(int axis, int p0, float pos, float len) {
        if (axis == View.Y_AXIS) return this;
        if (p0 == getStartOffset() && len >= getPreferredSpan(axis)) return this;
        float spanLeft = len;
    
        int p = p0;
        float x = pos;
        int end = getEndOffset();
        Row row = new Row(getElement());
        TabExpander te = (this instanceof TabExpander) ? (TabExpander)this : null;
        int breakWeight = BadBreakWeight;
        float breakX = 0f;
        float breakSpan = 0f;
        int breakIndex = -1;
        int n = 0;
    
        if (p0 == getStartOffset() && isStart()) row.setContainsStart(true);
    
        Vector<View> viewBuffer = new Vector<View>(10, 10);
        while (p < end && spanLeft >= 0) {
            View v = createView(p);
            if (v == null) break;
    
            int bw = v.getBreakWeight(axis, x, spanLeft);
            if (bw >= ForcedBreakWeight) {
                View w = v.breakView(axis, p, x, spanLeft);
                if (w != null) {
                    viewBuffer.add(w);
                } else if (n == 0) {
                    viewBuffer.add(v);
                }
                break;
            } else if (bw >= breakWeight && bw > BadBreakWeight) {
                breakWeight = bw;
                breakX = x;
                breakSpan = spanLeft;
                breakIndex = n;
            }
    
            float chunkSpan;
            if (v instanceof TabableView) {
                    chunkSpan = ((TabableView)v).getTabbedSpan(x, te);
                } else {
                    chunkSpan = v.getPreferredSpan(axis);
                }
            if (isEnd() && v.getEndOffset() == end) {
                if ((chunkSpan <= spanLeft) && (chunkSpan > spanLeft)) {
                    spanLeft = chunkSpan - 1;
                }
            }
            if (chunkSpan > spanLeft && breakIndex >= 0) {
    
                    if (breakIndex < n) {
                        v = viewBuffer.get(breakIndex);
                    }
                    for (int i = n - 1; i >= breakIndex; i--) {
                        viewBuffer.remove(i);
                    }
    
                    v = v.breakView(axis, v.getStartOffset(), breakX, breakSpan);
            }
    
            spanLeft -= chunkSpan;
            x += chunkSpan;
            viewBuffer.add(v);
            p = v.getEndOffset();
            n++;
        }
        // cloning:
        Vector<View> viewBuffer2 = new Vector<>();
        for (int j = 0; j < viewBuffer.size(); j++) {
    
            View v = viewBuffer.get(j);
            if (v instanceof MyLabelView) {
                v = (View) ((MyLabelView)v).createClone();
            }  else {
                throw new RuntimeException("SentenceView can only contain MyLabelView");
            }
            viewBuffer2.add(v);
        }
        View[] views = new View[viewBuffer2.size()];
            viewBuffer2.toArray(views);
            row.replace(0, row.getViewCount(), views);
            return row;
        }       
    

    这里有几点需要注意。首先,breakView 将返回自身或SentenceView.Row。其次,我使用MyLabelView 而不是LabelView,主要是因为前面提到的换行错误。我还是 Java 和 Java Swing 的新手,我在使用 Cloneable 接口时有点挣扎,clone 是受保护的和最终的。所以,我只是实现了createClone 来调用clone。不是很优雅,但也不是我的主要担心。我需要克隆,因为子视图在添加到Row 时会重新设置父视图。但是,如果在稍后阶段原始的SentenceView 再次显示(因为不再需要中断),这会导致问题。我认为更好的方法可能是将所有子视图重新设置为this,但我不知道在哪里最好地做到这一点。不过,我会欢迎 cmets 在这方面。此外,为了实际实现可折叠性,SentenceView 必须与创建的Rows 共享其可折叠状态。我通过在对象中包装一个布尔值然后共享该对象来做到这一点。

    我将给出以下不带代码的方法,因为它们不太难或者可以从ParagraphView复制/修改。

    protected SizeRequirements calculateMajorAxisRequirements(int axis,
                                                     SizeRequirements r)
    protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r)
    public View createFragment(int p0, int p1)
    protected View createView(int startOffset)
    
    protected void layoutMinorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
        baselineLayout(targetSpan, axis, offsets, spans);
    }
    

    最后,还有Row 内部类:

    class Row extends SentenceView {
        Row(Element elem) {
            super(elem);
            containsStart = false;
            containsEnd = false;
        }
        public float getAlignment(int axis) {
            if (axis == View.X_AXIS) return 0;
            return super.getAlignment(axis);
        }
    
        public AttributeSet getAttributes() {
            View p = getParent();
            return (p != null) ? p.getAttributes() : null;
        }
        protected int getViewIndexAtPosition(int pos) {
            if(pos < getStartOffset() || pos >= getEndOffset())
                return -1;
            for(int counter = getViewCount() - 1; counter >= 0; counter--) {
                View v = getView(counter);
                if(pos >= v.getStartOffset() &&
                   pos < v.getEndOffset()) {
                    return counter;
                }
            }
            return -1;
        }
        protected void loadChildren(ViewFactory f) {
        }
    }
    

    这行得通。有点遗憾的是,我最终复制了相当多的现有代码,但我不清楚如何在 paragraphView 的 X 轴上实现 breakView 或如何找到另一种方法来实现这一点。此外,为了实现可折叠性,还需要做更多的工作。需要将paint 方法更改为在前面绘制[-](如果containsStart 为真)。 minimumSizepreferredSize 需要考虑这一额外的空间(我为此使用了插图)。它创建的SentenceViewRows 需要共享折叠状态。如果视图被折叠,minimumSizepreferredSize 将等于 [+] 标记的大小。 viewToModelmodelToViewgetNextEastWestVisualPositionFrom 有一些摆弄。这都类似于Folding (collapsible) area in the JEditorPane/JTextPane。在我的代码中,我唯一喜欢的一点是鼠标侦听器中检查事件是否发生在SentenceView 内的部分,因为我避免使用viewToModel。所以,让我也给它:

    MouseListener sentenceCollapse = new MouseAdapter() {
        public void mouseClicked(MouseEvent e) {
            JEditorPane src = (JEditorPane)e.getSource();
            View v = src.getUI().getRootView(src);
            Insets ins = src.getInsets();
            int x = ins.left;
            int y = ins.top;
            Point p = e.getPoint();
            p.translate(-x, -y);
            Rectangle alloc = new Rectangle(0,0,Short.MAX_VALUE,Short.MAX_VALUE);
    
            while (v != null && !(v instanceof SentenceView)) {
                View vChild = null;
                int n = v.getViewCount();
                for (int i = 0; i < n; i++) {
                    Rectangle r = v.getChildAllocation(i, alloc).getBounds();
    
                    if (r.contains(p)) {
                        vChild = v.getView(i); 
                        alloc = (Rectangle) r.clone();
                    }
                }
                v = vChild;
            }
            if (v != null && v instanceof SentenceView) {
            <<< unfold or fold the Sentence View >>>
            src.repaint();
        }
    };
    

    希望这是有用的。

    【讨论】:

      猜你喜欢
      • 2021-10-16
      • 2011-04-17
      • 2015-12-10
      • 2020-05-10
      • 1970-01-01
      • 1970-01-01
      • 2011-12-31
      • 1970-01-01
      • 2014-01-18
      相关资源
      最近更新 更多