# Spielfeld Gitter - Einzelne Zellen verändern



## Chasor (26. Jan 2010)

Ich möchte ein Spielfeldgitter, wie es z.B. bei Sudoku ist, erstellen.
Es sollen ausschließlich Zeichenoperationen verwendet werden (Canvas, paint()-Methode).

Meine Idee ist es, ein JPanel/JLabel mit "Feldern" zu füllen.
Dafür erstelle ich ein 2-dimensionales Array Fields[][], das ich dem Panel hinzufüge.
Die Field-Klasse extends Canvas, soll also ein Gitter zeichnen, mit x viel Zeilen und y viel Zeilen.

Jemand Ideen dazu, wie genau sich das nun implementieren lässt?
Mein Problem ist es, das Gitter so zu "malen", dass jede einzelne Zelle durch das Speichern in einem Array noch veränderbar ist (z.B. bei Mausklick Farbe ändern). Dazu muss ich wissen, welche Zelle ich bei welcher Mausposition anspreche (x-y-Werte der Zelle mit denen der Maus vergleichen, aber wie bekomme ich x-y-Werte der Zelle?).


Was ich also im Klartext brauche:
Ein Gitterfeld, bei dem die Zellen bei Mausklick ihre Farben ändern.


----------



## Lexi (26. Jan 2010)

Du könntest dir eine Klasse Feld mit x/y Koordinate der oberen linken, unteren rechten Ecke, Farbe und die Koordinaten des Feldes im gesamten Gitter machen. Die tust du in eine Liste über die beim Zeichnen immer iteriert wird.
Die Klasse Feld sollte eine Methode wie 
	
	
	
	





```
boolean isOnField(Point mousePosition)
```
 haben die dann von einer Schleife im MouseListener, bzw ggf in einem eigenen WorkerThread aufgerufen wird. Gibt sie true zurück kannst du ja nach belieben weiter verfahren.


----------



## Michael... (27. Jan 2010)

Man könnte dem Container ein GridLayout verpassen und für jedes Feld eine JComponent oder entsprechende Derivate verwenden.
Ist vermutlich einfacher als alles in einer Komponente selbst zu zeichnen und man tut sich evtl. leichter mit dem MouseListener.


----------



## Chasor (27. Jan 2010)

Also, das Gitter habe ich mit paint()-Methode hinbekommen:

Gui.java

```
public Gui()
{
    // GUI-Layout
    Container c=getContentPane();
    panel = new JPanel(new GridLayout());
    panel.add(new Field(), BorderLayout.CENTER);
    c.add(panel, BorderLayout.CENTER);
    setTitle("Monogramme"); 
    setSize(550,600);
}
```


Field.java

```
public void paint(Graphics g)
	{
		for(i=0;i<=x;i++)
		{
			// Wert gibt Maße der Zellen an
			g.drawLine(wert*i, wert*x, wert*i , 0);
		}

		for(i=0;i<=y;i++)
		{
			g.drawLine(0 , wert*i, wert*y, wert*i);
		} 
		
		for(int k = 0; k < x; k++)
		{
			for(int l = 0; l < y; l++)
			{
				if(array[k][l] == 1)
				{
					g.setColor(Color.BLACK);
					g.fillRect(0+k*wert,0+l*wert,wert,wert);
				}
			}
		}
	}
```


Was ich nun möchte:
Sobald die Maus gedrückt wurde, fragt er x- und yPos der Maus ab. Diese Werte werden mit denen des Gitters verglichen. Das 2dimensionale Array bekommt an der Stelle, wo die Maus gedrückt wurde, eine 1 gespeichert, damit die Gitterzellen ausgefüllt werden.
Mein MouseListener scheint aber nicht zu funktionieren:


```
panel.addMouseListener(new MouseAdapter()
    {
    public void mousePressed(MouseEvent e)
    {
    	xPos = e.getX(); 
    	yPos = e.getY();
    	for(int i = 0; i < field.x; i++)
    	{
    		for(int j = 0; j < field.y; j++)
    		{
    			if(xPos<(i+1)*field.wert && xPos>i*field.wert && yPos<(j+1)*field.wert  && yPos>j*field.wert)
    			{
    				field.array[i][j] = 1;
    				field.repaint();
    			}
    			
    		}
    	}
    }
    });
```

