将基于 Swing 的开发工具插入 Eclipse 中

原创 2005年05月28日 17:32:00
Terry Chan(terrych@ca.ibm.com)
软件工程师,IBM Canada Ltd.
2003 年 1 月

    学习如何将独立的、基于 Swing 的编辑器作为插件集成到 Eclipse Platform 中。通过使用一些简单的技术,您就可以在 Swing 工具、Eclipse Platform 和各种 SWT 小窗口(widget)之间共享资源,而且这些资源可以通过相互知晓性(mutual awareness)通信。工具供应商如果打算将基于 Eclipse 的开发工具引入市场,又想最低限度地重新编码,也将发现本文有所帮助。

引言
Eclipse Platform 为工具开发提供一组健壮的服务和 API。它使来自完全不同的供应商的工具之间的集成变得平滑,为不同类型的开发工作创建了一个无缝的环境。

Eclipse Platform 的软件组件之一就是 SWT。尽管 SWT 不是 Platform 的一个核心组件集,但它还是不可或缺的,因为它为产品和插件开发者提供了一组基于 Java 的 GUI 小窗口。SWT 与操作系统无关且非常方便,然而它的底层 JNI 接口将展现本机平台的外观和感觉(look-and-feel)以及性能。

总体上讲,对于那些想要编写在 Platform 的各种框架中运行良好且视觉上有吸引力的插件的开发者和供应商来说,SWT 提供了一个优秀的解决方案。然而,SWT 与 Java 的 Swing GUI 小窗口之间的互操作性程度相当低,这一点对 SWT 影响很大。例如,Swing 和 SWT 使用完全不同的事件处理机制。这个差异常常会使由 Swing 和 SWT 共同组成的 GUI 不可用。

为了在 Swing 和 SWT 之间提供一个接口以便提供可接受级别的兼容性,我们已经做了一些工作,比如使开发者能够将 Swing 小窗口嵌入到 SWT 中的 org.eclipse.swt.internal.swt.win32.SWT_AWT 实用程序类。但是,这些方法仍然是实验性的,尚未获得官方支持 ? 由此包名内含有“internal”。

这个拙劣的互操作性对于 Eclipse 项目和工具供应商来说,都是令人遗憾的障碍。目前,大量软件开发和测试工具提供用 Swing 编写的用户界面。将一个带有复杂的 Swing GUI 的现有工具移植到 SWT 需要来自供应商的相当多的时间和投资。尽管 Eclipse Platform 具有了所有先天的优势,但是 Swing 和 SWT 之间拙劣的互操作性导致开发成果不那么吸引人。

本文向您说明了如何实现下列操作:

    * 启动一个基于 Swing 的编辑器以编辑 Eclipse Platform Workbench 中任何名为“ThirdParty.java”的 Java 文件
    * 将 Swing 编辑器中所作的源代码更改带回到 Workbench 中
    * 使用 Preference Page 框架控制 Swing 编辑器的属性
    * 使 Swing 编辑器成为“Workbench 知晓的”
    * 从 Swing 编辑器中启动一个 SWT 小窗口

本文引入了一些简单的技术来实现上述操作,无需使用任何不被支持的 API。我们不引用任何内部类并且遵守所有通用的插件规则。为了最有效地使用这些技术,您应该具有编写插件和使用插件开发环境(Plug-in Development Environment)的基本知识,您还应该具有对基于 Swing 的编辑器的源代码的访问权。

假定的 Swing 编辑器:Ed
为了模拟真实的各种工具集成的情况,我们来使用一个假定的基于 Swing 的编辑器(名为“Ed”)。下面是 Ed 的一些特征:

    * Ed 是基于 Swing 的编辑器。
    * Ed 继承了 JFrame。
    * Ed 只处理具有特定名称 ThirdParty.java 的 Java 文件上。
    * Ed 用一个 JEditorPane 和一个 JButton 作为私有域。JEditorPane 显示 ThirdParty.java 的所有源代码。JButton 保存源代码。
    * Ed 是带有一个 main() 方法的独立的 Java 应用程序。
    * Ed 被设计为在命令提示符下运行。它并不知晓 Eclipse Platform Workbench。

