I do contract work for a small Wireless ISP in my rural area and am creating a desktop app to keep track of jobs and scheduling.

The main part of my app visually is a calendar and in each visible day there will be lists as containers for jobs belonging to that day, also there will be a job que window with a list containing jobs not scheduled, and I can drag and drop from list to list, etc.

A job is just a data class I created to represent each job and I was wondering how I would display a job on the screen, it seems that if I add "extends JButton" to my Job class, I just add it anywhere you can add a JButton, and it displays as a button with width/height dictated by the contstructor, etc.

My question: Is this a proper use of class extension to make my data object a renderable component for the UI?

If not, what would be the proper method?

Recommended Answers

All 20 Replies

Generally a very bad idea to build a dependency where a data class is dependent on a particular visual representation. Suppose your next project is to display the same info in HTML? Its normal for a GUI class to depend on the underlying data model, but not v.v.
Personally I see nothing wrong in extending a Swing component to make something customised for display or manipulation of a particular data type, but in that case I would subclass JButton in a new GUI class and have the associated Job as an instance variable, passed into the constructor.

Thanks for the reply, makes perfect sense, I felt like it was probably not a standard method, and not having any formal programming training I am always finding that the solutions I come up with, although they work, tend not to be reusable.

I was just using JButton as an example but for the drag and drop I took an example that will allow drag and drop between many JLists which is what I want, and it appears that in the example at least, using the DefaultListModel, only String objects can be added to to lists.

(Bear with me here, I have no programming training and only started messing with Java about 10 days ago.)

When an item(job) is moved from one list to another I need to know what list it came from, what list it is being dropped into, and an ID that would be unique to each job, and hence the string item in the list.

The job's representation in the list could certainly contain the job ID number in the string and I found that adding an object other than a String to the ListModel would use the object's toString() method to obtain the String that actually gets added to the JList, so I @Override toString() to return what I want to show in the list, including the job ID in the string and the drag and drop still works.

I use putClientProperty to add properties to the JLists that gives me useful identification of the source and target of a drag event.

In the transfer handler I am able to identify the source and target lists, and by intercepting a copy of the String being moved and parsing out the job ID, I will now be able to update the appropriate records when a job is moved from one place to another, ie. from day to day, or different job ques.

Since my data class is standalone it should be okay to override the toString()??

When hyou're getting the value associated with a list index you get the actual data Object, not its String representation, so I don't see why you are having to parse the strings for IDs.
Overriding toString for a data class is a very common thing to do. However, it's usually overidden to give a relevant description of the contents for eugeneral use (eg) in a System.out.println. Using it to make a String specifically for the needs of a particular UI problem is the wrong kind of data/UI coupling. To create a representation specifically for use in a particular JList situation, write a simple ListCellRenderer to give you what you need.

When I add my data object without overriding the toString() method, in the list I get "<myclassname>@<memory address>" displayed in the list and the drag/drop does not work.

However if I override the toString() method to return a string I create, and make no other changes, the object that gets added to the list is THE String object returned from my overridden toString() method.

I used:

Object o = list.get(index);
System.out.printf( o.getClass() );

and it says the object is a String, not my data class.
That is unless I do not override the toString() method, then the handler doesn't even get to that part of the code to see what the object is.

Not knowing any better I suspect it's the dataFlavor testing in the example code that may be stopping the drag and drop from working when my toString() is not overridden.

I looked at the listCellRenderer a little bit but was not sure if it was something I could use, I will look into it more, you don't have any links to a good tutorial on it do you?

I understand James' point about toString, and he is correct, but if I were you I would probably just write the toString method (without calling it toString though) and use it to get an ideal String representation for your list to display. I can't forsee problems with that in the future.

Hi BJSJC
If the method isn't called toString() the the default cell renderer won't call it automatically, and we're back to writing a simple cell renderer (in which case that's where the special formatting should be).
However, I'm pretty baffled by the OP's test that appears to show his Objects being converted into Strings when he adds them to the default list model. That's exectly NOT what I would expect - the default list model should keep the original Objects, and the default cell renderer calls their toString() to populate a JLabel.
Hazard - can you post enough of the code so I can reproduce the behaviour here?
Thanks
James

