窗口布局(Layout)其实是指Composite中组件的一种定位原则的实现,当Composite改变大小时,会自动调用Composite初始化时设置的Layout对象来重新调整所有组件的位置。 一般的UI框架都提供了一些默认布局,比如SWT中的FillLayout,GridLayout…如果使用WindowBuilder开发UI,可以在Design界面下看到所有SWT提供的布局对象,见下图
有的时候,使用SWT提供的布局是无法满足需要的,这种情况下,就需要自实现所需的特殊布局。 实现自定义的Layout并不复杂, 以下是org.eclipse.swt.widgets.Layout的简要注释说明:
package org.eclipse.swt.widgets; import org.eclipse.swt.graphics.*; /** * 布局抽象类, * 用于控制组件内所有子对象的位置和尺寸 */ public abstract class Layout { /** * 必须实现的抽象方法 * 返回容器组件(父窗口)的Client区域尺寸 */ protected abstract Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache); protected boolean flushCache (Control control) { return false; } /** * 必须实现的抽象方法 * 设置所有容器组件(父窗口)内所有子组件的位置和大小 * @param composite 将被重新设置布局的容器组件(父窗口) * @param flushCache <code>true</code> means flush cached layout values */ protected abstract void layout (Composite composite, boolean flushCache); }从上面的代码可以知道,只要实现抽象类org.eclipse.swt.widgets.Layout的两个抽象方法就可以实现一个特殊布局了,SWT提供的那些默认布局类都是通过继承Layout实现的
关于Layout的详细原文说明参见SWT的javadoc http://help.eclipse.org/neon/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Layout.html
上一节讲完Layout的实现思路,下面就以以一个实例来说明如何实现自定义布局。 比如下面的图中矩形框,并不是画在背景图上的,而是背景透明的Composite,可以移动和改变尺寸(如何实现,参见我的上一篇博客《 java SWT入门:自定义背景透明且可鼠标拖动改变尺寸和位置的Composite》) 这些矩形用于对图像中的人脸位置进行标注,我们希望当图像大小和位置改变的时候,这些矩形在图像上的相对位置保持不变。
这种需求,SWT中现成的布局都不能满足要求,所以就要自己实现一个,以下是实现代码, ActiveRectContainer.java
package net.gdface.ui; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Decorations; import org.eclipse.swt.widgets.Layout; import org.eclipse.wb.swt.SWTResourceManager; /** * 活动矩形显示容器 * 窗口尺寸改变时所有{@link ActiveRectangle}对象自动等比例改变 * * @author guyadong * */ public class ActiveRectContainer extends Decorations { /** * 创建自定义的布局对象实现窗口内的ActiveRectangle对象能根据父窗口的尺寸改变而同步等比例改变, * 以保持每一个矩形在父窗口上的相对位置不变 * @author guyadong * */ class ZoomLayout extends Layout { @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { // 返回可以画图的区域 Rectangle bounds = composite.getClientArea(); return new Point(bounds.width, bounds.height); } @Override protected void layout(Composite composite, boolean flushCache) { Control[] childrens = composite.getChildren(); Rectangle originalbounds = getBackgroundImage().getBounds(); Rectangle bounds = composite.getBounds(); // 计算x/y轴缩放比例 float zoomX = (float) bounds.width / originalbounds.width; float zoomY = (float) bounds.height / originalbounds.height; for (Control children : childrens) { if (children instanceof ActiveRectangle) { // 对于ActiveRectangle对象调用zoom方法改变bounds ((ActiveRectangle) children).zoom(zoomX, zoomY); }else if(children.getLayoutData() instanceof Rectangle){ // 对于其他Control对象调用如果通过setLayoutData()设置了原始的对象尺寸,则可以根据父窗口尺寸同步改变bounds Rectangle originalBounds=(Rectangle) children.getLayoutData();// 获取Control原始位置尺寸 children.setBounds(new Rectangle((int)(originalBounds.x*zoomX),(int)(originalBounds.y*zoomY),(int)(originalBounds.width*zoomX),(int)(originalBounds.height*zoomY))); } } } } protected List<ActiveRectangle> annRects=EMPTY_RECTS; private static final List<ActiveRectangle> EMPTY_RECTS=new ArrayList<ActiveRectangle>(); /** * @param parent * @param image 显示的背景图像,为null时不显示 * @param rects 显示的矩形对象数组 * @param focusIndex 焦点矩形索引,超出 rects索引范围时无效 */ public ActiveRectContainer(Composite parent, Image image, Rectangle[] rects, int focusIndex) { super(parent, SWT.BORDER|SWT.RESIZE); if(null!=rects&&rects.length>0){ this.annRects=new ArrayList<ActiveRectangle>(); for(Rectangle rect:rects){ // 创建矩形对象(ActiveRectangle) annRects.add(new ActiveRectangle(this, false,rect)); } try{ // 设置焦点对象 this.annRects.get(focusIndex).focus=true; }catch(IndexOutOfBoundsException e){} } this.setBackgroundImage(null==image?SWTResourceManager.getMissingImage():image); // 设置自定义布局对象 this.setLayout(new ZoomLayout()); addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { // 调用重绘方法 paintImage(e.gc); } }); } /** * @param parent * @param url 背景图像的URL * @param rects * @param focusIndex * @see #AutoZoomRecContainer(Composite, Image, Rectangle[], int) */ public ActiveRectContainer(Composite parent, URL url, Rectangle[] rects, int focusIndex) { this(parent, SWTResourceManager.getImage(url),rects, focusIndex); } /** * 将 {@link #image} 重绘图像到窗口(缩放到整个窗口) * * @param gc */ protected final void paintImage(GC gc) { boolean isAdvanced = gc.getAdvanced(); try { gc.setAdvanced(true); gc.setAntialias(SWT.ON); Point destSize = getSize(); Image image = getBackgroundImage(); Rectangle imgSize = image.getBounds(); gc.drawImage(image, 0, 0, imgSize.width, imgSize.height, 0, 0, destSize.x, destSize.y); } finally { gc.setAdvanced(isAdvanced); } } /** * 以窗口中心为原点对窗口进行缩放 * @param zoomX x轴缩放比例 * @param zoomY x轴缩放比例 */ public void zoomCenter(float zoomX, float zoomY) { // 以背景图像的尺寸为当前对象的原始尺寸 Rectangle originalSize = getBackgroundImage().getBounds(); Rectangle bounds=getBounds(); // 缩放后的尺寸 int width=(int) (originalSize.width*zoomX); int height=(int) (originalSize.height*zoomX); // 中心点位置 int x=bounds.x+(bounds.width-width)/2; int y=bounds.y+(bounds.height- height)/2; super.setBounds(x, y, width, height); } /** * x/y轴等比例缩放 * @param zoom 缩放比例 * @see #zoomCenter(float, float) */ public void zoomCenter(float zoom) { zoomCenter(zoom,zoom); } @Override protected void checkSubclass() { } }注意:自定义布局实现在ActiveRectContainer.java的代码中是以一个内部类ZoomLayout 来实现的
以下是用WindowBuilder生成的测试代码 TestRectContainer.java
package testwb; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.wb.swt.SWTResourceManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.gdface.ui.ActiveRectContainer; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; public class TestRectContainer { static final Logger logger = LoggerFactory.getLogger(TestRectContainer.class); protected Shell shell; /** * Launch the application. * @param args */ public static void main(String[] args) { try { TestRectContainer window = new TestRectContainer(); window.open(); } catch (Exception e) { e.printStackTrace(); } } /** * Open the window. */ public void open() { Display display = Display.getDefault(); createContents(); shell.open(); shell.layout(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } } /** * 返回适合当前窗口尺寸完整显示图像的缩放比例,图像长宽都小于显示窗口时,则返回1 * * @return */ private static float fitZoom(Point parentSize,Point childrenSize) { if (childrenSize.x < parentSize.x && childrenSize.y < parentSize.y) return 1f; if (childrenSize.x * parentSize.y < childrenSize.y * parentSize.x) { return ((float) parentSize.y) / (float)childrenSize.y; } return ((float) parentSize.x) / (float)childrenSize.x; } private static Rectangle fitBounds(Point parentSize,Point childrenSize) { float zoom = fitZoom(parentSize, childrenSize); childrenSize.x*=zoom; childrenSize.y*=zoom; return new Rectangle((parentSize.x-childrenSize.x)/2,(parentSize.y-childrenSize.y)/2,childrenSize.x,childrenSize.y); } /** * Create contents of the window. */ protected void createContents() { shell = new Shell(); shell.setText("SWT Application"); shell.setSize(569, 459); Rectangle[] rects = new Rectangle[]{ new Rectangle(50,50,200,200), new Rectangle(350,100,150,250), new Rectangle(125,300,200,321)}; ActiveRectContainer canvas; //Image image = SWTResourceManager.getImage("http://pic8.nipic.com/20100704/3525627_110847063052_2.jpg"); Image image = SWTResourceManager.getImage("J:/workspace.neon/iadbui/src/image/3525627_110847063052_2.jpg"); //Image image = SWTResourceManager.getImage(this.getClass(),"/image/3525627_110847063052_2.jpg"); Point imgSize=new Point(image.getBounds().width,image.getBounds().height); canvas = new ActiveRectContainer(shell, image,rects,0); canvas.setBounds(fitBounds(shell.getSize(),imgSize)); Button btnNewButton = new Button(canvas, SWT.NONE); btnNewButton.setBounds(189, 95, 80, 27); // 如果这里通过setLayoutData设置了btnNewButton的原始尺寸,btnNewButton也会随着窗口尺寸改变而自动缩放 btnNewButton.setLayoutData(new Rectangle(189, 95, 80, 27)); btnNewButton.setText("New Button"); } }请注意: 对于一般的Control对象,如果没有通过setLayoutData方法设置原始的尺寸位置,则Layout对其无效,所以上面的测试代码中对btnNewButton调用了setLayoutData,指定了初始的位置和尺寸。这样它才能与父窗口同步缩放。如下图 如果注释掉 btnNewButton.setLayoutData(new Rectangle(189, 95, 80, 27));这一行,效果是这样的
《org.eclipse.swt.widgets.Layout》 《 java SWT入门:自定义背景透明且可鼠标拖动改变尺寸和位置的Composite》