基本概念
由于 Swing 和 SWT 互操作性的限制,难以获得这二者之间的直接通信(如事件处理)。在既不使用不被支持的 API 也不服从任何插件开发规则的情况下,实现这个目的的一条途径就是避免它们彼此嵌入,取而代之的是让它们拥有独立的 Frame。插件类(或者说 Singleton 实用程序类)将处理它们之间的通信,如图 1 所示。例如,Ed 上的 JButton 可以使一个 SWT Shell 出现,显示已编辑的 ThirdParty.java 的一些特定于 Workbench 的属性(如 Project References)。

图 1. Singleton 实用程序类
Singleton 实用程序类

编辑器集成
集成的主要目的是开发一个用 Ed 作为在 Workbench 中找到的任何 ThirdParty.java 的缺省编辑器的插件。

准备插件项目
在 Workbench 中,创建一个新的插件项目“org.eclipse.jumpstart.editorintegration”,然后选择 Create plug-in project 向导中的“Create plug-in using a template wizard”选项。单击 Next。选中“Add default instance acces”选项,然后单击 Finish。Workbench 切换到 Plug-in Development Perspective。一个空白的插件清单(manifest)文件以及继承了 AbstractUIPlugin 的插件类 EditorintegrationPlugin 是被自动创建的。还生成了插件类的一个私有静态实例以及 getter 方法。

插件清单文件编辑器应该是打开的;如果没打开,双击 plugin.xml 启动它。

该插件需要下面这些库。将它们添加到插件项目的 Java Build Path 下:

    * ECLIPSE_HOME/plugins/org.eclipse.core.resources/resources.jar
    * ECLIPSE_HOME/plugins/org.eclipse.jdt.core/jdtcore.jar
    * ECLIPSE_HOME/plugins/org.eclipse.jdt.ui/jdt.jar
    * ECLIPSE_HOME/plugins/org.eclipse.swt/swt.jar
    * ECLIPSE_HOME/plugins/org.eclipse.ui/workbench.jar

插件清单文件
因为这个插件只处理名为 ThirdParty.java 的 Java 文件,所以我们需要为这些 Java 文件指定一个编辑器。在插件清单文件编辑器中,切换到 Extensions 选项卡,然后添加扩展点“Internal and External Editors”。将 default 设为“true”,将 name 设为“Ed - Swing Editor”,将 filenames 设为“ThirdParty.java”,将 launcher 设为“org.eclipse.jumpstart.editorintegration.EdLauncher”。添加的扩展点的源代码看上去应该如清单 1 所示:
清单 1. 添加一个扩展点


<extension point="org.eclipse.ui.editors">
<editor
   name="Ed - Swing Editor"
   default="true"
   icon="icons/thirdparty.gif"
   filenames="ThirdParty.java"
   launcher="org.eclipse.jumpstart.editorintegration.EdLauncher"
   id="org.eclipse.jumpstart.editorintegration.ed">
</editor>
</extension>

Ed 现在是所有 ThirdParty.java 文件的缺省编辑器,如图 2 所示。

图 2. Ed 是所有 ThirdParty.java 文件的缺省编辑器
所有 ThirdParty.java 文件的缺省编辑器将是 Ed

请注意:一定要包括 icons/thirdparty.gif 文件,它被作为“Open With”菜单中所有 ThirdParty.java 文件的缺省编辑器显示。

集成 Ed 源代码
将 Ed 的源代码导入到插件项目中。如何调用 Ed 由您决定。插件类可以包含一个 Ed 私有域以及一个相应的公有 getter 方法:
清单 2. Ed 作为私有域


private Ed ed = null;

public Ed getEd()
{
   if (ed == null)
   {
      ed = new Ed ();
   }
   return ed;
}

另外,插件类可以为每个已启动的 ThirdParty.java 文件返回 Ed 的一个单独的实例。您在该插件维护和提供的 Singleton 实用程序类中实现这两种方式中的哪一种都可以。

编辑器启动程序(launcher)
因为插件使用扩展点 org.eclipse.ui.editors,所以它必须为 Eclipse Platform 提供一个清单文件中指定的编辑器启动程序类。

创建类 org.eclipse.jumpstart.editorintegration.EdLauncher 以实现接口 IEditorLauncher(如果没找到这个接口,请确保 workbench.jar 文件包含在 Project Path 中;请参阅准备插件项目)。请一定要选中 Wizard 中的“Inherited abstract methods”选项。