Weiß jemand, wo der Fehler im MouseListener liegt?
Selbst wenn ich vor die for-next-Schleife bsp. einen einfachen Code wie 
	
	
	
	





```
System.out.println("TEST");
```
 schreibe, passiert nichts. Er scheint das Event nie zu aktivieren. Ich vermute, dass das "panel", in dem das Gitter gezeichnet wird ("new Field()"), nicht mit MouseEvents vereinbar ist. Stellt sich mir nur die Frage, wieso?


----------



## Michael... (27. Jan 2010)

Field scheint ja von irgendeiner Komponente zu erben --> den MouseListener an die Instanz von Field hängen und nicht an panel.

Ansonsten bei Swingkomponenten die paintComponent(...) und nicht die paint(...) überschreiben.


----------



## Michael... (27. Jan 2010)

Korrektur!!!
Sollte normalerweise funktionieren, ausser es irgendwie intern an Field ein MouseListener angehängt - der würde dann die Events abfangen.


----------



## Chasor (27. Jan 2010)

Field extends Canvas wegen den Zeichenmethoden.
field.addMouseListener(...) in der Gui-Klasse tut's nicht, was genau ist mit "an die Instanz hängen" gemeint?

/Edit:
Im Field ist kein seperater Listener enthalten . klappen tuts trotzdem nicht.


----------



## Michael... (27. Jan 2010)

Ich würde eher Field extends JComponent o.ä. verwenden Canvas ist eine AWT Komponente

Woher kommt überhaupt die Variable field in der mousePressed()?
Im Konstruktor von Gui fügst Du ja eine Instanz von Field() eine ohne die Referenz darauf zu in einer Variablen zu speichern.


----------



## Chasor (27. Jan 2010)

Michael... hat gesagt.:


> Im Konstruktor von Gui fügst Du ja eine Instanz von Field() eine ohne die Referenz darauf zu in einer Variablen zu speichern.


Wie meinen?

Ich poste einfach mal die Klassen vollständig, vllt. erkennt man dann mehr:

gui.java

