0

I have a GUI (on its EDT thread) and another thread called "Recorder" created by the GUI's eventListener if a button is pressed:

if(actionEvent.getSource().equals(ui.record)) {
    if(recorderThread == null) {
        recorder = new Recorder();
        recorderThread = new Thread(recorder);
        recorderThread.start();
    }
}

In the same event listener, I also implemented a mouseListener.

public void mouseReleased(MouseEvent mEvent) {
    int x, y;

    x = mEvent.getXOnScreen();
    y = mEvent.getYOnScreen();
}

I want to pass these X and Y variables to my recorder object in my recorder thread when the mouse is clicked. I think I can bodge a solution with volatile variables, but I read somewhere that handlers can be used to pass information or invoke methods between two threads, and was interested in learning about it. I found this previous post that faced a similar problem to mine.

The solution to the post, however, quite confused me. I think the person passes the thread objects into the handler, that way any thread can just call all the objects inside that handler? For example:

handler(someObj);

then in another thread

handler.getSomeObj().methodInObj();

But I'm not entirely sure if that is how handlers work. In addition, they also seem to be dealing with Swing's background thread instead of a separate thread the user creates(If this is the same concept, apologies in advance).

Finally, the solution seems to have called a Handler class built into the Java library, whereas I want to write my own handler class so I can better learn how threads communicate (since I'm a really novice youtube taught programmer). If anyone can help me out, it'd be greatly appreciated. Thanks in advance!

Jeff
  • 610
  • 4
  • 12

2 Answers2

2

Differntiate between the concept of thread on one hand and the concept of classes (incl. instances with their members) on the other hand.

There are various ways threads can communicate (means read or write variables at a place which can be written or read by other threads. In your example I would have your Recorder class expose a public method addCoordinates(). The Recorder has a private list member where the added coordinates are stored. The real problem is the synchronized access to this list: You have to ensure that not one thread reads the list whereas the other thread adds a new record - at the same time. The easiest solution is to have a synchronized list:

private List<Coordinates> myCoordinates = Collections.synchronizedList( new ArrayList<>());

public void addCoordinates( Coordinates coordinates)
{ 
    // this runs in the context of your GUI thread
    myCoordinates.add(coordinates);
    synchronized(this)
    {
        this.notify();    // wakes up the recorder thread
    }

}

public void run()
{
    // this runs in the context of the Recorder thread
    while ( true)
    {
        synchronized(this)
        {
            this.wait();   // waits until the 'this' is notified
        }

        for( Coordinates c : myCoordinates)
        {
            // do something
        }
    }
}
Heri
  • 4,368
  • 1
  • 31
  • 51
2

The recorder simply creates a list of objects called "Point" that store the X and Y position of the mouseclick, to be used later by an autoclicker

Then the Recorder should not be running in its own thread as there's no need to do this, and instead you would simply write to the recorder directly from the GUI on the EDT thread. Otherwise you're over-complicating things greatly. The exception is if Recorder is in fact doing more, such as currently running an outside process.

For example:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.*;

@SuppressWarnings("serial")
public class TestRecorder extends JPanel {
    private static final int GAP = 3;
    private MyRecorder myRecorder = new MyRecorder();
    private boolean recording = false;

    public TestRecorder() {
        JPanel btnPanel = new JPanel(new GridLayout(1, 0, GAP, 0));
        btnPanel.add(new JButton(new StartAction("Start")));
        btnPanel.add(new JButton(new StopAction("Stop")));
        btnPanel.add(new JButton(new ShowAction("Show")));

        addMouseListener(new MyMouse());
        setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
        setPreferredSize(new Dimension(500, 400));
        setLayout(new BorderLayout());
        add(btnPanel, BorderLayout.PAGE_END);
    }

    private class MyMouse extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            if (recording) {
                myRecorder.addPoint(e.getPoint());
            }
        }
    }

    private class StartAction extends AbstractAction {
        public StartAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            recording = true;
        }
    }

    private class StopAction extends AbstractAction {
        public StopAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            recording = false;
        }
    }

    private class ShowAction extends AbstractAction {
        public ShowAction(String name) {
            super(name);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Points:");
            for (Point point : myRecorder.getPoints()) {
                System.out.println(point);
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        TestRecorder mainPanel = new TestRecorder();
        JFrame frame = new JFrame("TestRecorder");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.add(mainPanel);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }
}

public class MyRecorder {
    private List<Point> points = new ArrayList<>();

    public List<Point> getPoints() {
        return points;
    }

    public void addPoint(Point p) {
        points.add(p);
    }
}
  • Thanks for the response! Yeah I kind of realized that after not being sleep deprived and rage fueled at trying to understand threads -.- The thing was I also wanted to copy the same process onto my autoclicker thread that uses the robot class, and that would definitely need its own thread. I just chose to use the recorder as an example since its much simpler and wouldn't clutter the question. Thanks again though for the help! – Jeff Aug 20 '17 at 14:52
  • Maybe I should just merge it with the robot object instead? That way whatever list the recorder creates can be directly accessed by the robot without some other complicated passing of lists and variables. *sidenote: noticed you are the dude who's been extremely helpful in almost every question I posted, so just wanted to give a special SO and thank you (: – Jeff Aug 20 '17 at 14:54
  • 1
    @Crumble: you're making a big newbie mistake, confusing object with thread. Your autoclicker is an object, and it may run on one thread, and it may have methods running on a different thread. When you're passing information into the autoclicker, you're not passing it into a thread, but into the object, and usually if it is coming from the GUI, it will come into the autoclicker object on the Swing event thread. – DontKnowMuchBut Getting Better Aug 20 '17 at 14:56