# JSlider - wie unterscheide ich mit Maus verändert und durch setValue verändert.



## osix (28. Jul 2009)

Hallo,

wie kann ich unterscheiden, wenn der ActionListener "ChangeState" vom JSlider reagiert, ob das von einer Veränderung mit der Maus kam, oder durch setzten eines Wertes mit setValue() ?

Das Problem ist folgendes. Ich will einen Musikplayer schreiben, und ein JSlider ist der Fortschrittsbalken.
Somit muß ich einerseits, den Wert ständig neu setzen (0 - 100%) wenn das Lied abspielt.

Und darauf reagiert aber auch der "ChangeState" Listener. Der soll aber eigentlich nur reagieren, wenn ich mit der Maus den Slider ziehe (um das Lied vorzuspulen)

Wie kann ich das also abfragen, ob der Slider von der Maus gezogen wurde ?


----------



## MiDniGG (28. Jul 2009)

Du könntest mit dem MouseListener schaun ob die Maus gedrückt ist/wurde.


----------



## Beni (28. Jul 2009)

Entferne den Listener bevor du "setValue" aufrufst und fuege ihn danach wieder ein. Oder setz irgendeine Variable auf "jetzt wird der Slider veraendert" bevor du "setValue" aufrufst, und frage diese Variable im Listener ab.


----------



## Ebenius (28. Jul 2009)

Implementier ein eigenes BoundedRangeModel. Dieses Modell kann zum Beispiel so aussehen: 
	
	
	
	





```
public 
class MediaSliderModel extends DefaultBoundedRangeModel {

  /**
   * Called by the JSlider if the user drags the slider.
   * ...
   */
  @Override
  public void setValue(int n) {
    super.setValue(n);
    fireActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
          "sliderValueChanged"));
  }

  /**
   * Call this method for media progress change.
   * ...
   */
  public void updateMediaProgress(int n) {
    super.setValue(n);
    fireActionEvent(new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
          "mediaProgressChanged"));
  }

  /**
   * Fires the {@link ActionEvent} to all listeners.
   * 
   * @param event the event to fire
   */
  protected void fireActionEvent(ActionEvent event) {
    final Object[] list = listenerList.getListenerList();
    for (int idx = 0; idx < list.length; idx += 2) {
      if (list[idx] == ActionListener.class) {
        ((ActionListener) list[idx + 1]).actionPerformed(event);
      }
    }
  }

  /**
   * Adds the {@link ActionListener} to <code>this</code> instance.
   * 
   * @param listener the listener to add
   */
  public void addActionListener(ActionListener listener) {
    listenerList.add(ActionListener.class, listener);
  }

  /**
   * Removes the {@link ActionListener} from <code>this</code> instance.
   * 
   * @param listener the listener to remove
   */
  public void removeActionListener(ActionListener listener) {
    listenerList.remove(ActionListener.class, listener);
  }
}
```
Da kannst Du Dich dann als ActionListener dran hängen und anhand des ActionCommand entscheiden was Du tun willst.

Ebenius


----------



## osix (28. Jul 2009)

Danke, aber ich verstehe das letzte Beispiel nicht.
Mir scheint, daß wäre "schönes Java", das weiter oben, eher so ein paar Hilfstricks.

Wie mache ich dann ganz konrekt weiter, nachdem ich DefaultBoundedRange implementiert habe ?

Ist das dann selber ein neuer ActionListener, oder wie muß ich das verstehen ?


----------



## Ebenius (28. Jul 2009)

Dein Media-Abspiel-Zeugs aktualisiert nicht den [c]JSlider[/c], sondern das Modell selbst per [c]updateMediaProgress(progress)[/c]. Du hängst einen [c]ActionListener[/c] an das Modell und reagiert auf die [c]ActionEvent[/c]s und weiß anhand des im [c]ActionEvent[/c] mitgelieferten [c]getActionCommand()[/c]-Strings wer an der Änderung schuld ist.

Ebenius


----------



## osix (28. Jul 2009)

...wie verbinde ich dann den JSlider mit dem MediaSliderModel ?

Ich möchte es gerne so machen, weil das glaub ich echt "sehr sauber" ist.

WIe gesagt, ich verstehe es soweit schon, daß das MediaSliderModell zwei neue Methoden hat, die beide den Wert setzen, sich aber jeweils unterschiedlich "ausweisen" was ich abfragen kann.

Das ist wirklich schön sauberer Code. Aber wie krieg ich den JSlider dann da mit rein ?


----------



