# String zentrieren



## JavaGamer (3. Feb 2015)

Hallo,

ich habe mir mal ein paar Buttons programmiert (ohne irgendwelche Button-Klassen von Java zu verwenden). Jetzt habe ich aber eine Frage, wie bekomme ich den Text des Buttons zentriert?

Momentan sieht mein Code dazu so aus:

```
SEGraphics.getGraphics().drawString(text, getButton().x + (getButton().width / text.length() * 2) + 25, getButton().y + (getButton().height / 2) + 5);
```

Problem hierbei ist nur, manche Texte werden nicht zentriert dargestellt, sondern etwas versetzt währrend andere wiederum zentriert dargestellt werden. Zudem verwende ich wie man schon sehen kann, keine genauen Koordinaten, sondern nur Variablen, die je nach Button anders sind (teils x= -300, ein anderes mal x= +60 usw. [bei y natürlich genauso, wie bei width ung height])

Ich hoffe hierbei kann mir jm. helfen!
JavaGamer


----------



## Gucky (3. Feb 2015)

Auf dieser Seite wird erklärt, wie man die Breite eines Strings herausbekommt.
Damit müsstest du den Rest selber schaffen. Falls nicht bin ich da


----------



## Thallius (3. Feb 2015)

text.length() gibt Dir nicht die Breite des Textes in Pixeln zurück sondern die Anzahl der Zeichen 

Gruß

Claus


----------



## JavaGamer (3. Feb 2015)

Gucky hat gesagt.:


> Auf dieser Seite wird erklärt, wie man die Breite eines Strings herausbekommt.
> Damit müsstest du den Rest selber schaffen. Falls nicht bin ich da



Ok, ich habe es jetzt mal damit versucht, aber es klappt immer noch nicht. Der längere Text passt mittig, aber genauso wie der kürzere Text nicht von der Höhe und beim kürzeren Text stimmt die Position auf der x-Achse nicht.


```
private static Font font;
	private static FontRenderContext context;
	
	public Strings(Graphics2D g2d )
	{
		font = g2d.getFont();
		context = g2d.getFontRenderContext();
	}

	private static Rectangle2D getBounds(String message)
	{
		return font.getStringBounds(message, context);
	}
	
	public static double getWidth(String message)
	{
		Rectangle2D bounds = getBounds(message);
		return bounds.getWidth();
	}
	
	public static double getHeight(String message)
	{
		Rectangle2D bounds = getBounds(message);
		return bounds.getHeight();
	}
```


```
graphics.drawString(message, (int)Strings.getWidth(message) + x, (int)Strings.getHeight(message) + y);
```


----------



## Java20134 (3. Feb 2015)

Brauchst du den Text den unbedingt so oder kannst du das auch in ein JLabel legen. Dann müsstest du folgendes schreiben, um den Text zu zentrieren:

```
JLabel Label = new JLabel("Text", SwingConstants.CENTER);
```


----------



## JavaGamer (3. Feb 2015)

Also JLabel geht nicht, das sollte schon wohl so sein. Hat wohl seine Gründe warum ich kein JLabel verwende.


----------



## Java20134 (3. Feb 2015)

Als Experiment könnte man mal folgendes probieren. Setze den Text mal so grafisch mit Zahlen, dass du sagst, dass der Text zentral ist. Und dann guckst du dir die Werte an und kannst ein wenig gucken, was dieses ergeben würde. Also x + den anderen Wert den du dadurch herausbekommen könntest.


----------



## JavaGamer (3. Feb 2015)

Java20134 hat gesagt.:


> Als Experiment könnte man mal folgendes probieren. Setze den Text mal so grafisch mit Zahlen, dass du sagst, dass der Text zentral ist. Und dann guckst du dir die Werte an und kannst ein wenig gucken, was dieses ergeben würde. Also x + den anderen Wert den du dadurch herausbekommen könntest.



