Swing #4: Usuwanie narysowanych kształtów

Spis treści

Zadanie

Dodaj możliwość usuwania narysowanych kształtów za pomocą prawego przycisku myszy. Dodaj także możliwość przesuwania wcześniej narysowanych kształtów.

Podpowiedź 1: Klasa MouseEvent posiada metodę getButton(), która zwraca kod wciśniętego klawisza. Są w niej również zdefiniowane odpowiednie stałe, z którymi można porównywać wynik zwrócony przez metodę getButton(), np BUTTON1 ...

Podpowiedź 2: Przy przesuwaniu elementów wykorzystaj interfejs MouseMotionListener i metodę mouseDragged().

Rozwiazanie

Na początek kod ramki, który nie zmienił się od czasu lekcji:

import java.awt.EventQueue;
import javax.swing.JFrame;

public class Frame extends JFrame {
	public Frame() {
		super("MouseTest");

		add(new MouseTestPanel());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		pack();
		setVisible(true);
	}

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

a teraz kod panelu i poniżej jego wyjaśnienie.

Usuwanie kształtów:

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;

import javax.swing.JPanel;

public class MouseTestPanel extends JPanel implements MouseListener,
		MouseMotionListener {

	private static final int WIDTH = 200;
	private static final int HEIGHT = 200;

	// punkt, który będzie pamiętał współrzędne przesuwanego kwadracika
	Point movingPoint;

	ArrayList points = new ArrayList();

	public MouseTestPanel() {
		//dodajemy słuchaczy
		addMouseListener(this);
		addMouseMotionListener(this);
		setPreferredSize(new Dimension(WIDTH, HEIGHT));
	}

	@Override
	public void mouseClicked(MouseEvent e) {
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// współrzędne w których kliknięto
		int x1 = e.getX();
		int y1 = e.getY();
		// współrzędne kwadracików
		int x2, y2;

		// czy chcemy dodać, usunąć, lub przesunąć
		if (e.getButton() == MouseEvent.BUTTON3) {
			Point toRemove = null;
			for (Point p : points) {
				x2 = (int) p.getX();
				y2 = (int) p.getY();
				if (x1 >= x2 && y1 >= y2 && x1 <= x2 + 10 && y1 <= y2 + 10)
					toRemove = p;
			}
			// usuwamy kwadracik
			points.remove(toRemove);
		} else if (e.getButton() != MouseEvent.BUTTON3) {
			int index = 0;
			int size = points.size();
			Point p;
			while (movingPoint == null && index < size) {
				p = points.get(index);
				x2 = (int) p.getX();
				y2 = (int) p.getY();
				if (x1 >= x2 && y1 >= y2 && x1 <= x2 + 10 && y1 <= y2 + 10)
					movingPoint = p;
				index++;
			}
		}
		//jeżeli nie kliknięto na żaden kwadracik
		if (movingPoint==null && e.getButton() == MouseEvent.BUTTON1) {
			x1 = e.getX();
			y1 = e.getY();
			// dodajemy kwadracik
			points.add(new Point(x1, y1));
		}

		repaint();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		 movingPoint = null;
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		if (movingPoint != null) {
			movingPoint.x = e.getX();
			movingPoint.y = e.getY();
			repaint();
		}
	}

	@Override
	public void mouseMoved(MouseEvent e) {
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g); //wyczyści nasze tło
		Graphics2D g2d = (Graphics2D) g;
		g2d.setColor(Color.WHITE); //ustawiamy tło
		g2d.fillRect(0, 0, WIDTH, HEIGHT);
		g2d.setColor(Color.BLACK); //ustawiamy kolor kwadracików
		drawRectangles(g2d);
	}

	//metoda rysująca kwadraciki
	private void drawRectangles(Graphics2D g2d) {
		int x, y;
		for (Point p : points) {
			x = (int) p.getX();
			y = (int) p.getY();
			g2d.fillRect(x, y, 10, 10);
		}
	}
}

Zgodnie z podpowiedzią najpierw musimy rozpoznać, czy użytkownik chce narysować, czy usunąć jakiś kwadracik z planszy. W tym celu musimy dodać warunek sprawdzający, który przycisk myszy został wciśnięty. Informacja taka znajduje się w obiekcie MouseEvent przekazywanego do każdej z metod obsługi myszy, a dostać się do niej możemy wywołując na rzecz obiektu metodę getButton(). Zwróci nam ona jakąś liczbę typu całkowitego, nie musimy się martwić, czy będzie to 1, 5, czy 500, ponieważ klasa MouseEvent posiada również specjalnie do tego zdefiniowane stałe BUTTON1, BUTTON2, BUTTON3, dopiero przy większej liczbie przycisków robi się problem.