... aha! Maybe this explains it:
I just looked up some old code of mine that gets the Object behind a selected JList, and here's the relevant line:

list.getModel().getElementAt(i);

Using this approach to get the Object from the default model definitely returns the original Object, not its String representation.
Hazard: get your data object this way and forget about parsing display Strings!

Aha! =)

I don't have time to try that right now, running out the door to go work but I will try that as soon as I get home.

That does make a bit of sense now since the best I could tell, the intention of drag and drop was to transfer text, and it was a String object that appears visually in the JList so if all I do is get what's in the list as opposed to the listModel that the list is using, I am getting the String... am I understanding that correctly?

I really appreciate your help, I am pretty good at making things work when I don't know what I am doing, but it's usually held together with duct tape and bailing wire, and even though my code will look like a mess to a pro, I will feel much better using proper methods than hacked solutions.

Thank you!!!

You saved me a real headache, by diving into the transfer handler code to make sure I got the model from the correct list, and at the correct place in the code, I noticed that the code allows multiple selections so the user can select and move more than one item at a time.

I had not thought of this because I mistook this statement "list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);" to mean that only a single item could be selected at a time.

Lucky for me I took another look at the specific loop that handles the multiple transfer and realize that the code I added to track the transfers was fine when you only moved one item, but would only capture the data from the last item of a multiple selection move.

That could have been a nightmare if my users were moving multiple items at a time and I didn't even know it was possible...LOL

I could see it now, me wondering why my app was not capturing every item transfer, testing the code by moving one item at a time and finding it to work perfectly every time.... every time for single selection transfers that is =)

Here is what I found out, each listModel starts out filled with the number of instances of "MyClass" that I added to them.
When a transfer takes place, it only transfers the String object that is visible in the JList.

I confirmed this by iterating over the target listModel of a transfer and had it print the class name of each element.

I started with 5 instances of "MyClass" in each list, so after transferring via drag and drop, the target list then contained:
0 - class MyClass
1 - class MyClass
2 - class MyClass
3 - class MyClass
4 - class MyClass
5 - class java.lang.String <-- the item I just dropped into it.

Here is the example code I am trying to use, I removed a bunch of my extraneous code that has nothing to do with the example.

public class ListDnD {

    ReportingListTransferHandler arrayListHandler =
            new ReportingListTransferHandler();
    int count = 0;
    int dcount = 0;
    int idcount = 0;

    private JPanel getContent() {
        JPanel panel = new JPanel(new GridLayout(3, 4));
        panel.add(getListComponent("Day 1-Pend", "dayPend"));
        panel.add(getListComponent("Day 2-Pend", "dayPend"));
        panel.add(getListComponent("Day 3-Pend", "dayPend"));
        panel.add(getListComponent("Day 4-Pend", "dayPend"));
        panel.add(getListComponent("Day 1-Done", "dayDone"));
        panel.add(getListComponent("Day 2-Done", "dayDone"));
        panel.add(getListComponent("Day 3-Done", "dayDone"));
        panel.add(getListComponent("Day 4-Done", "dayDone"));
        panel.add(getListComponent("Ready Que", "queReady"));
        panel.add(getListComponent("Poss Que", "quePoss"));
        panel.add(getListComponent("Cancel Que", "queCancel"));
        panel.add(getListComponent("TODO Que", "queTODO"));



        return panel;
    }

    private JScrollPane getListComponent(String s, String t) {


        DefaultListModel model = new DefaultListModel();

        for (int j = 0; j < 5; j++) {

            count++;

            String listItem = String.format("%s %d ::Job %d", s, j + 1, count);

            MyClass mc = new MyClass(listItem);

            model.addElement(mc);
        
        }

        JList list = new JList(model);

        list.setName(s);
        list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
        list.setTransferHandler(arrayListHandler);
        list.setDragEnabled(true);
        

        return new JScrollPane(list);
    }

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new ListDnD().getContent());
        f.setSize(1000, 800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);



    }
}

Here is the transfer handler that came with the example:

public class ReportingListTransferHandler extends TransferHandler {

    DataFlavor localArrayListFlavor, serialArrayListFlavor;
    String localArrayListType = DataFlavor.javaJVMLocalObjectMimeType +
            ";class=java.util.ArrayList";
    JList source = null;
    int[] indices = null;
    int addIndex = -1; //Location where items were added
    int addCount = 0;  //Number of items added

