我们已经知道,JFace 用户界面框架对于在对话框中显示任务进度提供了基本支持(有关详细信息,请参阅长时间运行的操作)。在并行基础结构中,我们查看了并行和长时间运行操作的平台运行时支持。现在,我们将在 org.eclipse.ui.progress 包中查看平台用户界面是如何增强此基础结构的。此包提供了用于在工作台中显示作业进度的用户界面,并且对于在用户界面线程中运行的作业定义更多支持。
首先,让我们看一看可能正在运行的不同种类的后台操作及它们在工作台用户界面中如何显示:
用户启动的作业是用户已经触发的作业。工作台将在模态进度对话框中自动显示用户作业,该对话框具有一个按钮以使用户能够在后台运行操作的同时继续执行其它操作。有一个全局首选项,用来指示用户作业是否应该始终在后台运行。用户作业将与在 作业 API 中一样使用 Job#setUser 进行区分。用户作业的示例包括:构建和检出项目、与存储库同步、导出插件和搜索。
自动触发的作业对用户很有用,但不是由用户启动的。这些作业显示在进度视图和状态行中,但是,当这些作业运行时,就不会显示模态进度对话框。这种作业的示例包括:自动构建和已调度的同步。
系统操作不是由用户触发的,并且可以认为是平台实现详细信息。这些作业是通过使用 Job#setSystem 设置系统标志来创建的。系统作业的示例包括:延迟填充窗口小部件的作业或者计算视图的修饰和注释的作业。
假定一个环境中有几项操作可能会同时发生,则用户需要:
指示长时间运行的操作已启动。
用户作业显示在进度对话框中,将为用户提供立即反馈;而自动触发的作业则显示在状态行和进度视图中。影响部件的作业应该是调度作业或者已向部件注册的作业,以便工作台可以提示用户某些会影响部件的操作正在运行。
指示操作已结束。
用户可以很容易地知道用户作业何时结束了,因为用户作业结束时,进度对话框也就关闭了。对于非用户作业,提供了两种反馈机制。如果作业是调度作业或者已向部件注册的作业,则当作业完成时将显示部件的进度提示。如果作业返回了错误,则在状态行的右下角将出现错误指示器,它将提示您发生了错误。
指示有吸引的新结果或新信息,而不通过使用对话框来偷取焦点。
当操作完成时,用户作业可以直接向用户显示结果。对于非用户作业,建议使用除对话框之外的对象来显示结果,以免打扰用户。例如,可以在作业启动时打开一个视图,结果将显示在此视图中而不会干扰用户的工作流程。另外,可以将作业属性添加到作业,以指示它应该保留在进度视图中并指示它提供一个将显示结果的操作。在这种情况下,当作业保留在进度视图中并且具有要向用户显示的结果时,在状态行的右下角就会出现警告指示。
对正在运行的操作进行控制的总体感觉,并且能够监视和取消后台操作。
用户作业为用户提供了最佳控制,因为很容易取消用户作业,通过进度对话框的详细信息选项卡,用户作业对阻塞或并发操作运行提供了明确指示。注意,仅当插件使用 IProgressService#busyCursorWhile 或 IProgressService#runInUI 时,才会显示提供了详细信息区域的增强进度对话框。另外,通过进度视图可以访问正在运行的作业。
所有已安装插件的一致进度报告。
使用进度服务 API 的优点在于每个用户可以获得一致的进度体验。
工作台进度服务(IProgressService)是与工作台进度支持的主要接口。可以从工作台中获得进度服务,然后使用它来显示后台操作的进度和在用户界面线程中运行的操作的进度。此类的主要用途是对正在运行的操作提供“一站式采购机制”,从而使插件开发者无需决定应该使用哪种机制来显示给定情况下的进度。它的另一个优点是,使用这些方法显示的进度对话框能够很好地指示一个操作何时被另一个操作阻塞了,并且使用户能够控制冲突的解决。应该尽可能使用 IProgressService#busyCursorWhile 来运行长时间运行的操作:
IProgressService progressService = PlatformUI.getWorkbench().getProgressService(); progressService.busyCursorWhile(new IRunnableWithProgress(){ public void run(IProgressMonitor monitor) { //do non-UI work } });
此方法最初将显示一个繁忙光标,当操作的持续时间超过指定的时间阈值时,就会将该光标替换为进度对话框。与使用进度对话框比较起来,这种方法的优点在于,如果一个操作的运行时间较短,就不会显示进度对话框。如果操作必须更新用户界面,则您始终可以使用 Display.asyncExec 或 Display.syncExec 来运行用于修改用户界面的代码。
如果一个操作必须作为一个整体在用户界面线程中运行,则应该使用 IProgressService#runInUI。如果操作被阻塞,这种方法还会显示一个进度对话框,并为用户提供了控制权。
progressService.runInUI( PlatformUI.getWorkbench().getProgressService(), new IRunnableWithProgress() { public void run(IProgressMonitor monitor) { //do UI work } }, Platform.getWorkspace().getRoot());
第三个参数可以是空的,也可以是操作的调度规则。在此示例中,我们指定工作空间根,当此用户界面操作运行时,工作空间根实质上将锁定该工作空间。
还可以使用进度服务为作业系列注册一个图标,以便进度视图可以在正在运行的作业旁边显示该图标。以下是一个示例,它说明自动构建作业系列是如何与它的图标相关联的:
IProgressService service = PlatformUI.getWorkbench().getProgressService(); ImageDescriptor newImage = IDEInternalWorkbenchImages.getImageDescriptor( IDEInternalWorkbenchImages.IMG_ETOOL_BUILD_EXEC); service.registerIconForFamily(newImage, ResourcesPlugin.FAMILY_MANUAL_BUILD); service.registerIconForFamily(newImage, ResourcesPlugin.FAMILY_AUTO_BUILD);
IWorkbenchSiteProgressService 包含一个用于调度作业的 API,当作业在运行时,它就会更改工作台部件的外观。如果插件正在运行将影响部件的状态的后台操作,则可以通过部件来调度作业,并且用户将获得部件正处于繁忙状态的反馈信息。以下是一个示例:
IWorkbenchSiteProgressService siteService = (IWorkbenchSiteProgressService)view.getSite().getAdapter(IWorkbenchSiteProgressService.class); siteService.schedule(job, 0 /* now */, true /* use the half-busy cursor in the part */);
工作台为 IProgressConstants 中的作业定义与进程相关的属性。可以使用这些属性来控制如何在进度视图中显示作业。可以使用这些属性来告诉进度视图当作业完成之后仍将作业保留在视图中(IProgressConstants#KEEP_PROPERTY),或者在视图中一次只保留一个作业(IProgressConstants#KEEPONE_PROPERTY)。还可以使一个操作与一个作业相关联(IProgressConstants#ACTION_PROPERTY)。当一个作业具有相关联的操作时,进度视图就会显示一个超链接,从而使用户可以运行该操作。您还可以了解用户作业当前是否显示在进度对话框中(IProgressConstants#PROPERTY_IN_DIALOG)。当可以执行操作时,在状态行的右下角就会提供提示。以下示例使用这些属性:
Job job = new Job("Do Work") { public IStatus run(IProgressMonitor monitor) { // do some work. // Keep the finished job in the progress view only if it is not running in the progress dialog Boolean inDialog = (Boolean)getProperty(IProgressConstants.PROPERTY_IN_DIALOG); if(!inDialog.booleanValue()) setProperty(IProgressConstants.KEEP_PROPERTY, Boolean.TRUE); } }; job.setProperty(IProgressConstants.ICON_PROPERTY, Plugin.getImageDescriptor(WORK_IMAGE)); IAction gotoAction = new Action("Results") { public void run() { // show the results } }; job.setProperty(IProgressConstants.ACTION_PROPERTY, gotoAction); job.setUser(true); job.schedule();
应该尽可能在用户界面线程外部执行长时间运行的操作。但是,当该操作的目的是更新用户界面时,就不能始终都这样做。SWT 线程技术问题说明可以如何使用 SWT Display 来做到这一点。工作台定义了一个特殊作业 - UIJob,它的 run 方法将在 SWT asyncExec 内部运行。UIJob 的子类应该实现 runInUIThread 方法而不是 run 方法。
WorkbenchJob 扩展 UIJob,以便仅当工作台正在运行时才能调度或运行作业。通常,应该避免在用户界面线程中工作时间过长,因为在执行用户界面作业期间用户界面将不会刷新。