理解SWT布局

来源:互联网 发布:淘宝兼职怎么找商家 编辑:程序博客网 时间:2024/05/29 18:28
摘要
      当你用SWT编写应用程序的时候,你可能需要用布局(layout)来给你的窗口设置特特定的外观。布局控制组合窗口组件(composite)中的子组件的位置和大小。布局类都是抽象类Layout的子类。这篇文章为你展示了如何使用标准布局,以及如何编写定制你自己的布局。

作者:Carolyn MacLeod, OTI
    2001年3月22日
修订:Shantha Ramachandran, OTI
    2002年5月2日
翻译:Link
    2006年5月
原文载于Eclipse Corner http://www.eclipse.org/articles/Understanding%20Layouts/Understanding%20Layouts.htm
--------------------------------------------------------------------------------
布局
 
概述
    当你用标准小窗口工具箱(SWT)编写应用程序的时候,你可能需要用布局来为你的窗口定制特定的外观。布局控制组合窗口组件(composite)中的子组件的位置和大小。 布局类都是抽象类Layout的子类。SWT提供了很多标准的布局类,你可以编写定制的布局类。

    在SWT中,定位和尺寸缩放不能自动地发生。应用程序可以初始化地确定一个Composite的子组件的大小和位置,或者可以通过一个调整大小的监听器来实现。另一个选择是指定一个布局类来定位和缩放这些子组件。如果子组件没有给定的尺寸大小,它们将会具有零尺寸(zero size)并且是不可见的。

   下面的图中显示了一些讨论布局时通常使用的术语。Composite(在这个例子中,是一个TabFolder)有一个location,一个clientArea和一个trim。Composite的大小是clientArea的大小加上trim的大小。这个Composite有两个并排布置的子组件。布局(Layout)管理子组件的大小和位置。这个布局允许子组件之间的spacing,以及子组件和布局边缘之间的margin。布局的大小和Composite的clientArea的大小一样。

        

    小窗口部件(widget)的首选大小是显示它的内容所需要的最小的大小。在这个composite的例子中,首选大小是包含所有它的子组件的最小的矩形。如果子组件已经被应用程序定位,composite会基于这些子组件的大小和位置计算它自己的首选大小。如果composite用布局类来定位它的子组件,它要求布局来计算clientArea的大小,然后加上trim来决定它的首选大小。
标准布局
   在SWT库中的标准布局类是:

· FillLayout – 在单行或者单列中放置相同大小的小窗口部件。
· RowLayout – 在一行或者多行中放置小窗口部件,应用了fill,wrap和spacing等选项。
· GridLayout – 在网格中放置小窗口部件。
· FormLayout * 2.0新特性*通过定义四边的“粘贴”位置来放置小窗口部件。
为了使用这些标准布局,你必须导入SWT的布局包:
   import org.eclipse.swt.layout.*;
布局是可插件化的。为了设置composite小窗口部件的布局,你可以使用composite小窗口部件的setLayout(Layout)方法。在接下来的代码中,一个Shell(Composite的子类)用RowLayout来定位它的子组件:
  
   Shell shell = new Shell();
   shell.setLayout(new RowLayout());
   一个布局类可能有一个对应的布局数据(layout data)类 – 包括了定制子组件的布局数据的Object的子类。习惯上,布局数据类通过取代布局类类名中的Layout为Data为标识。例如:标准布局类RowLayout具有布局数据类RowData。布局类GridLayout使用布局数据类GridData,而布局类FormLayout具有名称为FormData的布局数据类。一个窗口小部件的布局数据类的设置如下:

   Button button = new Button(shell, SWT.PUSH);
   button.setLayoutData(new RowData(50, 40));
这个文档中的例子
这个文档中绝大多数的快照是通过运行下面的示例代码的变更版本来得到的。我们将会改变布局的种类,使用的选项,或者是子组件的数目。

import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;

public class LayoutExample {

      public static void main(String[] args) {
            Display display = new Display();
            Shell shell = new Shell(display);
            // Create the layout.
            RowLayout layout = new RowLayout();
            // Optionally set layout fields.
            layout.wrap = true;
            // Set the layout into the composite.
            shell.setLayout(layout);
            // Create the children of the composite.
            new Button(shell, SWT.PUSH).setText("B1");
            new Button(shell, SWT.PUSH).setText("Wide Button 2");
            new Button(shell, SWT.PUSH).setText("Button 3");
            shell.pack();
            shell.open();
            while (!shell.isDisposed()) {
                  if (!display.readAndDispatch()) display.sleep();
            }
      }
}
    运行以上代码得到如下的结果:
      
    如果用户调整了shell的大小使得右边的Button 3没有足够的空间,RowLayout将Button 3换行放置到下一行,如下所示:
    
    我们将会看到,使用布局和调整大小有紧密的关系。因此,这个文档中大多数的例子展示了当Composite变大或者变小时将会发生什么,以举例说明布局是如何工作的。
FillLayout
    FillLayout是最简单的布局类。它在单行或者单列中放置小窗口部件,强制它们为同一大小。开始,窗口小部件都和最高的容器小部件一样高,和最宽的窗口小部件一样宽。FillLayout不会换行,而且你不能定制空白(margin)和间隔(spacing)。当Composite只有一个子组件时FillLayout也能被使用。例如,如果一个Shell有一个Group子组件,FillLayout会使Group完全填充这个Shell。
这里是示例代码相关的一部分。首先我们创建了一个FillLayout,然后(如果我们需要垂直的样式)我们设定它的type域为SWT.VERTICAL,并且把它设定到Composite(一个Shell)中。这个Shell有三个按扭子组件,B1,B2和B3。注意在FillLayout中,子组件总是有相同的大小,而且充满可用空间。

    FillLayout fillLayout = new FillLayout();
    llLayout.type = SWT.VERTICAL;
    shell.setLayout(fillLayout);
    new Button(shell, SWT.PUSH).setText("B1");
    new Button(shell, SWT.PUSH).setText("B2");
    new Button(shell, SWT.PUSH).setText("Button 3");
下面的表格显示了水平和垂直样式的FillLayout,初始状态以及当父组件增大时的不同点

初始状态
缩放后
fillLayout.type = SWT.HORIZONTAL
(默认)
fillLayout.type = SWT.VERTICAL

RowLayout
    RowLayout比FillLayout的使用更为普遍,因为它能够换行(warp)和提供了可配置的空白(margin)和间隔(spacing)。RowLayout有很多可配置的域。而且,RowLayout中的每个小窗口部件的高度和宽度都可以通过使用setLayoutData设置小窗口部件的RowData对象来指定。
RowLayout的可配置域

Type
* 2.0新特性*  
    type域控制RowLayout在水平行中还是在垂直列中放置小窗口部件。默认情况下RowLayout是水平方式的。

Wrap
    warp域控制RowLayout当前行中没有足够的空间时是否把小窗口部件换行放放置到下一行。默认情况下RowLayout是换行的方式.
pack
     如果pack为true,RowLayout中的小窗口部件会采用它们正常的大小,而且它们会尽可能的左对齐。如果pack为false,小窗口部件会填充可用的空间,就象FillLayout中的小窗口部件一样。默认情况下RowLayout的pack为false。