```
package graphics;

import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.awt.Desktop;
import javax.swing.UIManager;
import operators.SaveListener;
 
@SuppressWarnings("serial")
public class Gui  extends JFrame
{  
	public static int xPos;
	public static int yPos;
	int state;
	public static String str = null; 
	Field field;
	JPanel panel;
	SaveListener save = new SaveListener();
	
	// Menueleiste
	JMenuBar menu=new JMenuBar(); 
	
	// Spielmenue
	JMenu jMenuGame=new JMenu("Spiel"); 
	JMenuItem mNew=new JMenuItem("Neu");  
	JMenuItem mSave=new JMenuItem("Spielstand Speichern");
	JMenuItem mLoad=new JMenuItem("Spiel laden");
	JMenuItem mSolve = new JMenuItem("Lösung anzeigen");
	JMenuItem mControl = new JMenuItem("Lösung kontrollieren");
	JMenuItem mExit=new JMenuItem("Beenden"); 
    
	// Ansichtmenue
	JMenu jMenuView=new JMenu("Ansicht"); 
	JMenuItem mIn=new JMenuItem("Zoom in"); 
	JMenuItem mOut=new JMenuItem("Zoom out");  
  
	// Hilfe-Menue
	JMenu jMenuHelp=new JMenu("Hilfe");  
	JMenuItem jMenuHelpText=new JMenuItem("Hilfetext");
	JMenuItem jMenuHelpAbout=new JMenuItem("Impressum");   
   
public Gui()
{ 
  	try
  	{ 
  		// Interface-Stil
  		UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); 
  	} 
    catch (Exception e)
    { 
    	e.printStackTrace();
    } 
    
    // GUI-Layout
    Container c=getContentPane();
    panel = new JPanel(new GridLayout());
    panel.add(new Field(), BorderLayout.CENTER);
    c.add(panel, BorderLayout.CENTER);
    setTitle("Monogramme"); 
    setSize(550,600);
    
    // "Spiel"
    jMenuGame.setMnemonic('S');// 
    jMenuGame.add(mNew);  
    jMenuGame.addSeparator(); 
    jMenuGame.add(mSave); 
    jMenuGame.add(mLoad);
    jMenuGame.add(mSolve); 
    jMenuGame.add(mControl); 
    jMenuGame.addSeparator(); 
    jMenuGame.add(mExit); 
    mNew.setFont(new Font("PLAIN", Font.BOLD, 14));
    
    // "Ansicht"
    jMenuView.setMnemonic('A'); // 
    jMenuView.add(mIn);     
    jMenuView.add(mOut);  
    // Funktionen mit bestimmten Tastenkombinationen erreichen
    mIn.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_MASK));
    mOut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_MASK)); 

    // "Hilfe"
    jMenuHelp.setMnemonic('H');//
    jMenuHelp.add(jMenuHelpAbout);  

    // Obere Reiter "Datei", "Bearbeiten", "Einstellungen", "Hilfe"
    menu.add(jMenuGame);     
    menu.add(jMenuView);     
    menu.add(jMenuHelp);     
    setJMenuBar(menu); 
    jMenuGame.setFont(new Font("PLAIN",Font.PLAIN,14)); 
    jMenuView.setFont(new Font("PLAIN",Font.PLAIN,14)); 
    jMenuHelp.setFont(new Font("PLAIN",Font.PLAIN,14));  
    jMenuGame.setBackground(Color.lightGray); 
    jMenuView.setBackground(Color.lightGray);  
    jMenuHelp.setBackground(Color.lightGray); 
    menu.setBackground(Color.lightGray); 

	// "Help"-Anzeige
    jMenuHelpAbout.addActionListener(new ActionListener()
    {  
    	public void actionPerformed(ActionEvent e)
    	{ 
    		JOptionPane.showMessageDialog(Gui.this, "TEXT", "Impressum",JOptionPane.INFORMATION_MESSAGE);
    	} 
	});
    
    // SaveListener
    mSave.addActionListener(new SaveListener());
    
    // MouseListener
    panel.addMouseListener(new MouseAdapter()
    {
    public void mousePressed(MouseEvent e)
    {
    	// Frage Mausposition ab
    	xPos = e.getX(); 
    	yPos = e.getY();
    	for(int i = 0; i < field.x; i++)
    	{
    		for(int j = 0; j < field.y; j++)
    		{
    			// Position der Maus wird bei Mausklick mit einer möglichen Zelle verglichen
    			// Die passende Zelle wird ausgewählt und der Inhalt auf 1 gesetzt, damit beim neu Zeichnen
    			// das Feld ausgefüllt wird.
    			if(xPos<(i+1)*field.wert && xPos>i*field.wert && yPos<(j+1)*field.wert  && yPos>j*field.wert)
    			{
    				field.array[i][j] = 1;
    				field.repaint();
    			}
    			
    		}
    	}
    }
    });
    }
    
// Methoden um Mausposition zu übergeben
public int getxPos()
{
	return xPos;
}
public int gety()
{
	return yPos;
}
}
```

Field.java

```
package graphics;

import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Field extends Canvas
{
	//int state = 0;
	int i;
	public int wert = 20;
	public int x = 20;
	public int y = 20;
	public int[][] array;

	// Array-Inhalte alle auf 0 setzen
	public Field()
	{
		array = new int[x][y];
		for(int m = 0; m < x; m++)
		{
			for(int n = 0; n < x; n++)
			{
				array[m][n] = 0;
			}
		}
	}

	public void paint(Graphics g)
	{
		// Gitter zeichnen
		for(i=0;i<=x;i++)
		{
			g.drawLine(wert*i, wert*x, wert*i , 0);
		}

		for(i=0;i<=y;i++)
		{
			g.drawLine(0 , wert*i, wert*y, wert*i);
		} 
		
		// Zellen ausfüllen, bei denen das Array den Inhalt "1" hat
		for(int k = 0; k < x; k++)
		{
			for(int l = 0; l < y; l++)
			{
				if(array[k][l] == 1)
				{
					g.setColor(Color.BLACK);
					g.fillRect(0+k*wert,0+l*wert,wert,wert);
				}
			}
		}
	}
	
	// Methoden zur Übergabe der Variablen
	public int getXF()
	{
		return x;
	}
	public int getYF()
	{
		return y;
	}
	public int getWert()
	{
		return wert;
	}
}
```