Ja używam myszy z trzema przyciskami, którym od lewej do prawej można nadać numery 1, 2, 3. Ponieważ usuwanie miało zachodzić po kliknięciu prawym przyciskiem sprawdzam, czy wciśnięty przycisk to BUTTON3.

Samo usunięcie jest dosyć proste. W liście przechowujemy dane lewego górnego wierzchołka każdego narysowanego kształtu. Należy więc wyszukać ten z nich, który spełnia iloczyn logiczny czterech warunków (wszystkie 4 warunki muszą zachodzić):

  • 1,2 - współrzędna X miejsca w którym kliknięto musi być większa od współrzędnej X lewego górnego punktu kwadratu, i mniejsza od tej wartości powiększonej o 10
  • 3,4 - analogicznie, jak w przypadku X (należy pamiętać, że współrzędne Y rosną w dół (lewy górny róg ekranu to punkt (0, 0)

Znaleziony punkt usuwamy następnie z kolekcji i odświeżamy panel przy pomocy repaint().

Przesuwanie kształtów:

Przesuwanie kształtów jest nieco bardziej skomplikowane. Podobnie jak w przypadku usuwania, musimy najpierw sprawdzić, czy w ogóle wciśnięto klawisz(inny niż prawy) na jakimś kwadraciku. Ponieważ, chcemy później aktualizować jego położenie w metodzie mouseDragged() zaimplementowanej na rzecz interfejsu MouseMotionListener, to jeżeli taki znajdziemy, przypisujemy wartość jego referencji do globalnie utworzonej zmiennej movingPoint. Dodatkowo za każdym razem przy zwolnieniu przycisku myszy przypisujemy do tej zmiennej wartość null (spróbuj usunąć tę linię kodu i zobacz, na czym polega błąd).

Poruszanie kształtu obsługujemy w metodzie mouseDragged(), która przypomnijmy - jest wykonywana, gdy użytkownik wciśnie klawisz na komponencie i porusza kursorem. Ponieważ my posiadamy zmienną movingPoint, w której pamiętamy, który punkt chcemy przesuwać, wystarczy zmieniać jego współrzędne wraz z poruszaniem kursora. Ja zrobiłem to po prostu przypisując wartości położenia kursora bezpośredni do pól movingPoint.x, oraz movingPoint.y. Można jednak do tego wykorzystać równie dobrze metodę setLocation klasy Point. Po każdym malutkim przesunięciu myszy należy oczywiście także odświeżyć panel.

Dla osoby, która rozpoczyna zabawę z obsługą zdarzeń może się to niewątpliwie wydawać nieco zagmatwane i skomplikowane. Warto jednak dokładnie przejrzeć przykład i zrozumieć co się w nim dzieje. Dobrze jest również wymyślić sobie kilka podobnych przykładów i po prostu je zrobić.

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.

Poniżej znajdziesz archiwalne wpisy z czasów, gdy strona była jeszcze hobbystycznym blogiem.

Ed

Jeszcze raz bo usuwane są automatycznie niektóre znaki przez co nie można własciwie sformatować polecenia Javy: Po ArrayList trzeba dać nawiasy trójkątne w których umieszczamy: Point Po drugim ArrayList tak samo. <b>ArrayList points = new ArrayList (); </b>

Slawek

no właśnie te błędy są przez to, że mi też wcięło nawiasy, zaraz poprawię. Oh a teraz widzę, że nawet spory kawałek kodu wcięło. Naprawię więc dopiero w domu.

Ype

nadal chyba kod nie jest poprawiony ;p

mandragorn

Private boolean firstStart = true; &lt;- nie jest nigdzie wykorzystywane

mateon1

Żeby kod działał trzeba zamienić wszystkie "point" w "Point" i pobawić się trochę z "}" na końcu

Slawek

Kod generalnie był ok poza tą niepotrzebną zmienną o której wyżej wspomniano, Tylko kolorowanie składni dokleja z niewyjaśnionych mi przyczyn na końcu znaczniki - wyłączyłem więc.

Witek

Witam Mam problem z metodą usunięcia kwadratu, posiadam starą myszkę „ AT4 – Tech” posiada ona dwa przyciski „lewy i prawy”, a pomiędzy nimi są dwa kółka do przesuwania stron. Program się uruchamia, ale po wciśnięciu lewego lub prawego przycisku rysowane są kwadraciki. Poprawiłem tylko ArrayList : ArrayList points = new ArrayList( ); Kombinowałem na różne sposoby z ustawieniem MouseEvent.BUTTON3, zmieniając na BUTTON1 itd., ale efekt był cały czas ten sam. Reszty kodu nie zmieniałem , nie mogę znaleźć błędu.

Mr_Fisq

Masz więcej przycisków niż trzy po prostu. Proponowałbym w funkcji <code> public void mousePressed(MouseEvent e) </code> Wrzucić linijkę: <code> System.out.print(e.getButton() + "\n"); </code> Co pozwoli zidentyfikować w konsoli numer wciskanego przycisku. Wtedy zamiast pisać: MouseEvent.BUTTON3, napiszesz np. 4. Albo inny numer jaki zostanie wypisany w terminalu przy naciśnięciu prawego przycisku.

Witek

Witam Robiłem tak jak napisałeś, ale efekt jest taki sam jak opisałem wyżej, nadal nie moge usunąć kwadratu. Może nie w tym miejsu wklejam tą linijkę kodu co podałeś. Nie bardzo rozumię " napisz np: nr. 4 ....", ale gdzie mam to wpisać bo poprostu nie wiem, możesz mi ty wytłumaczyć tak łopatologicznie, będę wdzięczny. Kod jest taki sam jak w zadanmiu. Pozdrawiam

Mr_Fisq

Więc modyfikujesz wyżej wymienioną funkcję do postaci: <code> public void mousePressed(MouseEvent e) { System.out.print(e.getButton() + "\n"); // współrzędne w których kliknięto int x1 = e.getX(); int y1 = e.getY(); // współrzędne kwadracików int x2, y2; // czy chcemy dodać, usunąć, lub przesunąć if (e.getButton() == MouseEvent.BUTTON3) { Point toRemove = null; for (Point p : points) { x2 = (int) p.getX(); y2 = (int) p.getY(); if (x1 &gt;= x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 &lt;= y2 + 10) toRemove = p; } // usuwamy kwadracik points.remove(toRemove); } else if (e.getButton() != MouseEvent.BUTTON3) { int index = 0; int size = points.size(); Point p; while (movingPoint == null &amp;&amp; index = x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 &lt;= y2 + 10) movingPoint = p; index++; } } //jeżeli nie kliknięto na żaden kwadracik if (movingPoint==null &amp;&amp; e.getButton() == MouseEvent.BUTTON1) { x1 = e.getX(); y1 = e.getY(); // dodajemy kwadracik points.add(new Point(x1, y1)); } repaint(); } </code> Dzięki czemu po każdym kliknięciu pojawi Ci się w terminalu numer wciskanego przez Ciebie przycisku. Uruchamiasz tak zmodyfikowany program, a następnie klikasz w nim przyciskiem którym chcesz usuwać kwadraty. W tym momencie powinien nam się wypisać (w terminalu/konsoli tam gdzie Ci się wszystko wypisuje) numer przypisany do tego przycisku. Teraz tą liczbą która Ci się wypisała po kliknięciu prawym przyciskiem zastępujesz: "MouseEvent.BUTTON3" oraz usuwasz wcześniej dodaną linijkę bo teraz jest już niepotrzebna. Przykładowo jeśli wypisało Ci się 4, a tak zapewne będzie, to kod powinien wyglądać mniej więcej tak: <code> @Override public void mousePressed(MouseEvent e) { // współrzędne w których kliknięto int x1 = e.getX(); int y1 = e.getY(); // współrzędne kwadracików int x2, y2; // czy chcemy dodać, usunąć, lub przesunąć if (e.getButton() == 4) { Point toRemove = null; for (Point p : points) { x2 = (int) p.getX(); y2 = (int) p.getY(); if (x1 &gt;= x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 &lt;= y2 + 10) toRemove = p; } // usuwamy kwadracik points.remove(toRemove); } else if (e.getButton() != 4) { int index = 0; int size = points.size(); Point p; while (movingPoint == null &amp;&amp; index = x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 &lt;= y2 + 10) movingPoint = p; index++; } } //jeżeli nie kliknięto na żaden kwadracik if (movingPoint==null &amp;&amp; e.getButton() == MouseEvent.BUTTON1) { x1 = e.getX(); y1 = e.getY(); // dodajemy kwadracik points.add(new Point(x1, y1)); } repaint(); } </code>

Mr_Fisq

Wieczorem, albo jutro opiszę co i jak.

Witek

Witam Wielkie dzięki za pomoc działa tak jak chciałem, czyli klikam rysuje mi kwadrat następnie klikam i przesuwa mi kliknięty kwadrat, a po wciśnięciu prawego klawisza myszy usuwa mi dany kwadrat i o to mi właśnie chodziło. Jeszcze raz dzięki. Pozdrawiam.

Mr_Fisq

Nie ma sprawy. Cieszę się, że udało mi się pomóc.

Dawid

Witam. Za nic nie rozumiem tej części kodu. Czy ktoś dałby radę napisać, do czego ona służy i wręcz linijka po linijce ją wytłumaczyć? Byłbym bardzo wdzięczny :) Pozdrawiam. } else if (e.getButton() != MouseEvent.BUTTON3) { int index = 0; int size = points.size(); Point p; while (movingPoint == null &amp;&amp; index = x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 &lt;= y2 + 10) movingPoint = p; index++; }

xxx

za nic mi nie działa ten kod czy mogl by ktos mi wyslac gotowy projekt? pracuje w Net Beansie jak cos to poprosze o projekt

Lukas

Witam, Mam jedno pytanie. Nie bardzo rozumiem dzialanie kodu w mouseDragged. Przypisujemy tam zmiennej (punktowi) nowe wspolrzedne w zalaznosci od tego gdzie aktualnie znajduje sie myszka. Natomiast nie dopisujemy wspolrzednych do macierzy points. Dlaczego wiec funkcja repaint ktora rysuje na podstawie wartosci w macierzy points rysuje aktualne wspolrzedne zmienniej movingPoint ? Mam nadzieje, ze to zrozumiale opisalem :) Łukasz