    public ReportingListTransferHandler() {
        try {
            localArrayListFlavor = new DataFlavor(localArrayListType);
        } catch (ClassNotFoundException e) {
            System.out.println(
                    "ReportingListTransferHandler: unable to create data flavor");
        }
        serialArrayListFlavor = new DataFlavor(ArrayList.class,
                "ArrayList");
    }

    @Override
    public boolean importData(JComponent c, Transferable t) {

        //System.out.printf("importData method...%n");
        JList target = null;
        ArrayList alist = null;
        if (!canImport(c, t.getTransferDataFlavors())) {
            //System.out.printf("can't import!%n");
            return false;
        }
        try {
            target = (JList) c;
            if (hasLocalArrayListFlavor(t.getTransferDataFlavors())) {
                alist = (ArrayList) t.getTransferData(localArrayListFlavor);
            } else if (hasSerialArrayListFlavor(t.getTransferDataFlavors())) {
                alist = (ArrayList) t.getTransferData(serialArrayListFlavor);
            } else {
                //System.out.printf("unable to create alist!%n");
                return false;
            }
        } catch (UnsupportedFlavorException ufe) {
            System.out.println("importData: unsupported data flavor");
            return false;
        } catch (IOException ioe) {
            System.out.println("importData: I/O exception");
            return false;
        }

        // If you want the dialog to appear only for drags from the
        // left list and drops on the right JList.
        //if (target.getName().equals("right") && target != source) {
        //    String message = "Do you want to drop here?";
        //    int retVal = JOptionPane.showConfirmDialog(target, message, "Confirm",
        //            JOptionPane.YES_NO_OPTION,
        //            JOptionPane.QUESTION_MESSAGE);
        //    System.out.println("retVal = " + retVal);
        //    if (retVal == JOptionPane.NO_OPTION || retVal == JOptionPane.CLOSED_OPTION) {
        //        return false;
        //    }
        //}



        //At this point we use the same code to retrieve the data
        //locally or serially.

        //We'll drop at the current selected index.
        int index = target.getSelectedIndex();

        //Prevent the user from dropping data back on itself.
        //For example, if the user is moving items #4,#5,#6 and #7 and
        //attempts to insert the items after item #5, this would
        //be problematic when removing the original items.
        //This is interpreted as dropping the same data on itself
        //and has no effect.
        if (source.equals(target)) {
            if (indices != null && index >= indices[0] - 1 &&
                    index <= indices[indices.length - 1]) {
                indices = null;
                return true;
            }
        }

        DefaultListModel listModel = (DefaultListModel) target.getModel();
        int max = listModel.getSize();
        System.out.printf("index = %d  max = %d%n", index, max);
        if (index < 0) {
            index = max;
        } else {
            index++;
            if (index > max) {
                index = max;
            }
        }
        addIndex = index;
        addCount = alist.size();
        
        for (int i = 0; i < alist.size(); i++) {

            listModel.add(index++, alist.get(i));
            
        }

        
		//
		// HERE IS WHERE I LIST THE CLASS OF EVERY ITEM IN THE TARGET LIST AFTER DRAG N DROP TRANSFER
		//
        System.out.printf("%n================================================%n%n");
        ListModel lm = target.getModel();
        for( int xx = 0; xx < lm.getSize(); xx++ ){
            
            System.out.printf("Element %d: %s%n",xx,lm.getElementAt(xx).getClass() );
            
        }
		
		

        return true;
    }

    @Override
    protected void exportDone(JComponent c, Transferable data, int action) {
        if ((action == MOVE) && (indices != null)) {
            DefaultListModel model = (DefaultListModel) source.getModel();

            //If we are moving items around in the same list, we
            //need to adjust the indices accordingly since those
            //after the insertion point have moved.
            if (addCount > 0) {
                for (int i = 0; i < indices.length; i++) {
                    if (indices[i] > addIndex &&
                            indices[i] + addCount < model.getSize()) {
                        indices[i] += addCount;
                    }
                }
            }
            for (int i = indices.length - 1; i >= 0; i--) {
                model.remove(indices[i]);
            }
        }
        indices = null;
        addIndex = -1;
        addCount = 0;
    }