Bild vom Gittermodell:





Schön wäre auch, wenn ich dem Panel mit dem Field Abstände zum Rand geben könnte, dass oben links also ein Abstand ist.


----------



## Michael... (27. Jan 2010)

Keine AWT und Swing Komponenten mischen!
Also wie gesagt mache Field extends JComponent - da kommt Dein MouseEvent auch an -
und überschreibe die paintComponent(...) anstelle der paint(...)

Um dann die in der mousePressed() geworfene NullPointerException zu vermeiden, schreibe anstelle von:

```
panel.add(new Field(), BorderLayout.CENTER);
```
das hier:

```
panel.add(field = new Field(), BorderLayout.CENTER);
```


----------



## Chasor (27. Jan 2010)

Genial, es funktioniert. Danke vielmals.
Wie schauts mit Seitenabständen nach oben/links aus? Wie bekomm ich das hin, ohne dass ich x- und y-Koordinante von den draw-Methoden ändern muss?

Weiterhin hätte ich gern eine ZoomIn-Funktion, d.h. dass ich die Variable "wert" modifizieren kann.
Problem hierbei ist, dass drawLine keine double-Werte akzeptiert. Jemand einen Ratschlag?


----------



## Lexi (27. Jan 2010)

Das mit dem Abstand könntest du evtl mit einer empty Border lösen. Du packst alle Field Componenten auf ein Panel und gibst dem Panel dann per 
	
	
	
	





```
setBorder()
```
 eine emptyBorder.


----------



## Chasor (27. Jan 2010)

Ok, die EmptyBorders sind drin, hat jetzt einen schönen Abstand.
Irgendwelche Ideen, wie ich die Größe des Spielfeld (Bild oben), also die Größe der "Zellen", nicht die Anzahl, mit der Fenstergröße zusammen dynamisch skalieren kann?
Sprich, wenn ich das Fenster per Maus größer ziehe, dass das Spielfeld auch automatisch etwas rangezoomt wird. Trotz dieser Relation soll das Spielfeld aber auch manuell heran-/herausgezoomt werden können.

Ich könnte _wert_ (Zellen-Größe) abhängig von der Fenster-Width und -Height machen, aber dann kann ich es seperat per zoomIn/zoomOut nicht mehr skalieren.


----------



## Lexi (27. Jan 2010)

Gibt es sowas wie SizeChangedEvent , welches von einer JComponent versendet wird, sobald desses Breite oder Höhe verändert wurde ?

Wenn ja wäre das genau das richtige für dich.


----------



## eRaaaa (27. Jan 2010)

Lexi hat gesagt.:


> Gibt es sowas wie SizeChangedEvent , welches von einer JComponent versendet wird, sobald desses Breite oder Höhe verändert wurde ?



Es gibt den ComponentListener mit 

public void componentResized(ComponentEvent e){}


ob der hier passt weiß ich nicht, da ich mir nicht alles durchgelesen habe :bae:


----------



## Lexi (27. Jan 2010)

eRaaaa hat gesagt.:


> Es gibt den ComponentListener mit
> 
> public void componentResized(ComponentEvent e){}
> 
> ...



Genau das habe ich mir gerade aus der API rausgesucht, wollte es soeben hier posten 

@TO in der 
	
	
	
	





```
componentResized(...)
```
 kannst du jetzt die Breite und Höhe der Felder setzten und ein repaint aufrufen. Wenn du  allerdings versuchst von woanders ( z.B. durch die Zoomfunktion ) die größe der Felder zu verändern, sollte das td kein Problem sein