每次双击 ThirdParty.java 文件时,Eclipse Platform 都执行 EdLauncher.open(IFile) 来调用该文件类型的缺省编辑器。Platform 将单击的构件作为 IFile 传送给方法。在这种情况下,IFile 是一个 Java 源文件,因此您可以将它因此您可以将它强制转型为 ICompilationUnit。

由于 Ed 并不是为了处理 JDT 对象而设计的,所以您必须从 ICompilationUnit 中抽取源代码内容并将它放到 Ed 中以便查看:


EditorintegrationPlugin.getDefault().getEd().getEditorPane().setText
   (ICompilationUnit.getSource());

EditorintegrationPlugin.getDefault().getEd().show();

一旦执行了 show() 方法,Ed 就被作为主 Workbench 窗口外部的一个 JFrame 显示(请参见图 3)。插件记录已编辑的 ThirdParty.java 的项目名称和包名称。当您试图保存 Ed 中所作的更改时,该信息是至关重要的。

图 3. Swing 编辑器显示在 Workbench 外面
Swing 编辑器显示在 Workbench 外面

双向传递(round-tripping):将源代码的更改返回到 Workbench 中
传统的编辑器将在平面文件、二进制资源库中保存源代码,或者将源代码保存到源代码控制系统中。作为一个编辑器,Ed 需要一些方法来保存它显示的对源代码的更改。

Ed 有一个“Save”按钮(JButton),如 Swing 编辑器:Ed 中所描述。按下按钮后,actionPerformed() 方法被调用,Save 按钮触发一个事件。实现一个事件侦听器的对象接收事件并执行源代码保存操作。

您可以用 Singleton 实用程序类(请参阅编辑器启动程序)作为实现事件侦听器的对象。实用程序类一接收到来自 Save 按钮的事件对象,就从 Ed 中抽取源代码,然后将源代码放入对应的 Workbench 对象中。保存到文件系统的实际工作被委托给 Eclipse Platform。

多个文件在 Workbench 中可能拥有相同的名称。这就是 ThirdParty.java 的项目名称和包名称有用的地方。该信息由插件存储。确切的实现方式由您决定。假定编辑器存储信息,您可以在实用程序类中使用下列代码片段(snippet):
清单 3. 管理文件名称


public void saveButtonPressed() {
   try {
      IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
   
      IProject myProj = root.getProject(getEd().getProjectname());
   
      IFolder myFolder = myProj.getFolder(getEd().getPackageName());
   
      IJavaElement myPackageFragment = JavaCore.create(myFolder);
   
      if (myPackageFragment != null) {
         IPackageFragment packageFrag = (IPackageFragment)myPackageFragment;
      
         String sourceFromEd = getEd().getJEditorPane1().getText();
      
         ICompilationUnit icu = packageFrag.getCompilationUnit("ThirdParty.java");
      
         icu.getBuffer().setContents(sourceFromValidator);
      
         icu.save(null, true);
      }
      else {
         System.out.println("myPackageFragment is null.");
      }
   } catch (Exception e) {
      e.printStackTrace();
   }
}

逆向进行双向传递
清单 3 处理“正向”双向传递。还需要“反向”双向传递来把用 Eclipse Platform 的 JDT Java 编辑器在 ThirdParty.java 中所作的任何更改带回到 Ed。

实用程序类可以实现接口 org.eclipse.jdt.core.IElementChangedListener,您可以用这个接口跟踪对任何 IElements(包括 ICompilationUnit)作的更改。当源代码更改被引入到 Workbench 中的 Java 文件内时,调用方法 elementChanged(ElementChangedEvent)。

您需要有选择地过滤出那些不涉及 Ed 插件的 IElement 更改。一种过滤方式是从 IElementChangedEvent 参数中抽取 IJavaElementDelta 对象并对其进行检查。例如,下面的语句过滤 Ed 插件情况下不相关的源代码更改。
清单 4. 过滤不相关的源代码更改


IJavaElementDelta delta = event.getDelta();

if (delta != null) {
   if(delta.getElement().getElementName().equalsIgnoreCase("ThirdParty.java")) {
      //code to update Ed's editor panel.
   }
}

