Announcement Announcement Module
Collapse
No announcement yet.
Question about Binding... Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Question about Binding...

    Hello

    I have a question with regards to the Binding in a form.

    I am binding an object Asset, it has a STRING property called assetType.
    This property will contain a code for a given assetType.
    Code:
    class Asset {
    ....
      private String assetType;
    }
    There are only a given number of assetTypes and they are coming from
    the database. I would like to bind the types to a combo box associated
    with the property assetType (which is a string in Asset).

    My query returns a List of AssetType objects.
    Code:
    class AssetType {
       private String code;
       private String name;
       ....// getters and setters
    }
    For display purpose, I would like the comboBox to contain only the "code" of the AssetTypes?
    Is it possible?

    I have also dabbled with writing a specific binder but the fact that my Asset class has a String rather than AssetType for the property assetType does not allow me to link them...

    1/ First option: How could I force the use of a specific binder whilst using a FormBuilder.add(...) method?

    2/ using the existing formBuilder and NO specific binder, how could I tell it to bind the assetType property of my Asset with the code property of my list of AssetType?
    My Asset Form does the following (I tried...)

    Code:
    formBuilder.add(binding.createBoundComboBox("assetType",new ValueHolder(assetTypeList),"code"));
    If my DB query was simply returning a string, it would work... I was hoping that the "code" parameter would tell SpringRC to use the code property on each assetType of the assetTypeList... obviously, it was wishful thinking...

    The exception I get seems to tell me that the ValueHolder has transformed my list of AssetType into a list of String???

    Code:
    [ERROR,79364,ApplicationLifecycleAdvisor,AWT-EventQueue-0] Invalid property 'code' of bean class [java.lang.String]: Bean property 'code' is not reada
    ble or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
    org.springframework.beans.NotReadablePropertyException: Invalid property 'code' of bean class [java.lang.String]: Bean property 'code' is not readable
     or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
            at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:652)
            at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:644)
            at org.springframework.richclient.list.ComboBoxAutoCompletionInterceptorFactory$ComboBoxAutoCompletionInterceptor$BeanPropertyValueComboBoxEdi
    tor.setItem(ComboBoxAutoCompletionInterceptorFactory.java:127)
            at javax.swing.JComboBox.configureEditor(JComboBox.java:1340)
            at javax.swing.plaf.basic.BasicComboBoxUI.configureEditor(BasicComboBoxUI.java:737)
            at javax.swing.plaf.metal.MetalComboBoxUI.configureEditor(MetalComboBoxUI.java:255)
            at javax.swing.plaf.basic.BasicComboBoxUI.addEditor(BasicComboBoxUI.java:688)
            at javax.swing.plaf.basic.BasicComboBoxUI$Handler.propertyChange(BasicComboBoxUI.java:1478)
            at javax.swing.plaf.basic.BasicComboBoxUI$PropertyChangeHandler.propertyChange(BasicComboBoxUI.java:587)
            at com.jgoodies.looks.plastic.PlasticComboBoxUI$PlasticPropertyChangeListener.propertyChange(PlasticComboBoxUI.java:268)
            at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:333)
            at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:270)
            at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:312)
            at java.awt.Component.firePropertyChange(Component.java:7178)
            at javax.swing.JComponent.firePropertyChange(JComponent.java:4191)
            at javax.swing.JComboBox.setEditable(JComboBox.java:379)
            at org.springframework.richclient.list.ComboBoxAutoCompletion.<init>&#40;ComboBoxAutoCompletion.java&#58;131&#41;
            at org.springframework.richclient.list.ComboBoxAutoCompletionInterceptorFactory$ComboBoxAutoCompletionInterceptor.processComponent&#40;ComboBoxAut
    oCompletionInterceptorFactory.java&#58;180&#41;
            at org.springframework.richclient.form.builder.support.ChainedInterceptorFactory$ChainedInterceptor.processComponent&#40;ChainedInterceptorFactory
    .java&#58;82&#41;
            at org.springframework.richclient.form.binding.support.AbstractBindingFactory.interceptBinding&#40;AbstractBindingFactory.java&#58;90&#41;
            at org.springframework.richclient.form.binding.support.AbstractBindingFactory.createBinding&#40;AbstractBindingFactory.java&#58;76&#41;
            at org.springframework.richclient.form.binding.swing.SwingBindingFactory.createBoundComboBox&#40;SwingBindingFactory.java&#58;119&#41;
            at net.objectlab.safemargin.gui.forms.AssetGeneralForm.createFormControl&#40;AssetGeneralForm.java&#58;71&#41;
            at org.springframework.richclient.form.AbstractForm.createControl&#40;AbstractForm.java&#58;233&#41;
            at org.springframework.richclient.factory.AbstractControlFactory.getControl&#40;AbstractControlFactory.java&#58;48&#41;
            at org.springframework.richclient.dialog.FormBackedDialogPage.createControl&#40;FormBackedDialogPage.java&#58;70&#41;
            at org.springframework.richclient.dialog.AbstractDialogPage$1.createControl&#40;AbstractDialogPage.java&#58;50&#41;
            at org.springframework.richclient.factory.AbstractControlFactory.getControl&#40;AbstractControlFactory.java&#58;48&#41;
            at org.springframework.richclient.dialog.AbstractDialogPage.getControl&#40;AbstractDialogPage.java&#58;201&#41;
            at org.springframework.richclient.dialog.CompositeDialogPage.prepareDialogPage&#40;CompositeDialogPage.java&#58;183&#41;
            at org.springframework.richclient.dialog.CompositeDialogPage.createPageControls&#40;CompositeDialogPage.java&#58;159&#41;
            at org.springframework.richclient.dialog.TabbedDialogPage.createControl&#40;TabbedDialogPage.java&#58;45&#41;
            at org.springframework.richclient.dialog.AbstractDialogPage$1.createControl&#40;AbstractDialogPage.java&#58;50&#41;
            at org.springframework.richclient.factory.AbstractControlFactory.getControl&#40;AbstractControlFactory.java&#58;48&#41;
            at org.springframework.richclient.dialog.AbstractDialogPage.getControl&#40;AbstractDialogPage.java&#58;201&#41;
            at org.springframework.richclient.dialog.TitledPageApplicationDialog.createTitledDialogContentPane&#40;TitledPageApplicationDialog.java&#58;78&#41;
            at org.springframework.richclient.dialog.TitledApplicationDialog.createDialogContentPane&#40;TitledApplicationDialog.java&#58;132&#41;
            at org.springframework.richclient.dialog.TitledApplicationDialog.addDialogComponents&#40;TitledApplicationDialog.java&#58;120&#41;
            at org.springframework.richclient.dialog.ApplicationDialog.createDialog&#40;ApplicationDialog.java&#58;298&#41;
            at org.springframework.richclient.dialog.ApplicationDialog.showDialog&#40;ApplicationDialog.java&#58;265&#41;
            at net.objectlab.safemargin.gui.view.AssetManagerView$AssetPropertiesExecutor.execute&#40;AssetManagerView.java&#58;292&#41;
            at net.objectlab.safemargin.gui.view.AssetManagerView$5.mouseClicked&#40;AssetManagerView.java&#58;175&#41;
            at java.awt.AWTEventMulticaster.mouseClicked&#40;AWTEventMulticaster.java&#58;212&#41;
    Many thanks!

    Benoit

  • #2
    Benoit,

    1/ First option: How could I force the use of a specific binder whilst using a FormBuilder.add(...) method?
    1st) create a custom binder that provides a combobox for editing the assetType property. If you have a look at the petclinic sample app there is a custom binder PetTypeBinder which would be helpful here.

    2nd) register this new binder with the binder selection strategy:

    Code:
    registerBinderForPropertyType&#40;Asset.class, "assetType", new AssetTypeBinder&#40;&#41;&#41;
    this binder will now be used whenever you FormBuilder.add(...) method for the assetType property.

    2/ using the existing formBuilder and NO specific binder, how could I tell it to bind the assetType property of my Asset with the code property of my list of AssetType?
    Extract the codes from each AssetType into a list and then use that (rather than the list of AssetTypes) as the selectable items list.

    HTH

    Ollie

    Comment


    • #3
      Along those lines...

      Hi Ollie

      Thanks for your answer.

      I was also working along those lines but I have found that I had to use the
      Code:
      registerBinderForPropertyName&#40;Class parentObjectType, String propertyName, Binder binder&#41;
      method rather than registerBinderForPropertyType.

      It works fine and I can even add more than 1 property for a given class.

      Code:
      registerBinderForPropertyName&#40;Asset.class,"assetType",new AssetTypeBinder&#40;&#41;&#41;;
      registerBinderForPropertyName&#40;Asset.class,"country",new CountryBinder&#40;&#41;&#41;;
      and the form is created by:

      Code:
      formBuilder.add&#40;"assetType"&#41;;
      formBuilder.add&#40;"country"&#41;;
      I have another question... How would you bind a listener for event on the "assetType"? Say that once an AssetType is selected, the GUI must change another attribute based on the object AssetType (not the String)... The Adapter in AssetTypeBinder knows which AssetType was selected...

      to be more precise:
      Code:
      formBuilder.add&#40;"assetType"&#41;;
      formBuilder.add&#40;bindingFactory.createBoundLabel&#40;"assetClass"&#41;&#41;;
      The assetClass should be automatically changed when the assetType is selected... How can I link the 2 binders? or change the value of the Asset.assetClass in the AssetTypeAdapter?

      Thanks!

      Regards from London,

      Benoit

      Comment


      • #4
        I was also working along those lines but I have found that I had to use the
        Code:
        registerBinderForPropertyName(Class parentObjectType, String propertyName, Binder binder)
        method rather than registerBinderForPropertyType.
        Ooops - typo on my behaf.

        I have another question... How would you bind a listener for event on the "assetType"? Say that once an AssetType is selected, the GUI must change another attribute based on the object AssetType (not the String)... The Adapter in AssetTypeBinder knows which AssetType was selected...
        You can listen for changes to any form property by adding a property change listener to the value model for that property.

        Code:
        formModel.getValueModel&#40;"assetType"&#41;.addValueChangeListener&#40;new PropertyChangeListener&#40;&#41; &#123;
            public void propertyChange&#40;PropertyChangeEvent evt&#41; &#123;
                updateTheGuiBecauseAsssetTypeHasChanged&#40;&#41;;
            &#125;
        &#125;&#41;

        Ollie

        Comment


        • #5
          cool

          Hi Ollie

          Cool stuff... now my killer question... the value of the assetClass depends on the AssetType OBJECT (not just the string...) so at the moment, my "binder" has the list of AssetTypes but the event triggered is a "assetType" with old/new string. Is there a way to get to the binder from the Model? or actually from the updateTheGuiBecauseAsssetTypeHasChanged() method?

          Or should I put in place a listener mechanism directly between the binder and the updateTheGuiBecauseAsssetTypeHasChanged()... How would you recommend doing this?

          Many thanks!!!

          Benoit

          Comment


          • #6
            From an architectural perspective your form object would be much cleaner if you just changed the class of the assetType property from String to AssetType - this would also solve all the problems above - however, I assume there's a good reason for not having done this in the first place.

            Or should I put in place a listener mechanism directly between the binder and the updateTheGuiBecauseAsssetTypeHasChanged()... How would you recommend doing this?
            No. This would intorduce a coupling between model and your view (the binder) which is exactly what we're trying to avoid by having a form modle

            In your AssetTypeBinder you must have some code that maps between the an assetCode and an actual AssetType instance - refactor this code into a separate static helper class and then use this new helper in both the AssetTypeBinder and in the updateTheGuiBecauseAsssetTypeHasChanged method

            Ollie

            Comment


            • #7
              thanks

              That is what I've done, but I was not too happy with it... was hoping that there could be something cleaner.

              getting late here...so I shall say thanks a lot for your help!

              regards from London,

              Benoit

              PS: BTW, do you have any problem with
              Code:
              TableUtils.sizeColumnsToFitRowData&#40;assetTable&#41;;
              The call does not seem to do anything... and the table has a model that contains lots of rows... No special setting for auto-resize or anything...

              Looks like the columns are still same size...
              http://www.objectlab.co.uk/jfree/table.jpg

              While I would have expected something like this (resized manually)
              http://www.objectlab.co.uk/jfree/table2.jpg

              Any suggestion?
              Cheers

              Benoit

              Comment


              • #8
                The call does not seem to do anything... and the table has a model that contains lots of rows... No special setting for auto-resize or anything...
                No idea - I don't use that class. Try tracing into the sizeColumnsToFitRowData methd and see what it's doing.

                Comment


                • #9
                  found the problem...

                  Hi Ollie & all,

                  I think that I have found the issue. The TableUtils.sizeColumnsToFitRowData only checks the first row (probably for speed issue) and, in my example, it just happen that the first row contains smaller strings... (had to be like that, Mr Murphy!)

                  I will look into this and post an alternative.

                  regards

                  Benoit

                  Comment


                  • #10
                    Alternative for column resizing.

                    Hi Ollie & all,

                    As discussed in previous post, the current implementation just looks at the first row. It is understandable if the table contains thousands of rows...

                    Here is an alternative solution; it takes 250ms on my old laptop (1GHz) to resize a table of 7 columsn and 2,000 rows.

                    Code:
                    /**
                    * Resize the columns based on the data contained in all rows and Header.
                    * The original width will be based on the value of the column header, and,
                    * will be expanded if at least one row requires more space.  The column header
                    * size is slightly padded to allow for sorting icons &#40;GlazedTableModel&#41;.
                    * @param table the table to resize
                    * @author Benoit Xhenseval &#40;modified&#41;
                    */
                    public static void sizeColumnsToFitAllRowData&#40;JTable table&#41; &#123;
                      // go through each column first to take the renderer.
                      for &#40;int col = 0; col < table.getColumnCount&#40;&#41;; col++&#41; &#123;
                        final TableColumnModel colModel = table.getColumnModel&#40;&#41;;
                        final TableModel model = table.getModel&#40;&#41;;
                    
                        TableColumn column = colModel.getColumn&#40;col&#41;;
                        TableCellRenderer r = colModel.getColumn&#40;col&#41;.getCellRenderer&#40;&#41;;
                    
                        // original width should be based on the size of the HEADER.
                        int cWidth = table.getTableHeader&#40;&#41;.getFontMetrics&#40;table.getTableHeader&#40;&#41;.getFont&#40;&#41;&#41;.stringWidth&#40;
                    	    model.getColumnName&#40;col&#41; + "XXXX"&#41;; // we have added 'XXXX' to allow space for sorting icons. 
                    
                        for &#40;int row = 0; row < table.getRowCount&#40;&#41;; row++&#41; &#123;
                    	if &#40;r == null&#41; &#123;
                    	    Object val = table.getValueAt&#40;row, col&#41;;
                    	    if &#40;val != null&#41; &#123;
                    		r = table.getDefaultRenderer&#40;val.getClass&#40;&#41;&#41;;
                    	    &#125;
                    	&#125;
                    	if &#40;r != null&#41; &#123;
                    	    Component c = r.getTableCellRendererComponent&#40;table, table.getValueAt&#40;row, col&#41;, false, false, 0,
                    		    col&#41;;
                    	    cWidth = Math.max&#40;c.getPreferredSize&#40;&#41;.width, cWidth&#41;;
                    	&#125;
                        &#125;
                    
                        column.setPreferredWidth&#40;cWidth + UIConstants.ONE_SPACE&#41;;
                        column.setWidth&#40;cWidth&#41;;
                      &#125;
                      int width = Math.min&#40;table.getColumnModel&#40;&#41;.getTotalColumnWidth&#40;&#41;, &#40;int&#41; &#40;WindowUtils.getScreenWidth&#40;&#41; * .75&#41;&#41;;
                      table.setPreferredScrollableViewportSize&#40;new Dimension&#40;width, 300&#41;&#41;;
                    &#125;
                    since the name is slightly different, could you add this method to the TableUtils? Thanks!

                    We have also experienced some optimizations by quickly going through the values of each row and simply doing a toString() to it, then picking up the row
                    where the string is the biggest and only try to render that row. It is not the most accurate method if you have some funny
                    rendering (say Date formatting that abbreviate a Date dramatically). But for big tables, it would be faster! For our example
                    of 2,000 rows & 7 columns, it is about 30% faster.

                    Here is the code, again, we have named the method slightly differently so they can co-exist:

                    Code:
                    public static void sizeColumnsToFitAllRowDataBasedOnStrings&#40;JTable table&#41; &#123;
                      for &#40;int col = 0; col < table.getColumnCount&#40;&#41;; col++&#41; &#123;
                        final TableColumnModel colModel = table.getColumnModel&#40;&#41;;
                        final TableModel model = table.getModel&#40;&#41;;
                    
                        TableColumn column = colModel.getColumn&#40;col&#41;;
                        TableCellRenderer r = colModel.getColumn&#40;col&#41;.getCellRenderer&#40;&#41;;
                    
                        // original width should be based on the size of the HEADER.
                        int cWidth = table.getTableHeader&#40;&#41;.getFontMetrics&#40;table.getTableHeader&#40;&#41;.getFont&#40;&#41;&#41;.stringWidth&#40;
                    	    model.getColumnName&#40;col&#41; + "XXXX"&#41;; // we have added 'XXXX' to allow space for sorting icons.
                    
                        // now simply spot the row that has the biggest "string"
                        int maxStringLength = 0;
                        Object maxRowValue = null;
                        int maxRowIndex = 0;
                        for &#40;int row = 0; row < table.getRowCount&#40;&#41;; row++&#41; &#123;
                    	Object value = table.getValueAt&#40;row, col&#41;;
                    	if &#40;value != null&#41; &#123;
                    	    final int length = value.toString&#40;&#41;.length&#40;&#41;;
                    
                    	    if &#40;length > maxStringLength&#41; &#123;
                    		maxStringLength = length;
                    		maxRowValue = value;
                    		maxRowIndex = row;
                    	    &#125;
                    	&#125;
                        &#125;
                    
                        // and uses that row for the rendering.
                        if &#40;r == null&#41; &#123;
                    	Object val = table.getValueAt&#40;maxRowIndex, col&#41;;
                    	if &#40;val != null&#41; &#123;
                    	    r = table.getDefaultRenderer&#40;val.getClass&#40;&#41;&#41;;
                    	&#125;
                        &#125;
                        if &#40;r != null&#41; &#123;
                    	Component c = r.getTableCellRendererComponent&#40;table, maxRowValue, false, false, 0, col&#41;;
                    	cWidth = Math.max&#40;c.getPreferredSize&#40;&#41;.width, cWidth&#41;;
                        &#125;
                    
                        column.setPreferredWidth&#40;cWidth + UIConstants.ONE_SPACE&#41;;
                        column.setWidth&#40;cWidth&#41;;
                      &#125;
                      int width = Math.min&#40;table.getColumnModel&#40;&#41;.getTotalColumnWidth&#40;&#41;, &#40;int&#41; &#40;WindowUtils.getScreenWidth&#40;&#41; * .75&#41;&#41;;
                      table.setPreferredScrollableViewportSize&#40;new Dimension&#40;width, 300&#41;&#41;;
                    &#125;
                    Please let me know if/when you could put this in CVS so I can remove my own class.

                    I took the liberty of raising an improvement JIRA:
                    http://opensource2.atlassian.com/pro...browse/RCP-189

                    Enjoy!

                    Thanks

                    Benoit

                    Comment


                    • #11
                      Benoit,

                      take a look at the ResizeTableColumnSupport class, see if you can use it. It should be very fast with large tables bacuse it intercepts rendering of the cell renders of the tables, and determines the preferred width for each renderer component. This way, it can determine the preferred column width. One side effect is that it only takes the visible rows into account.

                      Hope this helps,

                      Peter

                      Comment


                      • #12
                        Yep...but...

                        Hi Peter,

                        I had a look at that class a while ago and asked a question about it.
                        The issue is that by simply calling
                        ResizeTableColumnSupport.install(table)
                        it does not resize it immediately but wait for a user to resize or double click to automate it.

                        Is there a way to automatically resize all columns using that class? without user input.

                        Thanks

                        Benoit

                        Comment


                        • #13
                          I'll look into it...

                          Comment


                          • #14
                            Ping... any progress?

                            Thanks a lot

                            Benoit.

                            Comment

                            Working...
                            X