# Special design of Swing components



## meister-g (8. Jan 2010)

Hi!

I want do realize this in Swing:


 

- background: as you see there is a gradient and a image on the right side. the area should be resizable (add space between the image and the tabs).
- the tabs have a special design with graphics/gradients
- the background of the tabs is shared with the rest of the header
- the buttons also have gradients

the picture shows the header of the application. The single tabs contain panels below with standard components. The standard components remain like the look&feel or have - beside the descirbed buttons - only minor changes... like the colors in a table. But no more gradients or graphics.



 

so what is the best way to get this done in swing?

i'm not sure what to do - for example: do i use tabs or is it better to solve the whole thing with simple swing components (maybe containing only images) and "simulate" tabs. if i use images is it maybe the easiest way to create _everything_ with the images (also the text on it)? HTML may be also a good way*, but on which layer?! Design the single elements with html or a whole panel.
Or is it by far the best way do do it quick and dirty and see the whole header as one image (or several images in the different states), set it as an background of a panel and react pixelwise on mouseovers and clicks?!
I think this may be a general discussion of using images/html or altering the look of components with drawing etc. But if i draw gradients by myself: how do i realize tabs not only with with a gradient but also a different border, small yellow graphic on top etc?! Is it makeable (and not too complicated) by drawing manually, changing border and stuff?! Or use another way as described above or a completely different one!? If the tabs can be realized... how is it possible to connect the background of the tabs with the header above with one shared gradient?

* I think that's where the whole thing comes from: the design is from a company normally doing web design. But this is for an application... bad for me...

I did a lot of Java GUI programming before, layouting and stuff - but never had a strict design with gradients and images before. So i'm not sure how to begin the design and layout of the application. I appreciate any hint.


----------



## SlaterB (8. Jan 2010)

die 'Share this image on Twitter'-Links habe ich entfernt, oder haben die einen bestimmten Zweck?
alle deine bisherigen Postings (allerdings 2008) waren auf deutsch, wäre das nicht ein Hinweis wert


----------



## meister-g (8. Jan 2010)

geilomat... ich denke zuviel auf Englisch und habe hier eben schon lange nicht mehr gepostet.
Der mündige java Programmierer versteht das Englisch aber wohl... er darf aber auf Deutsch antworten 

Sorry, für die Twitter-Links, hab ich auch bemerkt, aber dann waren sie schon weg... gab auch mal ne Zeit da hat Imageshack nicht so nen Mist drangehängt.


----------



## U2nt (8. Jan 2010)

Mach einfach ein eigenes Look and Feel, steht hier ein wenig dazu (Englisch naja bist ja fast )

Custom Look & Feel


----------



## meister-g (11. Jan 2010)

Ein eigenes L&F ist wohl ein bisschen too much.

Bin schon etwas weiter: Ich zeichne den Header durch einen Gradienten und rechts kommt ein transparentes Bild aufs Panel.
Sieht soweit gut aus.

Problematisch sind aber vor allem die Reiter. Da die Reiter im Programm sowieso nicht ausgewählt werden können (werden durch "vor" und "zurück" angesteuert) denke ich, dass man die Reiter am besten über JLabels/JButtons/JToggleButtons realisiert.
Hier komm ich aber mit dem Layout nicht ganz zurecht, va. wegen der Transparenz.

Wie ordne ich zwei Zeilen mit Komponenten (siehe Bild) - einmal oben mit Rand und einmal unten ohne Rand - an und behalte dabei die Transparenz?

Hat da jemand Ideen wie ich das mit welchem Layout-Manager bewerkstellige?!
MigLayout ist ok -bin ich noch neu, wollte ich aber in diesem Projekt das erste mal einsetzen.

Zur Transparenz noch:
Da es wohl schwierig ist, die ganzen Controls direkt im header anzuordnen, wollte ich sie in ein JPanel packen. Problem dabei: mit setOpaque fällt entweder die Transparenz weg oder die innenliegenden Controls werden nicht dargestellt.
Das hier funktioniert, ist aber etwas ekelig:

[Java]
public class HeaderTabPanel extends JPanel {

	private JButton bu1;
	private JButton bu2;
	private JButton bu3;

	public HeaderTabPanel() {
		bu1 = new JButton("eins");
		bu2 = new JButton("zwei");
		bu3 = new JButton("drei");
		add(bu1);
		add(bu2);
		add(bu3);
		this.setOpaque(false);
		this.setLayout(new MigLayout("debug"));
	}