对于非 Java 构件的编辑器,IElementChangedListener 不能捕获在 Workbench 中所作的更改。Eclipse Platform 提供接口 org.eclipse.core.resources.IResourceChangeListener 来处理对非 Java 资源所作的更改。

首选项页面
要为用户提供丰富的、易于使用的功能,工具应该提供可以通过启动参数访问的、或者可以通过 GUI(它不是编辑器的核心图形界面的一部分)访问的可配置的选项。在用于 Eclipse Platform 的插件的情况中,强烈推荐通过 Platform 的 Preference Page 框架(Window -> Preferences)对这些选项进行配置。

为了举例起见,我们将 Ed 的颜色作为一个使用 Platform 首选项页面的可配置的选项来控制。

在插件清单文件中添加一个首选项页面扩展点
在 Eclipse Platform 中,首选项页面被定义为一个扩展点。要使用它,请将它添加到插件清单文件编辑器中,或者将下列代码放入 plugin.xml 中:
清单 5. 将首选项页面添加到 plugin.xml


<extension
   id="org.eclipse.jumpstart.editorintegration.pref"
   name="Ed Preference"
   point="org.eclipse.ui.preferencePages">
<page
   name="Swing Editor Preference Page"
   class="org.eclipse.jumpstart.editorintegration.EdPreferencePage1"
   id="Swing Editor Preference Page"
</page>
</extension>

首选项页面类
首选项页面继承了 org.eclipse.jface.preference.PreferencePage。在这个示例中,简单的首选项页面由三个最大值为 255 的滑动条(slider bar)组成,表示 Ed 的 java.awt.Color 对象的颜色(红、绿和蓝)。

在插件项目中创建清单文件中指定的类 org.eclipse.jumpstart.editorintegration.EdPreferencePage1。这个类必须继承 org.eclipse.jface.preference.PreferencePage 并实现接口 org.eclipse.ui.IWorkbenchPreferencePage。

首选项页面呈现出与编辑器启动程序类似的编码问题:JFace/SWT 将如何与 Swing 通信?幸运的是,同样的方式适用。例如,performApply() 方法可能看上去像这样:
清单 6. performApply() 方法


protected void performApply() {
   int red = redSWTSlider.getSelection();
   int green = greenSWTSlider.getSelection();
   int blue = blueSWTSlider.getSelection();
   
   java.awt.Color newColor = new java.awt.Color(red, green, blue);
   EditorintegrationPlugin.getDefault().getEd().getContentPane().setBackground(
      newColor);
}

插件应该使用 Platform 的 Preference Store 机制存储已配置的值,任何其他的插件也应该这么做。performOk() 方法可能看上去像这样:
清单 7. performOk() 方法


