Wielowątkowość, na przykładzie prostej animacji.

0

Cześć,
mam do napisania program, w którym przetestuję działanie wielowątkowości, sęk w tym, że nie wiem nawet od czego zacząć i jak się za to zabrać. Dodatkowo mamy korzystać z pluginu SwingDesigner (Window Builder). Oto założenia programu:

  • posiada 4 klasy (wątki): Lis, Kura, Świat, Run,
  • Lis porusza się po planszy i szuka w swoim otoczeniu jajka, które składa kura. Jeśli nie ma jajka w jego najbliższym otoczeniu to idzie w jednym z 4 kierunków losowo,
  • Kura - chodzi po planszy w dowolnym kierunku i składa jajka w losowych momentach czasu
  • Świat - postarza jajka, jeśli jajko ma więcej niż 10 lat i lis je zje to znika z planszy, po 30 latach jajko znika z planszy,
  • Szybkość mijającego roku ma być możliwe do zmiany w okienku programu, za pomocą np. suwaka,
  • Synchronizacja wątków (przez 'synchronize'):
  • Lis i Kura nie mogą wejść na to samo pole w tym samym czasie,
  • Kura nie może złożyć jajka na innym jajku,
  • Plansza ma mieć wymiary m x m,
  • Rysowanie ma się odbywać na ramce,
  • Kury, Lisy, Jajka mogą być na planszy reprezentowane dowolnie, tzn. np. za pomocą literek lub 'kwadracików' o różnych kolorach.

Bardzo, potrzebuję pomocy z tym zadaniem, tak naprawdę to nie wiem jeszcze nic, więc chciałbym się od Was dowiedzieć o czym muszę poczytać. Najchętniej zobaczyłbym jakieś przykłady podobnych programów, żeby mieć na czym się wzorować i w ogóle każda pomoc jest tutaj mile widziana.

Z góry dziękuję za każda odpowiedź.
Pozdrawiam.

0

Moje umiejetnosci niestety nie pozwalaja jeszcze na fachowe rady ale moze tutaj znajdziesz cos pomocnego, mowi on troche o watkach, timerze, kolizji obiektow itp. :)

1

Proponuję zacząć od obczajenia wielowątkowości - interfejs Runnable i klasa Thread, oraz nauki rysowania w Swing. Od razu, rada na przyszłość - oddzielaj mechanizm działania / funkcjonalność programu od GUI. Najlepiej żeby cała wizualizacja była w formie osobnej klasy rysującej - np. dziedziczącej po JPanel albo Canvas i tylko otrzymywała lub sczytywała aktualne dane do odrysowania.

0

Dziękuję za obie odpowiedzi. A czy wiecie gdzie mógłbym podpatrzeć jakieś podobne programy, tak aby mieć z czego czerpać inspirację?

0

http://www.pitt.edu/~marchegi/JavaProg/BounceBall.java

Tutaj masz kod programu ze skaczacymi pileczkami gdzie kazda kolejna jest nowym watkiem, mysle ze moze byc przydatne:)

0

Musisz wymyslic sposob "reprezentacji" (model) poszczegolnych pol na mapie. Moze to byc np. lista obiektów typu Field - obiekty te
będą zawierać koordynaty pola, które reprezentują oraz jego stan (czy zawiera jajka, kto znajduje sie na polu itp.). Wiadomo, że
kura, świat i lis to będą osobne wątki, które będą "współdzielić" między sobą listę tych obiektów - więc musisz się postarać żeby
operacje na obiektach tej listy były zsynchronizowane, bo inaczej pojawią się różne nieciekawe problemy związane z wielowątkowością :)

0

Naprawdę bardzo wszystkim dziękuję za odpowiedzi :) W miarę możliwości czasowych w zacznę pisać ten program i jak tylko natrafię na jakieś problemy to na pewno jeszcze napiszę :)
Jeszcze raz dziękuję i pozdrawiam :)

0

Ok, więc dopiero zaczynam z moim programem i chciałem przetestować działanie Timera i już natrafiłem na błąd, którego nie mogę przeskoczyć. Chciałem narysować kwadrat i sprawić aby się po prostu poruszał, byle jak i napisałem coś takiego:
Klasa zadanie:

import java.util.TimerTask;

public class Zadanie extends TimerTask
{
    int poz1 = 0;
    int poz2 = 0;
    
    @Override
    public void run(){
        poz1++;
        poz2++;
        
        Ramka.ramka.repaint();    
    }
}