Problem ist ja, wäre es nur ein fester String wäre es ja kein Problem, da mit einem String habe ich es schon hinbekommen, diesen zu zentrieren, nur ich kenne halt die String-Länge net, weshalb ich scheitere wenn ich String anderer Länge verwende. Also der "End-Anwender" gibt den String ein, der angezeigt werden soll und dementsprechen weiß ich nie wie lang oder kurz dieser ist.


----------



## Gucky (3. Feb 2015)

Hast du die Schriftgröße? Die wird doch in Punkt angegeben. Vielleicht kann man darpber etwas machen.


----------



## Flown (3. Feb 2015)

Warum so kompliziert hier mal ein kleines Programm:


```
import java.awt.Font;
import java.awt.Graphics;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Test {
  
  public static void main(String... args) {
    JFrame frame = new JFrame("Test");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setLocationByPlatform(true);
    frame.setSize(600, 400);
    
    frame.add(new MyComponent("This is a much larger test than the minimum size"));
    
    frame.setVisible(true);
  }
  
  static class MyComponent extends JComponent {
    
    private String str;
    
    public MyComponent(String str) {
      this.str = str;
    }
    
    @Override
    protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      g.setFont(Font.getFont(Font.MONOSPACED));
      int width = SwingUtilities.computeStringWidth(g.getFontMetrics(), str);
      int startString = (getX() + getWidth() >> 1) - (width >> 1);
      g.drawString(str, startString, getY() + getHeight() >> 1);
    }
  }
}
```


----------



## Thallius (3. Feb 2015)

Ich weis nicht wo jetzt das Problem ist.

Die Breite eines Strings in Pixeln bekommst Du so:


```
Graphics g=this.getGraphics();
Font font=new Font(fontName,Font.BOLD, 12);
g.setFont(font);
FontMetrics metrics= g.getFontMetrics();
metrics.stringWidth(text);
```

alles andere ist einfachste Mathematik

Gruß

Claus


----------



## JavaGamer (3. Feb 2015)

Thallius hat gesagt.:


> Ich weis nicht wo jetzt das Problem ist.
> 
> Die Breite eines Strings in Pixeln bekommst Du so:
> 
> ...



Wo das Problem ist? Hier:
Das kleine blaue Quadrat im roten unteren Button einfach ignorieren.

Und hier jetzt mein Code:

```
graphics.setColor(color);
		Font font = new Font("Comic Sans", Font.BOLD, 14);
		graphics.setFont(font);
		FontMetrics metrics = graphics.getFontMetrics();
		
		graphics.drawString(message, (int)metrics.stringWidth(message) + x, (int)Strings.getHeight(message) + y);
```

Die Klasse Strings werde ich jetzt hier nicht nocheinmal posten, da diese bereits ein paar Kommentare weiter oben zu finden ist.
Zudem handelt es sich bei dem Programm um eine Game-Engine (wie OpenGL, nur halt self-created und dementsprechend sehr einfach gehalten, wobei diese im Grunde auch auf diesen Tutorials basiert, da ich dadurch erst gelernt habe wie man so etwas programmiert.).


----------



## Harry Kane (4. Feb 2015)

Was x und y aus deinem obigen Post sind, brauchen wir wohl nicht zu wissen, hm? Dabei scheint die ganze Fehlpositionierung einzig an diesen Variablen zu hängen.
Und bei deiner Strings-Klasse fällt mir auf, daß sie einen Konstruktor hat, aber nur statische Methoden. Das heisst, sobald irgendwo eine Strings-Instanz erzeugt wird, ändern sich für alle Aufrufe der statischen Methoden möglicherweise die Parameter. Nicht notwendigerweise ein Fehler, aber ganz übles Design. Schlauerweise hast du uns nicht gezeigt, wann und wie eine Strings-Instanz erzeugt wird.


----------



## JavaGamer (4. Feb 2015)

Harry Kane hat gesagt.:


