我应该避免在Java Swing中使用set(Preferred | Maximum | Minimum)Size方法吗?

Hei*_*bug 471 java swing layout-manager

有几次我因为建议使用以下方法而受到批评:

  1. 有必要对setPreferredSize
  2. 了setMinimumSize
  3. setMaximumSize

Swing组件上.当我想在显示的组件之间定义比例时,我没有看到任何替代它们的用法.我被告知这个:

对于布局,答案总是相同的:使用合适的LayoutManager

我在网上搜索了一下,但我没有找到任何关于这个主题的综合分析.所以我有以下问题:

  1. 我应该完全避免使用这些方法吗?
  2. 已经定义了这些方法的原因.那我什么时候应该使用它们?在哪种情况下?出于什么目的?
  3. 使用这些方法的负面后果究竟是什么?(我只能考虑在具有不同屏幕分辨率的系统之间添加可移植性).
  4. 我认为任何LayoutManager都不能完全满足所有需要的布局需求.我是否真的需要为布局上的每个小变化实现一个新的LayoutManager?
  5. 如果4的答案是"是",那么这是否会导致LayoutManager类的扩散变得难以维护?
  6. 在我需要定义组件子级之间的比例的情况下(例如,child1应该使用10%的空间,child2 40%,child3 50%),是否可以在不实现自定义LayoutManager的情况下实现这一点?

kle*_*tra 236

  1. 我应该完全避免使用这些方法吗?

    是应用程序代码.

  2. 已经定义了这些方法的原因.那我什么时候应该使用它们?在哪种情况下?出于什么目的?

    我不知道,我个人认为它是一个API设计事故.稍微强迫复合组件对孩子大小有特殊想法."稍微",因为他们应该使用自定义LayoutManager实现他们的需求.

  3. 使用这些方法的负面后果究竟是什么?(我只能考虑在具有不同屏幕分辨率的系统之间添加可移植性.)

    一些(不完整的,不幸的是由于SwingLabs迁移到java.net而导致链接断开)技术原因例如在规则(hehe)中或在他/她对我的回答的评论中找到的@bendicott 链接中提到.在社交方面,为你不幸的家伙提供大量的工作,他必须维护代码并且必须追踪破碎的布局.

  4. 我认为任何LayoutManager都不能完全满足所有需要的布局需求.我是否真的需要为布局上的每个小变化实现一个新的LayoutManager?

    是的,有一些强大的LayoutManagers可以满足"所有布局需求"的非常好的近似.三巨头是JGoodies FormLayout,MigLayout,DesignGridLayout.所以不,在实践中,除了简单的高度专业化的环境外,你很少编写LayoutManagers.

  5. 如果4的答案是"是",那么这是否会导致LayoutManager类的扩散变得难以维护?

    (4的答案是"不".)

  6. 在我需要定义组件的子项之间的比例的情况下(例如,子项1应该使用10%的空间,子项2 40%,子项3 50%),是否可以在不实现自定义LayoutManager的情况下实现该比例?

    任何一个三巨头都可以,甚至不能GridBag(从不打扰真正掌握,太少的力量太麻烦).

  • 我无法相信接受的答案就是避免使用setXXX()方法.有时您只需要它们为布局管理器提供提示.如果您正在布置面板,那么您必须在必要时随意使用这些方法.说我认为如果你使用适当的布局管理器,你会发现自己不经常需要这些方法,但有时你只需要它们.尝试将JComboBox或JSpinner放在X_AXIS BoxLayout中而不使用它们,相信你会发现需要setMaximumSize(). (8认同)
  • 你总是说"使用一个像样的LayoutManager并告诉它你想要的大小"遍布stackoverflow,但是你永远不会给出任何"体面"LayoutManager的具体例子.并且没有一个标准管理者允许直接控制尺寸. (5认同)
  • 至少在两种情况下,我不完全确定我同意这个建议.1)自定义渲染组件2)使用带有HTML的`JEditorPane`本身并不表示宽度.OTOH我不确定我是否错过了什么.我会仔细查看该主题的回复,但如果你有任何意见,我会很感兴趣,尤其是后一种情况. (4认同)
  • @Michael不,我绝对不需要它 - 答案总是使用一个像样的LayoutManager并在_manager_级别(相对于组件级别)进行任何精细调整 (4认同)
  • @Andrew Thompson 1)自定义comps:它是comp本身负责返回有用的布局提示,如果他们不是impl是bug 2)甚至核心comps是bug ;-) 3)我不介意白色空间(虽然它不是故意这次,谢谢:-) (2认同)
  • @TiStrga好吧,_never_显然是错的(只是告诉我你没有真正尝试验证;-)-在一天结束时这样的建议是个人偏好(你甚至可以自己编写).而且我很乐意在任何时候指出我的个人偏好,目前是MigLayout. (2认同)
  • 你好,kleopatra,我发布了一个问题,我认为设置首选大小可能是不可避免的以获得理想的结果**即使我使用 MigLayout**。你能好心看一下吗?它是[这里](http://stackoverflow.com/questions/38659309/swing-miglayout-cannot-grow-fill-column-to-fill-container-length) (2认同)

tra*_*god 99

一些启发式:

  • 不要使用set[Preferred|Maximum|Minimum]Size(),当你真的要重写get[Preferred|Maximum|Minimum]Size(),因为可能会创建自己的组件,显示做在这里.

  • 不要使用set[Preferred|Maximum|Minimum]Size()时,你可以依靠组件的仔细覆盖getPreferred|Maximum|Minimum]Size,如这里及以下.

  • 不要使用set[Preferred|Maximum|Minimum]Size()导出后validate()的几何形状,如下所示和这里.

  • 如果组件没有首选大小,例如JDesktopPane,您可能必须调整容器的大小,但任何此类选择都是任意的.评论可能有助于澄清意图.

  • 当您发现必须遍历许多组件以获取派生大小时,请考虑备用或自定义布局,如这些注释中所述.