## Ebenius (28. Jul 2009)

Das ist das Modell des [c]JSlider[/c]s. Ein Modell wird bei den meisten Swing-Komponenten über die Methode [c]setModel(...)[/c] gesetzt.

```
final MediaSliderModel sliderModel = new MediaSliderModel();
mySlider.setModel(sliderModel);
```
Oder gleich im Konstruktor mitgegeben, wenn man den Slider erzeugt (geht auch bei den meisten Swing-Komponenten):

```
final MediaSliderModel sliderModel = new MediaSliderModel();
final JSlider mySlider = new JSlider(sliderModel);
```
Ebenius


----------



## osix (28. Jul 2009)

Das ist dann ähnlich wie JList und JListModell !

Achso, dann wird mir jetzt was klar.
Dann ersetzte ich quasi das "Standard-Modell" vom JSlider durch das MediaSliderModell, weil ich halt in meinem speziellen Fall des Players, so einen besonderen JSlider brauche.

Und mein speziell JSlider, der auf dem MediaModell beruht, hat dann auch die neuen Methoden


```
JSlider playerSlider = new JSlider(MediaSliderModell)

playerSlider.updateMediaprogress()
```


Achsoachsoachso....

Vielen Dank für Deine Hilfe, mensch, das Forum hier ist ja klasse !


P.S: Zusatzfrage: Kann ich auch den JSlider grafisch so verändern, daß er wie eine Progressbar aussieht ?
Denn als Fortschrittsbalken sieht ein JSlider halt nicht so gut aus. Eine Progressbar kann ich aber nicht "ziehen".

Kann ich das neue Aussehen vielleicht nicht gleich auch in das MediaSliderModell reingeben ?


----------



## Ebenius (28. Jul 2009)

Es heißt "ListModel" ohne "J" und ohne zweites "l". :-D