justify
     如果justify域为true,RowLayout中的小窗口部件从左到右在可用的空间中伸展开。如果父Composite变大,额外的空间均匀的分布在小窗口部件之中。如果packjustify都为true,窗口小部件采用它们正常的大小,而且额外的空间被放置在这些小窗口部件之间以保持它们完全的散开(justify)。默认情况下RowLayout不进行散开。
MarginLeft, MarginTop, MarginRight, MarginBottom和Spacing
    这些域控制小窗口部件之间的象素数(spacing)以及小窗口部件和父Composite的边界之间的象素数(margin)。默认情况下RowLayout为空白(margin)和间隔(spacing)保留3个象素。空白和间隔在下面的图中展示:
   
     
   RowLayout的例子
下面的示例代码中创建了一个RowLayout,设置它的所有域为非默认的值,然后把它设定到一个Shell中。
     RowLayout rowLayout = new RowLayout();
      rowLayout.wrap = false;
      rowLayout.pack = false;
      rowLayout.justify = true;
      rowLayout.type = SWT.VERTICAL;
      rowLayout.marginLeft = 5;
      rowLayout.marginTop = 5;
      rowLayout.marginRight = 5;
      rowLayout.marginBottom = 5;
      rowLayout.spacing = 0;
      shell.setLayout(rowLayout);
   如果你仅使用默认的域值,你只需要一行代码:
     shell.setLayout(new RowLayout());  
在下面的表格中,展示了设置指定的域的结果:

初始状态
缩放后
wrap = true
pack = true
justify = false
type = SWT.HORIZONTAL
(默认)
wrap = false
(没有足够的空间则进行修剪)
pack = false
(所有的小窗口部件都有相同的大小)
justify = true
(小窗口部件在可用空间中伸展开来)
type = SWT.VERTICAL
(窗口小部件被垂直地安排在一列中)

RowLayout一起使用RowData对象
     每由RowLaout控制的一个小窗口部件都能通过设置它的RowData对象指定它初始的宽度和高度。以下代码用RowData对象来改变一个Shell中按钮(Button)的初始大小。
  
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
public class RowDataExample  
   public static void main(String[] args) {
       Display display = new Display();
       Shell shell = new Shell(display);
       shell.setLayout(new RowLayout());
       Button button1 = new Button(shell, SWT.PUSH);
       button1.setText("Button 1");
      button1.setLayoutData(new RowData(50, 40));
       Button button2 = new Button(shell, SWT.PUSH);
      button2.setText("Button 2");
       button2.setLayoutData(new RowData(50, 30));
       Button button3 = new Button(shell, SWT.PUSH);
     button3.setText("Button 3");
       button3.setLayoutData(new RowData(50, 20));
     shell.pack();
       shell.open();
     while (!shell.isDisposed()) {
           if (!display.readAndDispatch()) display.sleep();
       }
    }
}
这是你运行这些代码后看到的结果。
      
GridLayout
    
    GridLayout可能是最有用最强大的标准布局,但它也是最为复杂的。在一个GridLayout中,一个Composite的小窗口部件子组件被放置在网格中。GridLayout有很多可配置的域,而且,和RowLayout一样,它所放置的小窗口部件可以有一个关联的布局数据对象,叫做GridData。GridLayout的强大在于它具有为GridLayout所控制的每一个小窗口部件配置GirdData的能力。

GridLayout的可配置域
NumColumns
numColumns域是GridLayout中最重要的域,而且它通常也是应用程序会设定的第一个域。小窗口部件被从左到右放置在不同的列中,当有numColumns + 1个小窗口部件被加到Composite中时,一个新的行就会被创建。默认情况下只有1列。接下来的代码创建了一个有着五个不同宽度的按钮(Button)的Shell,并用GridLayout来管理。以下的表格显示了当numColumns被设为1,2或者3时网格的情况。

Display display = new Display();
Shell shell = new Shell(display);
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 3;
shell.setLayout(gridLayout);
new Button(shell, SWT.PUSH).setText("B1");
new Button(shell, SWT.PUSH).setText("Wide Button 2");
new Button(shell, SWT.PUSH).setText("Button 3");
new Button(shell, SWT.PUSH).setText("B4");
new Button(shell, SWT.PUSH).setText("Button 5");
shell.pack();
shell.open();
while (!shell.isDisposed()) {
     if (!display.readAndDispatch()) display.sleep();
}
numColumns = 1
numColumns = 2
numColumns = 3

MakeColumnsEqualWidth
    makeColumnsEqualWidth域强制让列都具有相同的宽度。默认值为false。如果我们改变上面的代码让它具有相同宽度的3列,这就是我们将得到的(注意:在没有进一步说明的情况下,小窗口部件在列中都是左对齐的)。

     

MarginWidth, MarginHeight, HorizontalSpacing, 和VerticalSpacing
     GridLayout中的marginspacing域和RowLayout中的这些域是相同的。不同的地方在于左边和右边的空白(margin)被组合成marginWidth,而项部和底部的空白被组合成marginHeight。而且,在GridLayout中,你可以独立地指定horizontalSpacingverticalSpacing,然而在RowLayout中,spacing应用于水平或者垂直样式是取决于RowLayout的类型的。
GridData 对象域
   GridData是关联于GridLayout的布局数据对象。为了设置一个小窗口部件的GridData对象,你可以使用setLayoutData方法。例如:为了给一个按钮(Button)设定GridData,我们可以象下面这样做:

Button button1 = new Button(shell, SWT.PUSH);
button1.setText("B1");
button1.setLayoutData(new GridData());

   当然,这段代码只是用所有它的域的默认值创建了一个GridData对象,这各没有完全设置布局数据是一样的。有两种创建带有某些已设置域的GridData对象的方法。第一种方法是直接对域进行赋值,象这样:

GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
button1.setLayoutData(gridData);

第二种方法是是利用一些有用的API - GridData定义的“类型位”(style bits)。

button1.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL |    GridData.GRAB_HORIZONTAL));

   事实上,为了进一步的方便,还提供了一些常用的“类型位”的联合。
   button1.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

   注意到FILL_这些方便的类型设置了填充式排列(fill alignment)和抡占式(grab)填充。GridData的“类型位”只能被布尔型或者枚举型域使用。数据域必须直接进行设置。

(Swing程序员注意)在我们讲述它们的域之前关于GridData最后要注意的是:不要重用GridData对象。每一个Composite中被GridLayout管理的小窗口部件必须有一个唯一的GridData对象。如果在布局的时候一个GridLayout中的小窗口部件的布局数据为null,就会为它创建一个唯一的GridData对象。

HorizontalAlignment和VerticalAlignment
    Alignment域用于指定在一个网格单元中的哪个位置水平的和/或者垂直的放置一个小窗口部件。每一个alignment域可以有下列的其中的一个值:
•     BEGINNING
•      CENTER
•     END
•     FILL
默认的horizontalAlignment值是BEGINNING(或者左对齐)。默认的verticalAlignment值是CENTER。
   让我们回到我们那个在三个列中有五个按钮的例子,我们会改变Button 5的horizontalAlignment值。
horizontalAlignment = GridData.BEGINNING
(默认)
horizontalAlignment = GridData.CENTER
horizontalAlignment = GridData.END
horizontalAlignment = GridData.FILL



HorizontalIndent
  horizontalIndent域允许你通过指定一个特定数目的象素数来把一个小窗口部件右移。这个域典型的只在horizontalAlignment值为BEGINNING时有用。我们不能用一个“类型位”来设置缩进,所以我们象下面这样把我们的例子中的Button 5缩进4个象素:


GridData gridData = new GridData();
gridData.horizontalIndent = 4;
button5.setLayoutData(gridData);

HorizontalSpan and VerticalSpan
   Span域让小窗口部件占有多于一个网格单元的空间。它们通常和FILL排列(aligment)一起使用。我们象下面这样让我们例子中的的Button 5跨越最后两个网格单元:

   

GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
button5.setLayoutData(gridData);

如果我们决定让我们的Wide Button 2跨越两个网格单元,我们可以以这样的方式结束:



GridData gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.horizontalSpan = 2;
button2.setLayoutData(gridData);

或者我们可以让Button 3垂直地跨越两个网格单元:



GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.verticalSpan = 2;
button3.setLayoutData(gridData);

GrabExcessHorizontalSpace和GrabExcessVerticalSpace
    grabExcessHorizontalSpacegrabExcessVerticalSpace域典型的被较大的小窗口部件如Text,List或者Canvas使用,以允许当它们包含的Composite变大时它们也相应的增大。如果一个Text抢占(grab)了额外的水平空间并用用户缩放了Shell的宽度,那么这个Text会占有所有这些新的水平空间而同一行中的其他的小窗口部件会维持原来的宽度。当然,当Shell缩小时抢占了额外空间的小窗口部件也是最先收缩的。在缩放的上下文中总是考虑grabExcessSpace域是最容易的。作为一个简单的例子,我们重新使用前面Button 3垂直地跨越两个网格单元的例子。这是这个例子的又一次展现:

  

   如果我们缩放这个窗口,唯一发生的事情只是窗口变大了。



    现在,我们让Button 3抢占额外的水平和垂直的空间,并且让B1和B4垂直地填充(不使用抢占),然后我们再一次对窗口进行缩放:

  
  

    这一次,Button 3再两个方向上都增大了,而B4垂直的增大。其他的按钮维持原来的大小。因为Button 3是垂直地抢占的并且它跨越了两行,它所跨越的最后一行变高了。注意到B1并没有增大-虽然它是垂直填充的-因为它的行并没有增大。因为Button 3是水平地抢占的,它的列变宽了,并且因为它是水平的填充的,它的宽度变大了以填充这一列。这里是所有五个按钮的代码:

Button button1 = new Button(shell, SWT.PUSH);
button1.setText("B1");
GridData gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
button1.setLayoutData(gridData);

new Button(shell, SWT.PUSH).setText("Wide Button 2");

Button button3 = new Button(shell, SWT.PUSH);
button3.setText("Button 3");
gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
gridData.verticalSpan = 2;
gridData.grabExcessVerticalSpace = true;
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
button3.setLayoutData(gridData);

Button button4 = new Button(shell, SWT.PUSH);
button4.setText("B4");
gridData = new GridData();
gridData.verticalAlignment = GridData.FILL;
button4.setLayoutData(gridData);

new Button(shell, SWT.PUSH).setText("Button 5");

   在一个典型的应用程序窗口中,你通常需要有至少一个抢占的小窗口部件。如果多于一个窗口试图抢占同样的空间,那么这些额外的空间会被均匀的分配到这些抢占式的小窗口部件中:
   
      

    最后一个关于抢占需要注意的地方。如果一个小窗口部件抢占了额外的水平空间并且它的父Composite变宽了,那么包含这个小窗口部件的整个列都变宽了。如果一个小窗口部件抢占了额外的垂直空间并且它的父Composite变高了,那么包含这个小窗口的整个行都变高了。这其中的含义是说如果任何在同一受影响的列或者行中的小窗口部件有具有填充排列(fill alignment)的属性,那么它也会伸展。具有开始(beginning),居中(center) 或结束(end)的排列属性不会伸展-他们会维持在变宽的这一列或者变宽的这一行的开始,中间或者结束处。

WidthHint和HeightHint
如果不和GridLayout的约束系统中的其他需求发生冲突,widthHintheightHint域指出了你要让一个小窗口部件具有的宽或者高的象素数。回到五个按钮,三列的例子,假如我们要Button 5有70个象素宽40个象素高。我们象下面这样编码:

   GridData gridData = new GridData();
   gridData.widthHint = 70;
   gridData.heightHint = 40;
   button5.setLayoutData(gridData);

    Button 5在窗口中正常的大小如下左图所示,而70个象素宽40个象素高的Button 5如下右图所示:
    
       
  
    注意到,虽然如此,如果Button 5的horizontalAlignment是FILL,那么GridLayout就不能满足70个象素宽的需求。

    使用宽度和高度hint的最后一个意见。在一个平台上看起来很好的事情不一定在另一个平台也看起来很好。跨平台的字体大小和正常小窗口部件大小间的变化意味着硬编码的象素值通常不是布局窗口的最好办法。因此,如果你要使用大小hint,也让它尽可能少地被使用。

一个复杂的GridLayout的例子
    至到目前为止,为了展示各个域是如何工作的,GridLayout的例子都相当的简单。现在,我们把它们放在一起创建一个比较复杂的例子。我们由手画一个粗糙的我们将要创建的窗口的草图开始,用以决定例如网格将含有多少个列,一些小窗口部件是否需要被伸展等问题。

     

    然后,我们开始从图中编写这个例子。代码如下。注意到我们增加了一些逻辑使得这段代码更有趣,例如,Browse…打开一个文件对话框(FileDialog)来读入一张图片,Canvas在一个重绘监听器中显示这张图片,Delete删除这张图片,Enter打印当前的狗以及它的主人的信息。这个例子在一个单独的main函数中编码,这让我们集中于布局的代码编写而不用为编程风格而感到烦乱。

import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;

public class ComplexGridLayoutExample {
     static Display display;
     static Shell shell;
    static Text dogName;
     static Combo dogBreed;
     static Canvas dogPhoto;
     static Image dogImage;
     static List categories;
     static Text ownerName;
     static Text ownerPhone;

