通过System Filesystem跨模块传递控件

时间:2014-08-28 11:49:06

标签: java netbeans module netbeans-platform

我有一个Netbeans平台项目,有多个模块,我试图保持松散耦合。我需要一个模块的顶部组件来显示由其他模块定义的按钮 - 这些按钮通常会打开定义它的模块的顶部组件。

我已经考虑过将Swing控件传递给Lookups,但经过一些阅读后我觉得现在最好的方法就是使用“System Filesystem”

我认为显示按钮的模块应该定义一个文件夹,希望显示按钮的模块应该将它们放在该文件夹中。我认为实际上只需要为每个按钮传递三个变量 - 按钮文本,操作(我认为NetBeans自动生成顶部组件打开操作),还有一个布尔值,用于定义是否应始终启用按钮或仅在显示顶部组件处于特定状态时启用。

问题是,我不知道如何设置它。我该如何设置?理想情况下,这些按钮应该在加载相应模块时出现,即使最初未加载该模块,因此显示模块需要监听系统文件系统。

2 个答案:

答案 0 :(得分:0)

在这里我是如何做到的,虽然可能有更好的解决方案:

1.通过文件系统传递控制数据

  1. 将xml图层文件添加到将显示控件的模块中(在新文件向导的Module Development下查找。)并使其如下所示:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
    <filesystem>
        <folder name="UI">
            <folder name="MyAppTools">
            </folder>
        </folder>
    </filesystem>
    

    这将在系统文件系统中定义一个文件夹,其他模块可以将其控制数据放入其中。

  2. 在想要添加控件的模块中创建一个看起来像这样的xml图层文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
    <filesystem>
        <folder name="UI">
            <folder name="MyAppTools">
                <file name="module-package-path-buttonName">
                    <attr name="text" stringvalue="Button Text" />
                    <attr name="actionName" stringvalue="package.path.Class.handlerMethod" />
                    <attr name="alwaysEnabled" boolvalue="true" />
                </file>
            </folder>
        </folder>
    </filesystem>
    

    将一个文件放在要创建的每个控件的文件夹中。您必须确保XML文件所在的包is public或这些文件在运行时不会影响文件系统。 (我花了很长时间才解决这个问题)

  3. 将这些字段放在组件中以显示工具:

    public static final String TOOLS_PATH = "UI/MyAppTools";
    public static final String ATTR_NAME_BUTTON_NAME = "text";
    public static final String ATTR_NAME_BUTTON_ACTION = "action";
    public static final String ATTR_NAME_BUTTON_ACTION_NAME = "actionName";
    public static final String ATTR_NAME_BUTTON_BOOL_PROPERTY = "alwaysEnabled";
    private FileObject systemFsTools;
    

    正确地说,公共常量应该放在任何模块可以依赖的公共包中,以便提供工具数据的模块可以通过读取这些值来动态生成XML文件。但我没有正确地做到这一点。

  4. 在显示组件的构造函数中添加一个FileChangeListener,当任何人将文件放入我们的特殊文件夹(或删除一个文件夹)时将触发该文件:

    systemFsTools = FileUtil.getConfigFile(TOOLS_PATH);
    systemFsTools.addFileChangeListener(new FileChangeAdapter() {
        @Override
        public void fileDataCreated(FileEvent fe) {initControlPanel();}
        @Override
        public void fileDeleted(FileEvent fe) {initControlPanel();}
    });
    
  5. 将此方法添加到显示组件,以便在更改特殊文件夹的内容时,显示按钮的面板将重新填充:

    private void initControlPanel()
    {
        // start fresh each time we build this
        panelTools.removeAll();
        selectionDependantTools.clear();
        // go to the folder where we can find the info for the buttons we're making
        DataFolder toolsFolder = DataFolder.findFolder(systemFsTools);
        DataObject[] children = toolsFolder.getChildren();
        // we're assuming each child in the folder represents one button
        for (DataObject child : children) {
            FileObject file = child.getPrimaryFile();
            // get the attributes that tell us what the button will be like
            Object actionMethodName = file.getAttribute(ATTR_NAME_BUTTON_ACTION_NAME);
            Object btnText = file.getAttribute(ATTR_NAME_BUTTON_NAME);
            Object btnAlwaysActive = file.getAttribute(ATTR_NAME_BUTTON_BOOL_PROPERTY);
            JButton btn = new JButton();
            if (btnText != null) {
                btn.setText((String)btnText);
            }
            if (actionMethodName != null) {
                final String identifier = (String)actionMethodName;
                btn.addActionListener(new java.awt.event.ActionListener()
                    {
                        @Override
                        public void actionPerformed(ActionEvent e)
                        {
                            // Whoever designated this button should handle its event
                        }
                    });
            } else {
                btn.setEnabled(false);
            }
            if (btnAlwaysActive != null) {
                if (((Boolean)btnAlwaysActive).booleanValue())
                {
                    // something to handle button state
                }
            }
            panelTools.add(btn);
        }
        panelTools.revalidate();
    }
    

    顺便说一句,我将panelTools作为带有FlowLayout的JPanel。

  6. 这里缺少的一件事是如何让指定按钮的模块处理它的事件。尽可能地尝试在模块A中创建代码在模块B中调用一个方法而不依赖于B,因此即使告诉处理程序&#,显示组件也不能简单地调用另一个模块中的处理程序39;签名完全正确。所以......

    2.使用处理程序

    将事件传递回模块

    要做到这一点,我做了一个&#34;事件总线&#34;使用查找系统

    1. Central Lookup放入所有模块可依赖的公共包中。

    2. 在同一个包中创建一个界面:

      import java.awt.event.ActionEvent;
      
      /**
       * This class defines something that modules can pass to each other through lookups
       * to "poke" each other when certain events happen
       * @author toby
       */
      public interface InterModuleEvent
      {    
          public ActionEvent getEvent();
      
          public String getIdentifier();
      
          public boolean hasIdentifier(String id);
      }
      

      这样模块就能够通过这个接口处理通过事件总线传递的对象。标识符是这样的,接收(侦听)模块可以告诉哪些事件属于它们(它们与它们作为文件系统中的标识符放置的字符串相同,以制作按钮)

    3. 我在显示模块中使用类实现了接口:

      import java.awt.event.ActionEvent;
      import org.sil.jflavourapi.InterModuleEvent;
      
      public class ToolEvent implements InterModuleEvent
      {
          private ActionEvent event;
          private String identifier;
      
          public ToolEvent(ActionEvent event, String identifier)
          {
              this.event = event;
              this.identifier = identifier;
          }
      
          @Override
          public ActionEvent getEvent()
          {
              return event;
          }
      
          @Override
          public String getIdentifier()
          {
              return identifier;
          }
      
          @Override
          public boolean hasIdentifier(String id)
          {
              return identifier.equals(id);
          }
      
      }
      
    4. 然后我需要做的就是,每当从其中一个按钮发出事件时,我就会将其中一个对象放入中央查找中,并使用来自文件系统中按钮数据的标识符。因此,在initConrolPanel方法中,注释为// Whoever designated this button should handle its event,请输入以下代码:

      CentralLookup.getDefault().add(new ToolEvent(e, identifier));
      
    5. 到目前为止一切顺利。接下来是指定按钮的模块,用于侦听具有正确标识符的对象的事件总线。诀窍是这个模块需要在TopComponent打开之前监听,所以我们需要在加载模块后立即设置监听器。要做到这一点,我们需要制作一个&#34;安装程序&#34;正在侦听事件总线的模块中的类(再次查看新文件向导的Module Development下)。

    6. 我尝试让Install成为事件总线的监听器,但这并不起作用,所以我做了另一个类来做这件事。我决定将它作为模块的TopComponent类的私有子类,它正在监听事件总线:

      private static class InterModuleEventHandler implements LookupListener
      {
      
          private Lookup.Result<InterModuleEvent> result = null;
          public final String MODULE_ID = "org.sil.jflavouritemeditor.JFlavourItemEditorTopComponent";
          public final String NEW_ITEM_ACTION_ID = "editNewItem";
      
          public InterModuleEventHandler()
          {
              result = CentralLookup.getDefault().lookupResult(InterModuleEvent.class);
          }
      
          public void startListening()
          {
              result.addLookupListener (this);
          }
      
          public void stopListening()
          {
              result.removeLookupListener (this);
          }
      
          @Override
          public void resultChanged(LookupEvent le)
          {
              Collection<? extends InterModuleEvent> allEvents = result.allInstances();
              if (!allEvents.isEmpty()) {
                  InterModuleEvent event = allEvents.iterator().next();
                  if (event.hasIdentifier(MODULE_ID + '.' + NEW_ITEM_ACTION_ID))
                  {
                      MyClientTopComponent.myEventHandler();
                      CentralLookup.getDefault().remove(event);
                  }
              }
          }
      }
      

      它是静态的,因此可以在没有主机类(TopComponent)实例化的情况下使用它。它侦听中央查找,当事件匹配时,它调用主机类中的处理程序并从总线中删除事件。

    7. 将此代码放在主机类中。所有这些都是静态的,因此我们不需要在必要时实例化TopComponent。

      private static InterModuleEventHandler imeHandler = new InterModuleEventHandler();
      
      public static void startHandlingInterModuleEvents()
      {
          imeHandler.startListening();
      }
      
      public static void stopHandlingInterModuleEvents()
      {
          imeHandler.stopListening();
      }
      
      public static void myEventHandler()
      {
          // This is where the button event handling actually happens
      }
      
    8. 最后,在&#34;安装程序&#34; class把它放在restored()方法

      MyClientTopComponent.startHandlingInterModuleEvents();
      
    9. 这非常令人费解,并使用全部三个registration methods listed in the wiki。也许其他人知道一种更简单的方法。

      我很惊讶这种事情不是常见用例,至少不足以让Netbeans维基解决,或者SO用户能够提供任何关于如何解决的提示它