Im Prinzip wiederholt sich das bei allen nicht-trivialen Swing-Komponenten so oder ähnlich: 
[c]JTable[/c] → [c]TableModel[/c] (abstrakte Implementierung: [c]AbstractTableModel[/c], eine Standardimplementierung: [c]DefaultTableModel[/c]
[c]JList[/c] → [c]ListModel[/c] (abstrakte Implementierung: [c]AbstractListModel[/c], eine Standardimplementierung: [c]DefaultListModel[/c]
[c]JButton[/c] → [c]ButtonModel[/c] (eine Standardimplementierung: [c]DefaultButtonModel[/c]
[c]JSlider[/c] → [c]BoundedRangeModel[/c] (eine Standardimplementierung: [c]DefaultBoundedRangeModel[/c]
[c]JSpinner[/c] → [c]SpinnerModel[/c] (abstrakte Implementierung: [c]AbstractSpinnerModel[/c], einige Standardimplementierungen: 
	
	
	
	





```
SpinnerDateModel
```
, 
	
	
	
	





```
SpinnerListModel
```
, 
	
	
	
	





```
SpinnerNumberModel
```
)
[c]JComboBox[/c] → [c]ComboBoxModel[/c] und [c]MutableComboBoxModel[/c] (eine Standardimplementierung: [c]DefaultComboBoxModel[/c])
Bei den Textkomponenten ([c]JTextComponent[/c], also [c]JTextField[/c], [c]JTextArea[/c], [c]JTextPane[/c], [c]JEditorPane[/c]) ist das fast genauso, aber hier gibt's als Modell immer ein [c]Document[/c]. Überraschungsfrei heißen dann die entsprechenden Methoden [c]setDocument(...)[/c].

Ebenius


----------



## Ebenius (28. Jul 2009)

osix hat gesagt.:


> P.S: Zusatzfrage: Kann ich auch den JSlider grafisch so verändern, daß er wie eine Progressbar aussieht ?
> Denn als Fortschrittsbalken sieht ein JSlider halt nicht so gut aus. Eine Progressbar kann ich aber nicht "ziehen".


Die saubere Variante wäre, selbst ein SliderUI zu implementieren und das Zeichnen selbst zu übernehmen. Der Aufwand ist recht hoch und kompliziert ist's obendrein.

Du könntest etwas in der Art zaubern, aber das solltest Du gegen jedes LookAndFeel einzeln testen. Metal funktionierts. Aber sinnvoll finde ich's nicht: 
	
	
	
	





```
/* (@)JSliderStyledAsProgressBar.java */

/* Copyright 2009 Sebastian Haufe

 * Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       [url]http://www.apache.org/licenses/LICENSE-2.0[/url]

 * Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License. */

package com.ebenius;

import java.awt.BorderLayout;
import java.awt.Graphics;

import javax.swing.*;

/**
 * Strange hack to paint a progress bar above a {@link JSlider}.
 * 
 * @version $Revision$ as of $Date$
 * @author Sebastian Haufe
 * @since Playground-3.8
 */
public class JSliderStyledAsProgressBar extends JSlider {

  private JProgressBar progressBar;

  // -------------------------------------------------------------------------
  // Constructors
  // -------------------------------------------------------------------------

  /** Creates a new <code>JSliderStyledAsProgressBar</code>. */
  public JSliderStyledAsProgressBar() {
    installProgressBar();
  }

  /**
   * Creates a new <code>JSliderStyledAsProgressBar</code>.
   * 
   * @param brm the model
   * @see JSlider#JSlider(BoundedRangeModel)
   */
  public JSliderStyledAsProgressBar(BoundedRangeModel brm) {
    super(brm);
    installProgressBar();
  }

  /**
   * Creates a new <code>JSliderStyledAsProgressBar</code>.
   * 
   * @param orientation orientation
   * @param min minimum value
   * @param max maximum value
   * @param value current value
   * @see JSlider#JSlider(int, int, int, int)
   */
  public JSliderStyledAsProgressBar(
        int orientation,
        int min,
        int max,
        int value) {
    super(orientation, min, max, value);
    installProgressBar();
  }

  /**
   * Creates a new <code>JSliderStyledAsProgressBar</code>.
   * 
   * @param min minimum value
   * @param max maximum value
   * @param value current value
   * @see JSlider#JSlider(int, int, int, int)
   */
  public JSliderStyledAsProgressBar(int min, int max, int value) {
    super(min, max, value);
    installProgressBar();
  }

  /**
   * Creates a new <code>JSliderStyledAsProgressBar</code>.
   * 
   * @param min minimum value
   * @param max maximum value
   * @see JSlider#JSlider(int, int, int, int)
   */
  public JSliderStyledAsProgressBar(int min, int max) {
    super(min, max);
    installProgressBar();
  }

  /**
   * Creates a new <code>JSliderStyledAsProgressBar</code>.
   * 
   * @param orientation orientation
   */
  public JSliderStyledAsProgressBar(int orientation) {
    super(orientation);
    installProgressBar();
  }

  // -------------------------------------------------------------------------
  // Progress bar look
  // -------------------------------------------------------------------------

  private void installProgressBar() {
    progressBar = new JProgressBar(getModel());
    progressBar.setOrientation(getOrientation());
    add(progressBar);
  }

  @Override
  public void invalidate() {
    super.invalidate();
    progressBar.invalidate();
  }

  @Override
  protected void validateTree() {
    super.validateTree();
    progressBar.setBounds(getBounds());
    progressBar.validate();
  }

  @Override
  public void setModel(BoundedRangeModel newModel) {
    super.setModel(newModel);
    if (progressBar != null) { // might be null during initialization
      progressBar.setModel(newModel);
    }
  }

  @Override
  public void setOrientation(int orientation) {
    super.setOrientation(orientation);
    if (progressBar != null) { // might be null during initialization
      progressBar.setOrientation(orientation);
    }
  }

  @Override
  protected void paintComponent(Graphics g) {
    progressBar.paint(g);
  }

  // -------------------------------------------------------------------------
  // Program Entry Point
  // -------------------------------------------------------------------------

  /**
   * Test main method.
   * 
   * @param args ignored
   */
  public static void main(String[] args) {
    final JPanel contentPane = new JPanel(new BorderLayout(6, 6));
    contentPane.add(new JSliderStyledAsProgressBar(0, 100, 36));

    final JFrame f = new JFrame("Test Frame: JSliderStyledAsProgressBar"); //$NON-NLS-1$
    f.setContentPane(contentPane);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.setVisible(true);
  }
}
```



osix hat gesagt.:


> Kann ich das neue Aussehen vielleicht nicht gleich auch in das MediaSliderModell reingeben ?


Auf keinen Fall. Ein Modell verwaltet die Daten einer Komponente. Nichts anderes. Diese Trennung ist wichtig, um Sackgassen zu vermeiden.

Ebenius


----------



## osix (28. Jul 2009)

WOW Ebenius. Vielen Dank nochmal für deine ausführliche Antwort !

Mir ist mit dem Model und der Ausführung echt was wesentliches klar geworden !
Jetzt weiß ich auch, daß man "Sonderwünsche" bei Swing-Komponenten immer nur so "sauber" bekommen kann, 
indem man ein eigenes Model programmiert, das von einem Abstrakten ModellInterface abgeleitet wird.

Das mit dem Grafischen Aussehen mit einer Progressbar hab ich mal überflogen und werde drüber nachdenken.
Hauptsache es funktioniert erstmal, da kann der JSlider auch noch so aussehen wie ein JSlider.


----------



## osix (28. Jul 2009)

Es gibt ein Problem.


proSlider hat nicht die mögliche neue Methode "updateMediaprogress"

Obwohl ich proSlider (für progressbarSlider) in analogie zur list mit dem MediaSliderModell als eigene Klasse initialisiert habe. Die Klasse MediaSliderModell ist fehlerfrei.

Auch tut der Code wie bisher, aber ich kann eben die neue Methode nicht aufrufen.


```
DefaultListModel listModel = new DefaultListModel();
    MediaSliderModel sliderModel = new MediaSliderModel();

    JProgressBar pb = new JProgressBar();

    JList listenfeld;
    JSlider proSlider;
```

[Java]
proSlider = new JSlider(sliderModel);
        proSlider.setModel(sliderModel);
        proSlider.setMajorTickSpacing(50);
        proSlider.setMinorTickSpacing(10);
        proSlider.setPaintLabels(true);
        proSlider.setPaintTicks(true);
        //proSlider.setFocusable(false);
        proSlider.addChangeListener(this);
[/code]


----------



## osix (28. Jul 2009)

[Java]
public void actionPerformed(ActionEvent event)
    {
        Object source = event.getSource();

        if (source == this.sliderModel)
        {
            if (event.getActionCommand().equals("sliderValueChanged"))
            {
                System.out.println("Slider-Wert wurde gesetzt");
            }

            if (event.getActionCommand().equals("mediaProgressChanged"))
            {
                System.out.println("Slider wurde gezogen");
            }

        }
[/code]

Noch ein Problem: es gibt keine Unterscheidung, es tritt immer der erste Fall auf, egal ob Wert vom Programm gesetzt wird oder mit der Maus. Immer Fall 1 tritt auf.

Hab kapiert, daß ich mit einem ActionListener mich ans sliderModel dranhänge. Den ChangeStateListener hab ich dann nicht mehr verwendet. Richtig so ?


----------



## osix (28. Jul 2009)

Hab grade noch rumgespielt.

klar, ich muß ja: slidermodell.updateMediaProgress machen, dann geht es.

Achso, weil ja das Model die Daten trägt und JSlider beruht auf diesem Modell.

Hab alles eingebaut - und OH WUNDER ! Es FUNZT WUNDERBAR. Der JSlider funktioniert und ich kann tatsächlich in MP3 Liedern vor und zurückspulen.

Allerdings nur mit Verzögerung. Aber das liegt an der langsamen MP3 Library die ich verwende...


----------



## Paddelpirat (28. Jul 2009)

Noch eine Idee, wenn dir die JSlider nicht gefällt: Du könntest einer JProgressBar auch einen Mouse(Motion)Listener hinzufügen und anschlißend mit e.getPoint().x den Wert für die JProgressBar bestimmen.

Edit: noch etwas Code (nicht schön aber sollte so in etwa gehen):


```
JProgressBar bar = new JProgressBar( 0, 1000000 );
    
    bar.addMouseMotionListener(new MouseMotionListener() {
    	public void mouseDragged(MouseEvent e) {
    		int factor = bar.getMaximum() / bar.getSize().width;
    		bar.setValue( e.getPoint().x *factor);
    	}
    	public void mouseMoved(MouseEvent e) {
    		
    	}
    });
```

entsprechend dann noch mit MouseListener für mouseReleased, bzw mouseClicked.


----------



## Ebenius (28. Jul 2009)

Ich rate von solchen Funktionalitätsänderungen eher ab. Den veränderten [c]JProgressBar[/c] kann ich nicht mit der Tastatur bedienen. Außerdem kann ein behinderter den auch nicht alternativ bedienen. Wenn man innerhalb eines [c]BoundedRangeModel[/c]s editieren will, sollte man einen [c]JSlider[/c] benutzen, der ist genau dafür da. Aussehen ändern ist immer besser, als Funktionalität in die falschen Komponenten einzuschrauben. Dabei muss man leider wirklich viel beachten.

osix, wenn das Thema erledigt ist, bitte den Knopf neben dem "Antworten"-Knopf drücken.

Ebenius


----------