   public static void main(String[] args) {
       display = new Display();
         shell = new Shell(display);
         shell.setText("Dog Show Entry");
         GridLayout gridLayout = new GridLayout();
         gridLayout.numColumns = 3;
         shell.setLayout(gridLayout);
  
         new Label(shell, SWT.NONE).setText("Dog's Name:");
         dogName = new Text(shell, SWT.SINGLE | SWT.BORDER);
         GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
         gridData.horizontalSpan = 2;
         dogName.setLayoutData(gridData);

         new Label(shell, SWT.NONE).setText("Breed:");
         dogBreed = new Combo(shell, SWT.NONE);
         dogBreed.setItems(new String [] {"Collie", "Pitbull", "Poodle", "Scottie"});
         dogBreed.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));

         Label label = new Label(shell, SWT.NONE);
         label.setText("Categories");
         label.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER));
  
         new Label(shell, SWT.NONE).setText("Photo:");
         dogPhoto = new Canvas(shell, SWT.BORDER);
         gridData = new GridData(GridData.FILL_BOTH);
         gridData.widthHint = 80;
         gridData.heightHint = 80;
         gridData.verticalSpan = 3;
         dogPhoto.setLayoutData(gridData);
         dogPhoto.addPaintListener(new PaintListener() {
             public void paintControl(final PaintEvent event) {
                 if (dogImage != null) {
                       event.gc.drawImage(dogImage, 0, 0);
                  }
             }
        });

        categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL);
        categories.setItems(new String [] {
             "Best of Breed", "Prettiest Female", "Handsomest Male",
            "Best Dressed", "Fluffiest Ears", "Most Colors",
             "Best Performer", "Loudest Bark", "Best Behaved",
             "Prettiest Eyes", "Most Hair", "Longest Tail",
             "Cutest Trick"});
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_FILL);
        gridData.verticalSpan = 4;
        int listHeight = categories.getItemHeight() * 12;
        Rectangle trim = categories.computeTrim(0, 0, 0, listHeight);
        gridData.heightHint = trim.height;
        categories.setLayoutData(gridData);
  
        Button browse = new Button(shell, SWT.PUSH);
        browse.setText("Browse...");
        gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
        gridData.horizontalIndent = 5;
        browse.setLayoutData(gridData);
        browse.addSelectionListener(new SelectionAdapter() {
           public void widgetSelected(SelectionEvent event) {
                 String fileName = new FileDialog(shell).open();
                  if (fileName != null) {
                     dogImage = new Image(display, fileName);

                    //原文没有这一句,这一句为作者添加

                    dogPhoto.redraw();


                 }
            }
       });
  
       Button delete = new Button(shell, SWT.PUSH);
       delete.setText("Delete");
       gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL |                 GridData.VERTICAL_ALIGN_BEGINNING);
      gridData.horizontalIndent = 5;
     delete.setLayoutData(gridData);
     delete.addSelectionListener(new SelectionAdapter() {
         public void widgetSelected(SelectionEvent event) {
              if (dogImage != null) {
                   dogImage.dispose();
                  dogImage = null;
                   dogPhoto.redraw();
              }
         }
     });
  
   Group ownerInfo = new Group(shell, SWT.NONE);
     ownerInfo.setText("Owner Info");
     gridLayout = new GridLayout();
     gridLayout.numColumns = 2;
   ownerInfo.setLayout(gridLayout);
   gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
     gridData.horizontalSpan = 2;
     ownerInfo.setLayoutData(gridData);
  
   new Label(ownerInfo, SWT.NONE).setText("Name:");
     ownerName = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
     ownerName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
  
   new Label(ownerInfo, SWT.NONE).setText("Phone:");
      ownerPhone = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
     ownerPhone.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
  
     Button enter = new Button(shell, SWT.PUSH);
     enter.setText("Enter");
     gridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
     gridData.horizontalSpan = 3;
     enter.setLayoutData(gridData);
     enter.addSelectionListener(new SelectionAdapter() {
       public void widgetSelected(SelectionEvent event) {
              System.out.println("/nDog Name: " + dogName.getText());
              System.out.println("Dog Breed: " + dogBreed.getText());
             System.out.println("Owner Name: " + ownerName.getText());
              System.out.println("Owner Phone: " + ownerPhone.getText());
              System.out.println("Categories:");
              String cats[] = categories.getSelection();
              for (int i = 0; i < cats.length; i++) {
                   System.out.println("/t" + cats[i]);
              }
         }
   });
  
     shell.pack();
     shell.open();
     while (!shell.isDisposed()) {
       if (!display.readAndDispatch()) display.sleep();
     }
     if (dogImage != null) {
       dogImage.dispose();
     }
   }
}

    这是当Mary Smith输入Fifi后窗口看起来的样子:

  

   如果窗口被放大,布局调整如下所示:



注意下面这些问题:
• 这里有3列7行。
dogPhoto这个Canvas会变宽和变高同,这是因为它是填充的并且水平地和垂直的抢占的(虽然我们本应该缩放图片,但我们没有对图片进行缩放)。
dogBreed这个Combo会变宽,因为它是水平的填充的,并且它和Canvans在同一列。
dogName这个Text会变宽,那是因为它是水平填充的,并且它所跨越的其中一列包含有Canvas。
   • Categories这个List会变高,这是因为它是垂直填充的,并且它和Canvas跨越了相同的行。
• 因为categories这个List变高了,它的垂直滚动条消失了(它并没有变宽)。
ownerInfo这个Group会变宽,这是因为它是水平的填充的,并且它所跨越的其中一列包含有Canvas。
• 作为Composite的子类,ownerInfo这个Group有它自己的2列2行的GridLayout。
ownerNameownerPhone这两个Text会变宽,这是因为Group会变宽,并且它们在Group的GridLayout中是填充的和水平抢占的。
browsedelete这两个Button会稍微的缩进,而且因为它们都是水平填充的,它们有相同的宽度。
delete这个Button在它的行的顶部垂直的对齐。
• “Categories”这个Label在categories这个List的顶端居中。
enter这个Button是水平地对齐到它所跨越的3列的最右边。
dogPhoto这个Canvas用宽度和高度hint来创建,这是因为如果可能的话,我们要让Image有80象素×80象素的大小。
categories这个List用List的字体的12倍的高度hint值来创建,这是因为我们要让List在初始状态下显示十二个项。
FormLayout * 2.0新特性*

   FormLayout通过为小窗口部件创建四边的Form附加值(attachment)来进行工作,并且把这些Form附加值存储在布局数据中。一个附加值让一个小窗口部件指定的一边粘贴(attach)到父Composite的一个位置或者这个布局中的另一个小窗口部件上。这为布局提供了极大的便利,并且这也允许你指定布局中单个小窗口部件的放置。

FormLayout的可配置域

MarginWidth, MarginHeight
     FormLayout中的margin域类似于GridLayout中的相同域。左边和右边边距(margin)被定义成marginWidth,而顶部和底部的边距被定义成marginHeight。边距也能根据每个小窗口部件的附加值来定义。FormLayout边距在默认情况下为0。
  
     为了设置这些边距,我们创建一个FromLayout,然后设置它的边距域。下面这段代码会在父Composite的四个边都设置5个象素的边距。

Display display = new Display ();
Shell shell = new Shell (display);
FormLayout layout= new FormLayout ();
layout.marginHeight = 5;
layout.marginWidth = 5;
shell.setLayout (layout);
FormData对象域
      FormData对象用于指定FormLayout中的每一个小窗口部件怎么样放置。每一个FormData对象定义了小窗口部件四个边的附加值值。这些附加值决定在哪里定位小窗口部件的四个边。你可以用setLayoutData方法来设定一个小窗口部件的FormData对象。例如:

   Button button1 = new Button(shell, SWT.PUSH);
   button1.setText("B1");
   button1.setLayoutData(new FormData());

     但是,这段代码创建了一个没有附加值的FormData对象。在这个例子中,定义了默认的附加值值,这没有体现出FormLayout的目的和效用。默认的附加值值让小窗口部件粘贴到父Composite的左边和顶部。如果FormLayout中的每一个小窗口部件都使用了默认的附加值值,它们就会全部被重叠的放置在父Composite的左上角。

Left, Right, Top和Bottom
     FormLayout中的left, right, topbottom域分别指定了与小窗口部件的左边,右边,顶部和底部相关联的Form附加值对象。这些域被象下面的例中一样设定:

FormData formData = new FormData();
formData.top = new FormAttachment (0,60);
formData.bottom = new FormAttachment (100,-5);
formData.left = new FormAttachment (20,0);
formData.right = new FormAttachment (100,-3);
button1.setLayoutData(formData);

     一个Form附加值对象定义了一个小窗口部件指定的边的附加值。有很多方法能定义小窗口部件的边的粘贴:相对于父Composite的一个位置,相对于Composite的一个边,相对于另一个小窗口部件的相邻边,相对于另一个小窗口部件的相对边,或者和另一个小窗口部件居中。相对于父Composite的一个位置粘贴放置了小窗口部件的边,因此通常用相对于父Composite的百分比来表示。如果贴粘到父Composite的边缘,这个百分比就是0%或者100%。

      相对于另一个小窗口部件的相邻边粘贴保证了小窗口部件的指定边总是与另一个小窗口部件的最近边相邻。相对于另一个小窗口部件的相对边粘贴保证了小窗口部件的指定边总是与另一个小窗口部件的最远边对齐。最后,相对于另一个小窗口部件居中让小窗口部件在另一个小窗口部件中居中。这些方法都可以加或者不加偏移值来实现。
      如果没有为FormData对象定义附加值,默认会让小窗口部件粘贴到父Composite的左上角,象这样:

    

button1.setLayoutData(new FormData());

     注意到如果多于一个小窗口部件被定义为没有任何附加值的时候,这些小窗口部件都会被放置到窗口中相同的默认位置上,并且会发生重叠。Form附加值在Form附加值对象这一节中将进行更仔细的叙述。

Width和Height
     FormData中的widthheight域用来指定小窗口部件所需的宽度和高度。如果所请求的宽度和高度与附加值设置的约束发生冲突,那么这些请求的高度和宽度就不能遵从了。虽然通过设置附加值也以决定宽度和高度值,但有些情况下你不希望为小窗口部件的所有边都定义附加值值。在这种情况下,它们就可以很有用的象下面这样设定小窗口部件的宽度和高度值:

FormData formData = new FormData(20,30);
formData.top = new FormAttachment (0,60);
formData.left = new FormAttachment (20,0);
button1.setLayoutData(formData);

     如果你希望只设定宽度和高度值,你可以直接设置FromData对象的宽度和高度值:

FormData formData = new FormData ();
formData.width = 30;
formData.top = new FormAttachment (0,60);
formData.bottom = new FormAttachment (100,-5);
formData.left = new FormAttachment (20,0);
button1.setLayoutData(formData);

     注意,如果一个按钮粘贴到父Composite的两边,当这父Composite进行缩放时,这个按钮会随着父Composite增大和缩小。

FormAttachment对象
    From附加值是一个用来定义小窗口部件的指定边的附加值的对象。不需要总是为小窗口部件的四个边都设置附加值。通常只指定一个或者多个小窗口部件的边就能完全指定它的放置。为了更适当的放置你的小窗口部件,你必须至少为FormData中left或者right之一,以及top或者bottom之一定义附加值。如果你只希望粘贴小窗口部件的左边而不是右边,那么这个小窗口部件将用它的边来进行定位,并且这个小窗口部件会采用它的正常大小(或者当设置了请求大小时,采用它的请求大小)。如果你没有贴粘左边或者右边,默认的定义会把你的小窗口部件粘贴到窗体的左边。对于顶部和底部也是采用了相同的逻辑。

相对一个位置进行粘贴
     存在很多种类型的附加值。第一种就是粘贴到相对于父Composite的一个位置。通过定义一个100以内的百分值就能实现,例如:

  

FormData formData = new FormData();
formData.top = new FormAttachment (50,0);
button1.setLayoutData(formData);

    这里设置Button的顶部到相对于父Composite(一个Shell)高度的50%的位置,偏移量为0。当这个Shell进行缩放的时候,Button的顶部会始终在50%的位置,像这样:



   如果我们选择设定一个偏移值,Button的上部就会被设置为父Composite的50%加上或者减去为偏移量设置的象素数。

    我们也能定义不使用百分比定义按钮的位置,例如:
FormData formData = new FormData();
formData.top = new FormAttachment (30,70,10);
button1.setLayoutData(formData);

    如果父Composite的高度被定义为70个单位,这里设置了Button的顶部在父Composite顶部以下30个单位,再加上10个象素的地方。

相对于父Composite进行粘贴
     第二种类型的附加值是相对于父Composite的边缘进行粘贴。这与相对于一个位置进行粘贴的实现方法差不多,但是这个位置值只能是0%或者100%。当处于垂直样式的时候,0这个位置被定义为父Composite的顶部,但处于水平样式的时候被定义为左边。右边和底部边缘被定义为100。因此,如果我们要粘贴一个小窗口部件到父Composite的右边,我们只需要简单的创建一个附加值,然后设置它的位置值为100:

    

FormData formData = new FormData();
formData.right = new FormAttachment (100,-5);
button1.setLayoutData(formData);

     这里把Button的右边粘贴到了父Composite(一个Shell)的右边缘,并有一个5个象素的偏移值。注意,附加值是往一个方向增加的。如果你要让一个小窗口部件向下或者所右偏移,偏移值必须是正的。如果要小窗口部件向上或者向左偏移,这个偏移值必须是负的。现在当这个Shell被缩放时,这个Button就总是偏离右边缘5个象素:

   

相对于另一个小窗口部件进行粘贴
     第三种类型的附加值是相对于父Composite中的另一个控件进行粘贴。小窗口部件的边可以被粘贴到另一个控件的相邻边(默认),粘贴到另一个控件的相对边,或者小窗口部件可以相对于另一个控件居中,这些都认使用或者不使用偏移值。

   相对于另一个控件进行粘贴最常用的方法是粘贴到它的相邻边。例如下面的代码:

   FormData formData = new FormData();
   formData.top = new FormAttachment (20,0);
   button1.setLayoutData(formData);
  
   FormData formData2 = new FormData();
   formData2.top = new FormAttachment (button1,10);
   button2.setLayoutData(formData2);
  
把按钮2的顶部粘贴到了按钮1的底部,这是因为按钮1的底部和按钮2的顶部是相邻的。

  

    注意到当窗口被缩放时,按钮1会移动让它的顶部总是定位在Shell的20%的位置,而按钮2也是会移动让它的顶部总是在按钮1的相邻边(底部)以下10个象素的位置。
   
  
   虽然默认的情况下是相对于一个控件的相邻边进行粘贴,FromAttachment也能被创建用来相对一个控件的相对边进行粘贴。当要排列小窗口部件的时候这就非常的有用了。在这种情况下,你可以用TOP, BOTTOM, LEFT或者RIGHT排列(alignment)创建相对于另一个控件的附加值,例如:
  
formData2.top = new FormAttachment (button1,0,SWT.TOP);
  
     在接下来的例子中,按钮1的顶部边被定位在Shell的20%的地方。按钮2使用了TOP排列,它的顶部边和按钮1的顶部边对齐。这意味着按钮2的顶部也被定位在这个Shell的20%的位置。注意到当指定了顶部附加值,只有小窗口部件的垂直放置会被定义。仍然需要为按钮2设置左边附加值以得Button不会发生重叠。

FormData formData = new FormData(50,50);
formData.top = new FormAttachment (20,0);
button1.setLayoutData(formData);