Klasa Ramka:

package lab04_pop;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Timer;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Ramka extends JFrame {
    Zadanie zadanie = new Zadanie();
    Timer timer = new Timer();
    static JFrame ramka;
    
    public Ramka (){
        setSize (600, 400);
        setTitle ("Plansza");
        
        timer.scheduleAtFixedRate (zadanie, 10, 10);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setVisible(true);
}

    public void paint (Graphics g){
        g.setColor (Color.RED);
        g.fillRect (zadanie.poz1, zadanie.poz2, 100, 100);
        
    }

    
public static void main (String[] args){
    EventQueue.invokeLater(new Runnable() {
        @Override
        public void run() {
            new Ramka();
        }
    });
}
}

Jest ktoś w stanie mi powiedzieć co robię źle? Eclipse mi wyrzuca błąd:

Exception in thread "Timer-0" java.lang.NullPointerException
at lab04_pop.Zadanie.run(Zadanie.java:14)
at java.util.TimerThread.mainLoop(Unknown Source)
at java.util.TimerThread.run(Unknown Source)

1

Tworzysz obiekt Zadanie, który wewnątrz metody run() odwołuje się do Obiektu Ramka, który to nie jest jeszcze stworzony. Ponadto nie jestem pewien czy w JFrame w ogóle da się rysować. Lepiej w konstruktorze Ramki stwórz jakiś JPanel i na nim rysuj i do niego się odwołaj w swojej metodzie run().

0

Mam kolejny problem. Trochę poczytałem i postanowiłem sporo zmienić, udało mi się wprawić obiekty w ruch i trochę przetestować rysowanie. Chciałem przejść do narysowania planszy (postanowiłem reprezentować ja przez tablicę dwuwymiarową), po której będą chodzić moje Lisy i Kury, ale niestety nie generuje się to co chciałem, czyli plansza tylko pusta (szara) ramka. Oto mój kod:

Klasa animacja:

package lab04_pop;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Animacja extends JPanel {
    Kura kura = new Kura(this);
    Świat świat = new Świat (this);

    public void plansza (){
        świat.zrobPlansze();
    }

    /*private void move() {
        kura.move();
    }*/

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        /*kura.paint(g2d);*/
        świat.paint(g2d);
        
    }

    public static void main(String[] args) throws InterruptedException {
        JFrame frame = new JFrame("Kurnik");
        Animacja animacja = new Animacja();
        frame.add(animacja);
        frame.setSize(400, 400);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        while (true) {
            /*animacja.move();*/
            animacja.repaint();
            Thread.sleep(10);
        }
    }
}

Klasa świat:

package lab04_pop;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.Random;

public class Świat {
    @SuppressWarnings("unused")
    private Animacja animacja;
    int plansza [][] = new int [20][20];
    
    public Świat(Animacja animacja) {
        this.animacja= animacja;
    }
    
    public void zrobPlansze (){
        Random gen = new Random();
        for (int i=0; i<plansza.length; i++){
            for (int j=0; j<plansza[0].length; j++){
                plansza[i][j]=gen.nextInt(2);
            }
        }
    }
    
    public void paint(Graphics2D g) {
        for (int i=0; i<plansza.length; i++){
            for (int j=0; j<plansza[0].length; j++){
                switch (plansza[i][j]){
                case 0:
                    g.setColor(Color.GRAY);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 1:
                    g.setColor(Color.ORANGE);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 2:
                    g.setColor(Color.RED);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                }
            }
        }
    }
}

Co robię źle?

0

Błędy są dwa:

  • nigdzie nie wywołujesz metody zrobPlansze, zatem w tablicy plansza są same zera,
  • masz zły zakres losowania, musi być
plansza[i][j]=gen.nextInt(3);
0

Ale przecież jest:

public void plansza (){
        świat.zrobPlansze();
    }

To nie wystarczy? W taki sam sposób wywołuję metodę move() i ona działa.

1

To nie jest wwywołanie metody, to jest deklaracja metody w klasie Animacja. Dopisz wywołanie.

...
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        animacja.plansza();

A metodę move wywołujesz tu:

        while (true) {
            /*animacja.move();*/ tu!!!!
            animacja.repaint();
            Thread.sleep(10);
        }
0

A tak, przepraszam i dziękuję za pomoc :)

0

Mam kolejny problem, mianowicie, udało mi się zrobić wprawić moje Kury w ruch, ale metoda którą to robię musi znajdować się w klasie Świat, bo gdy daję ją do klasy Kura to nic się nie dzieję.