	public void repaint() {
		if (bu1 != null)
		  bu1.repaint();
		if (bu2 != null)
		  bu2.repaint();
		if (bu3 != null)
		  bu3.repaint();
	}
}[/Java]

Das ist keine gute Lösung, oder?!
Wie geht es besser?

Außerdem habe ich damit leider Abstände der Buttons zwischeneinander und zum unteren Rand des Headers. Wie werde ich die los?

Oder kann man die Controls ((Pseudo)-Tabs,Buttons und Labels) links im Header besser ohne/mit Zwischenpanel realisieren?


Ach so: Resizen soll nur in X-Richtung möglich sein; Controls Anker links, Bild rechts oben Anker rechts


----------



## meister-g (12. Jan 2010)

ob es schön ist weiss ich nicht, jedenfalls habe ich das Layout durch verschiedene LayoutManager erstellen können.

Das komplexe Problem ist jetzt auf ein paar kleine zusammengeschrumpft bzw habe ich ein komplett neues, möchte aber keinen neuen Thread deshalb aufmachen.

Folgendes:
Meine Tabs habe ich durch JPanel realisiert und zeichne sie in paint() komplett selbst.
Funktioniert wunderbar, einziges Problem die Größe der Labels und damit die Größe der Komponente.

Im Konstruktor liefert getGraphics leider ein null-Graphics Objekt.
Wenn ich mit FontMetrics in paint() den Text zentriert darstelle und Minimum,Preferred- und Maximum-Size auf Basis des Texts setze funktioniert das, allerdings sind die Komponenten initial winzig.
Wie komme ich im Konstruktor der von JPanel abgeleiteten Klassen an ein Graphics-Objekt?! Nicht nur, dass es nicht funktioniert, es ist ja auch unschön und unnötig in paint() die Größe jedesmal zu setzen. Oder wie kann ich sonst ohne Graphics Objekt die Textgröße ermitteln?

Oder: Wie kann ich eine andere Klasse (JLabel/JButton) verwenden um dort den automatisch zentrierten Text und die automatisch gesetzte Größe der Komponente nutzen, den Rest der Komponente allerdings komplett neu zeichnen?!


Selbiges gilt auch für die Buttons (siehe Bild erstes Posting). Das Layout bekomme ich mittels Gradienten etc. selbst gezeichnet; aber von welcher Klasse leite ich am besten ab? Generell bzw damit die Komponentengröße automatisch auf basis des Texts/Labels gesetzt wird.

Hinweis/Ansatz noch: Zeichne ich in meiner Komponente das Label nicht selbst, sondern adde ich ein JLabel, setze es opaque und rufe dessen repaint aus paint() auf, funktioniert das - allerdings ist der Schrifthinterrund des JLabels sichtbar. Setze ich opaque auf false ist alles unsichtbar. Den Hintergrund anzugleichen wäre möglich, dann habe ich aber verloren, wenn ich mit einem Gradienten arbeite. Gibts in die Richtung sonst ne Lösung?!


----------



## Ebenius (12. Jan 2010)

meister-g hat gesagt.:


> Hinweis/Ansatz noch: Zeichne ich in meiner Komponente das Label nicht selbst, sondern adde ich ein JLabel, setze es opaque und rufe dessen repaint aus paint() auf, funktioniert das - allerdings ist der Schrifthinterrund des JLabels sichtbar. Setze ich opaque auf false ist alles unsichtbar. Den Hintergrund anzugleichen wäre möglich, dann habe ich aber verloren, wenn ich mit einem Gradienten arbeite. Gibts in die Richtung sonst ne Lösung?!


Genau deshalb überschreibt man [c]paint()[/c] nicht. Überschreibe einfach [c]paintComponent()[/c], setze ein Label drauf und kümmere Dich nicht darum, wie gezeichnet wird. Das tut die [c]paint()[/c]-Methode selbst richtig.

Folgende allgemeine Anmerkungen:


In Swing nicht [c]paint()[/c] sondern [c]paintComponent[/c] überschreiben
Eine Komponente kann kein Graphics haben, so lange sie nicht darstellbar ist ([c]isDisplayable()[/c])
Eine Komponente kann nicht darstellbar sein, bevor sie fertig erzeugt ist; also kein Graphics im Konstruktor
Die eigene Größe sollte *niemals* innerhalb irgendwelcher [c]paintXYZ()[/c]-Methoden verändert werden

Wenn Du unbedingt die Strings selber darstellen musst, dann überschreib [c]getPreferredSize()[/c] in etwa so: 
	
	
	
	





