Announcement Announcement Module
Collapse
No announcement yet.
MDI multiple instances of a view in internal frames Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • MDI multiple instances of a view in internal frames

    Hi

    For quite a while, I have been interested in having several internal frames of the same view but with different data. An MDI app could have Order 1 and Order 2 open, both using the OrderView.

    I assume that this was not possible since I got no solutions from the forum. I am going to present one way to achieve it... it may be bad, in which case I'd grateful if you could correct me.

    Ok, Let's get started.

    First of all, I am using the classes developed as part of RCP 56 by P.de Bruycker.
    Since these classes are not part of any release in Spring (not even the sandbox???), I have added them to my source path and I have added the following code to DesktopApplicationPage

    Code:
    ....
        // + New Stuff for managing Singleton views...
        private Map<String,Boolean> singletonViews = new HashMap<String,Boolean>(); 
        
        /**
         * This method is called each time a new internal frame is being launched.
         * @return null means that this view is not launched yet and therefore a new one will be created.
         */
        @Override
        protected PageComponent findPageComponent(String viewDescriptorId) {
            Boolean isSingleton = singletonViews.get(viewDescriptorId);
            if (isSingleton==null) {
                // fetch the bean and check if it is a singleton.
                BeanFactory factory = Application.instance().getServices().getBeanFactory();
                isSingleton = factory.isSingleton(viewDescriptorId);
                singletonViews.put(viewDescriptorId,isSingleton);
            }
            
            if (isSingleton) {
                return super.findPageComponent(viewDescriptorId);
            } else {
                return null;
            }
        }
    }
    This overrides the findPageComponent as I have found out that if that was returning null, it would generate a new PageComponent (ie internal frame).
    I can have both singleton and non singleton views, this is determined in the XML file:

    Code:
    <bean id="myView"
      class="org.springframework.richclient.application.support.DefaultViewDescriptor"
      singleton="true">
      <property name="viewClass">
        <value>net.objectlab.....MyView</value>
      </property>
      <property name="viewProperties">
      <map>
      ...
      </property>
    </bean>
    If singleton="false" then the findPageComponent will return null and a new internal frame created! Yeehaaa!
    I could have simply extended that class...

    The second issue mentioned in the forum was to find a way to pass some information to a new view dynamically. The typical use would be a "double click" on a table and, on that event, you want to display some data in an internal frame that the user can decide to keep around.

    I thought of using the ViewDescriptor and more explicitely the DefaultViewDescriptor which has a Map (viewProperties), it seems to make eminent sense to use that mechanism to pass whatever I may want to the view being launched.

    Unfortunately, DefaultViewDescriptor does not let you touch the viewProperties (why not???) so I extended it like this:
    Code:
    /**
     * This class extends the DefaultViewDescriptor as the original code does
     * not expose the viewProperties.
     * 
     * @author Benoit Xhenseval www.objectlab.co.uk
     */
    public class ConfigurableViewDescriptor extends DefaultViewDescriptor {
        private Map localMap;
    
        public ConfigurableViewDescriptor() {
            super();
        }
    
        /**
         * This method overrides the super class one and keep a reference to the
         * viewProperties, that way, we can modify them as and when required.
         */
        @Override
        public void setViewProperties(Map viewProperties) {
            localMap = viewProperties;
            super.setViewProperties(viewProperties);
        }
        
        /**
         * Add a property to the View property.
         * @param key
         * @param value
         */
        public void addViewProperty(final String key, final Object value) {
            if (localMap != null) {
                localMap.put(key,value);
            }
        }
    
        /**
         * remove a property.
         * @param key
         */
        public void removeViewProperty(final String key) {
            if (localMap != null) {
                localMap.remove(key);
            }
        }    
    }
    So, how do I launch a new internal frame programmatically based on some data?
    simple:

    Code:
    final ViewDescriptorRegistry viewDescriptorRegistry = Application.services().getViewDescriptorRegistry();
    
    ViewDescriptor vd = viewDescriptorRegistry.getViewDescriptor("myView");
    if (vd instanceof ConfigurableViewDescriptor) {
       ((ConfigurableViewDescriptor)vd).addViewProperty("myProperty", anyOBJECT);
    }
    Application.instance().getActiveWindow().getPage().showView(vd);
    This implies that my view class has the getter/setter for myProperty (getMyProperty/setMyProperty(Object o)). The property will be set before the control is created so your page can use it.

    and the xml for it is:
    Code:
    <bean id="myView"
      class="net.objectlab........ConfigurableViewDescriptor"
      singleton="false">
      <property name="viewClass">
         <value>net.objectlab....MyView</value>
      </property>
      <property name="viewProperties">
        <map>
    ....
    Well, I hope this helps! Feel free to incorporate it to your code/Wiki/Spring RC!

    Best regards

    Benoit

  • #2
    Hi benoitx,

    Pretty interesting. I am kind of in the same boat but my internal view will be much more rich than just a view, it needs to be able to query in and of itself. But the user can have multiple open, same as you have. I am just starting Spring RCP and trying to evaluate if I should spend the time to learn it. And since you have run into this already what would you suggest about having each new view actually have the service that will do the query injected.

    In essence it would be


    Code:
    MyMiniApp extends JInternalframe {
         //inject this (not sure how this works yet)
         private QueryServiceFacade service;
         
         public MyMiniApp() {
              //get your data when its created
              presentationModel = service.query(...);
         }
    
    }

    Comment


    • #3
      Another way this can be done is by overriding the ApplicationPage such as the VLDockingApplicationPage and adding a public #showView() method like this one (Cut/Pasted from the private one in AbstractApplicationPage):

      Code:
      public View showView(ViewDescriptor viewDescriptor, boolean setInput, Object input) {
        Assert.notNull(viewDescriptor, "viewDescriptor cannot be null");
      
        View view = (View)findPageComponent(viewDescriptor.getId());
        if(view == null) {
          view = (View)createPageComponent(viewDescriptor);
      
          if(setInput) {
            // trigger control creation before input is set to avoid npe
            view.getControl();
            view.setInput(input);
          }
      
          addPageComponent(view);
        } else {
          if(setInput) {
            view.setInput(input);
          }
        }
        setActiveComponent(view);
      
        return view;
      }
      Then create a custom ViewDescriptorRegistry like this one:

      Code:
      /**
       * <p>
       * A <code>ModifiableBeanFactoryViewDescriptorRegistry</code> is a ViewDescriptorRegistry
       * that stores registered view descriptors in a map for later lookup. A LinkedHashMap was used to
       * ensure the entire list of beans is returned in the same order in which they came from the
       * application context.
       * </p>
       *
       * @author aarmistead
       */
      public class ModifiableBeanFactoryViewDescriptorRegistry extends ApplicationObjectSupport implements ViewDescriptorRegistry {
      
        /**
         * The map of view descriptor id's to view descriptors.
         */
        private Map<String, ViewDescriptor> descriptors;
      
        /**
         * Creates a new instance of <code>ModifiableBeanFactoryViewDescriptorRegistry</code>.
         */
        public ModifiableBeanFactoryViewDescriptorRegistry() {
          descriptors = new LinkedHashMap<String, ViewDescriptor>();
        }
      
        /**
         * Creates a new instance of <code>ModifiableBeanFactoryViewDescriptorRegistry</code>
         * with the specified list of view <code>descriptors</code> to initially register.
         *
         * @param descriptors The list of view descriptors to register.
         */
        public ModifiableBeanFactoryViewDescriptorRegistry(List<ViewDescriptor> descriptors) {
          Assert.notNull(descriptors, "descriptors cannot be null");
          for(ViewDescriptor descriptor : descriptors) {
            addViewDescriptor(descriptor);
          }
        }
      
        /**
         * Add the specified view <code>descriptor</code> to this registry.
         *
         * @param descriptor The view descriptor to register.
         */
        public void addViewDescriptor(ViewDescriptor descriptor) {
          Assert.notNull(descriptor, "descriptor cannot be null");
          descriptors.put(descriptor.getId(), descriptor);
        }
      
        /**
         * Gets the view descriptor with the specified <code>viewDescriptorId</code>, returns null if
         * a view descriptor has not been registered with the specified id.
         *
         * @param viewDescriptorId The id of the view descriptor to return.
         * @return The view descriptor with the specified id, or null if one is not found.
         */
        @Override
        public ViewDescriptor getViewDescriptor(String viewDescriptorId) {
          Assert.hasText(viewDescriptorId, "viewDescriptorId cannot be empty");
          ViewDescriptor descriptor = descriptors.get(viewDescriptorId);
      
          // If a ViewDescriptor was not found, see if we can find a ViewDescriptor bean to add and return from the application context.
          if(descriptor == null) {
            try {
              ViewDescriptor bean = (ViewDescriptor)getApplicationContext().getBean(viewDescriptorId, ViewDescriptor.class);
              addViewDescriptor(bean);
              descriptor = bean;
            } catch(NoSuchBeanDefinitionException e) {
              // Do nothing, as a ViewDescriptor bean was not found.
            }
          }
      
          return descriptor;
        }
      
        /**
         * Gets the list of all registered view descriptors.  Loads all beans of type ViewDescriptor from
         * the application context and registers them if they are not found in the registry before
         * returning the list.
         *
         * @return The list of all registered view descriptors, including all ViewDescriptor beans in the
         * application context.
         */
        @Override
        public ViewDescriptor[] getViewDescriptors() {
          Map<String, ViewDescriptor> beans = getApplicationContext().getBeansOfType(ViewDescriptor.class, false, false);
          // Registers any unregistered ViewDescriptor beans.
          for(String beanName : beans.keySet()) {
            if(!descriptors.containsKey(beanName)) {
              addViewDescriptor(beans.get(beanName));
            }
          }
      
          return descriptors.values().toArray(new ViewDescriptor[descriptors.size()]);
        }
      
        /**
         * Removes the specified view <code>descriptor</code> from this registry.
         *
         * @param descriptor The view descriptor to unregister.
         */
        public void removeViewDescriptor(ViewDescriptor descriptor) {
          Assert.notNull(descriptor, "descriptor cannot be null");
          descriptors.remove(descriptor.getId());
        }
      
      }
      Don't forget to register the new ViewDescriptorRegistry with the application services in the application context:

      Code:
      <bean id="viewDescriptorRegistry" class="org.blah.myapp.ModifiableBeanFactoryViewDescriptorRegistry"/>
      
      <bean id="applicationServices"
          class="org.springframework.richclient.application.support.DefaultApplicationServices">
        ... other services ...
        <property name="viewDescriptorRegistryId">
          <idref bean="viewDescriptorRegistry"/>
        </property>
      </bean>
      Now you can create prototype view descriptors for views you want to dynamically display:

      Code:
      <bean id="PrototypeView"
          class="org.springframework.richclient.application.docking.vldocking.VLDockingViewDescriptor" scope="prototype">
        <property name="viewClass" value="org.blah.myapp.view.ClientView" />
        <property name="autoHideEnabled" value="true" />
        <property name="closeEnabled" value="true" />
        <property name="floatEnabled" value="true"/>
        <property name="maximizeEnabled" value="true"/>
        <property name="dockGroup" ref="Forms"/>
        <property name="resizeWeight" value="1"/>
        <!--<property name="securityControllerId" value="adminController"/>-->
      </bean>
      and pass them data when shown with a command like this:

      Code:
      public class ShowClientViewCommand extends ApplicationWindowAwareCommand {
      
        /**
         * The id value used to access the prototype client view.
         */
        private String prototypeViewId;
      
        /**
         * Sets the id value used to access the prototype client view to <code>prototypeViewId</code>.
         *
         * @param prototypeViewId The new prototype client view id.
         */
        public void setClientViewId(String prototypeViewId) {
          this.prototypeViewId= prototypeViewId;
        }
      
        /**
         * Creates a new instance of <code>ShowClientViewCommand</code>.
         */
        public ShowClientViewCommand() {
          super("showClientViewCommand");
        }
      
        @Override
        protected void doExecuteCommand() {
          Client client = // Get the currently selected client, however you do that in your application...
      
          if(client != null) {
            ApplicationPage page = getApplicationWindow().getPage();
            if(page instanceof VLDockingApplicationPageEx) {
              VLDockingApplicationPageEx vlpage = (VLDockingApplicationPageEx)page;
              ModifiableBeanFactoryViewDescriptorRegistry registry = (ModifiableBeanFactoryViewDescriptorRegistry)vlpage.getViewDescriptorRegistry();
      
              // Create a new id for the new view instance.
              String newId = prototypeViewId + " - " + client.getClientCode();
      
              // Check for a prototype view for the selected client.  This allows me
              // to open a view for a client, but if I try to open it again for the same
              // client, everything finds the first one I opened, so a new one isn't    
              // created, this avoids opening duplicate windows.
              DefaultViewDescriptor viewDescriptor = (DefaultViewDescriptor)registry.getViewDescriptor(newId);
              if(viewDescriptor == null && Application.instance().getApplicationContext().isPrototype(prototypeViewId)) {
                // Create a new instance of the prototype view if one with the new name wasn't found.
                viewDescriptor = (DefaultViewDescriptor)Application.instance().getApplicationContext().getBean(prototypeViewId);
                viewDescriptor.setId(newId);
                registry.addViewDescriptor(viewDescriptor);
              }
      
              // Show the prototype view, passing it the selected client.
              View view = vlpage.showView(viewDescriptor, true, client);
            }
          }
        }
      
      }
      Now you can register the command in your commands context and use it in menus/toolbar/other forms.

      Code:
      <bean id="showClientViewCommand"
          class="org.blah.myapp.command.showClientViewCommand">
            <property name="clientViewId" value="PrototypeView"/>
          <!--<property name="securityControllerId" value="adminController"/>-->
      </bean>
      
      <bean id="toolBar"
          class="org.springframework.richclient.command.CommandGroupFactoryBean">
        <property name="members">
          <list>
            <value>newCommand</value>
            <value>saveCommand</value>
            <value>printCommand</value>
            <value>separator</value>
            <value>propertiesCommand</value>
            <value>showClientViewCommand</value>
          </list>
        </property>
      </bean>
      This of course does not handle loading/saving these prototype views. If you are using VLDocking support and saving your ApplicationPage layouts to xml, this will make them fail when they try to load the dynamic view id's you created. To fix this, you'll need to override the #loadInitialLayout() method in your custom VLDockingApplicationPage and pre-register dockables for your prototype views BEFORE reading in the xml file.

      Anyway, this kind of thing is why Spring Rich Rocks, hope this helps someone.

      Comment

      Working...
      X