Klasa Kura:

package lab04_pop;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.Random;

public class Kura {
    Random gen = new Random();
    Świat świat = new Świat (this);
    private Animacja animacja;

    public Kura(Animacja animacja) {
        this.animacja= animacja;
    }

    public void move() {
        for (int i=0; i<świat.plansza.length; i++){
            for (int j=0; j<świat.plansza[0].length; j++){
                if (świat.plansza[i][j] == 1){
                    int kierunek = gen.nextInt(3);
                    switch (kierunek){
                        case 0:
                            try{
                                świat.plansza[i][j-1]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 1:
                            try{
                                świat.plansza[i][j+1]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 2:
                            try{
                                świat.plansza[i+1][j]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 3:
                            try{
                                świat.plansza[i-1][j]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                    }
                }
            }
        }
    }

    public void paint(Graphics2D g) {
        for (int i=0; i<świat.plansza.length; i++){
            for (int j=0; j<świat.plansza[0].length; j++){
                switch (świat.plansza[i][j]){
                case 0:
                    g.setColor(Color.GRAY);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 1:
                    g.setColor(Color.ORANGE);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 2:
                    g.setColor(Color.RED);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                }
            }
        }
    }
}

Klasa Animacja:

package lab04_pop;

import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Animacja extends JPanel {
    Kura kura = new Kura(this);
    Świat świat = new Świat (this);


    private void zrobPlansze (){
        świat.zrobPlansze();
        świat.wypPlansze();
    }

    private void move() {
        kura.move();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        świat.paint(g2d);
        
    }

    public static void main(String[] args) throws InterruptedException {
        JFrame frame = new JFrame("Kurnik");
        Animacja animacja = new Animacja();
        animacja.zrobPlansze();
        frame.add(animacja);
        frame.setSize(420, 440);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        
        while (true) {
            animacja.move();
            animacja.repaint();
            Thread.sleep(1000);
        }
    }
}

Chciałbym żeby to poruszanie się po planszy było realizowane w klasie Kura. Algorytm działa ale tylko jak wkleję metodę move() do klasy Świat i oczywiście zmienię z "świat.plansza" na samo "plansza" oraz w klasie Animacja w metodzie move() z kura.move() na świat.move().

1
  1. Unikaj polskich liter w nazwach klas (Świat), bywają problemy przy zmianie SO.
  2. Co robi metoda wypPlansze() w klasie Świat?
  3. Kod klasy Kura wygląda absurdalnie:
    • dlaczego w tej klasie tworzysz obiekt typu Świat, każda kura żyje w osobnym świecie?
    • co robi w tej klasie metoda paint odmalowująca cały świat?
    • obsługa wyjątków jest kosztowna, nie należy jej używać do banalnego sprawdzenia czy nie przekraczasz rozmiaru tablic.
    • przeczytaj wreszcie dokumentację klasy Random, ta instrukcja
int kierunek = gen.nextInt(3);

nigdy nie wylosuje liczby 3.

0
  1. Metoda wypPlansze wypełnia moja planszę zadaną ilością kur i lisów.
    public void wypPlansze (){
        int lKur = 8;
        int lLisow = 8;
        for (int i=0; i<lKur; i++){
            int y = gen.nextInt(plansza.length);
            int x = gen.nextInt(plansza[0].length);
            if (plansza[y][x] == 0)    
                plansza[y][x] = 1;
        }
        for (int i=0; i<lLisow; i++){
            int y = gen.nextInt(plansza.length);
            int x = gen.nextInt(plansza[0].length);
            if (plansza[y][x] == 0)    
                plansza[y][x] = 2;
        }
    }

a) nie wiedziałem jak mam to zrobić żeby się "dostać" do tablicy plansza z klasy Świat dlatego utworzyłem obiekt tej klasy. Robię jeszcze sporo błędów właśnie takich, przez to, że jeszcze nie do końca rozumiem te mechanizmy.
b) jaka metoda paint powinna być w klasie kura? Odrysowująca tylko kurę?
c) ok, zrobie to na if'ie
d) już to poprawiłem, chociaż nie wiedzieć czemu moje kury chodziły w dół i to często czyli te 3 musiało być losowane.

1

3 to jest ruch do góry, i tego ruchu nie było. Po usunięciu metody paint z klasy Kura działanie programu nie zmienia się. Świat do klasy Kura możesz przekazać tak:

public class Animacja extends JPanel {    
    Świat świat = new Świat (this);
    Kura kura = new Kura(this,świat);
    ...
public class Kura {
    Random gen = new Random();
    private Świat świat = null;
    private Animacja animacja = null;
 
    public Kura(Animacja animacja,Świat świat) {
        this.animacja = animacja;
        this.świat = świat;
    }
0

Dziękuję za odpowiedzi :)
Mam kolejne pytanie, jak najłatwiej i najlepiej rozwiązać problem "składania jaj" przez kury w losowych odstępach czasu? To znaczy chodzi mi o samo wykonywanie się algorytmu w losowych odstępach czasu, bo z samym algorytmem sobie poradzę :)

0

Aby wykonywać coś w odstępach czasu zazwyczaj korzysta się z generowania opóźnień np. przez uśpienie wątku za pomocą Thread.sleep(m); m możesz generować losowo i problem rozwiązany. Czyli wykonujesz funkcję w osobnym wątku usypianym co jakiś czas.

2

Jest też inne rozwiązanie. Dla każdej kury losujesz kierunek przesunięcia:

if (świat.plansza[i][j] == 1){
    int kierunek = gen.nextInt(4);

dodaj losowanie czy kura ma znieść jajko;

    float zniesJajo = gen.nextFloat();
    if(zniesJajo < p){
        //kura znosi jajo

Graniczne p możesz wybrać dowolnie.

0

Ok, udało mi się zaimplementować wszystkie funkcję jakie chciałem, teraz najtrudniejsze - wielowątkowość. Przede wszystkim muszę zrobić tak aby moje kury i lisy nie wchodziły na to samo pole w tym samym czasie. Z tego co wyczytałem to muszę zaimplementować interfejs Runnable. Tylko pytanie gdzie to zrobić? Czy wszystko co mam w mainie wrzuci po prostu do metody run()? To ma sens?

1

Do metody run() wrzuć tylko to co wymaga "bycia" nowym wątkiem. Może to być metoda jakiejś klasy zarządzającej całością. Mająca referencje do planszy, pozycji kur i lisów oraz odpowiadająca za losowanie ścieżki - taki jakby mózg sterujący całością.

Albo bardziej naturalnie - każde zwierzę to osobny wątek. Każde sprawdza czy może wejść na dane pole czy już jest zajęte. Algorytmy sterowania kurą i lisem różniłyby się, ale nie oznacza to, że musiałbyś pisać parę różnych klas z różnymi metodami run(). Wystarczyłoby zrobić jedną dla wszystkich zwierząt. Część kodu byłaby wspólna - np losowanie kierunku, sprawdzanie zajętości pola, poruszanie się, a część wydzielona jakimiś ifami - np. znoszenie jaj tylko dla kury.

0

albo napisać jedną ogólną w której by było poruszanie się itp i dwie dziedziczące po niej dokładające już specyficzne metody dla każdej z nich

0

A czy możliwe jest zrobienie z metod "kura.move()" i "lis.move()" wątku, tak aby kura i lis nie wchodziły w tym samym czasie na to samo pole? Albo żeby 2 kury nie wchodziły na to samo pole w tym samym czasie?
Oto moja główna klasa:

package lab04_pop;

import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Animacja extends JPanel {
    Świat świat = new Świat (this);
    Kura kura = new Kura (this, świat);
    Lis lis = new Lis (this, świat);


    private void zrobPlansze (){
        świat.zrobPlansze();        // tworzenie planszy
        świat.wypelnijPlansze();    // wypełnianie planszy zadaną ilościa kur i lisów
    }

    private void move() {            // poruszanie sie kury i lisa
        kura.move();                // w tej metodzie kura ma dodatkowo zaimplementowane skladanie jaj
        lis.move();
    }

    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2d = (Graphics2D) g;
        świat.paint(g2d);            // odrysowywanie świata
        kura.paint(g2d);            // odrysowywanie pol z jajkami
    }

    public static void main(String[] args) throws InterruptedException {
        JFrame frame = new JFrame("Kurnik");
        Animacja animacja = new Animacja();
        animacja.zrobPlansze();
        frame.add(animacja);
        frame.setSize(420, 440);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        
        while (true) {
            animacja.move();
            animacja.repaint();
            Thread.sleep(100);
        }
    }
}
1

poczytaj o interfejsie Runnable i klasie ReentrantLock i słowie kluczowym synchronized te trzy rzeczy powinny Ci pomóc rozwiązać problem :-)

0