在此输入图像描述

import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

/**
 * @see https://stackoverflow.com/questions/7229226
 * @see https://stackoverflow.com/questions/7228843
 */
public class DesignTest {

    private List<JTextField> list = new ArrayList<JTextField>();
    private JPanel panel = new JPanel();
    private JScrollPane sp = new JScrollPane(panel);

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

            @Override
            public void run() {
                DesignTest id = new DesignTest();
                id.create("My Project");
            }
        });
    }

    private void addField(String name) {
        JTextField jtf = new JTextField(16);
        panel.add(new JLabel(name, JLabel.LEFT));
        panel.add(jtf);
        list.add(jtf);
    }

    private void create(String strProjectName) {
        panel.setLayout(new GridLayout(0, 1));
        addField("First Name:");
        addField("Last Name:");
        addField("Address:");
        addField("City:");
        addField("Zip Code:");
        addField("Phone:");
        addField("Email Id:");
        KeyboardFocusManager.getCurrentKeyboardFocusManager()
            .addPropertyChangeListener("permanentFocusOwner",
            new FocusDrivenScroller(panel));
        // Show half the fields
        sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        sp.validate();
        Dimension d = sp.getPreferredSize();
        d.setSize(d.width, d.height / 2);
        sp.setPreferredSize(d);

        JInternalFrame internaFrame = new JInternalFrame();
        internaFrame.add(sp);
        internaFrame.pack();
        internaFrame.setVisible(true);

        JDesktopPane desktopPane = new JDesktopPane();
        desktopPane.add(internaFrame);

        JFrame frmtest = new JFrame();
        frmtest.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frmtest.add(desktopPane);
        frmtest.pack();
        // User's preference should be read from java.util.prefs.Preferences
        frmtest.setSize(400, 300);
        frmtest.setLocationRelativeTo(null);
        frmtest.setVisible(true);
        list.get(0).requestFocusInWindow();
    }

    private static class FocusDrivenScroller implements PropertyChangeListener {

        private JComponent parent;

        public FocusDrivenScroller(JComponent parent) {
            this.parent = parent;
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            Component focused = (Component) evt.getNewValue();
            if (focused != null
                && SwingUtilities.isDescendingFrom(focused, parent)) {
                parent.scrollRectToVisible(focused.getBounds());
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @DavidKroukamp:谢谢.我尊重kleopatra的更多经验,但我看到批判性地审视相反观点的价值. (5认同)
  • (您可能已经猜到了:-)不同意“外部因素”的推理:XXSize属性旨在表达“仅内部”的需求。从外部进行调整是滥用,也就是黑客入侵。如果您想要一个(内部或J-)框架,且具有相对于首选框架的特定大小...调整框架的大小,而不是内容的大小 (2认同)
  • @kleopatra:只是有点坚持:如果setXXSize方法永远不应该从外面使用,为什么还没有被声明为私有或受保护?这不是缺乏设计吗?public修饰符是否隐式告诉用户可以使用这些方法? (2认同)
  • 我必须同意@kleopatra:`setPreferredSize()`总是用任意选择替换组件的计算. (2认同)
  • @trashgod +100 给你我认为覆盖这些方法没有问题,甚至调用它们(但这当然意味着你有一个自定义组件,因此覆盖会更好) (2认同)

Dav*_*amp 46

我应该完全避免使用这些方法吗?

不,没有正式证据表明不允许调用或覆盖这些方法.事实上,Oracle表示这些方法用于提供大小提示:http://docs.oracle.com/javase/tutorial/uiswing/layout/using.html#sizealignment.

扩展 Swing组件时(而不是在自定义组件实例上调用方法),它们也可能被覆盖(这是Swing的最佳实践)

最重要的是,无论您如何指定组件的大小,请确保组件的容器使用的布局管理器遵循所请求的组件大小.

已经定义了这些方法的原因.那我什么时候应该使用它们?在哪种情况下?出于什么目的?

当您需要为容器布局管理器提供自定义大小提示时,以便组件布局良好

使用这些方法的负面后果究竟是什么?(我只能考虑在具有不同屏幕分辨率的系统之间添加可移植性).

  • 许多布局管理器不关注组件所请求的最大大小.然而,BoxLayoutSpringLayout做.此外,GroupLayout还提供了明确设置最小,首选或最大尺寸的功能,而无需触摸组件.

  • 确保您确实需要设置组件的确切大小.每个Swing组件都有不同的首选大小,具体取决于它使用的字体和外观.因此,设置大小可能会在不同的系统上产生不同的UI 外观

  • 有时可能遇到问题GridBagLayout和文本字段,其中如果容器的大小小于首选大小,则使用最小大小,这可能导致文本字段显着缩小.

  • JFrame不强制重写getMinimumSize()仅调用setMinimumSize(..)其工作

我认为任何LayoutManager都不能完全满足所有需要的布局需求.我是否真的需要为布局上的每个小变化实现一个新的LayoutManager?

如果通过实施你的意思是使用然后是的.没有人LayoutManger可以处理所有事情,每个LayoutManager都有其优点和缺点,因此每个可以一起使用来产生最终的布局.

参考:

  • +1"不,没有正式证据表明不允许调用或覆盖这些方法." 发现.标记为答案的帖子是普通BS. (13认同)
  • @David:我来看看`setXxxSize`方法是一个红旗,我可能会在这里[http://stackoverflow.com/a/6785121/230513],即使文档建议它.我_almost always_应该覆盖`getXxxSize`,其中一个人可以访问所需的几何; 甚至简短的例子也得到了比我想要的更多的回收.+1用于提及布局管理器之间的差异并引用该教程. (4认同)
  • _提供自定义大小提示_这本身就是一个矛盾:提供大小提示(以px!为单位)是组件的_exclusive_任务.它根据内部状态细节计算它们,除了它自己以外没有其他方可以知道(也无法跟踪).从客户端的角度来看,定制方法可以是一个合适的LayoutManager和/或专用于组件的api,它允许根据"语义"大小相关属性,文本组件中的行/列数来配置大小要求 (3认同)
  • @kleopatra我仍然想知道为什么Oracle告诉我们如何使用这些方法以及使用它们的正确方法.我们可能有自己的偏好但我们不能说设计师在没有证据表明这一点时不会被使用.但这就是我提出赏金的原因,也许是否会吸引其他可以从可信的来源提供信息的地方oracle声明根本不使用这些方法(因此如果你这样做会使它变得不好,例如setMinimumSize必须是调用JSplitPane之类的东西,这可以在拆分窗格的Oracle教程中看到. (3认同)

Jas*_*n C 25

有很多在这里很好的答案,但我想补充一点关于原因,为什么你通常应该避免这些(问题只是在重复的主题又上来):

除了少数例外情况,如果您使用这些方法,您可能会对GUI进行微调,使其在特定的外观(以及您的系统特定设置,例如您首选的桌面字体等)上看起来很好.这些方法本身并不是邪恶的,但使用它们的典型原因.一旦开始调整布局中的像素位置和大小,就会在其他平台上面临GUI破坏(或者至少看起来很糟糕)的风险.

作为示例,请尝试更改应用程序的默认外观.即使只是平台上提供的选项,您可能会惊讶于结果的呈现效果不佳.

因此,在所有平台上保持GUI功能和漂亮的名称(请记住,Java的主要优点之一是它的跨平台性),您应该依赖布局管理器等来自动调整大小.您的组件,以便它在您的特定开发环境之外正确呈现.

总而言之,你当然可以设想这些方法合理的情况.同样,它们本身并不是邪恶的,但它们的使用通常是一个红旗,表明潜在的GUI问题.只要确保你在使用它们的时候意识到并发症的可能性很大,并且总是试着想一想你的问题是否有另一种看起来独立的解决方案 - 你经常会发现这些方法是没有必要的.

顺便说一句,如果您发现自己对标准布局管理器感到沮丧,那么有许多优秀的免费开源第三方管理器,例如JGoodiesFormLayout,或者MigLayout.一些GUI构建器甚至内置了对第三方布局管理器的支持 - 例如,Eclipse的WindowBuilder GUI编辑器附带支持FormLayoutMigLayout.

  • +1一个体贴的答案 - 只是不同意_they并不是天生就是邪恶_仅仅是因为它们是:-)通常情况下,外部客户没有任何机会猜测 - 而且仅仅假设就像外人所能得到的那样 - 中途正确的布局提示:只有组件本身确实拥有所有信息以返回任何有用的信息.而且,在外人干涉的那一刻,他们有责任保持这些最新的暗示,而这些暗示是他们无法做到的. (2认同)
  • 嗯,你知道的,我对这类事情有更多的“枪不杀人,人杀人”的看法。:) *如果*有人使用这些方法,他们*需要*注意诸如您提出的关于不可预测的布局提示使用的优点(这就是为什么这些方法适用的情况确实很少见)。 (2认同)

Tom*_*Tom 20

如果你在Java Swing中遇到布局问题,那么我强烈推荐FormLayoutKarsten Lentzsch 在这里免费提供的JGoodies 作为Forms免费软件库的一部分.

这个非常流行的布局管理器非常灵活,允许开发非常精美的Java UI.

你会发现Karsten的文档中这里,并从Eclipse的一些比较好的文档在这里.


Mic*_*ael 16

大多数人都很难理解这些方法.你绝对不应该忽略这些方法.如果他们遵守这些方法,则由布局管理员决定.此页面有一个表格,显示哪些布局管理员会尊重以下哪些方法:

http://thebadprogrammer.com/swing-layout-manager-sizing/

我已经写了8年以上的Swing代码,JDK中包含的布局管理器一直满足我的需求.我从来没有需要第三方布局管理器来实现我的布局.

我会说你不应该尝试用这些方法给出布局管理器提示,直到你确定需要它们为止.在没有给出任何大小提示的情况下进行布局(即让布局管理器完成其工作),然后如果需要,可以进行小的修正.

  • 不是_methods_是提示,_properties_是:组件应该为所有提示报告合理的东西,有些(如fi JComboBox)没有 - 返回maxInteger左右.这是一个错误,应该由组合修复.至于你的习惯:当维护人员不得不清理它时,一定要离得太远:)硬编码的提示往往会在最轻微的变化时破坏布局,很难被发现作为布局破坏的原因. (5认同)

Tho*_*mas 15

在我需要定义组件的子项之间的比例(子项1应该使用10%的空间,child2 40%,child3 50%)的情况下,是否可以在不实现自定义布局管理器的情况下实现这一点?

也许GridBagLayout会满足你的需求.除此之外,网上还有大量的布局管理人员,我打赌有一个适合您的要求.


Gee*_*Bee 5

我看到它与接受的答案不同.

1)我应该完全避免使用这些方法吗?

永远不要避免 他们在那里向布局管理器表达组件的大小限制.如果您没有使用任何布局管理器并尝试自行管理可视布局,则可以避免使用它们.

不幸的是,Swing没有合理的默认尺寸.但是,不是设置组件的尺寸,而是以合理的默认值下降自己的组件是更好的OOP.(在这种情况下,您在后代类中调用setXXX.)或者,您可以覆盖getXXX方法以获得相同的效果.

2)已经定义了方法的原因.那我什么时候应该使用它们?在哪种情况下?出于什么目的?