> Was x und y aus deinem obigen Post sind, brauchen wir wohl nicht zu wissen, hm? Dabei scheint die ganze Fehlpositionierung einzig an diesen Variablen zu hängen.
> Und bei deiner Strings-Klasse fällt mir auf, daß sie einen Konstruktor hat, aber nur statische Methoden. Das heisst, sobald irgendwo eine Strings-Instanz erzeugt wird, ändern sich für alle Aufrufe der statischen Methoden möglicherweise die Parameter. Nicht notwendigerweise ein Fehler, aber ganz übles Design. Schlauerweise hast du uns nicht gezeigt, wann und wie eine Strings-Instanz erzeugt wird.



Die x- und y-Variablen sind die Startpunkte des Rechtecks. Also das Rechteck beginnt an Position XY und von da aus gehts dann runter und nach links. Wie viele Pixel nach unten bestimmt die Variable height und wie weit anch links die Variable width. Zudem returned die getButton() Methode das Objekt rect, das im Konstruktur des Buttons initialisiert wird ([class itself:] Rectangle rect;   [konstruktor:] this.rect = new Rectangle(x, y, width, height)

Beim rendern (läuft über die render()-Methode des Threads) wird einfach die Methode drawString von SEGraphics aufgerufen, die static ist, aber auch wenn ich den Inhalt dieser Methode direkt in die nicht static Methode vom Button reinkopiere, das Ergebnis bleibt das selbe. Zu erwähnen wäre vielleicht noch, dass nun alle Methoden der Klasse Strings nicht länger static sind, genauso wie alle Variablen, weshalb vorher noch ein neues Objekt der Klasse String erzeugt wird.


```
SEGraphics.drawString(text, getButton().x, getButton().y, Color.BLUE);
```

In der Methode drawString von SEGraphics wird nun dieser Code ausgeführt:


```
public static void drawString(String message, int x, int y, Color color)
	{
		Strings utils = new Strings();
		
		graphics.setColor(color);
		//Font font = new Font("Comic Sans", Font.BOLD, 14);
		//graphics.setFont(font);
		//FontMetrics metrics = graphics.getFontMetrics();
		
		graphics.drawString(message, (int)utils.getHeight(message) + x, (int)utils.getHeight(message) + y);
	}
```

Dann hier nochmal der Inhalt der Klasse Strings:


```
private Font font;
	private FontRenderContext context;
	
	public Strings() 
	{
		Graphics2D g2d = (Graphics2D) SEGraphics.getGraphics();
		
		font = g2d.getFont();
		context = g2d.getFontRenderContext();
	}
	
	private Rectangle2D getBounds(String message)
	{
		return font.getStringBounds(message, context);
	}
	
	public double getWidth(String message)
	{
		Rectangle2D bounds = getBounds(message);
		return bounds.getWidth();
	}
	
	public double getHeight(String message)
	{
		Rectangle2D bounds = getBounds(message);
		return bounds.getHeight();
	}
```

Zudem möchte ich noch anmerken, dass nach diesen ganzen aktuellen Änderungen nun beide Texte an der selben Stelle auf der x-Achse beginnen, obwohl diese unterschiedlich lang sind und zudem beginnen diese auch ein paar Pixel nach Anfang der Buttons und nicht in der Mitte.

Ich hoffe mal, dass dies weiter hilft.


----------



## Thallius (4. Feb 2015)

Ganz ehrlich das ist ein pures Code-Chaos was Du da fabrizierst. Und das soll eine GFX Engine werden? Eventuell solltest Du erstmal ein Konzept ausarbeiten wie Du das sauber programmierst.

In drawString benutzt du plötzlich ein graphics das es vorher gar nicht gibt. Woher kommt das?

Wenn ich einen String in einem Rechteck zentriert zeichnen will, dann braucht die Methode die das macht mindestens den Text und das Rechteck. Du giebst ihm x und y und holst dir width und height aus irgendeiner util? Und wieso sollte etwas zentriert sein wenn ich auf eine Width ein X Aufaddiere?


```
public static void drawString(String message, int x, int y, int width, int height, Color color)
{
        Graphics2D g2d = (Graphics2D) SEGraphics.getGraphics();

        Font font = new Font("Comic Sans", Font.BOLD, 14);
	g2d.setFont(font);
	FontMetrics metrics = g2d.getFontMetrics();
		
	g2d.drawString(message, x+(width-metrics.stringWidth(message)/2, y+(height-metrics.stringHeight(message)/2);
}
```

Es macht doch keinen Sinn in einer Mathode Variablen aus allen möglichen Quellen zu benutzen. Das hat doch mit OOP nichts zu tun. Kapselung ist das magische Wort. Und ich sehe eigentlich auch gar nicht ein warum drawString oder sogar SEGraphics static sein müssen. Warum soll ich nicht mit mehreren SEGRaphics arbeiten dürfen?

Claus


----------



## JavaGamer (4. Feb 2015)

Was meinst du mit GFX Engine?

Die Variable graphics kommt aus der Klasse SEGraphics selbst und wird beim starten der Engine automatisch initialisiert.
Die Strings-Klasse habe ich oben gepostet, also ist net irgendeine Klasse. Und wo du's jetzt sagst, das mit XY zur width und height vom String hinzufügen, ergibt auch keinen Sinn, aber wie man's jetzt anders machen muss, weiß ich auch nicht, auf jeden Fall wird die XY-Position benötigt.

Also wenn du meinst, dass das nichts mit OOP zu tun hat, dann möchtest du garnicht erst meinen Code von normalen Java-Anwendungen sehen und würdest wahrscheinlich von Stuhl fallen wenn du den Code meines Freundes sehen würdest. xD Wobei meiner Meinung nach ist das sehr sauber programmierter Code etc.

Also das SEGraphics static ist, auf die Idee bin ich gekommen, als ich mir mal ein wenig den OpenGL-Code angeschaut habe. Zudem werden alle Variablen aus SEGraphics vorher im Hauptthread der Engine initialisiert, noch bevor irgendeine Methode des Games zugriff drauf hat und ich finde es irgendwie praktischer wenn die Methoden dieser Klasse static sind.


----------



## Harry Kane (4. Feb 2015)

JavaGamer hat gesagt.:


> Die Strings-Klasse habe ich oben gepostet, also ist net irgendeine Klasse. Und wo du's jetzt sagst, das mit XY zur width und height vom String hinzufügen, ergibt auch keinen Sinn, aber wie man's jetzt anders machen muss, weiß ich auch nicht, auf jeden Fall wird die XY-Position benötigt.


Lies den Code von Flown ein paar Beiträge weiter oben. Da stehen die benötigten Zeilen drin.


----------



## JavaGamer (4. Feb 2015)

Also mit den Zeilen:

```
int swidth = SwingUtilities.computeStringWidth(graphics.getFontMetrics(), message);
		 int startString = (x + width >> 1) - (swidth >> 1);
		 graphics.drawString(message, startString, y + height >> 1);
```

von Flown (with nach swidth umbenannt und statt getWidth und getHeight die Parameter width und height in die Methode eingefügt) klappt es fast, die x-Koordinaten sind noch ein Stückchen zu weit links und die y-Koordinaten vom String noch zu weit oben. 
Wobei das Addieren von zusätzlichen Pixeln zur y-Varialbe nur wenig erfolg hat. Damit bekomme ich den ersten String zwischen Button 1 und 2 und der zweite String wird auf Button 2 plaziert.


----------



## JavaGamer (7. Feb 2015)

Ok, ich habe es jetzt einigermaßen hinbekommen:


```
int swidth = SwingUtilities.computeStringWidth(graphics.getFontMetrics(), message);
int startString = (x + width >> 1) - (swidth >> 1);
graphics.drawString(message, startString + 10, y + height - height / 3);
```

Das einzige Problem hierbei ist nur, sollte jemand auf die Idee kommen die Größe der Button's zu ändern, so funktioniert dies nicht mehr... Und ich denke mal, für viele Zwecke reicht die jetztige Button-Größe nicht aus, z.B. bei größeren Game-Fenstern (wie z.B. Full-Screen).

Hätte irgendeiner eine Idee, wie ich dies unabhängig von der Größe machen kann?


----------



## Gucky (7. Feb 2015)

Bei jeder Größenänderung mit den neuen Werten neu berechnen und neu positionieren.


----------



## JavaGamer (7. Feb 2015)

Gucky hat gesagt.:


> Bei jeder Größenänderung mit den neuen Werten neu berechnen und neu positionieren.



Das ganze läuft ja bei Programmstart nur einmal durch, mit den Größen des Buttons, also mit Größenänderung würde sowieso einen Neustart des Programms bedeuten.
Zudem wird es ja für jeden Button einzelnt und ständig neu berechnet mit den entsprechenden Größenwerten des Buttons, allerdings funktioniert es trotzdem nicht... Je nachdem welche Größen man verwendet funktioniert es oder auch nicht. Also ob es funktioniert mit dem zentrieren des Strings hängt von der Größe des Buttons ab und dies liegt glaube ich daran, dass ich zu dem "Mittelwert" der berechnet wird noch Werte dazu addiere, aber ohne diese funktioniert es garnicht.


----------



## Gucky (7. Feb 2015)

Dann zeichnest du den Button in paintComponent, was bei jeder Größenänderung aufgerufen wird.


----------



## JavaGamer (8. Feb 2015)

Gucky hat gesagt.:


> Dann zeichnest du den Button in paintComponent, was bei jeder Größenänderung aufgerufen wird.


Du verstehst nicht was ich meine... Die *Größe vom Button kann nicht geändert werden*, es kann nur ein neuer Button mit anderer größe erstellt werden und da ist das Problem.

Hat Button 1 ein Länge die z.B. kleiner ist als 209, dann ist der String nicht zentriert. Hat Button 1 eine Länge von 209 oder länger, dann ist der String zentriert. Ist die Höhe von Button 1 eine andere als 30, dann ist der String nicht zentriert, ist die Höhe exakt 30, dann ist der String zentriert.

Kurzfassung:


```
boolean correct = false;

if(width >= 209)
    correct = true;
else
    correct = false;

if(heigth == 30)
    correct = true;
else
    correct = false;
```

Ich denke mal, dass reicht um das Problem zu beschreiben, so dass es jeder versteht.


----------



## Ruzmanz (8. Feb 2015)

> Dann zeichnest du den Button in paintComponent, was bei jeder Größenänderung aufgerufen wird.



Seine Funktion ist total falsch, aus diesem Grund bringt das überhaupt nichts.

x+(buttonBreite-textBreite)/2
y+(buttonHöhe-textHöhe)/2

Dein Button ist 30 Pixel hoch und dein Text hat eine Höhe von 10 Pixel. Also müsste man den Text bei y+10 positionieren ...


```
int swidth = SwingUtilities.computeStringWidth(graphics.getFontMetrics(), message);
    int startString = (x + width >> 1) - (swidth >> 1);
    graphics.drawString(message, startString + 10, y + height - height / 3);
```

hoehe - hoehe/3 = 30 - 30/3 = 20 :bahnhof:


----------



## Thallius (8. Feb 2015)

Ruzmanz hat gesagt.:


> Seine Funktion ist total falsch, aus diesem Grund bringt das überhaupt nichts.
> 
> x+(buttonBreite-textBreite)/2
> y+(buttonHöhe-textHöhe)/2



Nicht das ich diese Lösung nicht bereits in #15 geschrieben hätte 

Aber der TO scheint sich hier fürchterlich zu übernehmen mit seinem Projekt. Von daher ist es mühsam hier zu helfen.

Gruß

Claus


----------



## Ruzmanz (8. Feb 2015)

Ich hatte noch überlegt darauf zu verweisen ... Hätte sich nicht gelohnt, weil sein buttonHeight und buttonWidth nicht die richtigen Werte beinhalten. Somit erziehlt er selbst mit deiner korrekten Methode nicht die richtigen Ergebnisse ...


----------



## JavaGamer (9. Feb 2015)

Thallius hat gesagt.:


> Nicht das ich diese Lösung nicht bereits in #15 geschrieben hätte
> 
> Aber der TO scheint sich hier fürchterlich zu übernehmen mit seinem Projekt. Von daher ist es mühsam hier zu helfen.
> 
> ...


Was bedeutet eigentlich TO?
Und die Funktion aus deinem Kommentar bei #15 bringt Fehler:


```
public static void drawString(String message, int x, int y, int width, int height, Color color)
	{
		Graphics2D g2d = (Graphics2D) SEGraphics.getGraphics();
	 
		Font font = new Font("Comic Sans", Font.BOLD, 14);
		g2d.setFont(font);
		FontMetrics metrics = g2d.getFontMetrics();
	 
		g2d.drawString(message, x+(width-metrics.stringWidth(message)/2, y+(height-metrics.stringHeight(message)/2);
	}
```



Nach der ersten 2: Syntax error, insert ")" to complete Expression
The method stringHeight(String) is undefined for the type FontMetrics
 Nach der zweiten 2 ein weiterer Fehler, wobei Eclipse sich weigert mir zu sagen was dies für ein Fehler ist, somit gehe ich einfach mal davon aus, dass es der selbe Fehler wie der erste ist.

Und übernommen? Vielleicht ein wenig...ich weiß zwar nicht wieso, aber ich habe schon mal ein paar 2D-Games erstellt und bin bisher immer am Menü gescheitert oder an den Buttons.
Man wird ja sehen was aus dem Projekt wird, das hier ist ja erst der Anfang...Lese mich ja momentan in Dingen wie FileFormats usw. ein, für 3D-Modell-Imports... xD Bin ja mal gespannt wie viele Probleme noch kommen werden, sobald dieses hier mal endlich gefixed ist. 



Ruzmanz hat gesagt.:


> Seine Funktion ist total falsch, aus diesem Grund bringt das überhaupt nichts.
> 
> x+(buttonBreite-textBreite)/2
> y+(buttonHöhe-textHöhe)/2
> ...



Dies bringt nur nichts, wenn man nun die Größe des Textes ändert (was möglich sein soll).


----------



## Gucky (9. Feb 2015)

Dann musst du die zehn durch diese Größe ersetzen. Entweder per getter oder per Variable.


----------



## Ruzmanz (9. Feb 2015)

Okey, noch einmal ... die Formel lautet:

x+(buttonBreite-textBreite)/2
y+(buttonHöhe-textHöhe)/2

Es gibt *keinen* anderen Weg, um den Text zu zentrieren. Alle korrekten Lösungswege führen auf diese Formel zurück. Sofern man keinen Plan hat und das Ergebnis auch nicht korrekt aussieht, dann schaut man sich die Formal mit festen Werten an! Vielleicht (hier auf jeden Fall) stimmen die Eingabewerte nicht, somit kann nie das richtige Ergebnis berechnet werden.

System.out.println("Der Button sollte 209 Pixel breit sein: " + buttonBreite);
System.out.println("Der Button sollte 30 Pixel hoch sein: " + buttonHöhe);
System.out.println("Der Text sollte ??? Pixel breit sein:" + textBreite);
System.out.println("Der Text sollte 10 Pixel hoch sein:" + textHöhe);

textX = buttonX+(buttonBreite-textBreite)/2;
textY = y+(buttonHöhe-textHöhe)/2;



> The method stringHeight(String) is undefined for the type FontMetrics



Das ist ein Fehler, den man mit 30 Sekunden googeln korrigieren kann. Suche mal in FontMetrics (Java Platform SE 7 ) nach "height".


----------



## JavaGamer (10. Feb 2015)

Ok. Habe es jetzt denke ich mal hinbekommen...funktioniert zumindest mit jedem Wert den ich getestet habe.


```
g2d.drawString(message, x + (width - metrics.stringWidth(message)) / 2, 
		y + (height - (int) metrics.getStringBounds(message, g2d).getCenterY()) / 2);
```

Ich weiß jetzt auch was ich falsch gemacht hatte, dass es die male vorher net ging, da es ja die Methode stringHeight net gab habe ich einfach getHeight genommen.... (was die Standard-Höhe des Strings mit der bestimmten Font zurückgibt)

Danke nochmal für die Hilfe.


----------