Szczerze mówiąc to w teorii rozumiem, jak to wszystko działa, mam problem z tym, żeby zaimplementować interfejs Runnable w moim programie. Nie wiem, w którym miejscu ma się on znaleźć ani jak go zaimplementować. Proszę o wyrozumiałość, to dopiero mój 3 program w javie, stąd moje problemy z oczywistymi, pewnie dla niektórych, rzeczami.

1

klasa kura


package lab04_pop;
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.Random;
 
public class Kura implements Runnable {
    Random gen = new Random();
    Świat świat = new Świat (this);
    private Animacja animacja;
 
    public Kura(Animacja animacja) {
        this.animacja= animacja;
    }
 
    public void move() {
        for (int i=0; i<świat.plansza.length; i++){
            for (int j=0; j<świat.plansza[0].length; j++){
                if (świat.plansza[i][j] == 1){
                    int kierunek = gen.nextInt(3);
                    switch (kierunek){
                        case 0:
                            try{
                                świat.plansza[i][j-1]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 1:
                            try{
                                świat.plansza[i][j+1]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 2:
                            try{
                                świat.plansza[i+1][j]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                        case 3:
                            try{
                                świat.plansza[i-1][j]=1;
                                świat.plansza[i][j]=0;
                            }
                            catch (IndexOutOfBoundsException e){
                                świat.plansza[i][j]=1;
                            }
                            break;
                    }
                }
            }
        }
    }
 
@Override
    public void run() {
     
      this.move();
      this.repaint();
    
       
    }

    public void paint(Graphics2D g) {
        for (int i=0; i<świat.plansza.length; i++){
            for (int j=0; j<świat.plansza[0].length; j++){
                switch (świat.plansza[i][j]){
                case 0:
                    g.setColor(Color.GRAY);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 1:
                    g.setColor(Color.ORANGE);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                case 2:
                    g.setColor(Color.RED);
                    g.fillRect(20*j, 20*i, 20, 20);
                    break;
                }
            }
        }
    }
}

w ten sposób zamiast wywolywać w klasie animacja kura.move wywołujesz

   Thread t=new Thread(kura);
   t.start();

i powinno wykonywać Ci ruch kury w nowym wątku. Tak mi się wydaje ale nie sprawdzałem. Dopisać warunek sprawdzający czy na planszy akuratnie nie siedzi nic inneg ( zwykły if wystarczy) i będzie elegacko. przydałoby się jeszcze zabezpieczenie żeby kura i lis nie mogły się na raz dostać do zmiennej plansza. o ile się nie mylę powinno się to uzyskać poprzez dodanie słówka synchornized do dwóch nowych metod w klasie Swiat np. zmien_plansze która by zmieniała plansze i odczyt_plansze która by odczytywała zmienną z planszy do której chcesz wpisać.
powinno to wygladać tak

//w klasie swiat

public synchronized int odczyt_plansza(int i, int j){

     try{
          return this.plansza[i][j];
     }catch(NullPointerException e){
     }

}


public synchronized void zmien_plansza(int i, int j,int war){

     try{
          this.plansza[i][j]=war;
     }catch(NullPointerException e){
     }

}

o ile się nie mylę powinno to zapewnić że dwa wątki na raz się nie dostaną do planszy. i użyć tych metod zamiast przepisywania wartości w klasie kura i lis.

Niech ktoś przytaknie bo nie mam pewności że tak jest ;-)

pzdr

1

Odpowiem już w poście.

Tak robi ponieważ napisałem to na szybko i nie pomyślałem :P

Powinno być w bloku catch np return 100. Tłumaczę już dlaczego. Jeżeli funkcja zwróci błąd (tzn podasz za duży indeks ) wskoczy ci do bloku catch i nic nie zwróci a tak być ni może i kompilator to wychwytuje.

public synchronized int odczyt_plansza(int i, int j){
 
     try{
          return this.plansza[i][j];
     }catch(NullPointerException e){
      return 100;
     }
 
}
 

teraz będzie dobrze dobrze a jak pojawi się wartość 100 na wyjściu to będziesz wiedział że coś jest nie halo

pzdr

0

To ja jeszcze wtrącę taką uwagę, że prawie nigdy nie powinno się bezpośrednio używać metody paint() tylko lepiej użyć protected void paintComponent(Graphics g){super.paintComponent(g); ... reszta kodu...} a potem wywoływać tą metodę za pomocą repaint();

1 użytkowników online, w tym zalogowanych: 0, gości: 1