《Eclipse SWT/JFACE 核心应用》 清华大学出版社 24 Eclipse表单
24.4.1 表格布局(TableWrapLayout)
表格布局(TableWrapLayout)是基于网格的布局,它与 SWT 的通用 GridLayout 非常类似,不同之处在于,布局规则更类似于 HTML 中的布局算法,可以支持文本的自动换行。
下面来比较一下 GridLayout 与 TableWrapLayout 两种布局的效果:
通过两种布局的比较可以看出,TableWrapLayout 可以将超出的文本折行显示,这是在 GridLayout 中所不能实现的。
与 GridLayout 对应的 GridData 类似,TableWrapLayout 对应 TableWrapData。下面就以一个复杂的示例程序来说明如何使用 TableWrapLayout 进行布局设置。
package com.testrcp.myrcp.forms;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Label;
//import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
public class FirstPage extends FormPage {
public static final String ID = "com.testrcp.myrcp.forms.FirstPage";
public FirstPage(FormEditor editor) {
// 构造方法,设置Form页的ID和名称
super(editor, ID, "第一页");
}
// 覆盖父类中的方法
// 在该方法中创建表单区域的各种控件
protected void createFormContent(IManagedForm managedForm) {
// 通过managedForm对象获得表单工具对象
FormToolkit toolkit = managedForm.getToolkit();
// 通过managedForm对象获得ScrolledForm可滚动的表单对象
ScrolledForm form = managedForm.getForm();
// 设置表单文本
form.setText("这是第一页,Hello, Eclipse 表单");
// 创建表格布局
TableWrapLayout layout = new TableWrapLayout();
layout.numColumns = 2;// 表格的列数
layout.bottomMargin = 10;// 下补白
layout.topMargin = 10;// 上补白
layout.leftMargin = 10;// 左补白
layout.rightMargin = 10;// 右补白
form.getBody().setLayout(layout);// 设置表格的布局
// 创建第一个标签
@SuppressWarnings("unused")
Label l1 = toolkit.createLabel(form.getBody(), "这是很长的一段文本文本1", SWT.WRAP);
// 创建第二个标签
Label l2 = toolkit.createLabel(form.getBody(), "这是文本2", SWT.WRAP);
// 创建一个TableWrapData对象,设置为水平和垂直充满式填充
TableWrapData td = new TableWrapData(TableWrapData.FILL_GRAB);
// 将布局数据应用到第二个标签
l2.setLayoutData(td);
Label l3 = toolkit.createLabel(form.getBody(), "这是文本3", SWT.WRAP);
// 第三个标签的布局数据
td = new TableWrapData();
td.colspan = 2;// 设置单元格的跨两列
l3.setLayoutData(td);
// 第四个标签的布局数据
Label l4 = toolkit.createLabel(form.getBody(), "这是文本4", SWT.WRAP);
td = new TableWrapData();
td.rowspan = 2;// 设置单元格跨两行
td.grabVertical = true;// 垂直抢占
l4.setLayoutData(td);
@SuppressWarnings("unused")
Label l5 = toolkit.createLabel(form.getBody(), "这是文本5", SWT.WRAP);
@SuppressWarnings("unused")
Label l6 = toolkit.createLabel(form.getBody(), "这是文本6", SWT.WRAP);
form.getBody().setBackground(form.getBody().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
}
}
效果如下:
缩放后的效果如下:
24.4.2 列布局(ColumnLayout)
列布局(ColumnLayout)与 SWT 中的 RowLayout 布局类似,相对于 TableWrapLayout 布局简单的所。该种布局的列数可根据窗口的大小进行调整,总是试图使用更多的列来显示。当窗口宽度减小时,列数也随之减少。总之,列布局(ColumnLayout)的目的是为了使各个控件都均匀的分布在界面上。
package com.testrcp.myrcp.forms;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.events.ExpansionAdapter;
import org.eclipse.ui.forms.events.ExpansionEvent;
import org.eclipse.ui.forms.widgets.ColumnLayout;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
public class SecondPage extends FormPage {
public static final String ID = "com.testrcp.myrcp.forms.SecondPage";
public SecondPage(FormEditor editor) {
super(editor, ID, "第二页");
}
protected void createFormContent(IManagedForm managedForm) {
ScrolledForm form = managedForm.getForm();
form.setText("ColumnLayout");
//创建列布局
ColumnLayout layout = new ColumnLayout();
layout.topMargin = 0;//上补白
layout.bottomMargin = 5;//下补白
layout.leftMargin = 10;//左补白
layout.rightMargin = 10;//右补白
layout.horizontalSpacing = 10;//水平方向控件的距离
layout.verticalSpacing = 10;//垂直方向控件的距离
layout.maxNumColumns = 4;//最大的列数
layout.minNumColumns = 1;//最小的列数
form.getBody().setLayout(layout);//设置表单的布局为列布局
//创建四个内容区
for (int i = 0; i < 4; i++)
createSectionWithLinks(managedForm, "Section" + i, "This is Section " + i, i);
}
//创建内容区及其控件
private void createSectionWithLinks(IManagedForm mform, String title, String desc, int nlinks) {
//创建内容去面板
Composite client = createSection(mform, title, desc, 1);
FormToolkit toolkit = mform.getToolkit();
//创建内容区中的控件
for (int i = 1; i <= nlinks; i++)
toolkit.createHyperlink(client, "link" + i, SWT.WRAP);
}
//创建内容区的方法
private Composite createSection(IManagedForm mform, String title, String desc, int numColumns) {
final ScrolledForm form = mform.getForm();
FormToolkit toolkit = mform.getToolkit();
//创建内容区
Section section = toolkit.createSection(form.getBody(), Section.TWISTIE | Section.TITLE_BAR | Section.DESCRIPTION | Section.EXPANDED);
section.setText(title);
section.setDescription(desc);
Composite client = toolkit.createComposite(section);
GridLayout layout = new GridLayout();
layout.marginWidth = layout.marginHeight = 0;
layout.numColumns = numColumns;
client.setLayout(layout);
//设置内容区的面板
section.setClient(client);
section.addExpansionListener(new ExpansionAdapter() {
public void expansionStateChanged(ExpansionEvent e) {
form.reflow(false);
}
});
return client;
}
}
显示效果如下:
奇怪,上面的第二行显示在中间了,谁会调整的给我留言。
调整宽度后的效果:
24.5 表单的高级应用
表单的高级应用部分主要体现在提供了 Master/Details 模式的应用,通过表单可以轻松的实现这种模式。
24.5.1 Master/Details 模式
Master/Details 是 UI 设计中常见的一种模式。该种模式由一组(列表或成树状结构)Master 和一个被选中 Master 驱动的 Details 集组成。Master 是一些不同的对象,通过对 Master 的驱动,驱动 Details 的 UI 发生变化。Master/Details 非常适用于 Eclipse 表单编程。
例如,编辑 plugin.xml 文件的扩展页面时就是使用的该种模式。如下图,编辑扩展的左侧是所有的扩展列表,也就是 Master 部分。当单击左侧的不同扩展信息时,右侧会产生不同的详细信息,也就是Detail 部分。
24.5.2 实现 Master/Details 示例程序
下面就以一个具体的例子来说明如何使用 Eclipse 表单来实现 Master/Detail 的界面效果。
在这个示例程序中,作为 Master 的一个树界面,显示的是文件的结构目录,但左侧选中不同的文件或文件夹,右侧的 Detail 部分显示对应的详细信息。随着选中的不同,详细信息也不同。
显示文件夹时:
显示文件时:
选择“垂直布局”后的显示效果:
下面就来详细看一下如何实现这样的程序,步骤如下:
(1)该页面为编辑器页面的一个页面,首页首先要在对应的编辑器页面添加一个页面。在 MyMutiForm.java 类的源代码中,在 addPages 方法中添加一个页面,代码如下:
addPage(new MasterDetailPage(this));
(2)所添加的 MasterDetailPage 对象,即为本例所示的 Master/Detail 页面。与之前所学习的创建页面的方法相同,该类继承自 FormPage 类,以下为代码:
package com.testrcp.myrcp.forms;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import com.testrcp.myrcp.forms.advance.FileMasterDetailsBlock;
public class MasterDetailPage extends FormPage {
public static final String ID = "com.testrcp.myrcp.forms.MasterDetailPage";
// 声明MasterDetail页面部分对象
private FileMasterDetailsBlock block;
public MasterDetailPage(FormEditor editor) {
super(editor, ID, "Master/Detail页");
block = new FileMasterDetailsBlock(this);
}
/*
* ManagedForm封装了form元素的生命周期管理与各个form元素之间的事件通知
* ManagedForm本身并不是一个form,他包含了一个form并且可以注册IFormPart。
* 可以将ManagedForm看作是'viewers',form和managed form之间的关系就好像
* Table与TableViewer的关系一样。
*/
protected void createFormContent(IManagedForm managedForm) {
// 获得表单对象
ScrolledForm form = managedForm.getForm();
// 设置表单的标题
form.setText("这是一个浏览文件的Master/Detail页面");
// 该方法非常重要,负责创建Master和Detail区域,尽量在最后调用
block.createContent(managedForm);
}
}
该页面与之前使用的编辑器页面不同,在构造方法中创建了一个 FileMasterDetailsBlock 对象,通过该对象的 createContent 方法可以创建 Master/Detail 页面中的各种控件。稍后详细讲述该类。
(3)具体创建页面控件的部分是在 FileMasterDetailsBlock 类中实现的。该类继承自 MasterDetailsBlock,具有 Master/Detail 模式的页面类都要继承自 MasterDetailsBlock 类,并实现该类的3个抽象方法。
◇ createMasterPart:在该方法中创建 Master 部分的控件,可以是表格、树或列表等。本例中使用的是 TreeViewer。
◇ createToolBarActions:创建页面的 Action 操作的方法,本例中创建两个按钮,可以设置垂直布局或是水平布局。
◇ registerPages:注册 Master 对应的 Detail 部分的控件,可同时注册多个 Detail 页面。实际上,一个 Master/Detail 页面是通过一个 SashForm 来布局的,Master 放置在 SashForm 的左侧或上侧,而 Detail 则放置在 SashForm 的右侧或下侧。
FileMasterDetailsBlock.java:
package com.testrcp.myrcp.forms.advance;
import java.io.File;
import myrcp.Activator;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.*;
import org.eclipse.ui.forms.*;
import org.eclipse.ui.forms.editor.FormPage;
import org.eclipse.ui.forms.widgets.*;
public class FileMasterDetailsBlock extends MasterDetailsBlock {
@SuppressWarnings("unused")
private FormPage page;
public FileMasterDetailsBlock(FormPage page) {
this.page = page;
}
//父类中的抽象方法,创建Master部分
protected void createMasterPart(final IManagedForm managedForm, Composite parent) {
FormToolkit toolkit = managedForm.getToolkit();
//创建一个内容区
Section section = toolkit.createSection(parent, Section.DESCRIPTION | Section.TITLE_BAR);
section.setText("浏览文件");
section.marginWidth = 10;
section.marginHeight = 5;
//创建内容区的面板
Composite client = toolkit.createComposite(section, SWT.WRAP);
//绘制该面板的边框,与表单的风格一致
toolkit.paintBordersFor(client);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginWidth = 2;
layout.marginHeight = 2;
client.setLayout(layout);
//创建一个树,使用toolkit对象创建
Tree tree = toolkit.createTree(client, SWT.NULL);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.heightHint = 20;
gd.widthHint = 100;
tree.setLayoutData(gd);
section.setClient(client); // 书上没有这行,明明连tree都显示不了嘛。。。
/*
IFormPart管理了整个Part的dirty state, saving, commit, focus, selection changes等等这样的事件。
并不是表单中的每一个-空间站都需要成为一个IFormPart,最好将一组control通过实现IFormPart变成一个Part.
一般来说Section就是一个自然形成的组,所以Eclipse Form提供了一个SectionPart的实现,
它包含一个Section的对象。
*/
final SectionPart spart = new SectionPart(section);
//注册该对象到IManagedForm表单管理器中
managedForm.addPart(spart);
//将普通的树包装成MVC的树
TreeViewer viewer = new TreeViewer(tree);
//注册树的选择事件监听器
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
//当单击树中某一个节点时
public void selectionChanged(SelectionChangedEvent event) {
//通过IManagedForm来发布IFormPart所对应的事件
managedForm.fireSelectionChanged(spart, event.getSelection());
}
});
//设置树的内容
viewer.setContentProvider(new MasterContentProvider());
//设置树的标签
viewer.setLabelProvider(new MasterLabelProvider());
//设置初始化输入的类
viewer.setInput(new File("E:\\Data"));
}
//注册详细页面部分
protected void registerPages(DetailsPart detailsPart) {
//将DirectoryDetailPage对象注册
detailsPart.registerPage(File.class, new DirectoryDetailPage());
}
//创建表单区的Action
protected void createToolBarActions(IManagedForm managedForm) {
final ScrolledForm form = managedForm.getForm();
//水平布局操作
Action hAction = new Action("horizon", Action.AS_RADIO_BUTTON) {
public void run() {
sashForm.setOrientation(SWT.HORIZONTAL);
form.reflow(true);
}
};
hAction.setChecked(true);
hAction.setToolTipText("水平布局");
hAction.setImageDescriptor(Activator.getImageDescriptor("icons/hor.gif"));
//垂直布局操作
Action vAction = new Action("vertical", Action.AS_RADIO_BUTTON) {
public void run() {
sashForm.setOrientation(SWT.VERTICAL);
form.reflow(true);
}
};
vAction.setChecked(false);
vAction.setToolTipText("垂直布局"); //$NON-NLS-1$
vAction.setImageDescriptor(Activator.getImageDescriptor("icons/ver.gif"));
//将这两个操作添加到表单的工具栏管理器中
form.getToolBarManager().add(hAction);
form.getToolBarManager().add(vAction);
}
public class MasterContentProvider implements ITreeContentProvider {
public Object[] getChildren(Object element) {
return ((File) element).listFiles();
}
public Object[] getElements(Object element) {
return ((File) element).listFiles();
}
public boolean hasChildren(Object element) {
Object[] obj = getChildren(element);
return obj == null ? false : obj.length > 0;
}
public Object getParent(Object element) {
return ((File) element).getParentFile();
}
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
class MasterLabelProvider implements ILabelProvider {
public Image getImage(Object element) {
File file = (File) element;
if (file.isDirectory())
return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER);
else
return PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE);
}
public String getText(Object element) {
String text = ((File) element).getName();
if (text.length() == 0) {
text = ((File) element).getPath();
}
return text;
}
public void addListener(ILabelProviderListener listener) {
}
public void dispose() {
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void removeListener(ILabelProviderListener listener) {
}
}
}
通过以上代码可以看出,创建的 Master 部分其实就是之前学习的浏览文件的 TreeViewer。但注意所不同的是,单击树中的某一个节点事件处理部分,需要通过 IManagedForm 的 fireSelectionChanged 方法来发布事件。
作为接收的事件处理,需要在注册的 Detail 页面进行,所注册的详细信息的部分都在 registerPages 方法中实现。本例中 中注册了一个 Detail,如下:
detailsPart.registerPage(File.class, new DirectoryDetailPage());
当然也可以注册多个 Detail 页面。
(4)最后看一下如何实现本例中的 Detail 页面,注册的 DirecotryDetailPage 对象即为具体实现 Detail 部分。该类需要实现 IDetailsPage 接口,主要是在 createContents 方法中创建 Detail 所需使用的控件,并且在 selectionChanged 方法中处理当 Master 对象选中事件发生的代码,具体实现如下:
package com.testrcp.myrcp.forms.advance;
import java.io.File;
import java.util.Date;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.IDetailsPage;
import org.eclipse.ui.forms.IFormPart;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.forms.widgets.TableWrapData;
import org.eclipse.ui.forms.widgets.TableWrapLayout;
public class DirectoryDetailPage implements IDetailsPage {
private IManagedForm mform;
private File file;
private Section fileSection;
private Text fileName;
private Text filePath;
private Text lastModify;
private Button isRead;
private Button isWrite;
private Composite client;
@SuppressWarnings("deprecation")
public void createContents(Composite parent) {
//设置父类面板的布局
TableWrapLayout layout = new TableWrapLayout();
layout.topMargin = 5;
layout.leftMargin = 5;
layout.rightMargin = 2;
layout.bottomMargin = 2;
parent.setLayout(layout);
//获得表单工具对象
FormToolkit toolkit = mform.getToolkit();
//创建Detail的内容区
fileSection = toolkit.createSection(parent, Section.DESCRIPTION|Section.TITLE_BAR);
TableWrapData td = new TableWrapData(TableWrapData.FILL, TableWrapData.TOP);
td.grabHorizontal = true;
fileSection.setLayoutData(td);
//创建内容区的所设置的面板
client = toolkit.createComposite(fileSection);
fileSection.setClient( client );
GridLayout gridLayout = new GridLayout();
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
gridLayout.numColumns = 2;
gridLayout.horizontalSpacing=10;
client.setLayout(gridLayout);
//创建Detail部分具体的各种控件
toolkit.createLabel( client , "名称:");
fileName = toolkit.createText( client ,"");
toolkit.createLabel( client , "路径:");
filePath = toolkit.createText( client , "");
toolkit.createLabel( client , "最后修改:");
lastModify = toolkit.createText( client , file!=null?new Date(file.lastModified()).toLocaleString():"");
isRead = toolkit.createButton( client , "是否可读" ,SWT.CHECK);
isWrite = toolkit.createButton( client , "是否可写" ,SWT.CHECK);
}
public void initialize(IManagedForm form) {
this.mform = form ;
}
public void dispose() {
}
public boolean isDirty() {
return false;
}
public void commit(boolean onSave) {
}
public boolean setFormInput(Object input) {
return false;
}
public void setFocus() {
}
public boolean isStale() {
return false;
}
public void refresh() {
}
//当Master区域选中事件发生时
public void selectionChanged(IFormPart part, ISelection selection) {
//首先获得选中的对象
IStructuredSelection currentSelection = (IStructuredSelection)selection;
if (currentSelection.size()==1)
file = (File)currentSelection.getFirstElement();
//如果选中的对象不为null,则刷新控件所显示的值
if (file != null)
update();
}
//刷新值
@SuppressWarnings("deprecation")
public void update (){
//如果选中的为文件夹
if ( file.isDirectory()){
fileSection.setText("文件夹");
fileSection.setDescription("这是一个文件夹");
}else{//否则
fileSection.setText("文件");
fileSection.setDescription("这是一个文件");
}
//设置文件名
fileName.setText(file.getName());
//设置路径
filePath.setText(file.getAbsolutePath());
//设置上次修改
lastModify.setText(new Date(file.lastModified()).toLocaleString());
//设置是否只读
isRead.setSelection( file.canRead());
//设置是否可写
isWrite.setSelection( file.canWrite() );
}
}
这样,一个完整的 Master/Detail 模式实现的表单就完成了。