有没有一种自动应用ComponentOrientation的方法?

3
如果我想将文本翻转到适合默认语言环境的方向,我可以调用applyComponentOrientation函数,它将遍历组件树一次,正确设置方向。
问题是,它只会执行一次。因此,如果您的GUI中动态添加了组件,则这些动态添加的组件将带有错误的方向。
以下是一个快速的、人为的演示程序:
import java.awt.ComponentOrientation;
import java.awt.FlowLayout;
import java.util.Locale;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class OrientationTest implements Runnable {
    public static void main(String[] args) {
        // Deliberately set locale to one with RTL orientation.
        Locale.setDefault(new Locale("ar", "SA"));

        SwingUtilities.invokeLater(new OrientationTest());
    }

    @Override
    public void run() {
        JFrame frame = new JFrame("Test program");
        JPanel contentPane = new JPanel();
        contentPane.setLayout(new BoxLayout(contentPane, BoxLayout.PAGE_AXIS));
        frame.setContentPane(contentPane);
        contentPane.add(makePanel());

        frame.applyComponentOrientation(
            ComponentOrientation.getOrientation(Locale.getDefault()));

        // usually dynamically added later:
        contentPane.add(makePanel());

        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }

    private JPanel makePanel() {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
        panel.add(new JLabel("First"));
        panel.add(new JLabel("Second"));
        return panel;
    }
}

即使没有上述问题,我仍然需要手动将其添加到应用程序中的每个帧和对话框中。这不仅因为有很多对话框,而且因为不是团队中的每个开发人员在创建新的对话框时都会考虑这种事情。即使我以某种方式引入了一个正确执行此操作的 FixedJDialog 类,也无法保证每个人都会使用它。
这也有点愚蠢,因为每个组件的 ComponentUI 在构造每个组件时都会自动设置,而无需我做任何事情,因此您可能认为方向可以使用相同的机制。
这让我思考了一种可能自动实现此操作的方法——编写多个自定义 LAF,每个都是其他实际外观的子类。在 installDefaults() 中,设置组件方向,然后委托给真正的UI。
不幸的是,每个LAF都有很多UI类,并且并非所有UI类都适合于允许委派的整洁系统中。此外,在某些情况下,使用非标准LAF会触发库中微妙的错误,这些库使用UI类名称检查来确定要绘制其自己的东西的样式。因此,这种方法并不是很吸引人。
还有更好的方法吗?
编辑:
我现在有一个解决方案,可用于单个对话框内的内容。
    ContainerListener listener = new ContainerAdapter() {
        @Override
        public void componentAdded(ContainerEvent e) {
            e.getChild().applyComponentOrientation(
                e.getContainer().getComponentOrientation());
            addListenersToTree(e.getChild());
        }
    };
    addListenersToTree(frame);

addListenersToTree 就是递归遍历整个层次结构,在任何地方添加监听器。这是一个不太优雅的解决方案,我不知道拥有所有这些额外监听器的内存成本是多少,但它至少可以解决一部分问题,对于一个对话框而言是有效的。

2个回答

1

将组件方向应用于最内层的 JPanel

解决方案1

private JPanel makePanel() {
    FlowLayout flowLayout = null;
    if (ComponentOrientation.getOrientation(Locale.getDefault()).isLeftToRight()) {
        flowLayout = new FlowLayout(FlowLayout.LEFT);
    } else {
        flowLayout = new FlowLayout(FlowLayout.RIGHT);
    }
    JPanel panel = new JPanel(flowLayout);
    ...
}

解决方案2
private JPanel makePanel() {
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
    panel.applyComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
    ...
}

这也适用于在应用组件方向后添加组件的情况。
private JPanel makePanel() {
    JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING));
    panel.add(new JLabel("First"));
    panel.applyComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
    panel.add(new JLabel("Second"));
    return panel;
}

请查看如何使用FlowLayout

正如问题所述:“这不仅因为有很多对话框,而且因为团队中并不是每个开发人员在创建新的对话框时都会考虑到这种情况,所以这是不切实际的。” 好吧,我知道我可以通过添加监听器来解决出现新面板的问题。这可能足以解决单个对话框中的问题。然后问题又回到了“但我们仍然有很多对话框”。 - Hakanai
另外,第二个标签的方向也是错误的。尝试添加一个图标并将其对齐方式设置为 SwingConstants.LEADING,你会发现即使 Second 在 First 的左侧,它的图标也在文本的左侧,这与指定的方向不匹配。 - Hakanai
为什么不为这种类型的行为创建一个自定义类,并将 new FlowLayout() 替换为 new MyFlowLayout(),例如 class MyFlowLayout extends FlowLayout{public MyFlowLayout(){//orientation based code goes here}} - Braj
同样对话框也进行相同类型的定制。将通用代码放在一个地方是很好的设计。 - Braj
替换整个应用程序中的布局将是一个简单的一次性操作,但这将破坏所有GUI构建器表单,并且新创建的表单将再次使用普通布局。我必须修改NetBeans以使用我们定制的布局... - Hakanai

0

我在测试 HVLayout时遇到了这个问题(并找到了这个问题),我没有找到完全自动的解决方案,但也许部分手动解决方案可以帮助某些人。

当您动态添加/删除组件时,如果添加一个像 JScrollPane 这样自己进行渲染的组件,则必须通知 Swing 更新 UI ,而这是通过revalidate() / repaint()(甚至是对话框中的pack())的组合完成的。在此“更新UI”过程中,可以使用父/根容器的组件方向调用applyComponentOrientation(..)方法。
这并不能解决所有当前对话框的问题,如果没有通用/共享的实用程序方法来在添加/删除组件后更新UI,那么它可能永远不起作用。

但是,正如@Braj发现的那样,在布局管理器配置为使用绝对位置时(例如LEFT而不是LEADING),所有这些都几乎没有影响。避免使用不使用相对位置的布局管理器。

关于手动解决方案的详细信息,您可以查看test UI源代码(搜索“buildTextScrollerCheckBox”)。


那段代码很有趣,因为它进行了递归调用,但 applyComponentOrientation 已经是递归的了... 至于你提到必须调用 revalidate() 的观点,实际上是不正确的。如果您通过正常方式添加组件,Swing 将自动为您调用这些方法(我曾在调试器中验证过,因为我自己也不确定)。但是,如果您有一个正在进行自己渲染的组件,则必须调用 revalidate()、repaint() 等方法。或者,如果您要将组件添加到对话框中,则可能需要使用 pack() 方法。 - Hakanai
@Trejkaz 进行了更多的测试,并发现了您指出的错误(感谢您,这是我的疏忽)。我更新了答案,但我不确定它是否还是一个答案... - vanOekel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接