----------



## Chasor (27. Jan 2010)

Wenn ich nur die Komponente resize, werden die Variablen in der paint()-Methode ja garnicht verändert, dementsprechend wird in normaler Größe gezeichnet oder?
Kann ich vllt. meine "wert"-Variable abhängig von der Component-Größe machen, also bei Breite quasi panel.width() (grad kA, wie die Methode dafür aussieht) geteilt durch die Anzahl der Spalten ist dann die Zellenbreite?


----------



## Lexi (27. Jan 2010)

Ich denke es macht Sinn die Breite und Höhe eines Feldes ( und somit allen Feldern ) als Instanzvariable zu halten und diese je nach Anwendungsfall zu verändern.

- Fenster bzw Panel wird skaliert -> In deiner componentResized() werden Breite und Höhe der Felder in Abhängigkeit von der Gesamtgröße des Panels gesetzt.

- Gitternetz wird "gezoomt" -> Die Größe eines Feldes wird durch den ZoomAlgorithmus bestimmt.

In deiner paintComponent() werden die Felder jetzt einfach immer mit de Werten der Instanzvariablen gezeichnet.


----------



## Chasor (28. Jan 2010)

Wenn ich meinem Panel die Borders gebe, muss ich bei dem MouseListener die jeweiligen Randwerte subtrahieren. Gibt es eine Möglichkeit, dem Container Borders zu geben, dass der Panel quasi ohne Borders funktioniert, aber zentriert im Container mit etwas Abstand ist?
Ansonsten wären dynamische Border-Werte sinnvoll, die prozentual von der Panel-Größe abhängen, aber EmptyBorder kann hier nicht mit double-Variablen arbeiten. :/

Die Zoomfunktion über Menü habe ich nun hinbekommen. 
*Was ich brauche ist:*
Wenn die Zeichnung über das Panel hinausragt, sollen Scrollbars erscheinen, durch die man den Panel durchscrollen kann. Dafür müsste ich einen ScrollPane benutzen. Ich habe mein Zeichenfeld "Field" in ein JPanel gelegt, dieses JPanel wird in dem Container platziert.  Den Container in ScrollPane umzuschreiben tut es nicht und mein panel bekomme ich nicht umgeschrieben, da dann die ganzen Attribute nichtig werden (Borders etc.). Dann müsste ich nämlich dem Container die Borders verpassen, was nicht geht. Jemand Ideen, das zu lösen?

Die Zoomfunktion über Mausrad scheint auch irgendwie nicht zu wollen... deshab mal wieder ein wenig Code zum überprüfen, warum er die If-Anweisungen nie erreicht:

Über Menü-Buttons:

```
// ZoomIn
    mIn.addActionListener(new ActionListener()
    {  
    	public void actionPerformed(ActionEvent e)
    	{
    		zoom = zoom * 1.2;
    		field.repaint();
     	} 
    });
    // ZoomOut
    mOut.addActionListener(new ActionListener()
    {
    	public void actionPerformed(ActionEvent e)
    	{
    		zoom = zoom * 0.8;
    		field.repaint();
    	}
    });
```



Über Mausrad (funktioniert nicht):

```
// MouseListener
    panel.addMouseListener(new MouseAdapter()
    {
    public void mousePressed(MouseEvent e)
    {
    	...
    }
    public void mouseWheelMoved(MouseWheelEvent e)
    {
    	if(e.getWheelRotation() > 0)
    	{
    		zoom = zoom*0.8;
    		field.repaint();
    	}
    	else if(e.getWheelRotation() < 0)
    	{
    		zoom = zoom*1.2;
    		field.repaint();
    	}
    }
    });
```






Zu dem Zoom selbst. Hier möchte ich die Fenstergröße nach dem ZoomIn/ZoomOut mit dem gleichen Zoom-Modifikator vergrößern/verkleinern, damit der PaintPanel nicht über den Rand hinaus geht und man das Fenster manuell nachziehen muss.

Bilder, wie es derzeit aussieht:

(normal)




(zoomed in)




(zoomed out)


----------