public boolean performOk() {
   getPreferenceStore().setValue("redValue", redSWTSlider.getSelection();
   getPreferenceStore().setValue("greenValue", greenSWTSlider.getSelection());
   getPreferenceStore().setValue("blueValue", blueSWTSlider.getSelection());
   return true;
}

图 4 中显示了从首选项页面控制 Swing 编辑器的颜色。

图 4. 从首选项页面控制 Swing 编辑器的颜色
从首选项页面控制 Swing 编辑器的颜色

Workbench 知晓性
由于大多数编辑器最初被设计为独立的 Java 应用程序设计,所以它们不注意 Workbench 的存在。它们可能不能处理 Platform 的一些环境属性,这就限制了编辑器与 Platform 集成的亲密度。为了给用户提供一个更平滑更一致的开发体验,开发者和插件供应商应该认真考虑增强他们现有的 Swing 工具从而使其变为 Workbench 知晓的。

例如,Ed 被编码为直接处理基于文件系统的 Java 文件。因此,Platform 的 Java Project 和 Project Reference 与 Ed 无关。在这一部分中,我们将把 JButton 添加到 Ed 以启动一个 SWT 对话框,该对话框显示了已编辑的 ThirdParty.java 被引用的项目。从用户角度看,他单击一个 Swing 小窗口,触发了一个显示特定于 Workbench 的信息的 SWT 窗口,这表示 Swing 编辑器、SWT 以及 Workbench 彼此正在紧密交互。

增强编辑器
假设您具有对 Ed 源代码的访问权,您可以添加额外的 Swing 小窗口以获得额外的 Workbench 知晓性功能。将 JButton 添加到编辑器的主内容窗格,然后它会启动一个 SWT 对话框。将 JButton 的文本设置为“Referenced Project”。

Referenced Project 按钮的事件处理机制的工作方式将与 Save 按钮(请参阅双向传递:将源代码的更改返回到 workbench 中)的工作方式类似。插件实用程序类将侦听来自这个按钮的事件。实用程序类一接收到 Referenced Project 按钮触发的一个事件对象,它就会执行必要的操作来检索项目引用信息并在 SWT 中显示该信息。

检索项目引用信息
在 SWT 对话框可以被显示之前,插件需要弄明白包含已编辑的 ThirdParty.java 的项目引用了 Workbench 中的哪些项目。这是插件类的工作,而且它可以使用如清单 8 所示的一种方法,其中传入该方法的字符串变量是项目的名称:
清单 8. 检索项目引用信息


private String[] getReferencedProjectArray(String arg) {
   String[] projectNameArray = null;
   
   try {
      IProject[] referencedProjects = 
         ResourcesPlugin.getWorkspace().getRoot().getProject(
            arg).getReferencedProjects();
      
      int referencedProjectsLength = referencedProjects.length;
      
      if (referencedProjectsLength == 0) {
         projectNameArray = new String[1];
         projectNameArray[0] = "none";
      }
      else {
         projectNameArray = new String[referencedProjectsLength];
         for (int i=0; i < referencedProjectsLength; i++) {
            projectNameArray[i] = referencedProjects[i].getName();
         }
      }
      return projectNameArray;
   } catch (Exception e) {
      e.printStackTrace();
      return null;
   }
}

SWT 对话框
Project Referenced SWT 对话到底应该会是什么样子要由插件 GUI 设计师决定。在这个示例中,一个带有 List 对象的简单的 SWT Shell(要显示引用的项目)就足够了:
清单 9. 带有 List 对象的 SWT Shell


public class SWTProjectReferenceFrame implements Runnable {
   private Shell shell;
   private Display display;
   Thread myThread;
   
   public void run() {
      open();
   }
   
   public void open() {
      display = new Display();
      shell = new Shell(display);
      shell.setLayout(new org.eclipse.swt.layout.GridLayout());
      shell.setText("Projects Referenced - SWT Frame");
      shell.setSize(400, 400);
      createListGroup();
      shell.open();
      
      while (!shell.isDisposed()) {
         if (!display.readAndDispatch()) {
            EditorintegrationPlugin.getDefault().getEd().repaint();
            display.sleep();
         }
      }
      myThread = null; // disposing the thread when the SWT window is disposed.
   }
   
   // Other methods appear here ...
}

方法 createListGroup() 准备了 List 对象并设置其内容以包含 projectNameArray(请参阅检索项目引用信息)。
清单 10. 准备 List 对象


private void createListGroup() {
   Group listGroup = new Group(shell, SWT.NULL);
   listGroup.setLayout(new GridLayout());
   listGroup.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL |
                                        GridData.HORIZONTAL_ALIGN_FILL |
                                        GridData.VERTICAL_ALIGN_FILL));
   listGroup.setText("listGroup");
   
   List list = new List(listGroup, SWT.V_SCROLL);
   list.setItems(projectNameArray);
}

根据启动 SWT 对话框的方式,您可能需要在一个单独的线程(如清单 10 中的 myThread 对象所指出的那样)中执行 SWT 窗口以避免在 Swing 编辑器中的重绘制(repaint)问题。

图 5 中显示了 Swing 按钮启动一个 SWT 框架。

图 5. 从 Swing 按钮启动一个 SWT 框架
从 Swing 按钮启动一个 SWT 框架

结束语
这里描述的这些技术提供了一个临时的解决方案,它可以帮助您快速地将基于 Swing 的工具集成到 Eclipse Platform 中。但是,只要有可能,您就应该在现有的 Swing 小窗口上使用紧密集成的 SWT/JFace 组件。例如,编辑器应该用 Eclipse Platform 的 Preference Page 框架作为配置插件的中心入口点,而不是用各个引用对话框框架来处理多个用户引用。

尽管本文中的这些概念相对简单且易于实现,但是请不要将 Swing 小窗口作为永久设备留在插件中。要控制和利用 Eclipse 项目中的所有服务,您就应该逐渐减少插件中陈旧的 Swing 代码的数量以便支持 Eclipse 项目提供的各种框架。

