作者:梁祺 (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。
LayoutDialog dialog = new LayoutDialog(window.getShell());
dialog.open();
}
接下来我们来创建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。
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, true, true, 1, 1));
...
return composite;
}
}
下面我们看一下GridData,GridData有不少构造函数,其实我们只要记住下面这个就行了,以不变应万变。
boolean grabExcessHorizontalSpace, boolean grabExcessVerticalSpace,
int horizontalSpan, int verticalSpan)
- HorizontalAlignment: 水平方向左对齐,居中对齐,右对齐,或者充满整个格子。分别是 SWT.BEGINNING/CENTER/END/FILL。
- verticalAlignment:垂直方向上对齐,居中对齐,下对齐,或者充满整个格子。分别是 SWT.BEGINNING/CENTER/END/FILL。
- grabExcessHorizontalSpace:水平方向空间增加时,是否占据整个水平方向的空间。比如,当用户拖大对话框, 这个参数为true的空间将占据增加的部分。
- grabExcessVerticalSpace:垂直方向空间增加时,是否占据整个水平方向的空间。
- horizontalSpan: 空间在水平方向上占几个格子。
- verticalSpan: 空间在垂直方向上占几个格子。
所以composite占据1x1的格子,SWT.FILL说明它在两个方向都是充满整个格子,并且如果格子尺寸变大, 它在两个方向都会占据新增加的部分。
接下来看看各个控件是怎么布局的,整个代码比较长,我们先看看三个Group,它们的代码很类似, 设置了相同的LayoutData,都占据一个1x1的格子,水平方向充满整个格子,垂直方向上对齐,格子尺寸变化时, 水平方向占据新空间。它们也都将自己内部的布局方式设置为GridLayout,根据前面的分析,都是三列,列宽不要求相同。
Group group1 = new Group(composite, SWT.NONE);
group1.setText(\ "Properties\");
group1.setLayoutData( new GridData(SWT.FILL, SWT.BEGINNING, true, false, 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, true, false, 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, true, false, 1, 1));
group3.setLayout( new GridLayout(3, false));
再看一下Properties Group的第一行,label和text。label放在1x1的格子里,左对齐,上对齐,格子大小改变时, 控件尺寸不变。text放在2x1的格子里,充满水平方向,格子尺寸改变时,控件水平方向尺寸作相应的改变。
label.setText(\ "ID: \");
label.setLayoutData( new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 1, 1));
Text text = new Text(group1, SWT.BORDER);
text.setLayoutData( new GridData(SWT.FILL, SWT.BEGINNING, true, false, 2, 1));
再看一下Options Group的第二行,第一个label是一个占位控件,本身没有任何用处,就是为了占住这行的第一格, 以便Activator从第二列开始,看起来像从属于上一行的控件。因为占位控件没有文字, 并且它是第一列中唯一独占第一列的控件(其他都不单独属于第一列),所以它的宽度就决定了第一列的尺寸, 就需要为它指定宽度,否则第一列的宽度就变成0了(不考虑列边距)。这里我们设置它的宽度widthHint为10。
GridData layoutData = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false, 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, false, false, 1, 1));
text = new Text(group2, SWT.BORDER);
text.setLayoutData( new GridData(SWT.FILL, SWT.BEGINNING, true, false, 1, 1));
其它几个控件的布局,这里就不一一介绍了。
这个例子我们可以看到,使用GridLayout和容器控件(Composite和Group),我们可以构造任何形式的布局。 不过在动手之前,还是强烈建议画一个草图,确定每个GridLayout是几行几列,每个控件在水平方向和垂直方向各占几格, 是否需要填充格子,还是左对齐,右对齐,格子尺寸变化时,控件是否需要改变尺寸。如果控件位置不理想, 在后续开发中发现需要作重大调整的话,工作量还是挺大的,要比图形化布局编辑器要麻烦。那么适当的注释也很必要, 标记出控件之间的层次关系,便于理解。另外控件的布局也需要不断的调试,在运行态观察布局是否清晰,合理,美观。