总是.创建组件时,请根据该组件的使用设置其实际的最小/首选/最大大小.例如,如果您有一个JTextField用于输入国家/地区符号(例如UK),则其首选大小应该宽到适合两个字符(使用当前字体等),但可能让它变得更大没有意义.毕竟,国家符号是两个字符.相反,如果你有一个JTextField用于输入例如客户名称,它可以有一个首选大小,如20个字符的像素大小,但如果调整布局可以增大,所以将最大大小设置为更多.同时,拥有0px宽的JTextField是没有意义的,所以设置一个真实的最小尺寸(我会说像素大小为2个字符).

3)使用这些方法的负面后果究竟是什么?

(我只能考虑在具有不同屏幕分辨率的系统之间添加可移植性).

没有负面后果.这些是布局管理器的提示.

4)我认为任何LayoutManager都不能完全满足所有需要的布局需求.

我是否真的需要为布局上的每个小变化实现一个新的LayoutManager?

不,绝对不是.通常的方法是级联不同的基本布局管理器,例如水平和垂直布局.

例如,下面的布局:

<pre>
+--------------+--------+
| ###JTABLE### | [Add]  | 
| ...data...   |[Remove]|
| ...data...   |        |
| ...data...   |        |
+--------------+--------+
</pre>
Run Code Online (Sandbox Code Playgroud)

有两个部分.左右部分是水平布局.右边部分是添加到水平布局的JPanel,这个JPanel有一个垂直布局,垂直布置按钮.

当然,这可能会因现实生活布局而变得棘手.因此,如果您要开发任何严肃的东西,基于网格的布局管理器(如MigLayout)要好得多.

5)如果对4的回答是"是",这是否会导致LayoutManager类的扩散变得难以维护?

不,你肯定不会开发布局管理器,除非你需要非常特别的东西.

6)在我需要定义比例的情况下......

组件的子项之间(例如,child1应该使用10%的空间,child2 40%,child3 50%),是否可以在不实现自定义LayoutManager的情况下实现这一点?

基本上,一旦首选尺寸设置正确,您可能不希望以百分比做任何事情.简单地说,因为百分比是没有意义的(例如,JTextField占窗口大小的10%是没有意义的 - 因为可以缩小窗口以使JTextField变为0px宽,或者可以扩展窗口以使JTextField跨越两个显示器多显示器设置).

但是,有时您可以使用百分比来控制gui(例如面板)的更大构建块的大小.

您可以使用JSplitPane,您可以在其中预先设置双方的比例.或者,您可以使用MigLayout,它允许您以百分比,像素和其他单位设置此类约束.