Java Swing: Awfully slow cursor response when hovering over rectangles

血红的双手。 提交于 2019-12-08 13:22:38

问题


Below is code where a user is moving the mouse cursor over a bunch of rectangles and the cursor is supposed to change to a hand cursor while over the rectangles and revert to a default normal cursor when not on top of the given rectangles.

Problem: The solution works,but painfully slow.They are many ways "to skin a cat", therefore we can model the question in many variants and solutions could be of all kinds, but what is constant,is that we have to loop over each rectangle using an iterator and use the contain() method to ascertain if indeed a given point on the JPanel is within a rectangle or not,and change the cursor accordingly.

As simple as what is needed to be implemented sounds,i.e change cursor while hovering over specific rectangles, the program starts to slow considerably as I add different shapes and more rectangles.

The code below simply shows rectangles aligned to the x-axis and rectangles aligned to the y-axis.I split them(still can combine them into one list) into two List of rectangles.I iterate over the two lists with a while loop within another,each with the break keyword at the appropriate place.

I avoid using just one large list to hold both types of rectangle (or different types of shapes) because

  1. I need to add different shapes every other time, and its better and more readable to have different shapes categorized in there own list.
  2. I am instinctively trying to shorten a long process by using different list for different shapes and if possible,iterate over the right list only and not iterate over other shapes that are unnecessary.i.e One large list will grow linearly and iterate over ***all shapes***to get to the right one ***all the time.***Doesn't the one large list seem not a so smart implementation? Hoping my best that efforts to avoid the One-Large-List is not a case of premature optimization!!!This the point I thought to use threads to loop over different list concurrently or simultaneously but one thread misbehaves.

So then, I categorize shapes in different List, as the example below has two Lists.but this trick also fails as I have to iterate over each list sequentially.So I have a while loop within another.I have not avoided iterating over unnecessary list,as one loop must be within another(or start after another), then the inner loop (or the one that follows) pays an undue performance overhead since the first loop is totally unnecessary if we can ascertain before hand that a shape belongs to a certain group before hand.Mark you,to ascertain if a mouse cursor is hovering over a shape belonging to a List of circle,or a List of rectangles,is what we need to know beforehand !! so that we iterate over the specific list. It even gets better,if at this point you can still follow my reasoning, to know beforehand which List a shape belongs to has to been done without the contain() method, since contain() is to be used while iterating inside the List!!!

To summarize, the code below is simply a linear iteration over two list. To access the second List,you have to pass through the first.Is there a way one can by pass iterating the 1st list first?

If all my explanations and explorations are all wrong and don't make sense. Question. Then,how do I improve the cursor response of the code below.

EDIT

Sorry for posting code that does not compile, i has so moved on from this code snippet and was playing with my new toys called threads,till i ended up tying myself in an knot which i could not disentangle myself from.The truth is i choose threads as I hope to select the rectangles and move them about using setRect() method.I envisioned that moving around shapes of all kinds in predetermined motions i.e affinetransformation may require threads due to painting, repainting , searching and all manner of hard work that my benefit from some multi-threading.Anyways, the code below compiles and the cursor response is actually good!!!.Ouch! I have a similar implementation but probably its slowness is being caused by other classes which draw the rectangles, unlike in this SSCCE where they are drawn by a for loop.