参考资料

    * 如需更多背景知识,请阅读 eclipse.org 上 John Arthorne 撰写的文章“How You've Changed! Responding to resource changes in the Eclipse workspace”。

    * 有关 Eclipse Platform 及其操作方式的介绍,请阅读 Greg Adams 和 Marc Erickson 撰写的文章“Working the Eclipse Platform”(developerWorks,2001 年 11 月)。

    * 如需有关编写用于国际市场的 Eclipse 插件方面的建议,请阅读“Internationalizing your Eclipse plug-in”。您的计划可以从快速回顾国际化的目标和挑战开始,接着再看详细的说明。顺便看一看如何将这些步骤应用到 Eclipse Platform 本身的国际化上(developerWorks,2002 年 6 月)。

    * 然后,为了验证您已翻译的插件文件,请阅读“Testing your internationalized Eclipse plug-in”,这篇文章包括了处理常见错误的策略以及用于比较多个属性文件的便利插件的下载。这个插件将帮助您的翻译测试者更快捷地发现错误(developerWorks,2002 年 7 月)。

    * 请学习在线 developerWorks 教程“开发和部署用于 WebSphere Studio 的插件”(需要简短注册)。

    * 请访问 eclipse.org 网站以获取更多关于 Eclipse 的信息。

关于作者
Terry Chan 是位于安大略省多伦多市的 IBM 的一名软件工程师。他作为一名分析家为 IBM Global Services 工作,专门从事 OS/2 核心转储分析。后来,他加入了 IBM 多伦多软件实验室,他在那里是 VisualAge for Java 的一名客户服务分析师。2001 年,他加入了 WebSphere Studio Application Developer(WSAD)Jumpstart 小组,该小组的主要目的是帮助 ISV 根据 WSAD 创建商业产品。您可以通过 terrych@ca.ibm.com 与 Terry 联系。
    

Java Swing可视化开发工具SWT Designer的安装

工具下载地址:https://developers.google.com/web-toolkit/tools/download-gwtdesigner?hl=zh-CN&csw=1 打开Eclip...

Swing可视化开发工具

对于刚接触Swing的开发人员,由于水平所限,需要一个可视化的开发工具,来辅助理解学习和开发。也有时候是工作紧迫,需要可视化工具来提高工作效率,节省宝贵的时间。 在此介绍下,如何可视化开发Swing...

Swing可视化开发工具

对于刚接触Swing的开发人员,由于水平所限,需要一个可视化的开发工具,来辅助理解学习和开发。也有时候是工作紧迫,需要可视化工具来提高工作效率,节省宝贵的时间。 在此介绍下,如何可视化开发Swing组...
  • atco
  • atco
  • 2013年05月08日 10:01
  • 605

swing插件之可视化开发工具windowbuilder

windowbuilder,也就是原来的SWT Designer。Google收购了Instantiations,把它的工具也重新免费发布了。 用过swt designer的人都知它是非常好用的sw...
  • zfz1214
  • zfz1214
  • 2013年12月04日 09:57
  • 1051

swing/swt可视化开发工具windowbuilder免费了

windowbuilder,也就是原来的SWT Designer。Google收购了Instantiations,把它的工具也重新免费发布了。 用过swt designer的人都知它是非常好用的sw...

maven项目创建:cmd创建项目再导入eclipse开发工具

最近想要搭个基于Maven管理的spring mvc+mybatis+oracle简易框架的,无奈在eclipse上操作时屡屡碰壁,后来发现可以先在文件夹中将maven工程建出来再导入eclipse。...

一起学Java_Eclipse开发工具介绍及equals、toString方法的重写equals

###11.01_Java开发工具(常见开发工具介绍)(了解) * A:操作系统自带的记事本软件 * B:高级记事本软件 * C:集成开发环境 IDE     * (Integrated De...

黑马程序员--Java学习日记之eclipse开发工具的熟练使用

------- android培训、java培训、期待与您交流! ---------- 1:用Eclipse写一个HelloWorld案例,最终在控制台输出你的名字  A:创建项目  B:在s...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:将基于 Swing 的开发工具插入 Eclipse 中
举报原因:
原因补充:

(最多只允许输入30个字)