    private boolean hasLocalArrayListFlavor(DataFlavor[] flavors) {
        if (localArrayListFlavor == null) {
            return false;
        }

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i].equals(localArrayListFlavor)) {
                return true;
            }
        }
        return false;
    }

    private boolean hasSerialArrayListFlavor(DataFlavor[] flavors) {
        if (serialArrayListFlavor == null) {
            return false;
        }

        for (int i = 0; i < flavors.length; i++) {
            if (flavors[i].equals(serialArrayListFlavor)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean canImport(JComponent c, DataFlavor[] flavors) {
        if (hasLocalArrayListFlavor(flavors)) {
            return true;
        }
        if (hasSerialArrayListFlavor(flavors)) {
            return true;
        }
        return false;
    }

    @Override
    protected Transferable createTransferable(JComponent c) {
        if (c instanceof JList) {
            source = (JList) c;
            indices = source.getSelectedIndices();
            Object[] values = source.getSelectedValues();
            if (values == null || values.length == 0) {
                return null;
            }
            ArrayList<String> alist = new ArrayList<String>(values.length);
            for (int i = 0; i < values.length; i++) {
                Object o = values[i];
                String str = o.toString();
                if (str == null) {
                    str = "";
                }
                alist.add(str);
            }
            return new ReportingListTransferable(alist);
        }
        return null;
    }

    @Override
    public int getSourceActions(JComponent c) {
        return COPY_OR_MOVE;
    }

    public class ReportingListTransferable implements Transferable {

        ArrayList data;

        public ReportingListTransferable(ArrayList alist) {
            data = alist;
        }

        public Object getTransferData(DataFlavor flavor)
                throws UnsupportedFlavorException {
            if (!isDataFlavorSupported(flavor)) {
                throw new UnsupportedFlavorException(flavor);
            }
            return data;
        }

        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{localArrayListFlavor,
                serialArrayListFlavor
            };
        }

        public boolean isDataFlavorSupported(DataFlavor flavor) {
            if (localArrayListFlavor.equals(flavor)) {
                return true;
            }
            if (serialArrayListFlavor.equals(flavor)) {
                return true;
            }
            return false;
        }
    }
}

EDIT: oops, forgot MyClass that I was testing with:

public class MyClass {

    public String myname = "this is my name!";
    String myObjectString;

    public MyClass(String s) {

        myObjectString = s;

    }

    
    @Override
    public String toString() {

        return myObjectString;

    }
    
}

I generally don't expect people to do things for me, I love to learn as much as I can about programming, but this little problem is really holding up my project.

I would like to get a working app and call it a beta version, then as I learn more Java I will also find shortcomings in my implementation of the application and be able to take my time to rebuild it properly.

I am guessing that I really got the wrong example to apply to my situation here.

Great link, I think that is exactly it.

Funny thing and this is no lie, I woke up a 5am, sat up said to myself "I need to implement the serialize interface".

Of course that wouldn't have done it alone, at least my head was in the right place, I think it was the "serial" clues in the transfer handler... LOL

The Serializable interface was one I may have implemented anyway since the end product will use flat files locally but sync with a remote database, I have not decided exactly how I am going to format the data transfers over network yet. I am more a PHP guy and can really quickly build a MySql database with PHP interface.

This will make things nicer, I kept plugging away and have things working well but if I don't have to have the job id in the list it will be much better :)

If all your instance variables are simple, you don't need to do anything, just code the class as "implements Serializable" and the default mechanism will handle it all

commented: plus rep for the help to hazard +4

They are very simple, I went over the transfer handler again and identified the section that was causing a String object to be transferred.

This method was the culprit:

@Override
    protected Transferable createTransferable(JComponent c) {
        if (c instanceof JList) {
            source = (JList) c;
            indices = source.getSelectedIndices();
            Object[] values = source.getSelectedValues();
            if (values == null || values.length == 0) {
                return null;
            }
            ArrayList<String> alist = new ArrayList<String>(values.length);
            for (int i = 0; i < values.length; i++) {
                Object o = values[i];
                String str = o.toString();
                if (str == null) {
                    str = "";
                }
                alist.add(str);
            }
            return new ReportingListTransferable(alist);
        }
        return null;
    }

I changed it to this:

@Override
    protected Transferable createTransferable(JComponent c) {
        if (c instanceof JList) {
            source = (JList) c;
            indices = source.getSelectedIndices();
            Object[] values = source.getSelectedValues();
            if (values == null || values.length == 0) {
                return null;
            }

            // I CHANGED THE ARRAYLIST TO TYPE OBJECT INSTEAD OF STRING
            // I DECIDED THAT OBJECT WOULD BE SAFER/MORE RE-USEABLE THAN "MyClass" (?)

            ArrayList<Object> alist = new ArrayList<Object>(values.length);
            for (int i = 0; i < values.length; i++) {
                Object o = values[i];
                // HERE IS WHERE IT WAS GETTING TOSTRING()
                String str = o.toString();
                if (str == null) {
                    str = "";
                }
                // FINALLY I CHANGED THE FOLLOWING LINE TO ADD THE OBJECT TO THE LIST, NOT THE STRING
                alist.add(o);
            }
            return new ReportingListTransferable(alist);
        }
        return null;
    }

I also created the data flavor by simply replacing "java.util.ArrayList" in the data flavor that was being created previously to "MyClass" as follows:

String localArrayListType = DataFlavor.javaJVMLocalObjectMimeType +
            ";class=MyClass";
serialArrayListFlavor = new DataFlavor(localArrayListType);
            localArrayListFlavor = new DataFlavor(localArrayListType);

Then made MyClass implement Serializable.

The dnd works and seems to properly transfer the objects around now, verified by printing data members of the objects being transferred and check .getClass().

Without an overriding toString() the JList text is MyClass@<memory location> but with overriding toString() the JList text will be what that method returns and the transfers still work properly and do not change class during transfer.

Now can at least proceed writing my code based on actual objects being transferred instead of strings so I don't need to have the job numbers shown in the JList.

Then at my leisure I can dive into the cell renderer =) YAY!