Sławek Ludwiczak

Hej, algorytm jest taki, że: Wykryto wciśnięcie przycisku myszy Jeśli to prawy przycisk - przejrzyj kolekcję obiektów i jeśli kliknięto na jakiś to go usuń. <b>Jeśli to nie prawy przycisk - sprawdź kolekcję obiektów i jeśli kliknięto wewnątrz jakiegoś obiektu, to przypisz jego położenie (lewy górny wierzchołek) do zmiennej movingPoint</b> (Żeby przesuwać jakiś element powinniśmy go chwycić już w momencie kliknięcia, a nie dopiero przy przesuwaniu, czyli mouseDragged) Od tego momentu movingPoint wskazuje na konkretny punkt w kolekcji - zmieniając współrzędne movingPoint, zmieniasz równocześnie współrzędne jakiegoś obiektu w kolekcji points. Dlatego w mouseDragged tak naprawdę na bieżąco aktualizujesz współrzędne jakiegoś konkretnego punktu, a wywołując repaint() każde drobne przesunięcie powoduje poruszanie chwyconego kształtu. <pre name='code' class='java'> Point point1 = new Point(2, 3); Point point2 = point1; </pre> jeśli w takim kodzie zmienisz jakąś współrzędną w point2, np point2.setX(5), to jednocześnie zmieniasz to samo pole w point1 - obie referencje (point1, point2) wskazują na ten sam obiekt (new Point(2, 3))

