第五天:控件布局怎么这么麻烦

来源:互联网 发布:java reflection类 编辑:程序博客网 时间:2024/05/17 02:16

作者:梁祺 (eclipsesbs@gmail.com)

来自:http://www.benisoft.net/day5/index.html


在UI应用程序开发中,控件的布局是一个比较重要的方面,也另初学者很是头疼。Eclipse并没有象其他开发工具那样提供图形化工具,允许程序员通过拖拽控件来进行布局。不过有不少第三方为Eclipse提供商业的或非商业的图形化布局的工具。虽然直接写代码布局控件比较麻烦,但也有它的好处,后期维护阶段,不需要读懂工具自动生成的晦涩的代码。今天就来看一下Eclipse的控件布局是怎样的。

Eclipse的SWT(Standard Widget Toolkit)提供的基本布局有FillLayout,RowLayout,FormLayout和GridLayout,都在org.eclipse.swt.layout这个包里。前三种布局方式只能满足简单的布局需要,略有些复杂的布局就无法单独胜任了。而GridLayout非常强大,任何的控件布局需求它都能满足,所以这里只介绍GridLayout。从软件工程的角度出发,使用五花八门的布局方式,并不是一件好事,这意味着整个Team对不同的布局都有所了解,否则,修改一个你不熟悉的布局方式将是件非常痛苦的事。所以个人建议是在任何情况下都使用GridLayout,并贯彻到整个Team,这样就可以减轻维护的工作量。

GridLayout的基本思想是把整个矩形布局区域划分成M*N的格子,它允许将同一行或同一列相邻的格子合并成一个格子放置控件。通过Composite/Group等容器控件,并且在容器控件内继续使用GridLayout布局控件,我们就能够完成任何形式的UI布局。以下面这个对话框为例。

这个对话框有三个Group控件,分别是Properties,Options,和Rich Client Application,这三个Group控件构成了一个三行一列的表格。

  • 在Properties里面,每一行表示一个属性,除了最后一行有三个控件,其他都是两个控件,我们可以把它看成是五行三列的表格,前面四行的第二个控件占两列,第五行的控件各占一列。
  • 第二个Options也类似,可以看成四行三列的表格,其中第二行用一个空的Label来占住第一列,剩下两个控件各占一列。
  • 最后Rich Client Application就是一个一行三列的表格,三个控件各占一列。

首先来创建一个Plug-in项目,输入项目名”eclipse.tutorial.day5“,我们仍然用Hello World这个例子,只不过把SampleAction的run()方法改成打开LayoutDialog。

    public void run(IAction action) {
        LayoutDialog dialog = new LayoutDialog(window.getShell());
        dialog.open();
    }
Code Formatted by ToGoTutor

接下来我们来创建LayoutDialog类,点击工具栏上的"New Java Class"按钮。

在New Java Class对话框里输入包名为"eclipse.tutorial.day4.actions",类名为"LayoutDialog",继承自”org.eclipse.jface.dialogs.Dialog“,点击Finish。

LayoutDialog继承自Dialog,它重载createDialogArea()方法,这个方法有一个参数,提供一个容器用于放置我们想要创建的控件,一般我们都会创建一个Composite容器控件占满整个parent,然后我们就可以为composite指定我们需要的布局方式。在这个例子中,需要从上到下创建三个Group控件,是一个三行一列的表格,所以我们指定布局为GridLayout(只有一列),GridLayout的构造函数的第一个参数是表格的列数,第二个参数是表格的每列是否等宽,既然只有一列,也无所谓等不等宽了,所以true和false都可以。将layout对象通过setLayout()方法设置为composite的布局,同时我们也要指定调用setLayoutData()设置composite在parent中的位置。顺便说一下,基类Dialog将composite的布局方式为GridLayout。

public class LayoutDialog extends Dialog {

    protected LayoutDialog(Shell parentShell) {
        super(parentShell);
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout(1, true);
        composite.setLayout(layout);
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, truetrue, 1, 1));

      ...        
        return composite;
    }
}
Code Formatted by ToGoTutor

下面我们看一下GridData,GridData有不少构造函数,其实我们只要记住下面这个就行了,以不变应万变。

public GridData (int horizontalAlignment, int verticalAlignment, 
                boolean grabExcessHorizontalSpace, boolean grabExcessVerticalSpace, 
                int horizontalSpan, int verticalSpan) 