P.S. when I first started reading through the transfer handler and you came up with data flavors I was feeling a bit intimidated but dealing with both of them was a LOT easier than I thought it would be.

Years ago I first tried to get my head around OOP and just couldn't do it, too much BASIC and Z-80 assembly (old TRS-80 Model II), but now that I am getting a grasp of it it makes so much sense.
I have been writing some C code for a couple of ARM microprocessors I have and find myself wishing I could create classes.

Again I thank you for all of your help and I hope that in assisting me you have gained something or at least knocked a few cobwebs loose :)

Well, I gave him some reputation, and your gratefulness is probably enough anyway. After all, people don't usually help on forums because of all the money they'll make from it. :) But good luck on your project; sorry I couldn't be of more assistance, but I was in a rush and I didn't understand the full scope of your problem.

BJSJC: Thanks for the reputation - as you say we don't do this for the money, but a bit of recognition is always welcome.
Hazard: Glad I could help. But how did you know I was geriatric enough to suffer from cobwebs?

Thank you both, I also gave some much deserved reps :)

JC: it was something you said about dusting off or going through some old code, plus it happens to me all the time, but then again I switch gears a lot, one day working on HTML and javascript, then another day PHP, then using/learning C and bash shell scripting for Linux running on my microcontrollers, etc., now add learning Java.
Luckily the syntax is very similar through these languages, with the exception of Linux shell scripting, that makes my head hurt.

Right now I am half way through a Deitel Java programming book but it doesn't go into GUI's very far, otherwise I great book.
Anyway I was in more of a hurry with this project, generally when I want to learn something, I learn better by solving problems than reading a book.
So what I would do is find a good forum, like this one, and read through the "help me" posts and try to solve people's problems, that way I would learn how to overcome the problems I would face when building my own solutions.

I learned a lot of PHP by doing that here and in other forums, just trying to solve other peoples problems, and I did that with Blender 3D, I spent a couple of months in the Blender forum trying to solve other peoples problems before starting my own projects and learned a lot.

