Вопрос

As we know the ComboBoxModel interface is used to created a class in witch we can specify how to relate a collections of objects (the model) with the combobox, basically by supplying the necessary "information" on how to retrieve items and set current item. Generally I wrote those classes declaring as member a Collection <of a concrete type>, and just delegate some functionality to the collection object in the implemented methods. When the actual class of all the contained object are a Not-Proxied object everything goes fine (surely 90% of the times we have this situation), but this time a face the fact to have references to Proxied Objects and things goes strangely wrong. The JComboBox behavior goes wrong as it cannot change the current selection.

I am trying to get some more information, but by now I only know that the method setSelectedItem of ComboBoxModel interface, that any concrete classes implements is not invoking when there are proxied objects around. That is my question: What is happening and, more important, is it fixable?

I leave an example, ready to look and see for yourself.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;

/**
 *
 * @author Administrador
 */
public class AComboBoxWithProxyProblem extends JFrame implements ActionListener
{
     ComboBoxModel modelWithProxies = new ItemComboBoxModelWithProxies();
     ComboBoxModel modelWithoutProxies = new ItemComboBoxModelWithoutProxies();

        public AComboBoxWithProxyProblem()
        {
            final JComboBox comboBox = new JComboBox();
            comboBox.addActionListener( this );


            getContentPane().setLayout(new BoxLayout(this.getContentPane(),BoxLayout.LINE_AXIS));
            getContentPane().add(comboBox);

            JRadioButton btnProxy = new JRadioButton("Proxy model");
            btnProxy.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e)
                {
                    comboBox.setModel(modelWithProxies);
                    comboBox.setSelectedIndex(0);
                }
            });

            getContentPane().add(btnProxy);

            JRadioButton btnNoProxy = new JRadioButton("Non Proxy model");
            btnNoProxy.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e)
                {
                    comboBox.setModel(modelWithoutProxies);
                    comboBox.setSelectedIndex(0);
                }
            });

            getContentPane().add(btnNoProxy);

            ButtonGroup group = new ButtonGroup();
            group.add(btnProxy);
            group.add(btnNoProxy);

            setTitle("Mmmm...");
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            JComboBox comboBox = (JComboBox)e.getSource();
            Item item = (Item)comboBox.getSelectedItem();
            System.out.println("[actionPerformed] - " + item.getId() + " : " + item.getDescription() );
        }


    interface ItemInterface
    {

        String getDescription();

        int getId();

        @Override
        String toString();

    }

    class Item implements AComboBoxWithProxyProblem.ItemInterface
    {
        private int id;
        private String description;

        public Item(int id, String description)
        {
            this.id = id;
            this.description = description;
        }

        @Override
        public int getId()
        {
            return id;
        }

        @Override
        public String getDescription()
        {
            return description;
        }

        @Override
        public String toString()
        {
            return description;
        }

    }

    private class ItemComboBoxModelWithoutProxies extends AbstractListModel implements ComboBoxModel
    {
        List<ItemInterface> foos;
        ItemInterface selected;

        public ItemComboBoxModelWithoutProxies()
        {
            foos = new ArrayList<> ();
            foos.add(new Item(1,"One"));
            foos.add(new Item(2,"Two"));
            foos.add(new Item(3,"Three"));
        }

        @Override
        public Object getSelectedItem()
        {
            return selected;
        }

        @Override
        public void setSelectedItem(Object tournament)
        {
            System.out.println("[setSelectedItem] " + tournament);
            selected = (ItemInterface) tournament;
        }

        @Override
        public int getSize()
        {
            return this.foos.size();
        }

        @Override
        public Object getElementAt(int i)
        {
            return this.foos.get(i);
        }
    }

    private class ItemComboBoxModelWithProxies extends AbstractListModel implements ComboBoxModel
    {
        List<ItemInterface> foos;
        Object selected;

        public ItemComboBoxModelWithProxies()
        {
            foos = new ArrayList<> ();
            ItemInterface item;
            item = (ItemInterface) Proxy.newProxyInstance(Item.class.getClassLoader(),
                    Item.class.getInterfaces(),
                    new ItemInvocationHandler (new Item(1,"One")));
            foos.add(item);

            item = (ItemInterface) Proxy.newProxyInstance(Item.class.getClassLoader(),
                    Item.class.getInterfaces(),
                    new ItemInvocationHandler (new Item(2,"Two")));
            foos.add(item);

            item = (ItemInterface) Proxy.newProxyInstance(Item.class.getClassLoader(),
                    Item.class.getInterfaces(),
                    new ItemInvocationHandler (new Item(3,"Three")));
            foos.add(item);
        }

        @Override
        public Object getSelectedItem()
        {
            return selected;
        }

        @Override
        public void setSelectedItem(Object tournament)
        {
            System.out.println("[setSelectedItem] " + tournament);
            selected = (ItemInterface) tournament;
        }

        @Override
        public int getSize()
        {
            return this.foos.size();
        }

        @Override
        public Object getElementAt(int i)
        {
            return this.foos.get(i);
        }

        private class ItemInvocationHandler implements InvocationHandler {
            Item item;

            public ItemInvocationHandler(Item item)
            {
                this.item = item;
            }

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
            {
                return method.invoke(this.item, args);
            }
        }
    }

    public static void main(String[] args)
    {
        JFrame frame = new AComboBoxWithProxyProblem();
        frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible( true );
    }

}

Well, thats all!

Thanks!!

Victor.

Это было полезно?

Решение

The problem is, the JComboBox is using Object#equals to compare values.

JComboBox#setSelectedIndex is calling JComboBox#getSelectedItem, which is using...

for (int i = 0; i < dataModel.getSize(); i++) {
    E element = dataModel.getElementAt(i);
    if (anObject.equals(element)) {
        found = true;
        objectToSelect = element;
        break;
    }
}

To verify that the object exists within the model before setting it.

The problem is, your proxy object is calling equals not on the Proxy but is invoking equals in the object it is proxing, which ends up begin false (because Proxy#equals(Proxy) is more like Proxy.objectBeginProxied#equsl(Proxy)

This is actually noted in the Java Docs

An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler's invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.

I'm not sure how you would fix this though

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top