`

Eclipse首选项开发

 
阅读更多
JAVA.SWT/JFace: JFace篇之首选项2010年08月11日 星期三 10:36《Eclipse SWT/JFACE 核心应用》 清华大学出版社 18 首选项

    在一个应用系统中,首选项是可以根据用户的喜好来设定的,一般是以键(key)/值(value)来保存的。

18.1 首选项概述
有关保存首选项设置的类有:
◆ PreferenceStore:可以将设置以key/value的形式保存为文件,也可以载入文件后方便的获取所设定的值。
◆ PreferenceConverter:保存为文件的首选项为字符串的形式,对应像颜色、字体这些对象,无法直接保存为字符串,使用该类转换,将颜色保存为字符串的样式,并且将字符串转化为颜色对象。

有关首选项的设置的类有:
◆ FieldEditor:这是一个抽象类,虽然对首选项的设置可以使用最基本的标签、按钮等SWT控件实现,但是会很麻烦,为了简化创建各种设置的选项,可以使用FieldEditor对象创建常用的控件。
◆ BooleanFieldEditor:布尔型选项的设置。
◆ IntegerFieldEditor:整型值设置。
◆ StringFieldEditor:字符串型值的设置。
◆ RadioGroupFieldEditor:分组面板型的设置。
◆ ColorFieldEditor:颜色值的设置。
◆ FontFieldEditor:字体型值的设置。
◆ DirectoryFieldEditor:选择文件目录的设置。
◆ FileFieldEditor:选择文件的设置。
◆ PathEditor:选择路径的设置。

有关显示首选项页面的类有:
◆ PreferencePage:表示一个首选项页面,类似于向导式对话框中的一个向导页面WizardPage。
◆ PreferenceNode:浏览首选项的树型菜单的一个节点,一个节点对应一个首选项页面PreferencePage。
◆ PreferenceManager:负责管理每个节点和首选项页面,包括树的结构等。
◆ PreferenceDialog:显示首选项的对话框容器。

18.2 保存首选项的设置
保存首选项的类PreferenceStore。Java中使用.properties为扩展名的文件来保存首选项的设置。

1. 首选项值的设置和获取
首选项的设置是以key和value格式来表示的,如下:
Password=123
UserName=Janet
Database=mysql

要保存这样一个格式的文件代码如下:
   PreferenceStore preferenceStore = new PreferenceStore("F:\\myPreference.properties");
   preferenceStore.setValue("Database", "mysql");
   preferenceStore.setValue("UserName", "Janet");
   preferenceStore.setValue("Password", "123");
   try {
    preferenceStore.save();
   } catch (IOException e) {
    e.printStackTrace();
   }

读取设置的值:
   PreferenceStore preferenceStore = new PreferenceStore("F:\\myPreference.properties");
   try {
    preferenceStore.load(); // 装载文件
   } catch (IOException e) {
    e.printStackTrace();
   }
   String database = preferenceStore.getString("Database");

设置默认值:
setDefaultXXX。
当通过get方法获取一个设置时,程序首先查找保存的.properties文件中是否已设置了该值,如果没有则返回该选项的默认值。


2. 保存首选项所涉及的事件
当某个选项值改变时出发PropertyChangeEvent事件,如下:
   preferenceStore.addPropertyChangeListener(new IPropertyChangeListener() {
    public void propertyChange(PropertyChangeEvent event) {
     if (event.getProperty().equals("Database")) {
      System.out.println("old value:" + event.getOldValue());
      System.out.println("new value:" + event.getNewValue());
     }
    }
   });
   preferenceStore.setValue("Database", "sqlserver"); // 触发事件

18.3 显示首选项页面

1. 创建一个首选项页面
package www.swt.com.ch18;

import org.eclipse.jface.preference.IPreferenceStore;

public class SystemSettingPage extends PreferencePage {
public SystemSettingPage() {
}

private Text userName;
private Text password;
//该方法为必须实现的方法,在此方法中创建页面上的各种控件
protected Control createContents(Composite parent) {
   Composite composite = new Composite(parent, SWT.NONE);
   composite.setLayout(new GridLayout(2, false));
   //获取保存此页面的PreferenceStore对象
   IPreferenceStore preferenceStore = getPreferenceStore();

   new Label(composite, SWT.LEFT).setText("登录用户名:");
   userName = new Text(composite, SWT.BORDER);
   userName.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
   //设置用户名为保存在文件中的值
   userName.setText(preferenceStore.getString(Constants.USER_NAME));

   new Label(composite, SWT.LEFT).setText("登录密码:");
   password = new Text(composite, SWT.BORDER);
   password.setEchoChar('*');
   password.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
   //设置密码为保存在文件中的值
   password.setText(preferenceStore.getString(Constants.PASSWORD));
   return composite;
}

/*
* (非 Javadoc)
*
* @see org.eclipse.jface.preference.PreferencePage#performDefaults()
* 覆盖父类中的方法,但单击“恢复默认值”按钮时调用该方法
*/
protected void performDefaults() {
   IPreferenceStore preferenceStore = getPreferenceStore();
   userName.setText( preferenceStore.getDefaultString(Constants.USER_NAME));
   password.setText( preferenceStore.getDefaultString(Constants.PASSWORD));
}

/*
* (非 Javadoc)
*
* @see org.eclipse.jface.preference.PreferencePage#performOk()
* 覆盖父类中的方法,但单击“应用”按钮时调用该方法
*/
public boolean performOk() {
   IPreferenceStore preferenceStore = getPreferenceStore();
   if (userName != null)
    preferenceStore.setValue(Constants.USER_NAME, userName.getText());
   if (password != null)
    preferenceStore.setValue(Constants.PASSWORD, password.getText());
   return true;
}
/*
* (非 Javadoc)
*
* @see org.eclipse.jface.preference.PreferencePage#contributeButtons(org.eclipse.swt.widgets.Composite)
*/
protected void contributeButtons(Composite parent) {
   // super.contributeButtons(parent);
   Button bt1 = new Button(parent, SWT.NONE);
   bt1.setText("按钮一");
   ((GridLayout) parent.getLayout()).numColumns++;
   Button bt2 = new Button(parent, SWT.NONE);
   bt2.setText("按钮二");
   ((GridLayout) parent.getLayout()).numColumns++;

}
}
从以上代码中可以总结出,创建一个首选项页面时需要注意以下问题:
◆ 每个首选项页面都要继承自PreferencePage类,而且必须实现createContents的抽象方法。
◆ 在createContents方法中可以创建页面的各种控件。
◆ 覆盖父类的performDefaults方法可以添加“恢复缺省值”按钮的操作,覆盖父类中的performOk方法,可以添加“应用”按钮的操作。

2. 创建首选项页面所对应的节点
节点所对应的类为PreferenceNode类。该类的构造方法有3个,如下:
◆ PreferenceNode(String id):id为标识该节点的字符,使用该构造函数需要使用setPage方法将一个节点对象与一个页面对象关联起来,如下:
SystemSettingPage system = new SystemSettingPage();
   PreferenceNode node = new PreferenceNode("System");
node.setPage(system);
◆ PreferenceNode(String id, IPreferencePage preferencePage):id为该节点的唯一标识符,PreferencePage为该节点对应的页面。
SystemSettingPage system = new SystemSettingPage();
   PreferenceNode node = new PreferenceNode("System", system);
◆ PreferenceNode(String id, String label, ImageDescriptor image, String className):id为该节点的唯一标识符,label为节点显示的名称,image为节点的图标,className为该节点所对应页面对象类的名称,注意是全称,包括所在的包名。还可以另一种方式获得类的全称:SystemSettingPage.class.getName(),也可以动态的获得该类的全称。
   PreferenceNode nodeOne = new PreferenceNode("System", "系统设置",
    ImageDescriptor.createFromFile(Constants.class, "tree_mode.gif"), // 节点的图标
     SystemSettingPage.class.getName()); // 所对应的页面类的全称

    一个PreferenceNode节点对象不仅保存了该节点所关联的首选项页面,还保存了该节点的其他信息,包括子节点等。

3. 显示首选项对话框
    PreferenceManager负责管理所有的节点。该类有两个构造方法:
◆ PreferenceManager():无参的构造函数。使用默认的“.”字符作为分隔符使用该构造方法:
PreferenceManager manager = new PreferenceManager();
◆ PreferenceManager(char separatorChar):separatorChar为路径分隔符。将一个节点添加到PreferenceManager对象使用以下两种方法:
    ◇ addToRoot(IPreferenceNode node):添加到根节点中。
   //创建一个PreferenceManager对象
   PreferenceManager manager = new PreferenceManager();
   //创建一个节点对象
   PreferenceNode nodeOne = new PreferenceNode("System", "系统设置", null, SystemSettingPage.class.getName());
   //将该节点添加到根节点中
   manager.addToRoot(nodeOne);
    ◇ addTo(String path, IPreferenceNode node):将一个节点添加到指定路径的节点上,其中path为指定的路径,node为要添加的节点。

最后,使用PreferenceDialog创建将树型导航栏和选项页面显示出来:
package www.swt.com.ch18;

import java.io.IOException;

public class PreferenceTest {

public static void main(String[] args) {
   Display display = new Display();
   //创建一个PreferenceManager对象
   PreferenceManager manager = new PreferenceManager();
   //创建一个节点对象
   PreferenceNode nodeOne = new PreferenceNode("System", "系统设置", null, SystemSettingPage.class.getName());
   //将该节点添加到根节点中
   manager.addToRoot(nodeOne);
   //创建两个节点对象
   PreferenceNode one = new PreferenceNode("one", "第一页", null, PageOne.class.getName());
   PreferenceNode two = new PreferenceNode("two", "第二页", null, PageTwo.class.getName());
   //将第一页节点附加到System路径下
   manager.addTo("System",one);
   //将第二页节点附加到System.one路径下
   manager.addTo("System.one",two);
   PreferenceNode editorOne = new PreferenceNode("one", "编辑字段", null, FieldEditorPage.class.getName());
   manager.addToRoot(editorOne);
   /*********************
   //第二页节点为第一页节点的子节点
   one.add( two );
   //第一页节点为系统设置节点的子节点
   nodeOne.add(one);
   **********************/
   //manager.addTo("System",one);
   //manager.addTo("System.one",two);
   //定义一个首选项对话框,并将manager作为参数传入
   PreferenceDialog dlg = new PreferenceDialog(null, manager);
   //注册页面切换事件
   dlg.addPageChangedListener( new IPageChangedListener(){
    //当页面切换时
    public void pageChanged(PageChangedEvent event) {
     //获得当前页面
     IPreferencePage page = (IPreferencePage)event.getSelectedPage();
     //输出当前页面的标题
     System.out.println(page.getTitle());
    }  
   });
   //创建保存选项设置值对象
   PreferenceStore preferenceStore = new PreferenceStore("F:\\myPreference.properties");
   try {
    //装载该文件中的设置值
    preferenceStore.load();
    //将该设置赋值给该对话框
    dlg.setPreferenceStore(preferenceStore);
    //打开对话框
    dlg.open();
    //最后关闭对话框后,保存当前设置
    preferenceStore.save();
   } catch (IOException e) {
    e.printStackTrace();
   }
   display.dispose();
}
}

18.4 创建树型的导航菜单
    要创建树型菜单,有两种方法,一种是在创建节点时就创建了该节点的子节点,这种情况下使用PreferenceNode对象的add方法为节点添加子节点。另一种方法是通过PreferenceManager对象的addTo方法为指定路径下添加节点。

两个选项页的代码如下:
package www.swt.com.ch18;

import org.eclipse.jface.preference.PreferencePage;

public class PageOne extends PreferencePage {
protected Control createContents(Composite parent) {
   return parent;
}

/*
* (非 Javadoc)
*
* @see org.eclipse.jface.preference.PreferencePage#contributeButtons(org.eclipse.swt.widgets.Composite)
*/
protected void contributeButtons(Composite parent) {
   // super.contributeButtons(parent);
   Button bt1 = new Button(parent, SWT.NONE);
   bt1.setText("按钮一");
   ((GridLayout) parent.getLayout()).numColumns++;
   Button bt2 = new Button(parent, SWT.NONE);
   bt2.setText("按钮二");
   ((GridLayout) parent.getLayout()).numColumns++;

}
}
另外一个选项页:
package www.swt.com.ch18;

import org.eclipse.jface.preference.PreferencePage;

public class PageTwo extends PreferencePage{

protected Control createContents(Composite parent) {
   return parent;
}
}

第一种方法创建树型菜单:
   // 创建一个PreferenceManager对象
   PreferenceManager manager = new PreferenceManager();
   // 创建一个节点对象
   PreferenceNode nodeOne = new PreferenceNode("System", "系统设置", null, SystemSettingPage.class.getName());
   // 将该节点添加到根节点
   manager.addToRoot(nodeOne);
   // 创建两个节点对象
   PreferenceNode one = new PreferenceNode("one", "第一页", null, PageOne.class.getName());
   PreferenceNode two = new PreferenceNode("two", "第二页", null, PageTwo.class.getName());
   // 第二页节点为第一页节点的子节点
   one.add(two);
   // 第一页节点为系统设置节点的子节点
   nodeOne.add(one);

第二种方法创建树型菜单:
   // 创建一个PreferenceManager对象
   PreferenceManager manager = new PreferenceManager();
   // 创建一个节点对象
   PreferenceNode nodeOne = new PreferenceNode("System", "系统设置", null, SystemSettingPage.class.getName());
   // 将该节点添加到根节点
   manager.addToRoot(nodeOne);
   // 创建两个节点对象
   PreferenceNode one = new PreferenceNode("one", "第一页", null, PageOne.class.getName());
   PreferenceNode two = new PreferenceNode("two", "第二页", null, PageTwo.class.getName());
   // 将第一页节点附加到System路径下
   manager.addTo("System", one);
   // 将第二页节点附加到System.one路径下
   manager.addTo("System.one", two);

18.5 首选项的选项设置
    对于首选项的设置,可以使用SWT基本的控件就可以实现,但为了方便创建设置的选项,JFace提供了常用的一些设置选项值的字段对象。

1. 字段编辑器概述
    字段编辑器类FieldEditor类是一个抽象类,创建字段编辑器要实现其方法的子类。使用字段编辑器的好处是,不必考虑对选项的保存,只需要创建使用的编辑器对象即可。

2. 使用字段编辑器基本步骤
package www.swt.com.ch18;

import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.ColorFieldEditor;
import org.eclipse.jface.preference.DirectoryFieldEditor;
import org.eclipse.jface.preference.FieldEditorPreferencePage;
import org.eclipse.jface.preference.FileFieldEditor;
import org.eclipse.jface.preference.FontFieldEditor;
import org.eclipse.jface.preference.IntegerFieldEditor;
import org.eclipse.jface.preference.PathEditor;
import org.eclipse.jface.preference.RadioGroupFieldEditor;
import org.eclipse.jface.preference.ScaleFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;

public class FieldEditorPage extends FieldEditorPreferencePage {

public FieldEditorPage() {
   super(GRID);//页面的样式,还可以使用FLAT常量
}
//该方法为实现父类中的抽象方法,在该方法中添加所需的编辑器对象
protected void createFieldEditors() {

   //创建一个字符型编辑器对象
   StringFieldEditor userName = new StringFieldEditor(
     Constants.USER_NAME, //选项的key值
     "登录用户名:", //显示的标签名
     getFieldEditorParent());//该字段编辑器的父类面板
   //调用父类方法,将该方法添加到该页中
   addField(userName);
   // /*
   BooleanFieldEditor bfe = new BooleanFieldEditor("show","Boolean",getFieldEditorParent());
   addField(bfe);
   ColorFieldEditor cfe = new ColorFieldEditor("color","Color",getFieldEditorParent());
   addField(cfe);
   FontFieldEditor ffe = new FontFieldEditor("font","Font",getFieldEditorParent());
   addField(ffe);
   PathEditor pfe = new PathEditor("path","Path","请选择所选的路径",getFieldEditorParent());
   addField(pfe);
   RadioGroupFieldEditor rgfe = new RadioGroupFieldEditor(
     "group", //选项的key
     "RadioGroup",//分组框显示的文本
     2,//一行显示的单选按钮个数
     new String[][] { { "Radio one", "one"}, {"Radio two", "two"},{"Radio three", "three"} },//单选按钮的标签和值
     getFieldEditorParent(),//父类的面板
     true);//true表示使用分组面板,false将使用普通的面板
   addField(rgfe);
   ScaleFieldEditor sfe = new ScaleFieldEditor("scale","Scale",getFieldEditorParent(),0,100,5,10);
   addField(sfe);

   IntegerFieldEditor ife = new IntegerFieldEditor("int","Int",getFieldEditorParent());
   ife.setLabelText("这是int");
   addField(ife);
   DirectoryFieldEditor dfe = new DirectoryFieldEditor("dirctory","Directory",getFieldEditorParent());
   addField(dfe);
   FileFieldEditor filefe = new FileFieldEditor("file","File",getFieldEditorParent());
   addField(filefe);
   // */
}
}
显示效果:


使用字段编辑器应注意以下方面:
◆ 要使用字段编辑器,创建首选项页面时要继承自FieldEditorPreferencePage类,而不是继承自PreferencePage类,而且要实现createFieldEditors抽象方法。在该方法中创建各种编辑器对象。
◆ 创建编辑器对象时,至少要有3个参数,选项的key值、显示的标签名和该字段编辑器的父类面板。其中选项的key值,即为保存为文件中的key值,编辑对象会自动显示文件中所设置的值。
◆ 创建完编辑器对象后,调用父类的addField方法,将该编辑器对象添加到页面中。
◆ 使用编辑器时,不需要编写单击“恢复缺省值”和“应用”按钮的操作,这是因为FieldEditorPreferencePage是PreferencePage的子类,在该类中覆盖了PreferencePage类中的performOk方法,并且循环所有的字段编辑器对象,保存到存储设置的对象中。如下图所示,对界面上的内容修改后,点击“确定”关闭,重新打开程序时,显示的是上次设定的内容。如果点击“取消”关闭窗口,则重新打开程序时,上次修改的内容不会生效。


当输入的值不合法时,提示如下:


18.6 自定义首选项页面
    每个首选项页面都有一个“恢复缺省值”按钮和“应用”按钮。若想自定义首选项页面上的按钮,需要覆盖父类的contributeButtons方法。

参考本文中的SystemSettingPage。

在创建完一个按钮后,要调用((GridLayout) parent.getLayout()).numColumns++;方法,将父类面板中的列数加1。

18.7 首选项的事件处理
    当切换首选项页面时会触发PageChangedEvent事件。
   PreferenceDialog dlg = new PreferenceDialog(null, manager);
   //注册页面切换事件
   dlg.addPageChangedListener( new IPageChangedListener(){
    //当页面切换时
    public void pageChanged(PageChangedEvent event) {
     //获得当前页面
     IPreferencePage page = (IPreferencePage)event.getSelectedPage();
     //输出当前页面的标题
     System.out.println(page.getTitle());
    }
   });

分享到:
评论
1 楼 imu2008 2012-10-29  
       

相关推荐

    eclipse插件开发PDF

    此外,提到了SWT和JFace,这是Eclipse插件开发中常用的两个库,其中SWT是界面控件库,而JFace提供了很多高级组件,例如对话框、向导和首选项支持。 文档内容还提到,Eclipse插件开发可以显著提高开发速度,因为它...

    Eclipse 插件开发指南

    Eclipse的插件开发框架允许开发者创建自定义的编辑器、视图、菜单项、快捷方式、构建系统等,极大地提高了开发效率和灵活性。 **2.1 插件开发基础** 开发Eclipse插件首先需要理解其插件模型,该模型基于OSGi(Open...

    eclipse 3.6 rcp 开发

    - 实现PreferencePage类以创建自定义的首选项页面。 #### 10. 添加状态栏 - **介绍**: 状态栏用于显示简短的信息或状态。 - **实现**: 通过实现IStatusLineManagerListener接口。 #### 11. 透视图 - **概念**: ...

    自己动手写开发工具--基于Eclipse的工具开发

    - **首选项页面的创建**:通过创建首选项页面,用户可以自定义插件的行为。文章详细讲解了如何创建并配置首选项页面。 - **Eclipse资源API和文件系统的使用**:这部分内容涵盖了如何使用Eclipse提供的资源API进行...

    eclipse插件开发demo源码

    - **Preferences**:插件可以通过提供首选项页让用户自定义设置。 通过深入研究这个"eclipse插件开发demo源码",你可以了解Eclipse插件的结构、生命周期以及如何与IDE交互。这将为你进一步开发复杂的Eclipse插件...

    基于Eclipse的Android开发环境搭建

    在IT行业中,Android开发是一项非常重要的技能,而Eclipse曾是开发者首选的集成开发环境(IDE)之一。本文将详细讲解如何基于Eclipse搭建Android开发环境,以便进行应用程序的创建和调试。 首先,你需要下载Eclipse...

    Eclipse权威开发指南2.pdf

    2.1.5 首选项介绍..... 19 2.2 基本的Eclipse使用...... 20 2.2.1 在Eclipse中工作..... 21 2.2.2 了解Eclipse用户界面..... 22 2.2.3 任务、书签和问题..... 28 2.2.4 后台处理..... 29 2.2.5 Eclipse的帮助功能......

    通过例子学习Eclipse RCP开发.pdf

    9. **首选项(Preferences)**:用户可以通过首选项对话框调整应用的设置,开发者可以为插件添加自定义的首选项页面,以满足个性化需求。 10. **部署与更新**:Eclipse RCP支持自动更新功能,用户可以通过网络更新...

    Eclipse权威开发指南1.pdf

    2.1.5 首选项介绍..... 19 2.2 基本的Eclipse使用...... 20 2.2.1 在Eclipse中工作..... 21 2.2.2 了解Eclipse用户界面..... 22 2.2.3 任务、书签和问题..... 28 2.2.4 后台处理..... 29 2.2.5 Eclipse...

    Eclipse插件开发学习笔记15-18.rar

    接着,第16章《首选项(Preferences)》探讨了Eclipse中的首选项管理。首选项允许用户自定义IDE的行为,如字体大小、颜色主题等。开发者需要了解如何创建首选项页,保存和恢复用户的配置,以及在插件中使用这些首...

    使用Eclipse 进行Maven开发

    m2eclipse 提供了一系列的首选项设置,用于定制 Maven 的行为。这些设置可以通过“Window”-> “Preferences”-> “Maven”访问。在这里,可以配置 Maven 的全局设置、本地仓库位置、构建命令、依赖解析策略等。 ##...

    TeamCenter2007二次开发在Eclipse设置说明

    在Eclipse的首选项设置中,打开"Java" -> "Installed JREs",这里你可以添加、删除或修改已安装的JRE,确保所使用的JRE版本与TeamCenter 2007兼容。 接下来,为了在Eclipse中开发TeamCenter 2007的插件,你需要配置...

    Eclipse搭建Ruby开发环境

    - 在首选项对话框中,展开“Ruby”节点,点击“Installed Interpreters”。 - 点击“Add”按钮,输入 Ruby 的安装路径等信息,然后点击“OK”。 6. **解决错误**: - 在配置过程中可能会遇到一些错误提示,例如...

    eclipse插件开发学习笔记(扫描版15-28章)

    5. **第16章 首选项(Preferences)**:首选项设置允许用户个性化Eclipse的行为。开发者可以创建自己的首选项页面,让插件用户能够调整与插件相关的设置。本章可能涉及如何定义、存储和读取首选项,以及如何在用户...

    基于Eclipse平台的Python Flask项目开发插件设计源码

    本插件是为Eclipse平台设计的Python Flask项目开发工具,包含4778个文件,涵盖2477个Java源文件、1548个Python源文件、123个GIF图像、88个Eclipse首选项文件、81个PNG图像、47个文本文件、45个XML配置文件、37个HTML...

    Eclipse RCP 应用系统开发方法与实战 源代码

    首选项允许用户自定义应用的行为。开发者可以学习如何创建首选项页,存储和读取用户设置,这在"第9章.rar"中可能有详细的代码实现。 7. **调试和测试** 开发RCP应用时,调试和测试是非常重要的环节。源代码可能...

    Eclipse插件开发扩展点大全(中文)

    6. **Preference Page Extensions**(首选项页扩展):创建和集成自定义的首选项设置页面,让用户可以配置插件的行为。 7. **Builder Extensions**(构建器扩展):插件可以通过此扩展点参与到项目的构建过程中,...

    自己动手写开发工具--基于Eclipse的工具开发.pdf

    - JFace是建立在SWT之上的一个界面框架,用于提供一系列的界面组件和功能,如对话框、属性列表、首选项页面等。它抽象了更多的UI功能,简化了复杂界面的开发。 3. **GEF图形编辑框架**: GEF(Graphical Editing ...

Global site tag (gtag.js) - Google Analytics