FormData formData2 = new FormData();
FormData2.left = new FormAttachment (button1,5);
formData2.top = new FormAttachment (button1,0,SWT.TOP);
button2.setLayoutData(formData2);

    结果如下:
   
  
    最后一个相对于另一个控件进行粘贴的方法是相对于另一个控件居中。当两个控件有不同的大小的时候,这就显得很有用了。在这种情况下,你用CENTER排列(aligmnet)创建相对于另一个控件的附加值,例如:

   formData.top = new FormAttachment (button1,0,SWT.CENTER);

      这会放置这个小窗口部件的顶部在一个允许这个小窗口部件相对于另一个控件居中的位置上,并且有一个值为0的偏移值。只设定顶部,或者底部,或者它们两者为居中附加值会导致相同的结果。这个小窗口部件的顶部边不会居中,但是整个小窗口部件会居中,所以这只需要指定一次。这里有一个例子:
     

FormData formData1 = new FormData (50,50);
button1.setLayoutData(formData1)

FormData formData2 = new FormData ();
formData2.left = new FormAttachment (button1,5);
formData2.top = new FormAttachment (button1,0,SWT.CENTER);
button2.setLayoutData (formData2);

    使用不同类型的附加值允许布局以不同的方法来定义。FormLayout解决了一些FillLayout,RowLayout或者GridLayout不能解决的问题,让它成为定义布局很有用的类。

    重要的不要定义循环的附加值。例如,不要把按钮1的右边粘贴到按钮2的左边,然后又把按钮2的左边粘贴到按钮1的右边。这会过度约束布局,导致不可预知的行为。虽然运算会被中断,但结果是不可预知的。因此,确实保证你没有过度约束你的小窗口部件。仅提供所需要的附加值来适当的放置的小窗口部件。
一个FormLayout的例子
     到目前为止,所有使用FormLayout的例子都只是围绕着一两个Button,为了展示FormAttachment是如何工作 的。下面,我们会举一个有更多的Button的例子来展示使用附加值如何安排布局。我们会从画一张描绘我们要创建的附加值的基本草图开始。



FormData data1 = new FormData();
data1.left = new FormAttachment (0,5);
data1.right = new FormAttachment (25,0);
button1.setLayoutData(data1);
  
FormData data2 = new FormData();
data2.left = new FormAttachment (button1,5);
data2.right = new FormAttachment (100,-5);
button2.setLayoutData(data2);
  
FormData data3 = new FormData(60,60);
data3.top = new FormAttachment (button1,5);
data3.left = new FormAttachment (50,-30);
data3.right = new FormAttachment (50,30);
button3.setLayoutData(data3);
  
FormData data4 = new FormData();
data4.top = new FormAttachment (button3,5);
data4.bottom = new FormAttachment (100,-5);
data4.left = new FormAttachment (25,0);
button4.setLayoutData(data4);
  
FormData data5 = new FormData();
data5.bottom = new FormAttachment (100,-5);
data5.left = new FormAttachment (button4,5);
button5.setLayoutData(data5);

     在这个例子中,因为没有为按钮1和按钮2定义顶部附加值,它们粘贴到了布局的顶部上。钮3在左边和右边使用了百分比和偏移值而在布局中居中。按钮4和按钮5粘贴到了布局的底部,并有5个象素的偏移量。

   

    当我们进行缩放时,附加值就更显得明显了。按钮1的左边和右边都相对进行粘贴,所以当窗口被缩放时它也增大了。注意到它的右边总是在窗口的25%的位置。相同的缩放效果也被应用到按钮2上,因为它的两边也都相对进行了粘贴。左边相对按钮1进行了粘贴,所以它总是会在窗口的25%加上5个象素的位置。按钮3水平地保持在窗口的中间。按钮4的顶部和底部都相对进行粘贴,因此当窗口被缩放的时候它垂直地增大了,但是它只相对于左边进行了粘贴而对右边则没有,因此水平方向上它没有增大。按钮5不会增大或者缩小,但它的左边会保持在离按钮4有5个象素远,离窗口底部5个象素远的地方。
  
  

一个复杂的FormLayout的例子
    为了举例说明FormLayout如何能够用于更复杂的布局,我们用FormLayout来重做上面为GridLayout举例的“狗狗展示条目”的例子。这段代码创建一个一样的布局,但是用不同的观念来实现它。

     我们会从修改为网格布局所作的设计草图开始。我们会画出如何做,而不仅仅画出我们希望做成什么样。这人例子会用到所有的附加值类型。

  
import org.eclipse.swt.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;

public class ComplexFormLayoutExample {
   static Display display;
   static Shell shell;
static Image dogImage;
   static Text dogNameText;
   static Combo dogBreedCombo;
   static Canvas dogPhoto;
   static List categories;
   static Text nameText;
   static Text phoneText;