As far as the app that is the subject of this thread, it is coming along nicely, got the DnD lists passing my objects, and am successfully using my custom listCellRenderer to display my object's in the list's the way I need them to, for example I found that I needed to reduce the size dramatically to display the list's inside my calendar's day panels, so I reduce the font and use less chars if the list is being displayed there, otherwise if the list is being displayed in one of the larger ques, ie. list of jobs that are not scheduled, it displays larger and with more text.
I accomplished that by using two methods in my Job class, the overridden toString() for the larger displays with more text, and a toStringShort() method that is called if the list is being displayed in a calendar day that returns just abreviated job type and location code all lowercase.

I did run into a problem though, the lists in the calendar day panels are pretty small, I have them in scrollpanes and the scroll bars removed, and mousewheel scrolling enabled, the problem is that only 2 items are fully visible, the third item in the list is half visible.
No biggie, the user can scroll, but if you grab an item and move down like you want to drag an item to a day below the source day, and you do it fast enough that dragging through the source lists didn't scroll it so the last item was visible, then the listTransferHandler removes the last item instead of the actual item being moved.
If you do it slow enough that the source list scrolls to make the last item visible, then it removes the proper element.

I think it is a symptom of the handler trying to handle when an item is just moved around the same list, and trying to prevent a multi-item selection from being dropped into itself, it attempts to reorder the indices, etc., I will dig into the handler more to see if I can fix the problem, otherwise I do have a couple of band-aids I could use ;)

Though I would give you a peek at what I have so far...
hmm attach doesn't seem to work, here is link to image:
my app image
JPG is a little fuzzy, otherwise you can tell I am not really much of a graphic designer ;)

Just want it to be functional, easy to use, and not completely painful to look at.

Each day cell has two lists, the left one for jobs that are scheduled for that day and pending, the right one for jobs completed that day.

The four panes at the bottom each have a list making the total number of lists visible 88, all of them able to DnD jobs.

Clicking on the number of a day will replace the month display with a day display listing the jobs for that day in full and allow other actions to be performed for that day.

The final objective is for the application to finalize a pay period by alerting when any day within that pay period has not been accounted for, has any jobs pending, or otherwise unable to reconcile.

If the pay period is all accounted for, then the user can choose to reconcile the pay period and it will close all the days of that period and generate a detailed pay report for each employee.

Every job type has a cost associated with it, the cost is either hourly using the employees hourly rate, or a flat rate per job per employee, each employee object has it's own flat rates per job type, if they don't, each job type has a default flat rate that will be used in the absence of an employee per job rate.

This originally started out as a web app just for me to submit an invoice every two weeks to the company to pay me for the work I had done in that two weeks but I am tired of having to deal with the different browsers that a user might use (I'm talking about you IE), so I decided to give Java a shot and decided to write this app so it could be expanded to handle multiple employees instead of just me :)

Well, right now it's just the owner, his mother on phones and the books, and me, but it will make all our lives easier.

I believe I have fixed the problem with the transfer handler, it seems it wasn't removing the last item, it was removing the next item, ie. increasing the source item index by one.

It was only supposed to do this when moving an item in the same list but they didn't test for that since the target variable didn't exists in that method, so I added a variable that would be true when source list == target list and it seems to be working now.

The code the way it was in the exportDone() method, the notes by the author even tell the story "If we are moving items around in the same list", but no test for that condition, the code would always run, and I narrowed the index increase down to this block of code that appears right before removing the source item that was moved:

//If we are moving items around in the same list, we
            //need to adjust the indices accordingly since those
            //after the insertion point have moved.
            
            if (addCount > 0 ) {
                for (int i = 0; i < indices.length; i++) {
                    if (indices[i] > addIndex &&
                            indices[i] + addCount < model.getSize()) {
                        indices[i] += addCount;
                    }
                }
            }

My fix, using variable sourceEqTarget that is set to true or false in the importData() method where source and target both exist:

//If we are moving items around in the same list, we
            //need to adjust the indices accordingly since those
            //after the insertion point have moved.
            
            if (addCount > 0 && sourceEqTarget ) {
                for (int i = 0; i < indices.length; i++) {
                    if (indices[i] > addIndex &&
                            indices[i] + addCount < model.getSize()) {
                        indices[i] += addCount;
                    }
                }
            }

Funny thing is when running the example app for the DnD I couldn't always get the problem to happen, in my app where the scrollpane's are small and without scrollbars, it always happened.

Important thing is that in my app I have not had a recurrence of this problem after my fix.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.