```
Override
public Dimension getPreferredSize() {
  if (!isPreferredSizeSet()) {
    final FontMetrics fm = getFontMetrics(getFont());
    return new Dimension(SwingUtilities.computeStringWidth(fm,
          THE_STRING), fm.getHeight());
  }
  return super.getPreferredSize();
}
```
In [c]paintComponent()[/c] kannst Du per [c]SwingUtilities.layoutCompoundLabel(...)[/c] herausfinden, wie der String liegen muss.

HTH, Ebenius


----------



## meister-g (12. Jan 2010)

Danke, da waren hilfreiche Sachen dabei.

Vom Layout her funktioniert jetzt alles wunderbar... fehlen nur noch die Buttons, evtl habe ich da noch ne Frage, deshalb schließe ich noch nicht.


----------



## meister-g (14. Jan 2010)

klappt ja mittlerweile fast alles... wie erwartet habe ich aber noch eine kleine Frage zu den Buttons.
Und zwar gibt es ein Problem wenn verschiedene Panels mit CustomButtons existieren und dazwischen navigiert wird. Navigiere ich vor und zurück ist der CustomButton noch im "mouseOver-Status". Anders herum wenn ein neues Panel mit einem CustomButton eingeblendet wird, und der Mauszeiger ist unbewegt über dem Button, dann ist dieser nicht im "mouseOver-Status".
Sprich: warum wird bei ausbledenden (removen des super-Panels) bei der Komponente nicht ein MouseEvent ausgelößt bzw warum keiner beim adden/einblenden?
Wie kann ich das Problem lösen?

Hier mein CustomButton:
[Java]
import javax.swing.JLabel;
import javax.swing.AbstractButton;
import java.awt.event.*;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Graphics2D;
import javax.swing.border.EmptyBorder;

public class CustomButton extends AbstractButton implements MouseListener {

	private final static int BORDER_TOP_BOTTOM = 5;
	private final static int ARC_SIZE = 3;

	private JLabel label;
	private boolean mouseOver;
	private boolean mousePressed;

	public CustomButton(String text) {
		super();
		label = new JLabel(text);
		label.setFont(StyleConstants.FONT_NORMAL_BOLD);
		label.setForeground(StyleConstants.MAIN_BLUE);
		add(label);
		label.setBorder(new EmptyBorder(BORDER_TOP_BOTTOM,
										StyleConstants.BORDER,
										BORDER_TOP_BOTTOM,
										StyleConstants.BORDER));
		this.addMouseListener(this);
	}

	public void paintComponent(Graphics g) {

		Graphics2D g2 = (Graphics2D)g;

		Color colorTop = null;
		Color colorBottom = null;

		colorTop = StyleConstants.BUTTON_COLOR_2;
		colorBottom = StyleConstants.BUTTON_COLOR_2;

		if (mouseOver) {
			colorTop = StyleConstants.BUTTON_COLOR_2;
			colorBottom = StyleConstants.BUTTON_COLOR_1;
		}

		if (mousePressed) {
			colorTop = StyleConstants.BUTTON_COLOR_1;
			colorBottom = StyleConstants.BUTTON_COLOR_2;
		}

	    GradientPaint gradient = new GradientPaint(0, 0, colorTop,
	    										   0, getHeight(), colorBottom);
	    g2.setPaint(gradient);
	    g2.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, ARC_SIZE, ARC_SIZE);

	    g.setColor(StyleConstants.MAIN_BLUE);
	    g.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, ARC_SIZE, ARC_SIZE);

	}

	public void mouseClicked(MouseEvent me) {
		this.fireActionPerformed(new ActionEvent(this, 
				ActionEvent.ACTION_PERFORMED, "CustomButton \"" + label.getText() + "\" pressed."));
	}
	public void mouseEntered(MouseEvent me)  { mouseOver    = true;  repaint(); }
	public void mousePressed(MouseEvent me)  { mousePressed = true;  repaint(); }
	public void mouseReleased(MouseEvent me) { mousePressed = false; repaint(); }
	public void mouseExited(MouseEvent me)   { mouseOver    = false; repaint(); }

}
[/Java]


----------



## Ebenius (14. Jan 2010)

Dass kein Event kommt ist klar. MOUSE_ENTERED gibt's nunmal nur, wenn die Maus bewegt wird. Alles andere wäre Horror. So in etwa kannst Du abfragen, ob die Maus sich über der Komponente befindet, wenn diese hinzugefügt/entfernt wird: 
	
	
	
	





