窗口布局(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 true
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 annRects=EMPTY_RECTS;
private static final List EMPTY_RECTS=new ArrayList();
/** * @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();
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》