Mean while, if anyone has a way to get this good performance through threads, it would be highly appreciated.Thanks in advance.

    import java.awt.Color;
    import java.awt.Cursor;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    import java.awt.event.MouseMotionListener;
    import java.awt.geom.Rectangle2D;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import javax.swing.*;

    public class FlyingSaucerTwo extends JPanel {
     Rectangle2D.Double rec;
     Rectangle2D.Double rec1; 
     List<Rectangle2D.Double> recList;
     List<Rectangle2D.Double> recList2;

     Rectangle2D.Double mouseBoxx;  
     int f = 10;
     int g = 0;
     int s = 10;
     int y = 5;
     int z = 500;

     public FlyingSaucerTwo(){

    //FlyingSaucer needs to quickly identify specific points over given areas
    //enclosed in rectangles.They use a 'divide and conquer' approach where 
    //different types of rectangles are first identified and a worker thread
    //assigned to each category

     mouseBoxx = new Rectangle.Double();
     recList = new ArrayList<>();
     recList2 = new ArrayList<>();

     for(int i = 0; i < 15; i++){
         rec = new Rectangle2D.Double(2+f,10+g,5,1000);       
         f +=50;
         recList.add(rec);                
     }
     f = 10;

     for(int i = 0; i < 20; i++){
         rec1 = new Rectangle2D.Double(2+y,10+s,1000,5);       
         s +=35;
         recList2.add(rec1);                
     }
     s = 10;
    }


    public static void main(String[] args) {
        JFrame frame = new JFrame();
        FlyingSaucerTwo fs = new FlyingSaucerTwo();
        Laser laser = new Laser(fs);
        fs.addMouseMotionListener(laser);
        fs.addMouseListener(laser);
        frame.getContentPane().add(fs);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(700,700);
        frame.setVisible(true);     
   }

   //@Override
    protected void paintComponent(Graphics g) {
     super.paintComponent(g); 
     ((Graphics2D)g).setColor(Color.RED);
     int a = 10;
     int b = 10;

     for(int i = 0;i < recList.size();i++){               
       ((Graphics2D)g).draw(recList.get(i));
     }

     for(int i = 0;i < recList2.size();i++){               
        ((Graphics2D)g).draw(recList2.get(i));
     }
    }
    }

     class Laser implements MouseListener,MouseMotionListener{
      Rectangle2D.Double mouseBox;
      List<Rectangle2D.Double> recxList;
      Rectangle2D.Double recx;
      List<Rectangle2D.Double> recyList;
      Rectangle2D.Double recy;
      FlyingSaucerTwo fs;

     public Laser(FlyingSaucerTwo fs){
      this.fs = fs;
     }

     @Override 
     public void mouseClicked (MouseEvent e) { }
     @Override 
     public void mousePressed (MouseEvent e) { }
     @Override 
     public void mouseReleased(MouseEvent e) { }
     @Override 
     public void mouseEntered (MouseEvent e) { }
     @Override 
     public void mouseExited  (MouseEvent e) { }
     @Override 
     public void mouseDragged (MouseEvent e) { }

     @Override
      public void mouseMoved(MouseEvent e) {   
        SwingUtilities.invokeLater(new Runnable() { 
               @Override
               public void run() { 
                Point p = e.getPoint();
                recxList = fs.recList;                        
                recyList = fs.recList2; 
                Iterator <Rectangle2D.Double> recX = recxList.iterator();
                //FIRST LOOP over Y axis rectangles
                while(recX.hasNext()){
                     recx = recX.next();
                     if( recx.contains(p)){           
                         fs.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); 

                         break;                     
                     }
                     else if(recyList.size()>=0){
                         Iterator <Rectangle2D.Double> recY = recyList.iterator(); 
                             //SECOND LOOP over X axis rectangles
                            while(recY.hasNext()){
                                 recy = recY.next();
                                 if( recy.contains(p)){           
                                     fs.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));                                        
                                     break;                     
                                 }
                                 else{
                                     fs.setCursor(Cursor.getDefaultCursor());
                                 }
                            }
                     }
                     else{
                         fs.setCursor(Cursor.getDefaultCursor());
                     }
                }                      
            }
        });
    }
    }        

回答1:


IMHO your inner code should be like this:

Cursor cursor = Cursor.getDefaultCursor();
Iterator <Rectangle2D> recs = rowBuffY.iterator();
//FIRST LOOP over Y axis rectangles
while(recs.hasNext()){
    selectRec = recs.next();
    if( selectRec.contains(p)){           
        cursor = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); 
        dragging = false;
        moveLine = true;
        break;                     
    }
}
Iterator <Rectangle2D> recX = rowBuffX.iterator(); 
//SECOND LOOP over X axis rectangles
while(recX.hasNext()){
     selectRec = recX.next();
     if( selectRec.contains(p)){           
     cursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
     dragging = false;
     moveLine = true;
     break;                     
}
tpp.setCursor(cursor);

This code checks every rectangle exactly once and gives preference to X axis rectangles, whereas your code checks every X axis rectangle N times (for every Y axis rectangle that the mouse doesn't hover over).




回答2:


As Thomas Kläger suggests, you should un-nest the loops.

If there is still an issue, it probably has to do with the fact that you are calling setCursor() too much. In particular, in the nested loop example, if the cursor is not in a rectangle, you are calling setCursor() to set the default cursor hundreds of times. Each time you call setCursor(), the cursor is redrawn, which is a time consuming process.

You need to set the cursor at most once per mousemoved event. One way of doing this would be to set a boolean value about the desired cursor type while going through the loops, then setting the cursor only at the end, after both loops have exited, based on the boolean value. For further efficiency, you could also check whether the current cursor is already the one you want, and only call setCursor() if you need to change it.



来源:https://stackoverflow.com/questions/46290834/java-swing-awfully-slow-cursor-response-when-hovering-over-rectangles

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!