答案 1 :(得分:0)

我的第一个答案有效,但我认为现在我已经找到了一种更简单,更好的方法,使用现有的处理此类事物的结构,即NodeAction类。

我会使用Action接口,这是更通用的,除了在我的情况下,操作通常作用于显示顶部组件中的节点,并且可以启用或禁用,具体取决于是否选择了节点。所以NodeAction通过内置此功能帮助我进一步发展。

这里的想法很简单,模块将其动作放入特定的查找中,这是显示组件将在监听的位置,从而在其中找到的操作中进行控制。我还没有实现它,但很快就会尝试。

  1. 在所有模块可依赖的公共包中放置一个中央查找(如第一个答案的第2部分所述)
  2. 要显示控件的模块应该使类扩展NodeAction并覆盖performAction(Node[]),以便在选定节点上执行操作。还要覆盖其他方法以获得更多规范,例如getName()
  3. 这些模块应各自使用restored()方法创建一个安装程序类,该方法将NodeAction的实例放入中央查找。
  4. 显示顶部组件会保留NodeAction的中央查找的查找结果,并将其自身添加为lookupListener
  5. resultChanged(LookupEvent)方法中,使用查找中的NodeActions重新初始化控件
  6. 此解决方案的优势在于,此处使用的NodeActions可以在其他地方使用(例如节点本身的上下文菜单或应用程序菜单),而无需额外担心。此外,动作的启用和禁用(以及因此链接到它们的任何控件)都在Action对象内部处理,并由创建操作的模块设置。该模块还可以为动作指定图标。不再需要使用系统文件系统。