# JSpinner - Model <-> View, unterschiedliche Werte



## Kiamur (5. Mai 2008)

Hallo!

Ich habe einen JSpinner, der intern Integerwerte in seinem Model halten soll. Diese Werte sollen dann auch über die Spinner-buttons inkrementiert, bzw dekrementiert werden.
Das, was in dem Textfeld angezeigt werden soll, soll noch mit einem Faktor multipliziert werden, so dass sich evtl. auch float-Werte ergeben. Also im Grunde soll es so gehen:

1. Spinner Model value = 3000
1a. Textfeld zeigt Wert 3000 * x an
2. Nutzer klickt Pfeil nach oben.
3. Spinner Model value = 3001
3a. Textfeld zeigt Wert 3001 * x an, usw.

Super wäre dann natürlich auch, wenn man im Textfeld etwas eingibt (z.B. 3.5) und im Model landet der Wert 3.5/x.

Ich habe schon versucht ein eigenes Model zu schreiben, wo ich getValue() überschrieben habe. Das hat leider nicht funktioniert.

Ich würde ja auch lieber im Editor den Faktor mit einrechnen, aber da weiß ich leider nicht so recht, wo ich anfassen muss.

Hat jemand von euch eine Idee?

Gruß,
Maik


----------



## Marco13 (5. Mai 2008)

Ganz schwierig, da was "fundiertes" zu sagen, solange man nicht weiß, worauf das ganze rauslaufen soll. Eine Möglichkeit gäbe es - die klingt erstmal "unkonventionell", aber auf Basis dessen, was du beschrieben hast, wäre sie _vielleicht_ OK: Man könnte(!) ein "DelegatingSpinnerModel implements SpinnerModel" machen - also eine Implementierung des SpinnerModel-Interfaces, das alle Methodenaufrufe an ein anderes weiterreicht...

```
DelegatingSpinnerModel implements SpinnerModel
{
    SpinnerModel delegate =...
   
    public void setValue(Object value)
    {
        delegate.setValue(x * (Integer)value);
    }
...
}
```


----------



## Kiamur (5. Mai 2008)

Hallo und danke erst mal!

Also, worauf es hinauslaufen soll is eben, dass ich den Wert im Model mit einem Faktor multiplizieren möchte, bevor er im View nagezeigt wird. Der Wert im Model soll sich dabei NICHT verändern.

Vielleicht bin ich mit meiner "Denke" auch total auf dem Holzweg, aber ist das nicht genau der Vortaeil von dem ganzen Model/View/Control gedöns?!?

Wenn ich jetzt z.B. folgendes habe:


```
JSpinner spinner = new JSpinner();
spinner. setValue(3000);
. 
.
.
.
Integer i = (Integer) spinner.getValue(); // i.valueOf soll jetzt 3000 sein, aber im Textfeld vom Spinner soll halt 3000*x stehen
```

Das benötige ich aus genau dem Grund, der das Codebeispiel darstellt: Der User soll einen für ihn verständlichen (und deswegen umgerechneten) Wert sehen. Wenn ich aber im Programm intern per getValue() den Wert vom Spinner hole, dann soll dort der original (nicht umgerechnete) Wert zurückgegeben werden.

Also nach meinem Verständnis von MVC: das Model enthält die Rohdaten, der View stellt diese  für den User am besten zu interpretierend dar.

Wenn ich jetzt sehe, dass du da etwas mit einem eigenen Model vorschlägts, dann weiß ich nicht so genau, ob es wirklich das ist, was ich suche, da ich eher beim Editor des JSpinner anfangen würde (vielleicht täusche ich mich aber auch, da ich davon nicht so viel Ahnung habe). 
Leider weiß ich auch nicht so genau, was man mit einem DelegatingSpinnerModel so macht, und wie man es anwendet.

Gruß,
Maik


----------



## Marco13 (5. Mai 2008)

OK, da hatte ich glaubich was falsch verstanden... Vielleicht liegt es aber auch an der Mischung: Wann und warum soll man denn mit spinner.getValue() den Wert holen, und der soll dann _anders_ sein, als der, der angezeigt wird? Das wäre ziemlich ... komisch ...: Niemand sichert einem zu, dass der Spinner nicht intern seine eigenen Methoden verwendet, um festzustellen, was er anzeigen soll...