   public static void main(String[] args){
       display = new Display();
       shell = new Shell(display);
      FormLayout layout= new FormLayout();
       layout.marginWidth = 5;
       layout.marginHeight = 5;
       shell.setLayout(layout);
       shell.setText("Dog Show Entry");
  
       Group ownerInfo = new Group(shell, SWT.NONE);
       ownerInfo.setText("Owner Info");
       FormLayout ownerLayout = new FormLayout();
       ownerLayout.marginWidth = 5;
       ownerLayout.marginHeight = 5;
       ownerInfo.setLayout(ownerLayout);
  
      Label dogName = new Label(shell, SWT.NONE);
      dogName.setText("Dog's Name:");
       dogNameText = new Text(shell, SWT.SINGLE | SWT.BORDER);
       Label dogBreed = new Label(shell, SWT.NONE);
       dogBreed.setText("Breed:");
       dogBreedCombo = new Combo(shell, SWT.NONE);
     dogBreedCombo.setItems(new String [] {"Collie", "Pitbull", "Poodle", "Scottie"});
       Label photo = new Label(shell, SWT.NONE);
       photo.setText("Photo:");
        dogPhoto = new Canvas(shell, SWT.BORDER);
      Button browse = new Button(shell, SWT.PUSH);
       browse.setText("Browse...");
     Button delete = new Button(shell, SWT.PUSH);
       delete.setText("Delete");
        Label cats = new Label (shell, SWT.NONE);
     cats.setText("Categories");
      categories = new List(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        categories.setItems(new String [] {
           "Best of Breed", "Prettiest Female", "Handsomest Male",
           "Best Dressed", "Fluffiest Ears", "Most Colors",
           "Best Performer", "Loudest Bark", "Best Behaved",
         "Prettiest Eyes", "Most Hair", "Longest Tail",
          "Cutest Trick"});
       Button enter = new Button(shell, SWT.PUSH);
      enter.setText("Enter");
  
       FormData data = new FormData();
      data.top = new FormAttachment (dogNameText,0,SWT.CENTER);
      dogName.setLayoutData(data);
    
      data = new FormData();
      data.left = new FormAttachment (dogName,5);
      data.right = new FormAttachment (100,0);
      dogNameText.setLayoutData(data);
  
      data = new FormData();
       data.top = new FormAttachment (dogBreedCombo,0,SWT.CENTER);
      dogBreed.setLayoutData(data);
  
      data = new FormData();
      data.top = new FormAttachment (dogNameText,5);
      data.left = new FormAttachment (dogNameText,0,SWT.LEFT);
      data.right = new FormAttachment (categories,-5);
      dogBreedCombo.setLayoutData(data);
  
      data = new FormData(80,80);
      data.top = new FormAttachment (dogBreedCombo,5);
      data.left = new FormAttachment (dogNameText,0,SWT.LEFT);
      data.right = new FormAttachment (categories,-5);
      data.bottom = new FormAttachment (ownerInfo,-5);
      dogPhoto.setLayoutData(data);
      dogPhoto.addPaintListener(new PaintListener() {
           public void paintControl(final PaintEvent event) {
                 if (dogImage != null) {
                    event.gc.drawImage(dogImage, 0, 0);
                }
          }
       });
        
      data = new FormData();
      data.top = new FormAttachment (dogPhoto,0,SWT.TOP);
      photo.setLayoutData(data);
  
       data = new FormData();
      data.top = new FormAttachment (photo,5);
      data.right = new FormAttachment (dogPhoto, -5);
      browse.setLayoutData(data);
      browse.addSelectionListener(new SelectionAdapter() {
          public void widgetSelected(SelectionEvent event) {
              String fileName = new FileDialog(shell).open();
              if (fileName != null) {
                   dogImage = new Image(display, fileName);
              }
          }
      });
  
      data = new FormData();
      data.left = new FormAttachment (browse,0,SWT.LEFT);
      data.top = new FormAttachment (browse,5);
      data.right = new FormAttachment (dogPhoto, -5);
      delete.setLayoutData(data);
      delete.addSelectionListener(new SelectionAdapter() {
          public void widgetSelected(SelectionEvent event) {
              if (dogImage != null) {
                   dogImage.dispose();
                   dogImage = null;
                   dogPhoto.redraw();
              }
          }
      });
  
      data = new FormData(90,140);
      data.top = new FormAttachment (dogPhoto,0,SWT.TOP);
      data.right = new FormAttachment (100,0);
      data.bottom = new FormAttachment (enter,-5);
      categories.setLayoutData(data);
      
    data = new FormData();
    data.bottom = new FormAttachment (categories,-5);
    data.left = new FormAttachment (categories,0,SWT.CENTER);
    cats.setLayoutData(data);

    data = new FormData();
    data.right = new FormAttachment (100,0);
    data.bottom = new FormAttachment (100,0);
    enter.setLayoutData(data);
    enter.addSelectionListener(new SelectionAdapter() {
          public void widgetSelected(SelectionEvent event) {
              System.out.println("/nDog Name: " + dogNameText.getText());
              System.out.println("Dog Breed: " + dogBreedCombo.getText());
              System.out.println("Owner Name: " + nameText.getText());
              System.out.println("Owner Phone: " + phoneText.getText());
              System.out.println("Categories:");
              String cats[] = categories.getSelection();
              for (int i = 0; i < cats.length; i++) {
                   System.out.println("/t" + cats[i]);
              }
         }
    });
  
     data = new FormData();
     data.bottom = new FormAttachment (enter,-5);
     data.left = new FormAttachment (0,0);
     data.right = new FormAttachment (categories,-5);
     ownerInfo.setLayoutData(data);
  
     Label name = new Label(ownerInfo, SWT.NULL);
     name.setText("Name:");
      Label phone = new Label(ownerInfo, SWT.PUSH);
      phone.setText("Phone:");
      nameText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
      phoneText = new Text(ownerInfo, SWT.SINGLE | SWT.BORDER);
       
      data = new FormData();
      data.top = new FormAttachment (nameText,0,SWT.CENTER);
      name.setLayoutData(data);
        
      data = new FormData();
      data.top = new FormAttachment (phoneText,0,SWT.CENTER);
      phone.setLayoutData(data);
        
      data = new FormData();
      data.left = new FormAttachment (phone,5);
      data.right = new FormAttachment (100,0);
      nameText.setLayoutData(data);
       
      data = new FormData();
      data.left = new FormAttachment (nameText,0,SWT.LEFT);
      data.right = new FormAttachment (100,0);
      data.top = new FormAttachment (55,0);
      phoneText.setLayoutData(data);
      
    shell.pack();
    shell.open();
  
    while (!shell.isDisposed()) {
         if (!display.readAndDispatch())
              display.sleep();
    }
    display.dispose();
}
}
   这是在“狗狗展示”中Mary Smith输入Fifi后布局看起来的样子:

  
  
    当这个窗口被缩放时,象GridLayout的例子中一样,相同的控件会被缩放。
   
编写你自己的布局类
   有时候,你可能需要编写你自己的布局类。有可能你的布局的需求很复杂。或者你有些地方你可能需要相同的外观,而你希望改进代码的重用率。或者你希望扩大领域知识来创建一个很有效率的布局类。不管原因是什么,在编写一个新的类之前,有几件事情是你要考虑的:
• 这个布局能够用GridLayout或者FormLayout,以及可能是一些嵌套布局来实现吗?
• 希望达到的效果可以更容易的用一个缩放监听器来实现吗?
• 你定义了一个通用的布局算法还是只是布置了小窗口部件?
除非你正在编写一种很通用的可以被很多Composite小窗口部件使用的布局类,通常比较好和容易的做法是简单的在缩放监听器中计算大小并且布置子组件。很多的SWT定制小窗口部件就是用这个方法编写的,虽然一个新的小窗口部件可以被实现为一个Composite/Layout对,把它实现成一个在缩放监听器中进行布局,并且在computeSize中计算的合适大小的Composite却更为清晰。

   首先,我们会先来看看布局是怎么样工作的,然后我们再创建一个新的布局类。另一个编写你自己的而已的例子可以在“用SWT创建你自己的小窗口部件”(Creating Your Own Widgets Using SWT)中的“复合小窗口部件示例”(Compound Widget Example)一节中找到,它显示了如何用一个缩放监听器或者一个新布局类达到相同的外观。

布局是如何工作的
    Layout是所有布局的超类。它只有两个方法computeSizelayout。这个类的定义如下:

public abstract class Layout {
   protected abstract Point computeSize(Composite composite, int widthHint, int heightHint, boolean flushCache);
   protected abstract void layout(Composite composite, boolean flushCache);
}

    一旦这个Composite的子组件被确定大小并根据布局类中的编写的布局算法来放置的时候,computeSize方法计算包括所有这些子组件的矩形区域的宽度和高度。hint参数允许宽度和/或者高度被约束。例如,如果一个维被约束,一个布局可能选择在另一个维上增长。一个SWT.DEFAULT的hint意思是使用合适的(preffed)大小。
   layout方法用来布置Coposite的子组件和确定Composite的子组件大小。布局可以选择缓存(cache)布局相关的信息,例如每一个子组件的合适上下文。flushCache参数告诉布局冲洗(flush)已缓存的数据。

   既然一个布局控制着小窗口部件在一个Composite中的大小和放置,Composite中也有一些方法可以和布局一起使用。
   最先的两个方法允许设置和得到Composite中的Layout对象。

public void setLayout(Layout layout);
public Layout getLayout();

    一个应用程序可以强制一个布局重新计算子组件的大小,并通过调用父Composite的layout()方法重新定位这些子组件。

public void layout(boolean changed);
public void layout();
    // calls layout(true);

   你需要在你改变了任何子组件,可能导致子组件的大小和位置发生改变的时候做这件事情,比如改变了子组件的字体,改变了子组件的文本或者图片,增加了新的子组件,或者为子组件增加了子组件。(如果子组件可以适应这个变化,布局可能不会成功—例如,改变一个可滚动的多行的Text的字体或者文本)。既然这些改变都是通过编程来实现的,它们不会触发事件。从而,父构件不会知道这些变化,必须通过layout方法来告许它。这个策略导致了闪动,这是因为应用程序可能作了好几个改变然后才告诉父构件重新布局,并且子组件只被重画一次而不是每次改变都重画一次。如果没有调用layout()并改变是在shell打开后发生的,那么子组件可能不会被正确的放置直到shell由于某种原因进行了缩放。注意shell.open()导致一个布局过程的发生。

