7 安装节点的贡献
URCap可以贡献安装节点。安装节点将支持定制的安装节点屏幕和定制的功能。
7.1 安装节点视图用户界面
定制安装节点屏幕的布局由实现SwingInstallationNodeView接口的Java类定义,其中Swing GUI框架用于创建用户界面(UI)。实现必须指定实现安装节点功能的关联安装节点贡献(如第7.3节“安装节点的功能”所述)作为类型变量。
Listing 2: The view (UI) of the customized Hello World installation screen
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . installation . swing .
SwingInstallationNodeView ;
4 import com .ur. urcap . api. domain . userinteraction . keyboard . KeyboardTextInput ;
5
6 import javax . swing . BorderFactory ;
7 import javax . swing . Box ;
8 import javax . swing . BoxLayout ;
9 import javax . swing . JLabel ;
10 import javax . swing . JPanel ;
11 import javax . swing . JTextField ;
12 import javax . swing . JTextPane ;
13 import javax . swing . text . SimpleAttributeSet ;
14 import javax . swing . text . StyleConstants ;
15 import java .awt . Component ;
16 import java .awt . Dimension ;
17 import java .awt . event . MouseAdapter ;
18 import java .awt . event . MouseEvent ;
19
20 public class HelloWorldInstallationNodeView implements
SwingInstallationNodeView < HelloWorldInstallationNodeContribution > {
21
22 private final Style style ;
23 private JTextField jTextField ;
24
25 public HelloWorldInstallationNodeView ( Style style ) {
26 this . style = style ;
27 }
28
29 @Override
30 public void buildUI ( JPanel jPanel , final
HelloWorldInstallationNodeContribution installationNode ) {
31 jPanel . setLayout ( new BoxLayout ( jPanel , BoxLayout . Y_AXIS ) );
32
33 jPanel . add ( createInfo () );
34 jPanel . add ( createVerticalSpacing () ) ;
35 jPanel . add ( createInput ( installationNode ) );
36 }
37
38 private Box createInfo () {
39 Box infoBox = Box . createVerticalBox () ;
40 infoBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
41 JTextPane pane = new JTextPane () ;
42 pane . setBorder ( BorderFactory . createEmptyBorder () ) ;
43 SimpleAttributeSet attributeSet = new SimpleAttributeSet () ;
44 StyleConstants . setLineSpacing ( attributeSet , 0.5f);
45 StyleConstants . setLeftIndent ( attributeSet , 0f);
46 pane . setParagraphAttributes ( attributeSet , false ) ;
47 pane . setText (" The popup title below is shared between all Hello World
program nodes .\ nThe title cannot be empty .") ;
48 pane . setEditable ( false );
49 pane . setMaximumSize ( pane . getPreferredSize () ) ;
50 pane . setBackground ( infoBox . getBackground () );
51 infoBox .add( pane );
52 return infoBox ;
53 }
54
55 private Box createInput ( final HelloWorldInstallationNodeContribution
installationNode ) {
56 Box inputBox = Box . createHorizontalBox () ;
57 inputBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
58
59 inputBox . add ( new JLabel (" Popup title :")) ;
60 inputBox . add ( createHorizontalSpacing () );
61
62 jTextField = new JTextField () ;
63 jTextField . setFocusable ( false );
64 jTextField . setPreferredSize ( style . getInputfieldSize () );
65 jTextField . setMaximumSize ( jTextField . getPreferredSize () ) ;
66 jTextField . addMouseListener ( new MouseAdapter () {
67 @Override
68 public void mousePressed ( MouseEvent e) {
69 KeyboardTextInput keyboardInput = installationNode .
getInputForTextField () ;
70 keyboardInput . show ( jTextField , installationNode .
getCallbackForTextField () );
71 }
72 }) ;
73 inputBox . add ( jTextField );
74
75 return inputBox ;
76 }
77
78 private Component createHorizontalSpacing () {
79 return Box . createRigidArea ( new Dimension ( style . getHorizontalSpacing () , 0) )
;
80 }
81
82 private Component createVerticalSpacing () {
83 return Box . createRigidArea ( new Dimension (0 , style . getVerticalSpacing () ));
84 }
85
86 public void setPopupText ( String t) {
87 jTextField . setText (t);
88 }
89 }
清单2显示了HelloWorldInstallationNodeView.java文件的内容,该文件定义了用于Hello World安装节点的屏幕布局。该类使用各种Swing GUI组件构建用户界面。这些组件被添加到在buildUI(JPanel, InstallationNodeContribution)方法中作为参数提供的JPanel中。
该面板具有固定的尺寸,无法更改。边距已经由PolyScope添加,因此面板的整个区域可以用于UI组件。标题也将自动设置为SwingInstallationNodeService.getTitle(Locale)返回的值(如第7.2节所述,将自定义安装节点添加到PolyScope)。它已经设置了Swing UI管理器,这意味着没有额外样式的组件将看起来像本地的PolyScope组件,包括正确的字体类型。因此,为了模仿PolyScope,组件应该只使用有限的设计风格,例如字体大小或输入字段大小(如果适用的不适用)。调用PolyScope不支持的方法将导致抛出一个异常。
相应的安装节点贡献作为第二个参数传递给方法 buildUI(JPanel, InstallationNodeContribution),以使视图和贡献能够相互通信,以便传递值和对事件作出反应。
这种结构创建了模型-视图分离,其中视图在上述类中创建,模型在贡献中处理。相应的Java代码在以下两部分中介绍。
7.2 使定制的安装节点可用于PolyScope
为了使视图类中指定的布局和定制的安装节点对PolyScope可用,必须定义一个实现SwingInstallationNodeService接口的Java类。清单3显示了使Hello World安装节点对PolyScope可用的Java代码。
Listing 3: Hello World Installation node service
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . ViewAPIProvider ;
4 import com .ur. urcap . api. contribution . installation . ContributionConfiguration ;
5 import com .ur. urcap . api. contribution . installation . CreationContext ;
6 import com .ur. urcap . api. contribution . installation . InstallationAPIProvider ;
7 import com.ur. urcap . api. contribution . installation . swing .
SwingInstallationNodeService ;
8 import com.ur. urcap . api. domain . SystemAPI ;
9 import com.ur. urcap . api . domain . data . DataModel ;
10
11 import java . util . Locale ;
12
13 public class HelloWorldInstallationNodeService implements
SwingInstallationNodeService < HelloWorldInstallationNodeContribution ,
HelloWorldInstallationNodeView > {
14
15 @Override
16 public void configureContribution ( ContributionConfiguration configuration ) {
17 }
18
19 @Override
20 public String getTitle ( Locale locale ) {
21 return " Hello World ";
22 }
23
24 @Override
25 public HelloWorldInstallationNodeView createView ( ViewAPIProvider apiProvider
) {
26 SystemAPI systemAPI = apiProvider . getSystemAPI () ;
27 Style style = systemAPI . getSoftwareVersion () . getMajorVersion () >= 5 ? new
V5Style () : new V3Style () ;
28 return new HelloWorldInstallationNodeView ( style );
29 }
30
31 @Override
32 public HelloWorldInstallationNodeContribution createInstallationNode (
InstallationAPIProvider apiProvider , HelloWorldInstallationNodeView view
, DataModel model , CreationContext context ) {
33 return new HelloWorldInstallationNodeContribution ( apiProvider , model , view
);
34 }
35 }
SwingInstallationNodeService 接口需要两个类型变量,分别用于实现 InstallationNodeContribution 和 SwingInstallationNodeView 接口,以及以下定义的方法
. getTitle(Locale) 返回节点的标题,显示在安装选项卡的左侧,以访问定制的安装屏幕。为简单起见,标题被简单地指定为“Hello World”。在一个更现实的例子中,getTitle(Locale) 方法的返回值将根据提供的Locale参数,翻译成标准Java本地化指定的语言。标题也被用作PolyScope中安装节点屏幕的已固定标题。该方法只调用一次。
createView(ViewAPIProvider) 返回一个视图实例(在安装节点视图的第 7.1. 节 UI 中描述)。使用 ViewAPIProvider 访问有关软件版本、语言设置(可用于提供翻译的 UI)等的信息。
createInstallationNode(InstallationAPIProvider、SwingInstallationNodeView、DataModel、SwingInstallationNodeView.NodeFactory)
PolyScope 在需要创建安装节点实例时调用 CreateContext()。参数如下:
{ InstallationAPIProvider : 提供对与安装节点相关的各种 PolyScope 域 API 的访问
{SwingInstallationNodeView:由上述createView(ViewAPIProvider)创建的视图实例
{ DataModel:具有自动持久性支持的安装节点的模型 { CreationContext:调用createInstallationNode(...)的上下文
在7.3节“安装节点的功能”中讨论了方法createInstallationNode(…)的实现中使用的构造函数。加载现有安装时,安装节点构造函数对提供的数据模型的所有修改都会被忽略。这意味着理想情况下,安装节点构造函数不应设置数据模型中的任何内容。
. configureContribution(ContributionConfiguration) 在服务注册后调用一次。如果默认值不合适,请使用提供的参数配置贡献(请参阅 Javadoc 中的默认值)。如果默认值合适,请将此方法留空。
7.3 安装节点的功能
定制安装节点背后的功能必须在实现InstallationNodeContribution接口的Java类中定义。清单4显示了定义Hello World安装屏幕功能的Java代码。此类的实例由前一节中描述的HelloWorldInstallationNodeService类中的createInstallationNode(...)方法返回。
本质上,InstallationNodeContribution接口需要定义以下内容:
1.当用户进入和退出定制安装屏幕时会发生什么。
2.在安装URCap的情况下运行时,应添加到任何程序前缀中的脚本代码。
此外,该类包含链接到视图的代码(在安装节点视图的7.1. UI部分中提到),可以访问具有自动持久性和与节点关联的UR-Script生成的数据模型。
Listing 4: Java class defining functionality for the Hello World installation node
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . InstallationNodeContribution ;
4 import com .ur. urcap . api. contribution . installation . InstallationAPIProvider ;
5 import com .ur. urcap . api. domain . data . DataModel ;
6 import com .ur. urcap . api. domain . script . ScriptWriter ;
7 import com.ur. urcap . api. domain . userinteraction . keyboard . KeyboardInputCallback ;
8 import com.ur. urcap . api. domain . userinteraction . keyboard . KeyboardInputFactory ;
9 import com.ur. urcap . api. domain . userinteraction . keyboard . KeyboardTextInput ;
10
11 public class HelloWorldInstallationNodeContribution implements
InstallationNodeContribution {
12
13 private static final String POPUPTITLE_KEY = " popuptitle ";
14 private static final String DEFAULT_VALUE = " Hello World ";
15 private final HelloWorldInstallationNodeView view ;
16 private final KeyboardInputFactory keyboardFactory ;
17
18 private DataModel model ;
19
20 public HelloWorldInstallationNodeContribution ( InstallationAPIProvider
apiProvider , DataModel model , HelloWorldInstallationNodeView view ) {
21 this . keyboardFactory = apiProvider . getUserInterfaceAPI () .
getUserInteraction () . getKeyboardInputFactory () ;
22 this . model = model ;
23 this . view = view ;
24 }
25
26 @Override
27 public void openView () {
28 view . setPopupText ( getPopupTitle () );
29 }
30
31 @Override
32 public void closeView () {
33
34 }
35
36 public boolean isDefined () {
37 return ! getPopupTitle () . isEmpty () ;
38 }
39
40 @Override
41 public void generateScript ( ScriptWriter writer ) {
42 // Store the popup title in a global variable so it is globally available
to all Hello World program nodes .
43 writer . assign (" hello_world_swing_popup_title ", "\"" + getPopupTitle () + "
\"");
44 }
45
46 public String getPopupTitle () {
47 return model . get ( POPUPTITLE_KEY , DEFAULT_VALUE );
48 }
49
50 public void setPopupTitle ( String message ) {
51 if ("". equals ( message )) {
52 resetToDefaultValue () ;
53 } else {
54 model .set ( POPUPTITLE_KEY , message );
55 }
56 }
57
58 private void resetToDefaultValue () {
59 view . setPopupText ( DEFAULT_VALUE );
60 model .set ( POPUPTITLE_KEY , DEFAULT_VALUE );
61 }
62
63 public KeyboardTextInput getInputForTextField () {
64 KeyboardTextInput keyboardInput = keyboardFactory .
createStringKeyboardInput () ;
65 keyboardInput . setInitialValue ( getPopupTitle () );
66 return keyboardInput ;
67 }
68
69 public KeyboardInputCallback < String > getCallbackForTextField () {
70 return new KeyboardInputCallback <String >() {
71 @Override
72 public void onOk ( String value ) {
73 setPopupTitle ( value );
74 view . setPopupText ( value );
75 }
76 };
77 }
78 }
第7.2节中提到的数据模型使定制的安装节点可用于PolyScope,该数据模型通过DataModel对象传递给构造函数。所有需要与机器人安装一起保存和加载的数据都必须存储在该模型对象中并从中检索。
当用户与视图中的文本输入字段交互时,那里定义的侦听器将从贡献中请求键盘,当用户接受时,将调用getCallbackForTextField()。该方法内的代码负责将文本输入小部件的内容存储在数据模型中的POPUPTITLE_KEY键下,每当用户使用键盘接受键入的内容时。通过保存和加载机器人安装,您会注意到值被存储并从视图读取并返回到视图。
每当用户进入屏幕时,openView()方法就会被调用。它将视图中所定义的文本输入字段的内容设置为数据模型中存储的值。当用户离开屏幕时,closeView()方法就会被调用。
最后,安装此URCap运行的每个程序的序言中都会包含一个作业,如generateScript(ScriptWriter)方法的实现中所述。在作业中,名为“hello_world_swing_popup_title”的脚本变量被分配给一个字符串,该字符串包含存储在数据模型对象中的弹出标题。
7.4 贡献和视图的生命周期
每次创建新安装或加载不同安装时,SwingInstallationNodeService 接口中的 createView(...) 方法(请参阅第 7.2 节“使自定义安装节点可用于 PolyScope”)也会被调用,并且应返回新的视图实例。还将调用 SwingInstallationNodeService 接口中的 createInstallationNode(...) 方法以传入新的 DataModel 实例。
这意味着,如果忘记任何监听器或产生其他潜在的内存泄漏,Java垃圾收集有机会清理之前的实例。这也意味着,对这些类之外的视图实例或贡献实例的引用不应保留。尊重这一点将保护PolyScope不会耗尽内存。
8 程序节点的贡献
URCap可以贡献程序节点。节点由定制的视图部件和具有定制功能的部件提供。
8.1 程序节点视图用户界面
通过实现SwingProgramNodeView接口,自定义程序节点屏幕的布局与自定义安装节点屏幕的布局(见安装节点视图UI部分)类似。实现必须将实现程序节点功能的关联程序节点贡献(如第8.4节“程序节点功能”所述)指定为类型变量。提供给buildUI(JPanel, ContributionProvider<ProgramNodeContribution>)方法的面板具有与安装节点视图UI部分所述相同的限制和属性。
清单5显示了一个简单程序节点的布局定义。它包含一个用户可以键入名称的输入文本字段和两个标签,这两个标签提供将在运行时显示的弹出窗口的预览。视图中的标签将用于显示在安装中设置的标题。最终用户在Hello World程序节点中输入的名称将用于构造定制的弹出消息。此消息还将显示在名为“previewMessage”的预览标签中。
为了与实现程序节点本身功能的相应程序节点贡献进行通信,将提供者作为参数传递给方法 buildUI(JPanel , ContributionProvider<ProgramNodeContribution>)。对 ContributionProvider 对象调用 get() 方法将返回当前选定的程序节点。提供者有一个与关联贡献对应的类型变量。
视图和程序节点贡献的链接将在下一节中介绍。
Listing 5: The view (UI) of the customized Hello World program screen
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . ContributionProvider ;
4 import com .ur. urcap . api. contribution . program . swing . SwingProgramNodeView ;
5 import com .ur. urcap . api. domain . userinteraction . keyboard . KeyboardTextInput ;
6
7 import javax . swing . Box ;
8 import javax . swing . BoxLayout ;
9 import javax . swing . JLabel ;
10 import javax . swing . JPanel ;
11 import javax . swing . JTextField ;
12 import java . awt . Component ;
13 import java . awt . Dimension ;
14 import java . awt . Font ;
15 import java . awt . event . MouseAdapter ;
16 import java . awt . event . MouseEvent ;
17
18 public class HelloWorldProgramNodeView implements SwingProgramNodeView <
HelloWorldProgramNodeContribution >{
19
20 private final Style style ;
21 private JTextField jTextField ;
22 private JLabel previewTitle ;
23 private JLabel previewMessage ;
24
25 public HelloWorldProgramNodeView ( Style style ) {
26 this . style = style ;
27 }
28
29 @Override
30 public void buildUI ( JPanel jPanel , final ContributionProvider <
HelloWorldProgramNodeContribution > provider ) {
31 jPanel . setLayout (new BoxLayout ( jPanel , BoxLayout . Y_AXIS ) );
32
33 jPanel . add ( createInfo () );
34 jPanel . add ( createVerticalSpacing ( style . getVerticalSpacing () ));
35 jPanel . add ( createInput ( provider ));
36 jPanel . add ( createVerticalSpacing ( style . getExtraLargeVerticalSpacing () ));
37 jPanel . add ( createPreview () );
38 }
39
40 private Box createInfo () {
41 Box infoBox = Box . createHorizontalBox () ;
42 infoBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
43 infoBox .add (new JLabel (" This program node will open a popup on execution ."
));
44 return infoBox ;
45 }
46
47 private Box createInput ( final ContributionProvider <
HelloWorldProgramNodeContribution > provider ) {
48 Box inputBox = Box . createHorizontalBox () ;
49 inputBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
50 inputBox . add ( new JLabel (" Enter your name :"));
51 inputBox . add ( createHorizontalSpacing () );
52
53 jTextField = new JTextField () ;
54 jTextField . setFocusable ( false );
55 jTextField . setPreferredSize ( style . getInputfieldSize () );
56 jTextField . setMaximumSize ( jTextField . getPreferredSize () ) ;
57 jTextField . addMouseListener ( new MouseAdapter () {
58 @Override
59 public void mousePressed ( MouseEvent e) {
60 KeyboardTextInput keyboardInput = provider .get () .
getKeyboardForTextField () ;
61 keyboardInput . show ( jTextField , provider . get () . getCallbackForTextField
() );
62 }
63 }) ;
64
65 inputBox . add ( jTextField );
66 return inputBox ;
67 }
68
69 private Box createPreview () {
70 Box previewBox = Box . createVerticalBox () ;
71 JLabel preview = new JLabel (" Preview ");
72 preview . setFont ( preview . getFont () . deriveFont ( Font .BOLD , style .
getSmallHeaderFontSize () ));
73
74 Box titleBox = Box . createHorizontalBox () ;
75 titleBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
76 titleBox . add ( new JLabel (" Title :"));
77 titleBox . add ( createHorizontalSpacing () );
78 previewTitle = new JLabel ("my title ");
79 titleBox . add ( previewTitle );
80
81 Box messageBox = Box . createHorizontalBox () ;
82 messageBox . setAlignmentX ( Component . LEFT_ALIGNMENT );
83 messageBox .add (new JLabel (" Message :"));
84 messageBox .add ( createHorizontalSpacing () );
85 previewMessage = new JLabel ("my message ") ;
86 messageBox .add ( previewMessage );
87
88 previewBox .add ( preview );
89 previewBox . add ( createVerticalSpacing ( style . getLargeVerticalSpacing () )) ;
90 previewBox . add ( titleBox );
91 previewBox . add ( createVerticalSpacing ( style . getVerticalSpacing () )) ;
92 previewBox . add ( messageBox );
93
94 return previewBox ;
95 }
96
97 private Component createVerticalSpacing ( int height ) {
98 return Box . createRigidArea ( new Dimension (0 , height ));
99 }
100
101 private Component createHorizontalSpacing () {
102 return Box . createRigidArea ( new Dimension ( style . getHorizontalSpacing () , 0) )
;
103 }
104
105 public void setPopupText ( String popupText ) {
106 jTextField . setText ( popupText );
107 }
108
109 public void setMessagePreview ( String message ) {
110 previewMessage . setText ( message ) ;
111 }
112
113 public void setTitlePreview ( String title ) {
114 previewTitle . setText ( title );
115 }
116 }
8.2 将视图和贡献联系起来
视图(实现SwingProgramNodeView接口)和程序节点贡献(实现ProgramNodeContribution接口)必须能够通信,以便传递值并对事件作出反应。只有一个视图实例存在,但可能存在许多贡献实例。
为了使视图调用当前所选程序节点的基础贡献的方法,必须使用提供的提供程序。 ContributionProvider 上的 get() 方法将自动返回代表当前所选程序节点的 ProgramNodeContribution 实例,并依次返回其关联的数据模型。
另一方面,贡献对象是用唯一的一个视图实例实例化的,并且可以调用这个实例的方法,而不需要任何进一步的动作。
8.3 将定制的程序节点提供给PolyScope
为了在PolyScope中提供Hello World程序节点,需要一个Java类来实现SwingProgramNodeService接口。 列表6显示了使Hello World程序节点可用于PolyScope的Java代码。getId()方法返回此类程序节点的唯一标识符。 当存储包含这些程序节点的程序时,将使用该标识符。 此方法仅调用一次。 在发布的URCaps中不要更改此方法的返回值,因为这将破坏现有程序的向后兼容性。 现有程序中的URCap程序节点将无法正确加载,并且程序将无法再运行。其getTitle(Locale)方法为与该类型的程序节点相对应的结构选项卡中的按钮提供文本。 它也用作命令选项卡屏幕上的标题。对于此类节点,如果应该支持翻译的标题,请使用提供的Locale。此方法只调用一次。
在方法 configureContribution(ContributionConfiguration) 中提供,以配置程序节点贡献。此方法在服务注册后调用一次。如果默认值不合适,请使用提供的参数配置贡献(请参阅 Javadoc 中的默认值)。如果默认值合适,请将此方法留空。可以配置以下属性:
使用true调用setDeprecated()将使创建此类型的新程序节点变得不可能,但仍支持在现有程序中加载此类型的程序节点。
使用true调用setChildrenAllowed(),表示程序节点可能包含其他(子)程序节点。
使用false调用setUserInsertable()使程序节点只能以编程方式插入(即最终用户无法插入)。
调用getProgramDebuggingSupport()返回ProgramDebuggingSupport对象,可用于定制程序节点和子树中子节点的调试功能。
调试功能仅在e-Series平台上可用。有关详细说明,请参阅用户手册。
setAllowBreakpointOnNode()确定是否允许最终用户在程序节点上设置断点或单步执行此程序节点。
{ setAllowBreakpointOnChildNodesInSubtree() 确定是否允许最终用户在此程序节点子树中的任何子节点上设置断点或单步执行(仅当子节点本身允许断点时)。
setAllowStartFromNode()确定最终用户是否可以直接从此程序节点启动程序。
{ setAllowStartFromChildNodesInSubtree() 确定最终用户是否可以直接从该程序节点子树中的选定子节点启动程序(仅当子节点本身允许时)。
最后,createNode(ProgramAPIProvider , SwingProgramNodeView , DataModel , CreationContext)创建程序节点。参数是:ProgramAPIProvider:提供对与程序节点相关的各种API的访问权限。SwingProgramNodeView:这是通过createView(ViewAPIProvider)方法创建的视图实例(参见第8.1节“程序节点视图的UI”)。DataModel:这为用户提供了一个具有自动持久性的数据模型。CreationContext:创建此程序节点的上下文。createNode(...)方法为程序树中出现的每个此类节点创建一个新的Java对象。返回的对象用于与程序树中选择的特定节点的自定义程序节点屏幕视图进行交互。它必须使用提供的数据模型对象来检索和存储应该在机器人程序中保存和加载的数据以及相应的节点出现。请注意,应该只将与特定程序节点实例的当前配置相关的数据存储在数据模型中,即不存储全局或共享状态、与该节点实例无关的状态等。在程序加载期间使用的createNode(...)方法的构造函数在第8.4节“程序节点的功能”中讨论。在第8.6节“使用程序节点贡献加载程序”中讨论了createNode(...)方法在程序加载期间的调用。
Listing 6: Java class defining how Hello World program nodes are created
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . ViewAPIProvider ;
4 import com .ur. urcap . api. contribution . program . ContributionConfiguration ;
5 import com .ur. urcap . api. contribution . program . CreationContext ;
6 import com .ur. urcap . api. contribution . program . ProgramAPIProvider ;
7 import com.ur. urcap . api. contribution . program . swing . SwingProgramNodeService ;
8 import com.ur. urcap . api. domain . SystemAPI ;
9 import com.ur. urcap . api. domain . data . DataModel ;
10
11 import java . util . Locale ;
12
13 public class HelloWorldProgramNodeService implements SwingProgramNodeService <
HelloWorldProgramNodeContribution , HelloWorldProgramNodeView > {
14
15 @Override
16 public String getId () {
17 return " HelloWorldSwingNode ";
18 }
19
20 @Override
21 public void configureContribution ( ContributionConfiguration configuration ) {
22 configuration . setChildrenAllowed ( true ) ;
23 }
24
25 @Override
26 public String getTitle ( Locale locale ) {
27 String title = " Hello World ";
28 if ("ru". equals ( locale . getLanguage () ) ) {
29 title = " ";
30 } else if ("de". equals ( locale . getLanguage () )) {
31 title = " Hallo Welt ";
32 }
33 return title ;
34 }
35
36 @Override
37 public HelloWorldProgramNodeView createView ( ViewAPIProvider apiProvider ) {
38 SystemAPI systemAPI = apiProvider . getSystemAPI () ;
39 Style style = systemAPI . getSoftwareVersion () . getMajorVersion () >= 5 ? new
V5Style () : new V3Style () ;
40 return new HelloWorldProgramNodeView ( style );
41 }
42
43 @Override
44 public HelloWorldProgramNodeContribution createNode (
45 ProgramAPIProvider apiProvider ,
46 HelloWorldProgramNodeView view ,
47 DataModel model ,
48 CreationContext context ) {
49 return new HelloWorldProgramNodeContribution ( apiProvider , view , model );
50 }
51 }
8.4 程序节点的功能
Hello World程序节点的功能在清单7所示的Java类中实现。该类实现了ProgramNodeContribution接口,并且该类的实例由前一节中描述的HelloWorldProgramNodeService类的createNode(ProgramAPIProvider、SwingProgramNodeView、DataModel、CreationContext)方法返回。
Listing 7: Java class defining functionality for the Hello World program node
1 package com .ur. urcap . examples . helloworldswing . impl ;
2
3 import com .ur. urcap . api. contribution . ProgramNodeContribution ;
4 import com .ur. urcap . api. contribution . program . ProgramAPIProvider ;
5 import com .ur. urcap . api. domain . ProgramAPI ;
6 import com .ur. urcap . api. domain . data . DataModel ;
7 import com.ur. urcap . api. domain . script . ScriptWriter ;
8 import com.ur. urcap . api. domain . undoredo . UndoRedoManager ;
9 import com.ur. urcap . api. domain . undoredo . UndoableChanges ;
10 import com.ur. urcap . api . domain . userinteraction . keyboard . KeyboardInputCallback ;
11 import com.ur. urcap . api . domain . userinteraction . keyboard . KeyboardInputFactory ;
12 import com.ur. urcap . api . domain . userinteraction . keyboard . KeyboardTextInput ;
13
14 public class HelloWorldProgramNodeContribution implements
ProgramNodeContribution {
15 private static final String NAME = " name ";
16
17 private final ProgramAPI programAPI ;
18 private final UndoRedoManager undoRedoManager ;
19 private final KeyboardInputFactory keyboardFactory ;
20
21 private final HelloWorldProgramNodeView view ;
22 private final DataModel model ;
23
24 public HelloWorldProgramNodeContribution ( ProgramAPIProvider apiProvider ,
HelloWorldProgramNodeView view , DataModel model ) {
25 this . programAPI = apiProvider . getProgramAPI () ;
26 this . undoRedoManager = apiProvider . getProgramAPI () . getUndoRedoManager () ;
27 this . keyboardFactory = apiProvider . getUserInterfaceAPI () .
getUserInteraction () . getKeyboardInputFactory () ;
28
29 this . view = view ;
30 this . model = model ;
31 }
32
33 @Override
34 public void openView () {
35 view . setPopupText ( getName () );
36 updatePopupMessageAndPreview () ;
37 }
38
39 @Override
40 public void closeView () {
41 }
42
43 @Override
44 public String getTitle () {
45 return " Hello World : " + ( model . isSet ( NAME ) ? getName () : "");
46 }
47
48 @Override
49 public boolean isDefined () {
50 return getInstallation () . isDefined () && ! getName () . isEmpty () ;
51 }
52
53 @Override
54 public void generateScript ( ScriptWriter writer ) {
55 // Directly generate this Program Node ’s popup message + access the popup
title through a global variable
56 writer . appendLine (" popup (\" " + generatePopupMessage () + "\" ,
hello_world_swing_popup_title , False , False , blocking = True )");
57 writer . writeChildren () ;
58 }
59
60 public KeyboardTextInput getKeyboardForTextField () {
61 KeyboardTextInput keyboardInput = keyboardFactory .
createStringKeyboardInput () ;
62 keyboardInput . setInitialValue ( getName () );
63 return keyboardInput ;
64 }
65
66 public KeyboardInputCallback < String > getCallbackForTextField () {
67 return new KeyboardInputCallback <String >() {
68 @Override
69 public void onOk ( String value ) {
70 setPopupTitle ( value );
71 view . setPopupText ( value );
72 }
73 };
74 }
75
76 public void setPopupTitle ( final String value ) {
77 undoRedoManager . recordChanges ( new UndoableChanges () {
78 @Override
79 public void executeChanges () {
80 if ("". equals ( value )) {
81 model . remove ( NAME );
82 } else {
83 model .set (NAME , value ) ;
84 }
85 }
86 }) ;
87
88 updatePopupMessageAndPreview () ;
89 }
90
91 private String generatePopupMessage () {
92 return model . isSet ( NAME ) ? " Hello " + getName () + ", welcome to PolyScope !
" : "No name set ";
93 }
94
95 private void updatePopupMessageAndPreview () {
96 view . setMessagePreview ( generatePopupMessage () );
97 view . setTitlePreview ( getInstallation () . isDefined () ? getInstallation () .
getPopupTitle () : "No title set ");
98 }
99
100 private String getName () {
101 return model . get (NAME , "");
102 }
103
104 private HelloWorldInstallationNodeContribution getInstallation () {
105 return programAPI . getInstallationNode (
HelloWorldInstallationNodeContribution . class );
106 }
107
108 }
openView()和closeView()方法指定了当用户选择和取消选择程序树中的基础程序节点时会发生什么。getTitle()方法定义了节点在程序树中显示的文本。当值写入DataModel时,程序树中节点的文本会更新。isDefined()方法用于识别节点是已完全定义(绿色)还是仍未定义(黄色)。请注意,一个节点可以包含其他程序节点(见第8.3节“使定制的程序节点可用于PolyScope”),只要它有一个未定义的子节点,它就仍未定义。当值写入DataModel时,调用isDefined()方法,以确保程序树反映程序节点的正确状态。
最后,调用generateScript(ScriptWriter)将脚本代码添加到机器人程序中底层节点出现的位置。
当用户与文本输入字段交互时,使用视图实例在屏幕上显示构造的消息。如果 Hello World 安装节点已定义且程序节点中的名称非空,则每个 Hello World 节点都定义为绿色。执行时,它会显示一个简单的弹出对话框,标题在安装中定义,消息由名称构造。
弹出标题是脚本变量hello_world_swing_ popup_title的值。该变量由Hello World安装节点提供的脚本代码初始化。因此,脚本变量用于将数据从提供的安装节点传递到提供的程序节点。在这两个对象之间传递信息的另一种方法是直接通过ProgramAPI接口请求安装对象。Hello World程序节点在其updatePopupMessageAndPreview()方法中使用了这种方法。
8.5 更新数据模型
8.5.1 限制条件
在程序节点贡献界面(如第8.4节“程序节点的功能”所述)中定义的任何重写方法的实现中,不允许修改PolyScope调用的已提供数据模型。数据模型应因用户发起的事件(如按钮点击)而更新。
此外,如果URCap包含安装节点贡献,则安装贡献的数据模型不得从程序节点贡献中修改。如果在程序运行时调用openView()时发生此类修改,PolyScope将在每次在程序树中遇到URCap程序节点时自动停止程序,因为安装已更改。但是,允许从安装节点贡献中的数据模型中读取。
8.5.2 撤消/重做功能
所有用户发起的数据模型或程序树更改必须在UndoableChanges对象范围内使用UndoRedoManager接口记录更改。模型或程序树的多个更改可以在单个UndoableChanges对象内发生,这意味着所有这些更改将作为最终用户的单个操作可撤消。清单8中的一小段代码展示了这一点。
Listing 8: Code snippet for undo/redo
1 UndoRedoManager manager = apiProvider . getProgramAPI () . getUndoRedoManager () ;
2 manager . recordChanges (new UndoableChanges () {
3 @Override
4 public void executeChanges () {
5 model .set (NAME , "my name ");
6 insertChildNodes () ;
7 }
8 }) ;
只有当最终用户启动了操作(例如点击按钮)时,更改才应被记录为可撤消的操作。否则,最终用户将能够撤消他没有做的事情,并对他正在撤消的内容感到困惑。
未记录UndoableChanges中的更改将引发IllegalStateExeption异常。
如第8.3节所述,将定制的程序节点提供给PolyScope时,请记住只存储与模型中特定程序节点实例的当前配置相关的数据,即不应存储全局或共享状态、与此节点实例无关的状态等。
可撤消的操作仅适用于程序节点。对安装节点中的数据模型的更改不支持撤消/重做,也不会引发IllegalStateException异常。
当用户单击撤销时,数据模型中的先前值以及程序树都会恢复,如果当前选择了程序节点,则还会调用openView()方法,以便显示新值。
这也意味着成员变量中不应缓存任何值,而应始终从数据模型中检索,因为无法保证事物不会发生变化。还要记住,用户可能不会选择URCap的Command选项卡,因此无法保证调用openView()。加载已设置好的程序时可能会出现这种情况。
8.6 使用程序节点贡献加载程序
用户,创建节点(ProgramAPIProvider、SwingProgramNodeView、DataModel、CreationContext)方法中给出的数据模型对象为空,提供的CreationContext对象也将反映这一点。
加载程序时,将为每个持久化的程序节点调用createNode(...)方法以重新创建程序树。与新创建程序节点不同,数据模型现在包含持久化节点的数据,而CreationContext对象也将反映这种情况。程序节点构造函数对数据模型的所有修改都将被忽略。这意味着理想情况下,程序节点构造函数不应设置数据模型中的任何内容。
创建子树可以使用程序模型。在第10章URCap示例概述中的一些URCap示例中,演示了如何以编程方式生成子树。程序模型提供创建和操纵子树的接口 TreeNode。当用户在PolyScope中创建可贡献程序节点时,树节点没有子节点。程序模型可以通过ProgramAPI接口请求。
当程序加载时,每个程序节点都会被单独反序列化,这包括之前通过程序模型创建的子树。现在,通过程序模型请求的树节点是空的。程序模型接口中的 getProgramNodeFactory() 返回的程序节点工厂将返回没有任何功能的程序节点。特别是,createURCapProgramNode(Class<? extends URCapProgramNodeService>) 方法不调用
因此,在指定服务中的createNode(...)方法中,修改会被忽略。
createNode(...) 调用。
8.7 贡献和观点的生命周期
每次创建新程序或加载不同程序时,SwingProgramNodeService接口中的createView(...)方法(请参阅第8.3节“使自定义程序节点可用于PolyScope”)也会被调用,并且应返回新的视图实例。SwingProgramNodeService接口中的createNode(...)方法也会被调用,以传入新的DataModel对象。
这意味着,Java垃圾收集有机会清理之前的实例,以防忘记任何监听器或产生其他潜在的内存泄漏。这也意味着,对这些类之外的视图实例或贡献实例的引用不应保留。尊重这一点将保护PolyScope不会耗尽内存。
9 Daemon 的贡献
守护程序可以是控制箱上运行的任何可执行脚本或二进制文件。My Daemon Swing URCap 作为运行示例来解释此功能,是 Hello World Swing 示例的扩展。My Daemon Swing 示例从用户的角度提供了与 Hello World Swing 示例相同的功能。
然而,My Daemon Swing URCap通过可执行文件执行任务,该可执行文件充当某种驱动程序或服务器。可执行文件以Python 2.5脚本和C ++二进制文件实现。可执行文件通过XML编码的远程过程调用(XML-RPC)与Java前端和URScript执行器进行通信。第14页图3显示了My Daemon Swing URCap项目的结构。
9.1 守护程序服务
URCap可以通过实现DaemonService接口(参见清单9)贡献任意数量的守护进程可执行文件:
PolyScope将调用init(DaemonContribution)方法,该方法将返回一个DaemonContribution对象,该对象使URCap开发人员能够控制安装、启动、停止和查询守护程序的状态。第9.2节“与守护程序的交互”将讨论如何集成启动、停止和查询守护程序。
DaemonContribution 接口中的 installResource(URL url) 方法接受一个参数,该参数指向 URCap 归档(.urcap 归档)中的源代码。该路径可能指向单个可执行守护程序或包含守护程序和其他文件的目录(例如动态链接库或配置文件)。
getExecutable()的实现为PolyScope提供了将要启动的可执行文件的路径。
/etc/service目录包含指向当前正在运行的URCap守护进程的可执行文件的链接。如果守护进程的可执行文件存在链接,但实际上并没有运行,那么在查询守护进程的状态时将返回ERROR状态。守护进程的可执行文件链接的生存期与封装URCap的生存期相同,当URCap被删除时,这些链接将被删除。守护进程的初始状态是STOPPED,但是,如果需要,可以在守护进程安装完资源后立即在其init(DaemonContribution)方法中调用start(),以实现自动启动。
Listing 9: The My Daemon Service
1 package com .ur. urcap . examples . mydaemonswing . impl ;
2
3 import com .ur. urcap . api. contribution . DaemonContribution ;
4 import com .ur. urcap . api. contribution . DaemonService ;
5
6 import java . net . MalformedURLException ;
7 import java . net .URL;
8
9
10 public class MyDaemonDaemonService implements DaemonService {
11
12 private DaemonContribution daemonContribution ;
13
14 public MyDaemonDaemonService () {
15 }
16
17 @Override
18 public void init ( DaemonContribution daemonContribution ) {
19 this . daemonContribution = daemonContribution ;
20 try {
21 daemonContribution . installResource ( new URL (" file : com /ur/ urcap / examples /
mydaemonswing / impl / daemon /") );
22 } catch ( MalformedURLException e) { }
23 }
24
25 @Override
26 public URL getExecutable () {
27 try {
28 // Two equivalent example daemons are available :
29 return new URL (" file : com /ur/ urcap / examples / mydaemonswing / impl / daemon /
hello - world .py") ; // Python executable
30 // return new URL (" file :com /ur/ urcap / examples / mydaemonswing / impl / daemon /
HelloWorld "); // C++ executable
31 } catch ( MalformedURLException e) {
32 return null ;
33 }
34 }
35
36 public DaemonContribution getDaemon () {
37 return daemonContribution ;
38 }
39
40 }
与守护程序可执行文件处理过程相关的日志信息与守护程序可执行文件一起保存(请在 /etc/service 中查找守护程序可执行文件的符号链接,以定位日志目录)。
请注意,脚本守护进程必须在第一行包含解释器指令,以帮助选择正确的程序来解释脚本。例如,Bash脚本使用“#!/bin/bash”,Python脚本使用“#!/usr/bin/env python”。
9.2 与 Daemon 的交互
图4和图5分别显示了CB3机器人和e-Series机器人的My Daemon安装屏幕。代码可以在附录B第62页的清单19中找到。My Daemon程序和安装节点。
安装屏幕上添加了两个按钮,用于启用和禁用守护程序。在此示例中,默认情况下,在创建新安装时启用守护程序,并且对所需运行状态的未来更改将存储在数据模型中。
此守护程序与 PolyScope 并行运行,原则上可以独立更改其状态。因此,按钮下方的标签显示了守护程序的当前运行状态。此标签使用 java .util .Timer 类以 1 Hz 的频率更新。由于 UI 更新是从与 Java AWT 线程不同的线程启动的,因此计时器任务必须使用 EventQueue .invokeLater 功能。注意,当打开 My Daemon Installation 屏幕时添加计时器(参见 openView()),当用户离开屏幕时删除计时器(参见 closeView())以节省计算资源。
Java 和 URScript 有两种与守护进程通信的选项:
TCP/IP套接字可用于流式传输数据。
XML编码的远程过程调用(XML-RPC)可用于配置任务(例如相机校准)或服务执行(例如定位下一个对象)。
XML-RPC 优于套接字的优点是无需实现自定义协议或编码。URScript XML-RPC 实现支持所有 URScript 数据类型。此外,RPC 仅在函数执行完成后才返回。当下一个程序步骤依赖于从守护程序服务检索的数据时,这是可取的。另一方面,纯套接字对于数据流更有效,因为没有编码开销。这两种方法可以互补应用,可用于 Java $ daemon 和 URScript $ daemon 通信。
Listing 10: URScript XML-RPC example
1 global mydaemon_swing = rpc_factory (" xmlrpc ", " http ://127.0.0.1:40405/ RPC2 ")
2 global mydaemon_message = mydaemon_swing . get_message (" Bob ")
3 popup ( mydaemon_message , "My Title ", False , False , blocking = True )
清单10显示了一个URScript小示例,用于对XML-RPC服务器进行XML-RPC调用。
hello-world .py 示例守护程序(参见第 69 页的清单 22)可以用作 XML-RPC 测试服务器。只需在 My Daemon 中启动守护程序,并在 Script 节点中运行 URScript。
此 URScript 示例的目的是从守护程序中检索消息,以便在运行时显示(类似于 My Daemon 程序节点)。 rpc_factory 脚本函数创建与守护程序中的 XML-RPC 服务器的连接。新连接存储在全局 my_daemon_swing 变量中,并用作句柄。下一行请求守护程序中的 XML- RPC 服务器执行 get_message(...) 函数,并返回字符串参数 "Bob " 和结果。 RPC 调用的返回值存储在 mydaemon_message 变量中,以便在 popup(...) 脚本函数中进一步处理。
请注意,从URScript调用XML-RPC不需要任何额外的函数存根或URScript中要执行的远程函数的预定义。在XML-RPC返回之前,该URScript线程将自动阻塞(即不需要同步或等待)。标准XML-RPC协议不允许返回空值,而启用此功能的XML-RPC扩展并不总是兼容。
MyDaemonSwing示例还包括一个Java XML-RPC客户端示例,请参阅MyDaemonProgramNodeContribution和XMLRPCMyDaemonInterface类的组合(分别参见第65页的列表20和第67页的列表21)。请注意,XML-RPC调用的执行不在主Java AWT线程上,而是加载到一个单独的线程上。
9.3 C/C++ 后台程序可执行文件
CB3.0/3.1和CB5.0控制箱都运行最小的Debian 32位Linux操作系统。为了确保二进制兼容性,所有C/C++可执行文件都应在Linux下使用urtool3交叉编译器进行编译。urtool3交叉编译器包含在SDK安装中。
要测试 urtool3 是否正确安装,请在终端中键入以下内容
1 echo $URTOOL_ROOT ; i686 - unknown -linux -gnu -g++ -- version
正确的输出是
1 / opt / urtool -3.0
2 i686 - unknown -linux -gnu -g++ ( GCC ) 4.1.2
3 Copyright (C) 2006 Free Software Foundation , Inc .
4 This is free software ; see the source for copying conditions . There is NO
5 warranty ; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
如果在安装SDK后第一行没有被直接打印出来,请重新启动您的PC以更新环境变量。My Daemon Swing URCap附带了一个功能齐全的C++ XML-RPC服务器示例,相当于hello-world .py Python守护进程。只需在MyDaemonDaemonService类中的getExecutable()函数中切换注释(参见第33页的清单9),并重新编译以使用C++守护进程实现。在执行URCap期间,弹出标题现在应该追加“(C++)”而不是“(Python)”。C++守护进程目录结构如图6所示,第37页。为了管理C++守护进程的软件构建过程,使用了名为Scons的工具。SConstruct文件除其他外包含主配置、urtool3交叉编译器和libxmlrpc-c集成。SConscript文件用于定义编译目标,例如Hello World二进制文件。
对于示例URCap,该守护程序将作为Maven的URCap构建过程的一部分进行构建。但是,也可以通过在终端中键入以下内容来手动编译该守护程序
1 cd com .ur. urcap . examples . mydaemonswing / daemon
2 scons release =1
这将构建守护程序的发布版本。使用release=0将构建一个带有调试符号的可执行文件。
C++ 守护程序中的 XML-RPC 功能依赖于开源库 libxmlrpc-c (http://xmlrpc-c.sourceforge.net)。默认情况下,该库在 CB3.0/3.1 和 CB5.0 控制箱上可用。服务目录包含所有相关的 XML-RPC 代码。AbyssServer 是 libxmlrpc-c 支持的 XML-RPC 服务器实现之一。请查看 C++ 代码,以获取更多编程提示和相关文档的链接。
9.4 将不同的贡献联系在一起
新的My Daemon Swing URCap安装节点、程序节点和守护程序可执行文件通过清单11中的代码注册并提供给PolyScope。
注册了三个服务:
. MyDaemonInstallationNodeService
. MyDaemon程序节点服务
. MyDaemon守护进程服务
MyDaemonInstallationNodeService类可以访问MyDaemonDaemonService类的实例。当使用createInstallationNode(...)方法创建MyDaemonInstallationNodeContribution类型的新安装节点实例时,此实例将在构造函数中传递。通过这种方式,可以从安装节点控制守护程序的可执行文件。
1 package com .ur. urcap . examples . mydaemonswing . impl ;
2
3 import com .ur. urcap . api. contribution . DaemonService ;
4 import com .ur. urcap . api. contribution . installation . swing .
SwingInstallationNodeService ;
5 import com .ur. urcap . api. contribution . program . swing . SwingProgramNodeService ;
6 import org. osgi . framework . BundleActivator ;
7 import org. osgi . framework . BundleContext ;
8
9 public class Activator implements BundleActivator {
10 @Override
11 public void start ( final BundleContext context ) throws Exception {
12 MyDaemonDaemonService daemonService = new MyDaemonDaemonService () ;
13 MyDaemonInstallationNodeService installationNodeService = new
MyDaemonInstallationNodeService ( daemonService );
14
15 context . registerService ( SwingInstallationNodeService .class ,
installationNodeService , null );
16 context . registerService ( SwingProgramNodeService .class , new
MyDaemonProgramNodeService () , null ) ;
17 context . registerService ( DaemonService .class , daemonService , null );
18 }
19
20 @Override
21 public void stop ( BundleContext context ) throws Exception {
22 }
23 }