Code Formatted by ToGoTutor

  • HorizontalAlignment: 水平方向左对齐,居中对齐,右对齐,或者充满整个格子。分别是SWT.BEGINNING/CENTER/END/FILL。
  • verticalAlignment:垂直方向上对齐,居中对齐,下对齐,或者充满整个格子。分别是SWT.BEGINNING/CENTER/END/FILL。
  • grabExcessHorizontalSpace:水平方向空间增加时,是否占据整个水平方向的空间。比如,当用户拖大对话框,这个参数为true的空间将占据增加的部分。
  • grabExcessVerticalSpace:垂直方向空间增加时,是否占据整个水平方向的空间。
  • horizontalSpan: 空间在水平方向上占几个格子。
  • verticalSpan: 空间在垂直方向上占几个格子。

所以composite占据1x1的格子,SWT.FILL说明它在两个方向都是充满整个格子,并且如果格子尺寸变大,它在两个方向都会占据新增加的部分。

        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, truetrue, 1, 1));
Code Formatted by ToGoTutor

接下来看看各个控件是怎么布局的,整个代码比较长,我们先看看三个Group,它们的代码很类似,设置了相同的LayoutData,都占据一个1x1的格子,水平方向充满整个格子,垂直方向上对齐,格子尺寸变化时,水平方向占据新空间。它们也都将自己内部的布局方式设置为GridLayout,根据前面的分析,都是三列,列宽不要求相同。

        // Properties Group
        Group group1 = new Group(composite, SWT.NONE);
        group1.setText(\"Properties\");
        group1.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, truefalse, 1, 1));
        group1.setLayout(new GridLayout(3, false));
        
        // Options Group
        Group group2 = new Group(composite, SWT.NONE);
        group2.setText(\"Options\");
        group2.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, truefalse, 1, 1));
        group2.setLayout(new GridLayout(3, false));
        
        // Rich Client Application Group
        Group group3 = new Group(composite, SWT.NONE);
        group3.setText(\"Rich Client Application\");
        group3.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, truefalse, 1, 1));
        group3.setLayout(new GridLayout(3, false));
Code Formatted by ToGoTutor

再看一下Properties Group的第一行,label和text。label放在1x1的格子里,左对齐,上对齐,格子大小改变时,控件尺寸不变。text放在2x1的格子里,充满水平方向,格子尺寸改变时,控件水平方向尺寸作相应的改变。

        Label label = new Label(group1, SWT.NONE);
        label.setText(\"ID: \");
        label.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, falsefalse, 1, 1));
        Text text = new Text(group1, SWT.BORDER);
        text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, truefalse, 2, 1));
Code Formatted by ToGoTutor

再看一下Options Group的第二行,第一个label是一个占位控件,本身没有任何用处,就是为了占住这行的第一格,以便Activator从第二列开始,看起来像从属于上一行的控件。因为占位控件没有文字,并且它是第一列中唯一独占第一列的控件(其他都不单独属于第一列),所以它的宽度就决定了第一列的尺寸,就需要为它指定宽度,否则第一列的宽度就变成0了(不考虑列边距)。这里我们设置它的宽度widthHint为10。

        label = new Label(group2, SWT.NONE);
        GridData layoutData = new GridData(SWT.BEGINNING, SWT.BEGINNING, falsefalse, 1, 1);
        layoutData.widthHint = 10;
        label.setLayoutData(layoutData);
        label = new Label(group2, SWT.NONE);
        label.setText(\"Activator: \");
        label.setLayoutData(new GridData(SWT.BEGINNING, SWT.BEGINNING, falsefalse, 1, 1));
        text = new Text(group2, SWT.BORDER);
        text.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, truefalse, 1, 1));
Code Formatted by ToGoTutor

其它几个控件的布局,这里就不一一介绍了。

这个例子我们可以看到,使用GridLayout和容器控件(Composite和Group),我们可以构造任何形式的布局。不过在动手之前,还是强烈建议画一个草图,确定每个GridLayout是几行几列,每个控件在水平方向和垂直方向各占几格,是否需要填充格子,还是左对齐,右对齐,格子尺寸变化时,控件是否需要改变尺寸。如果控件位置不理想,在后续开发中发现需要作重大调整的话,工作量还是挺大的,要比图形化布局编辑器要麻烦。那么适当的注释也很必要,标记出控件之间的层次关系,便于理解。另外控件的布局也需要不断的调试,在运行态观察布局是否清晰,合理,美观。


原创粉丝点击