(Java) repaint () выполняется в случайное время - PullRequest
0 голосов
/ 27 мая 2020

Работая с GUI в Java, я заметил, что метод repaint() класса Component демонстрирует необычное поведение. Приведенный ниже код обновляет панель содержимого после печати 1 и до 2, или это было моим намерением.

public class ErrorTest {

    public static void main(String[] args) throws InterruptedException {

        JFrame F = new JFrame();
        F.setSize(100,100);

        Panel P = new Panel();
        F.setContentPane(P);
        F.setVisible(true);

        while(true) {
            System.out.println("\n1");
            P.repaint();
            System.out.println("2");
            Thread.sleep(100);

        }
    }
}

class Panel extends JPanel {

    @Override
    protected void paintComponent(Graphics G) {
        System.out.println("painted");
        G.drawOval(10, 10, 20, 30);
    }
}

Если Thread.sleep(100); включен, вывод этого кода:

1
2
painted

1
2
painted

и так далее. В противном случае вывод будет:

1
2

1
2
painted

и так далее, обычно просто печатается 1 и 2 и редко painted в случайных местах. Результат, который я ищу:

1
painted
2

1
painted
2

повторяется. Похоже, что этот метод update() вызывается в начале каждой итерации, независимо от того, где я на самом деле его пишу. Также, кажется, есть некоторая задержка перед выполнением метода. Что я могу сделать для достижения желаемого результата?

1 Ответ

1 голос
/ 27 мая 2020

TL; DR: То, что вы хотите, невозможно.

Существует вещь, называемая «EDT» - поток отправки событий. Свинг (и большинство UI-библиотек) заключается в том, что существует единственный поток (EDT), а система работает, вставляя события в очередь; поток постоянно берет самое верхнее событие из очереди и обрабатывает его.

Пользователь нажимает кнопку? Задание по вызову любого слушателя действий, определенного для этой кнопки, помещается в очередь. Это означает, что любой код в прослушивателе событий выполняется в EDT.

Этот вызов repaiont? Он вообще не вызывает paintComponent напрямую. Он вставляет событие в очередь, чтобы выполнить перерисовку, и затем перерисовка выполняется в EDT, и ТО вызывает paintComponent.

(Правило для Swing и других фреймворков заключается в том, что вы не можете редактировать никакие виджет из любого потока, кроме EDT, см. документацию по swing).

Другими словами, ваш основной метод - это один поток, ваш EDT - другой, а вызов repaint() передается из одного потока в другой : Пожалуйста, запустите это событие, а затем продолжите.

Еще одно правило для EDT состоит в том, что он никогда не должен «блокироваться» (ждать на диске, в сети или в другом потоке или приостанавливать выполнение по любой причине). Если вы заблокируете EDT, приложение будет выглядеть полностью не отвечающим, и вскоре ОС отобразит уведомление о том, что приложение, похоже, разбилось. Это потому, что различные GUI взаимодействия, такие как обновление курсора мыши до другой формы при наведении курсора на текстовое поле, также обрабатываются EDT, поэтому, если EDT заморожен, ничего из этого не работает.

Таким образом, мы приходим к выводу: то, что вы хотите, не может быть выполнено - единственный способ получить надежное «это происходит до этого», где «это» - это задание в одном потоке, а «это» - задание в другом, - это с блокировкой, которая вызывает зависания, что недопустимо в EDT *.

Но никто не выпускает приложение, которое «вызывает метод рисования перед продолжением после вызова перерисовки». Ясно, что у вас была какая-то работа, которую вы хотели выполнить, вы думали, что синхронизация repaint () и paint () - это путь к этому, и теперь вы задаете вопросы по этому второму вопросу. Но это была неправильная стратегия - ответ есть, но вызовы «syn c up рисование и перерисовка» - тупик.

Во-вторых, метод paintComponent вызывается каждый раз, когда необходимо отрисовать ваш составная часть. Выполнение чего-то столь же простого, как перетаскивание окна другого приложения через окно вашего приложения, вызовет события, которые вызовут paintComponent для выполнения работы. Вы не можете контролировать такие действия. Так что даже если вы взмахнете своей волшебной c палочкой и сделаете невозможное (вызовы syn c up repaint () и paintComponent ()), вы все равно обнаружите кучу вызовов paintComponent, которые не вызваны repaint ()

*) У вас может быть ваш основной поток, который вызывает repaint (), ждать на EDT, чтобы сделать хотя бы одну отрисовку, я думаю, с защелкой, но это звучит не очень хорошо plan, у вас нет никаких гарантий, когда действительно происходит перерисовка, и вы не знаете, действительно ли какой-то вызов paintComponent вызван тем, что вы вызвали перерисовку, или потому, что ОС решила, что пришло время спросить ваше приложение о том, какие пиксели оно хочет показать.

...