Pawel

Nie działa. Poprawiłem linie nr 16 na taką postać: ArrayList points = new ArrayList(); I działa :D Poprawkę ściągnąłem z poprzednich lekcji, ale nie rozumiem zmian ;(

Pawel

Edit hmmm Po zamieszczeniu poprawka wygląda tak samo jak to w listingu :( Może tak spróbuje: Po słowie ArrayList (obydwa wystąpienia) powinno być Point pomiędzy znakami większości i mniejszości. Wygląda jakby było w nawiasie, ale zamiast nawiasów są znaki większy i mniejszy ;)

Pawel

No tak pierwszy komentarz o tym mówił. Nie załapałem, że nawiasy trójkątne to to samo co znak większości :P Sorry za zamieszanie ;)

Artur87

hej tą część : while (movingPoint == null &amp;&amp; index = x2 &amp;&amp; y1 &gt;= y2 &amp;&amp; x1 &lt;= x2 + 10 &amp;&amp; y1 = p.x &amp;&amp; ym &gt;= p.y &amp;&amp; xm &lt;= p.x + 10 &amp;&amp; ym &lt;= p.y + 10) { movingPoint = p; }

Piotr

for (Point p : points) { x2 = (int) p.getX(); y2 = (int) p.getY(); if (x1 &gt; x2 &amp;&amp; x1 y2 &amp;&amp; y1 &lt; y2 + 10) // tmp = p; points.remove(p); } // points.remove(tmp); dlaczego w tym miejscu musi być zapamiętywany punkt usuwany, a nie od razu usuwany? nie rozumiem tego, bo jak chcę od razu usunąć, to za pierwszym razem wywala error jak nacisnę punkcik, a za drugim razem już usuwa ?:)

Piotr

nie można w trakcie przeglądania listy od razu usunąć elementu? Trzeba go zapamiętywać ?

Piotr

Dzięki :)

Sławek Ludwiczak

Usuwanie elementu w trakcie iterowania po kolekcji spowoduje błąd. Rozwiązaniem jest właśnie zapamiętanie referencji do obiektu do usunięcia lub druga możliwość skorzystanie z iteratora i skorzystanie z jego metody remove()