   Composite的computeSize方法计算Composite的合适大小,这是它由这个布局决定的客户区(client)的大小加上它的修剪(trim)。

public Point computeSize(int widthHint, int heightHint, boolean changed);
public Point computeSize(int widthHint, int heightHint);
    // calls computeSize(widthHint, heightHint, true);

Composite的clientArea是包含所有的子组件的矩形区。一个布局在客户区中放置子组件。

public Rectangle getClientArea ();

Composite的trim(修剪)是客户区外的区域。对于一些Composite,修剪的大小是零。修剪可以通过传递客户区的尺寸给computeTrim计算出来。

public Rectangle computeTrim (int x, int y, int width, int height);

   调用Composite的pack方法将把它缩放到它的合适大小。

public void pack(boolean changed);
   // calls setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, changed));
public void pack();
   // calls pack(true);

    layoutcomputeSizepack方法的布尔型参数是changed标志。如果为true,它表明Composite的内容已经被通过某些方法改变而影响了它的合适大小,所以布局所持有的任何缓存都需要被冲洗。当一个Composite被缩放时,它调用layout(false)来让自己的布局安排它的子组件;因此小窗口部件的内容缓存没有被冲洗掉。这让布局只在必要的进要的时候进行代价高昂的计算。

    缓存可以改进性能,但它也可能是有欺骗性的。你可以选择完全不缓存 — 事实上,最好不要试力用缓存,直到你的代码已经稳定。当你考虑缓存什么时,确保你没有缓存任何小窗口部件状态,例如一个标签的文本,或者一个列表的项的数目。
定制存局的例子
   如果在你的应用程序中你有一些垂直导向的Composite小窗口部件,你可能会选择编写ColumnLayout。我们将会展示一个简单的布局类版本,它把Composite子组件放置到一个单列中。这个类有固定的修剪和间距。子组件被赋予相同的宽度,但它们能有自己自然的高度。(注意,在*2.0*中,RowLayout已经被扩展为有ColumnLayout的功能,如果它的类型被设为SWT.VERTICAL的话。正因为如此,这个例子只是作为一个例子。事实上,如果你现在需要把小窗口部件放在一列中,你可以用RowLayout来实现。)

   这段ColumnLayout类的例子就在下面。注意,我们缓存了小窗口部件子组件的宽度,子组件的高度和(加上了间距),而且这些值被用来计算大小和放置子组件。如果flushCachetrue,它们会被重新计算。
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;

public class ColumnLayout extends Layout {
     // fixed margin and spacing
     public static final int MARGIN = 4;
     public static final int SPACING = 2;

    // cache
    Point [] sizes;
   int maxWidth, totalHeight;

     protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
         Control children[] = composite.getChildren();
         if (flushCache || sizes == null || sizes.length != children.length) {
               initialize(children);
    }
    int width = wHint, height = hHint;
    if (wHint == SWT.DEFAULT) width = maxWidth;
    if (hHint == SWT.DEFAULT) height = totalHeight;
     return new Point(width + 2 * MARGIN, height + 2 * MARGIN);
}

   protected void layout(Composite composite, boolean flushCache) {
      Control children[] = composite.getChildren();
       if (flushCache || sizes == null || sizes.length != children.length) {
           initialize(children);
   }
Rectangle rect = composite.getClientArea();
   int x = MARGIN, y = MARGIN;
   int width = Math.max(rect.width - 2 * MARGIN, maxWidth);
for (int i = 0; i < children.length; i++) {
        int height = sizes[i].y;
       children[i].setBounds(x, y, width, height);
        y += height + SPACING;
}
}

void initialize(Control children[]) {
      maxWidth = 0;
      totalHeight = 0;
     sizes = new Point [children.length];
     for (int i = 0; i < children.length; i++) {
           sizes[i] = children[i].computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
            maxWidth = Math.max(maxWidth, sizes[i].x);
             totalHeight += sizes[i].y;
     }
       totalHeight += (children.length - 1) * SPACING;
   }
}

     这里有一些简单的测试代码来测试ColumnLayout。Growshrink按钮展示了在改变了其中一个子组件的宽度后调用Shell的layout()方法强制一个重新布局的过程。调用layout()和调用layout(true)是一样的,它告诉ColumnLayout在设定子组件的边界的时候冲掉它的缓存。Shell在放置好子组件后也调用pack()方法。这强制让Shell采用新的大小。

import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.events.*;

public class ColumnLayoutTest {
     static Shell shell;
     static Button button3;

    public static void main(String[] args) {
            Display display = new Display();
            shell = new Shell(display);
            shell.setLayout(new ColumnLayout());
            new Button(shell, SWT.PUSH).setText("B1");
           new Button(shell, SWT.PUSH).setText("Very Wide Button 2");
           (button3 = new Button(shell, SWT.PUSH)).setText("Button 3");
           Button grow = new Button(shell, SWT.PUSH);
           grow.setText("Grow Button 3");
           grow.addSelectionListener(new SelectionAdapter() {
                 public void widgetSelected(SelectionEvent e) {
                      button3.setText("Extreemely Wide Button 3");
                       shell.layout();
                      shell.pack();
               }
          });
           Button shrink = new Button(shell, SWT.PUSH);
           shrink.setText("Shrink Button 3");
           shrink.addSelectionListener(new SelectionAdapter() {
                  public void widgetSelected(SelectionEvent e) {
                       button3.setText("Button 3");
                       shell.layout();
                       shell.pack();
                   }
            });
          shell.pack();
          shell.open();
          while (!shell.isDisposed()) {
                 if (!display.readAndDispatch()) display.sleep();
           }
      }
}

     如果我们运行这段测试代码,窗口会像左图看起来那样。按下Grow Button 3按钮会导致窗口如右图所示。用鼠标缩放窗口的大小也能使按钮变宽(或者变窄)但是它们不会变高。

      

重载Composite

     如果你正在编写自己的小窗口部件,就像“用SWT创建你自己的小窗口部件”(Creating Your Own Widgets Using SWT)中描述的那样,并且你子类化了Composite,那么你的实现要几点要考虑:
    • 如果在你的新Composite中提供了修剪(trimming),确保你重载了computeTrimgetClientArea
    • 绝对不要重载layout(),但是你可以重载layout(boolean)
   有的时候你想让你的新Composite有特殊的外观,并且你不想就用程序有能力指定一个布局。你的新Composite既可以在一个缩放处理(handle)里面,也可以用一个私有的定制布局来实现。在这种情况下,你可能需要做下在这些工作:
   • 重载setLayout让它不做任何事。
    • 重载layout(boolean)让它调用你的布局代码。
    • 重载computeSize让它正确的计算出你的Composite的大小。

概要
     SWT提供了很多不同的方法来放置小窗口部件。最简单的方法并且是你会最常使用到的方法,就是用这些标准布局类的其中一个:FillLayout,RowLayout,GridLayout或者FormLayout。
     在一些情况下,你可能需要编写你自己的布局类来提供一些特殊的外观或者重用很相似的布局代码,但是通常一个在父小窗口部件上的缩放监听器就已经显得足够了。
    为了得到进一步的帮助以了解SWT的布局类,你可以查看 org.eclipse.swt.examples.layoutexample中的例子。
原创粉丝点击