```
class JSpinner ...
{
    public void zeigMichAn()
    {
        zeigeAn(this.getValue());
    }
}
```
Woher soll er also wissen, ob er den echten Wert des Modells zurückgeben soll, oder den "gefakten"? Bei setValue ist's analog - die Implementierung dieser Methoden (setValue/getValue) sieht da ja erstmal z.B. so aus

```
public void setValue(Object value) 
{
    getModel().setValue(value);
}
```
D.h. der Spinner zeigt an, was im Modell liegt. (Natürlich könnte man das ändern, nur ist das vmtl. nicht wirklich praktikabel und sinnvoll). 

Wenn man sich die "internen" Daten holen will, müßte man das eben IMMER über das Modell machen, dass auch die internen Daten enthält... Und eine Möglichkeit wäre eben, ein ganz normales Model für die "internen" Daten zu erstellen, und dem JSpinner dann ein "lügendes" Modell unterzujubeln. Das wäre dann so ein DelegatingSpinnerModel (das ist keine fertige Klasse, sondern sollte nur ein Implementierungsvorschlag sein). Hier also nochmal ausführlicher - aber immernoch nur ein _Vorschlag_ - ob diese Lösung für dich passt, musst du entscheiden!

```
// By Marco13 for [url]http://www.java-forum.org/de/posting.php?mode=reply&t=68753[/url]

import javax.swing.*;
import javax.swing.event.*;

class DelegatingSpinnerModel implements SpinnerModel
{
    private SpinnerModel delegate = null;
    private float scale = 1.0f;

    public DelegatingSpinnerModel(SpinnerModel delegate, float scale)
    {
        this.delegate = delegate;
        this.scale = scale;
    }

    public Object getNextValue()
    {
        Integer i = (Integer)delegate.getNextValue();
        float f = i * scale;
        return f;
    }

    public Object getPreviousValue()
    {
        Integer i = (Integer)delegate.getPreviousValue();
        float f = i * scale;
        return f;
    }

    public Object getValue()
    {
        Integer i = (Integer)delegate.getValue();
        float f = i * scale;
        return f;
    }

    public void setValue(Object value)
    {
        Float f = (Float)value;
        int i = (int)(f / scale);
        delegate.setValue(i);
    }

    public void addChangeListener(ChangeListener l)
    {
        delegate.addChangeListener(l);
    }

    public void removeChangeListener(ChangeListener l)
    {
        delegate.removeChangeListener(l);
    }
}

class SpinnerModelTest
{
    public static void main(String args[])
    {
        new SpinnerModelTest();
    }

    public SpinnerModelTest()
    {
        JFrame frame = new JFrame("SpinnerModelTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Integer value = new Integer(50);
        Integer min = new Integer(0);
        Integer max = new Integer(100);
        Integer step = new Integer(1);

        final SpinnerModel delegate = new SpinnerNumberModel(value, min, max, step);
        final DelegatingSpinnerModel model = new DelegatingSpinnerModel(delegate, 1.5f);

        delegate.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                System.out.println("Event on delegate, value now "+delegate.getValue());
            }
        });

        model.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                System.out.println("Event on model   , value now "+model.getValue());
            }
        });


        JSpinner spinner = new JSpinner(model);
        frame.getContentPane().add(spinner);

        frame.pack();
        frame.setVisible(true);
    }


}
```


----------



## Kiamur (5. Mai 2008)

Hi!

Das sieht doch genau so aus, wie ich es haben möchte. Werde das gleich mal ausprobieren. Vielen Dank dafür!!

Ich hatte genau diese Zeilen in meinem ersten Versuch eines eigenen Models drin gehabt:


```
public Object getValue() 
    { 
        Integer i = (Integer)delegate.getValue(); 
        float f = i * scale; 
        return f; 
    }
```

Naja, nicht genau so, sondern so:

```
public Object getValue() 
    { 
        Integer i = (Integer)super.getValue(); 
        float f = i * scale; 
        return f; 
    }
```

und bin dann mit meinem Versuch über genau das hier gestolpert:

```
public void getValue(Object value) 
{ 
    getModel().getValue(value); 
}
```

Und ich habe mir im folgenden genau die selben Fragen gestellt, die du jetzt auch erwähnt hast (woher soll er wissen, welchen Wert er liefern soll, etc.

Ich habe nur die Umsetzung so nicht hinbekommen.

Ehrlich gesagt habe ich das mit dem delegieren immer noch nicht so richtig verstanden, aber ich werde mich mal durch deinen Code fräsen. . . . .


Da du dich nach wie vor etwas über den Sinn (oder Unsinn?!?) meiner Frage wunderst, will ich es vielleicht noch einmal erklären:

Ich baue mir gerade ein kleines Oszilloskop mit realer Hardware. Da habe ich einen 12bit AD-Wandler. In dem Spinner möchte ich gerne den triggerlevel einstellen. Das geht natürlich nur mit einem Wert von 0-4095. Das kann man mit dem Spinner natürlcih sehr schön durchführen, da er eine Bereichskontrolle durchführt, und noch andere schöne Dinge einfach so kann (die UP/Down-Buttons, etc.). Der Benutzer möchte allerdings lieber auf eine Spannung als auf einen binären Wert triggern. deswegen soll der Spinner intern den Bereich von 0-4095 durchtickern, aber eben den umgerechneten Wert anzeigen. Da ich intern immer nur den binärwert benötige wollte ich eben mit getValue() auch immer diesen bekommen. Das Anzeigen der errechneten Spannung soll halt so "nebenbei" funktionieren, weil ich diesen wert wirklich nur für die Benutzeranzeige brauche.
Vielleicht ist dieser Weg von vorn herein etwas umständlich, aber die Spinnereigenschaften haben mich am Anfang halt blind vor Freude gemacht . . . . 

Gruß,
Maik


----------



## Marco13 (5. Mai 2008)

OK, ggf. musst du an einigen Stellen dann noch ein bißchen aufpassen mit Rundungsfehlern (je nachdem, auf welche Spannungen diese Werte zwischen 0 und 4095 abgebildet werden). Aber an geeigeneten Stellen ein Math.round(..) einbauen dürfte es da schon tun.

Das delegate (vs. "super") ist eben gerade der Versuch, diese Uneindeutigkeiten zu lösen. Man weiß ja sonst nie, welchen Wert man bekommt, oder muss bei _jedem_ Zugriff die Umrechnung per Hand machen  :?  Das spart man sich so: Man arbeitet einfach überall ganz normal mit dem delegate, und NUR mit den Internen Werten. Das Delegating model braucht man im Prinzip garnicht persönlich zu kennen. Man könnte auch sowas schreiben wie
SpinnerModel internalModel = ...
JSpinner spinner = new JSpinner(new DelegatingSpinnerModel(internalModel), 1.5f);
d.h. es ist nur ein "schein-Modell", das garnicht wirklich verwendet wird, sondern nur dem Zweck dient, dem Spinner falsche Werte vorzugaukeln (oder die falschen Werte, die vom Spinner kommen, in "echte" zu übersetzen)


----------



## Kiamur (5. Mai 2008)

Hallo Marco!

Hoffentlich bist du noch in der Nähe . . . .

Also, es funktioniert super. Genau das habe ich gebraucht. Aaaber, jetzt kann ich von Hand nichts mehr in den Spinner eingeben. Woran kann das denn liegen?!?

Gruß,
Maik


----------



## Marco13 (5. Mai 2008)

Hmja, das liegt daran, wie der JSpinner seinen Editor erstellt....

```
protected JComponent createEditor(SpinnerModel model) {
	if (model instanceof SpinnerDateModel) {
	    return new DateEditor(this);
	}
	else if (model instanceof SpinnerListModel) {
	    return new ListEditor(this);
	}
	else if (model instanceof SpinnerNumberModel) {
	    return new NumberEditor(this);
	}
	else {
	    return new DefaultEditor(this);
	}
    }
```
:? 

Aber das gepostete war ja ohnehin nur ein stub. Wenn du weiterhin die "praktische" Spinner-Funktionalität mit der automatischen Begrenzung der Werte usw. haben willst, musst du das ganze sowieso auf ein SpinnerNumberModel ausweiten. 

Naja. Musst du nicht :wink: :

```
// By Marco13 for [url]http://www.java-forum.org/de/posting.php?mode=reply&t=68753[/url]

import javax.swing.*;
import javax.swing.event.*;

class DelegatingSpinnerModel extends SpinnerNumberModel
{
    private SpinnerNumberModel delegate = null;
    private float scale = 1.0f;

    public DelegatingSpinnerModel(SpinnerNumberModel delegate, float scale)
    {
        this.delegate = delegate;
        this.scale = scale;
    }

    public Object getNextValue()
    {
        Integer i = (Integer)delegate.getNextValue();
        if (i == null)
        {
            return null;
        }
        float f = i * scale;
        return f;
    }

    public Object getPreviousValue()
    {
        Integer i = (Integer)delegate.getPreviousValue();
        if (i == null)
        {
            return null;
        }
        float f = i * scale;
        return f;
    }

    public Object getValue()
    {
        Integer i = (Integer)delegate.getValue();
        float f = i * scale;
        return f;
    }

    public void setValue(Object value)
    {
        Float f = (Float)value;
        int i = (int)(f / scale);
        delegate.setValue(i);
    }

    public void addChangeListener(ChangeListener l)
    {
        delegate.addChangeListener(l);
    }

    public void removeChangeListener(ChangeListener l)
    {
        delegate.removeChangeListener(l);
    }

    public Comparable getMinimum()
    {
        Integer i = (Integer)delegate.getMinimum();
        float f = i * scale;
        return f;
    }

    public void setMinimum(Comparable minimum)
    {
        Float f = (Float)minimum;
        int i = (int)(f / scale);
        delegate.setMinimum(i);
    }

    public Comparable getMaximum()
    {
        Integer i = (Integer)delegate.getMaximum();
        float f = i * scale;
        return f;
    }

    public void setMaximum(Comparable maximum)
    {
        Float f = (Float)maximum;
        int i = (int)(f / scale);
        delegate.setMaximum(i);
    }

    public Number getStepSize()
    {
        Integer i = (Integer)delegate.getStepSize();
        float f = i * scale;
        return f;
    }
    public void setStepSize(Number stepSize)
    {
        Float f = (Float)stepSize;
        int i = (int)(f / scale);
        delegate.setStepSize(i);
    }
}

class SpinnerModelTest
{
    public static void main(String args[])
    {
        new SpinnerModelTest();
    }

    public SpinnerModelTest()
    {
        JFrame frame = new JFrame("SpinnerModelTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Integer value = new Integer(50);
        Integer min = new Integer(0);
        Integer max = new Integer(100);
        Integer step = new Integer(1);

        final SpinnerNumberModel delegate = new SpinnerNumberModel(value, min, max, step);
        final DelegatingSpinnerModel model = new DelegatingSpinnerModel(delegate, 1.5f);

        delegate.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                System.out.println("Event on delegate, value now "+delegate.getValue());
            }
        });

        model.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                System.out.println("Event on model   , value now "+model.getValue());
            }
        });


        JSpinner spinner = new JSpinner(model);
        frame.getContentPane().add(spinner);

        frame.pack();
        frame.setVisible(true);
    }


}
```


----------



## Guest (5. Mai 2008)

Hi!

Darf man ja nicht so laut sagen, aber du bist ein Schatz  :wink: !

Also ein paar Probleme mit deinem ersten Vorschlag hatte ich schon beseitigt (z.B. das ich eine Nullpointer Exception bekommen habe, wenn das Model in seinem Delegate Werte setzen woillte, die außerhalb von min und max lagen).

Ich denke aber, dass es wohl so sauberer ist, wenn man gleich das DelegatingModel von NumberModel ableitet. Ich werde diese Version dann auch mal auf herz und Nieren testen, und hoffe, dass ich weitere Probleme selber in den Griff bekomme.

Danke noch mal!

Gruß,
Maik


----------



## Kiamur (5. Mai 2008)

Sorry, war nicht eingeloggt . . . . . 

Maik


----------