```
class SomeComponent extends JComponent {


  @Override
  public void addNotify() {
    super.addNotify();
    boolean mouseInComponent = getMousePosition() != null;
    updateMouseOverStatus(mouseInComponent);
  }

  @Override
  public void removeNotify() {
    boolean mouseInComponent = getMousePosition() != null;
    updateMouseOverStatus(mouseInComponent);
    super.removeNotify();
  }
}
```
Ebenius


----------



## meister-g (15. Jan 2010)

ok, das ist es schon.... fast!

problem: das erste mal, wenn ein panel mit einem solchen button zum ersten mal geadded wurde ist die position des buttons (warum auch immer) 0,0.
mag logisch erscheinen, weil man ein reihenfolgenproblem vermutet.
(beim zweiten mal adden ist die position des buttons richtig und der gewünschte effekt funktioniert.

gut, dachte ich mir, und bastel meinen eigenen notifier:
der controller, der die views verwaltet entfernt einen view, added den nächsten und ruft dann erst einen eigenen notify für die buttons auf diesem panel aus. gleiches resultat. beim ersten mal ist die position der buttons 0,0 und somit die mausposition null. auch wenn ich explizit auf dem view (container, der die buttons enthält) explizit invalidate() aufrufe und dann eben das notify für die buttons ausführe. ich verstehe das nicht. kann hier evtl einer helfen?!

zur erklärung:
es handelt sich um einzelne schritte. bei next informiert der view den controller. der entfernt einen view/schritt aus dem fenster und added einen neuen view.
die back/next buttons sind bestandteil der einzelnen schritte, weil zwischen den zwei buttons noch andere buttons je nach schritt eingefügt werden.
aktuell drückt man next und der neue next-button (im nächsten view an gleicher stelle) hat keinen mouseevent, wird eben nicht aktualisiert, weil die mouseposition (warum auch immer) null ist und so ist der button nicht im mouseover status. bei zurück->next (also nach dem zweiten adden) funktioniert das ganze.
eine ganz fiese lösung wäre jetzt erstmal alle (derzeit 5) views zu adden und zu removen, aber sowas muss nicht sein.


----------



## Ebenius (15. Jan 2010)

Du kannst statt [c]addNotify[/c] auch [c]validate[/c] (mit super-Aufruf) überschreiben. Dann sollte der Effekt weg sein.  [c]removeNotify[/c] sollte drin bleiben.

Hab jetzt nicht Deine ganze Ausführung gelesen; wenn ich falsch liege, frag einfach nochmal.

Happy Hacking! Ebenius


----------



## meister-g (18. Jan 2010)

validate dieser Komponente undauch des Containers in den überschriebenen Notify-Methoden sowie den eigenen Notify-Methoden habe ich ausprobiert und trotzdem wird danach immer als MousePosition null geliefert.


----------



## Ebenius (18. Jan 2010)

Okay, dann rate ich halt nicht mehr. Jetzt hab ich's probiert. 

Mach's einfach so: 
	
	
	
	





```
class SomeComponent extends JPanel {

  private final Border blackBorder =
        BorderFactory.createLineBorder(Color.BLACK, 5);
  private final Border redBorder =
        BorderFactory.createLineBorder(Color.RED, 5);

  /** Creates a new {@code MouseOverTest.SomeComponent}. */
  public SomeComponent() {
    setBorder(blackBorder);
    enableEvents(AWTEvent.MOUSE_EVENT_MASK);
    enableEvents(AWTEvent.COMPONENT_EVENT_MASK);
  }

  @Override
  protected void processComponentEvent(ComponentEvent e) {
    super.processComponentEvent(e);
    updateMouseOverStatus(getMousePosition() != null);
  }

  private void updateMouseOverStatus(boolean mouseInComponent) {
    setBorder(mouseInComponent ? redBorder : blackBorder);
  }

  @Override
  protected void processMouseEvent(MouseEvent e) {
    super.processMouseEvent(e);
    switch (e.getID()) {
    case MouseEvent.MOUSE_ENTERED:
      updateMouseOverStatus(true);
      break;
    case MouseEvent.MOUSE_EXITED:
      updateMouseOverStatus(false);
      break;
    }
  }

  @Override
  public void removeNotify() {
    updateMouseOverStatus(false);
    super.removeNotify();
  }
}
```
Ebenius


----------



## meister-g (18. Jan 2010)

edit: danke, jetzt geht es.
Musste in deinem letzten Listing noch die addNotify() hinzufügen


----------

