HI guys, as my next application I thought I'd build a simple converter, something that allows me to convert from, say, Km to miles and so on. The framework I will use is vaadin (unfortunately) but it will still be java anyway :-).
So, I just thought I'd see if anybody has some good suggestions really. This is how I think I could go about it.
The UI class will obviously hold the GUI stuff, so no need to talk about it. I will have another class, converter.java which will have a list of options as radio button selection, something like
-From Km to miles
-From miles to Km
-From F to C
etc
Then when you make your selection you get some text fields to input data, a click button to confirm and process your selection and then another text field for displaying the results.
The text fields to input data will probably be always the same 2 for all the options rather than having to create two for each options and they will be displayed/hidden as necessary
How does that sound?

Recommended Answers

All 47 Replies

That's all well and good but how about making it modern? That is, why must it have a convert button at all? If one were to respond to changes in the entry boxes then it would be instant conversion from F to C and so on.

Think mobile, phone users.

I hate to tell you, but if you are planning to use a UI framework (Vaadin) to implement business logic, you should first learn basic OO principles.

I'm with stultuske on this one. Looking at your posts here you are trying to do way too many things at the same time. Stop messing about with frameworks and code repositories and concentrate on mastering the basics of Java coding.

while you guys are 100% right, unfortunately I have to use that framework, wheter I want it or not, because that's what they use at work and I have to get more and more familiar with it, and pretty quickly. And so I thought that a converter wouldn't be too difficult to implement.
As you probably know, I also need to practice Java and I thought I'd combine the two things together.

That's all well and good but how about making it modern? That is, why must it have a convert button at all? If one were to respond to >changes in the entry boxes then it would be instant conversion from F to C and so on.

Think mobile, phone users.

You've hit upon one of my pet peeves. Actually more than a pet peeve. I usually LIKE having a convert button. Makes me feel like I am in control. I imagine you're thinking the design should be that if I enter in 212 in the Fahrenheit box, I should immediately get 100 in the Celsius box without having to hit a button. I imagine that would involve listening to the keyboard rather than the button, so every keystroke spurs a conversion.

So I type '2' and when I do, the Celsius equivalent of 2 degrees Fahrenheit shows up in the Celsius field. I don't care about that conversion, but it's blindingly fast and I can ignore it. No worries. Then I type '1', so there's now '21' in the Fahrenheit box. The Celsius conversion of 21 degrees Fahrenheit gets filled in. Again, I don't care, but it happens so much faster than I can type that it doesn't bother me. Then I hit '2' and I have 212 degrees in the Fahrenheit box and in a blink of an eye, "100" shows up in the Celsius box. That's the conversion I wanted and I was able to get it by typing three keys ('2', then '1', then '2') rather than typing three keys, then the Enter key or three keys, then a button. Hooray! Less work and a faster answer for me, the user. What could possilbly be wrong with that?

Well, nothing's wrong with that when it actually works that way. But I've noticed that it often DOESN'T work that way. Very often each keystroke spurs not a very quick calculation but some computationally intensive or buggy code that actually distracts me or CAN'T keep up with my typing or it tries to guess what I might type in next. If I start typing a word, it takes a few letters and tries to guess the word that I might eventually type and fill it in, resulting either in guessing the WRONG word getting filled in and me having to "undo" that helpful guess or me having to pause to type the next letter because the super-fast guessing algorithm isn't that fast after all and it isn't done by the time the next key is pressed and it gets called twice, not at all, gets interrupted, etc. "Auto-correct" is fine and dandy, but sometimes computers think they are smarter than I am and "correct" things I don't want corrected.

Or they don't "correct" it, they have an error pop up. For example, what if I screw up and type "21q" instead of "212" in the Fahrenheit box, the conversion is attempted, and a big error message popup comes up: "21q is not a valid Fahrenheit temperature". So I have to click "OK" and re-type in "212" and maybe when I did the old "21q" wasn't erased, so when I type "212", it tried to APPEND it to the textbox instead of starting over, so more error popups, etc. I'd rather type "21q", then the backspace, then '2', then "Enter" or the button. It's faster and less aggravating.

The rebuttal to this would probably be that the coder will take all this into account and test the application thoroughly and the QA team will find it, send it back with a bug report, which will of course be corrected before it's released to the public, so the interface will be stress-free, bug-free, and perfect in every way because all developers are perfect and therefore every app is perfect as well, so the problem I described above has never happened and never will happen.

Anyway, that's my two cents. Yes, done well, this all works and saves typing and fewer buttons means fewer button listeners need to be coded and more other stuff can fit on the screen. Just don't forget that a whole new crop of potential problems can crop up every time you try to "help" the user type less, get faster answers, and press fewer buttons.

commented: Thanks. The more I work on mobile apps the more we move away from OK and such buttons. The screen is only so big. +10

Apologies for possibly hijacking the thread from the OP's program.

No hijack at all AssertNull, quite the contrary, you raised some good valid points. Personally, I agree with you, I'd rather have a button than not because it makes me feel more in control and if the button is big enough it will be OK in touch devices like phones etc (not that I plan to make that application available to anyone as it's just an exercise).
As for tht silly Vaadin, I personally hate it, but alas, that's what it seems I have to do

Personally I prefer not to have a button when there's no need for one. If you have difficult validation then OK, but otherwise you just make things more cluttered and slower. Not to mention that until you press OK your window is in an invalid state (displaying a C and an F that are not the same temperature) and I hate UIs that allow the user to gwet into an invcalid state.

As it happens I did a temp converter as a teaching aid a while back - one window with text fields for C and F, a separate window with two sliders. Change any one and the others all update immediately, zero detactable lag. Non-numeric input is simply ignored.
Just think about the sliders for a moment... do you want an OK button that you have to press after moving a slider?

while you guys are 100% right, unfortunately I have to use that framework, wheter I want it or not, because that's what they use at work and I have to get more and more familiar with it, and pretty quickly. And so I thought that a converter wouldn't be too difficult to implement.
As you probably know, I also need to practice Java and I thought I'd combine the two things together.

Violet, nobody ever said you shouldn't learn Vaadin, but you have to use that framework: good, use it. But use it for the tasks it's meant to, being: UI, not business logic.
You will not be 'more familiar' with it if you start using it for tasks it's not supposed to be used for. Of course, a converter is easy to implement, but it should not be implemented in Vaadin, but in a business module which can be called by your Vaadin code.

that's what I usually do with vaadin, meaning I implement the UI in vaddin, like layout (vertical, horizontal etc), components (in the case of the converter application the various text fields, and the possible options) and then in a separate class I'd implement the logic. Is that what you meant stultuske? Of course open to other suggestions, at the end of the day I'm only learning
cheers

A separate class is a good start, a better approach would be different packages, but there are also arguments to put it in different projects, that use each other.

for instance: do you really want to create a new release of your entire application, including the UI, if all you change is a simple implementation fix in the business?

Ah, I see what you mean, but I presume that's more applicable to large scale projects, to save time and resources. In any case the answer to your question is no, I wouldn't necessarily want to create a new release of the whole thing. How do you link different projects then?

you import one project in the other as a library (maven dependency for instance) and you call it's methods through it's public api

I usually LIKE having a convert button.

I've actually done a bit of a 180 on this since reading/thinking about some of the posts on this thread and elsewhere. Not a complete 180 mind you, I'm still in mourning for my "Convert" and "OK" buttons and I'm still a desktop kind of guy. But I'm wondering if it's me just not getting with the times regarding phones now being the dominant web-surfing tool now and you're right, space is at a premium. Also, I was violating my own rule of "Don't punish good coders for bad coders not knowing what they're doing", which is why I'm really a C guy at heart personality-wise. I'm still in the grieving process as far as getting rid of my buttons, sort of like I'm still in denial that Pluto isn't a planet anymore.

Anyway, I'll bow out of this thread because I have no opinion at all on Vaadin. Just wanted to report that I've seen the light, sort of.

That's interesting stultuske. I think I start with different packages now, but the idea of separating things with projects is nice. I will ask at work what they do, if they use more the packages approach or the project one
thanks

The important thing here is to separate your code into classes with high cohesion and low coupling.
If you have more than a few classes, consider grouping them in multiple packages with high cohesion and low coupling. If the project is large, with mutliple developers then consider splitting it into mutiple sub-projects (with high etc).
Packages and projects are just ways to organise large or multi-developer codebases. But they can't do anything for you unless you can divide the code into appropriate classes. Seeing where you are in your learning curve, I'd concentrate on class structure for the moment.

thanks, I will try and I will post back in this thread when I have a bit of code, so I leave this open

Right chaps, it took me a little longer as I didn't have much time, but the converter, version 1.0, is built and it's working. Before we look at the code, a couple of things. I'm not sure the way I built is the best way though. Basically, for each type of conversion (I have only two at the moment but I will add more) I have a static final variable, like private static final String conversion1 = "Km to miles"; etc. The disadvantage is that whatever new conversion I want to add, I have to add another variable here, another statement within the switch statement to figure out what conversion I'm trying to make etc. Of course I'm not a seasoned java developer so there is an awful lot I don't know, and I might have taken the wrong approach. I tried to keep in mind your suggestions as well, but I eventually decided I will go with a button that, when click, kicks things started - that's perhaps because I'm old school and I like the user to have control.
When the application starts you only get the options (in my case 2), when you click on one of the radio buttons you get an input field, a submit button and a reset button. When you type your value to convert in the input field (assuming it's a number) when you click submit you get a result input field with the conversion result. If you don't input anything you get an errror.
The UI is just that, it takes care of displaying components:
MyUI.java

package com.vaadin.project.converter;    
import javax.servlet.annotation.WebServlet;    
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.annotations.Widgetset;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.Button;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextField;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;

@Theme("mytheme")
@Widgetset("com.vaadin.project.converter.MyAppWidgetset")
public class MyUI extends UI {

    @Override
    protected void init(VaadinRequest vaadinRequest) {
        final VerticalLayout layout = new VerticalLayout();        
        ConverterComponent converterComponent = new ConverterComponent();
        Button button = new Button("Click Me");
        layout.addComponents(converterComponent.getRadioButtons(),converterComponent.getUserInput(),converterComponent.getSubmitButton(), converterComponent.getResetButton(), converterComponent.getResult() );
        layout.setMargin(true);
        layout.setSpacing(true);

        setContent(layout);
    }

    @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
    @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
    public static class MyUIServlet extends VaadinServlet {
    }
}

ConverterComponent.java

package com.vaadin.project.converter;

import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.server.Page;
import com.vaadin.ui.Button;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.Notification;
import com.vaadin.ui.OptionGroup;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Button.ClickEvent;    
public class ConverterComponent extends CustomComponent{        
    private TextField name2 = new TextField();
    private OptionGroup single;
    private double conversionResult;
    private TextField userInput;
    private TextField result;
    private Button submit;
    private Button reset;
    private static final String conversion1 = "Km to miles";
    private static final String conversion2 = "Miles to Km";
    private static final long serialVersionUID = 1L;
    public ConverterComponent(){
        submit = new Button("Submit");
        reset = new Button("Reset");
        result = new TextField();
        userInput = new TextField();
        result.setEnabled(false);
        result.setVisible(false);
        userInput.setVisible(false);
        reset.setVisible(false);
        submit.setVisible(false);           
        single = new OptionGroup("Select the conversion");
        single.addItems(conversion1, conversion2);          
        reset.addClickListener(new Button.ClickListener(){          
            @Override
            public void buttonClick(ClickEvent event){
                clearFields();      
                getResult().setVisible(false);
                //System.out.println("getting radio button " + single.getConnectorId());
                //System.out.println("getting radio button " + single);

            }
        });
        submit.addClickListener(new Button.ClickListener(){         
            @Override
            public void buttonClick(ClickEvent event) {             
                getResult().setVisible(true);
                //NEED TO KNOW WHICH RADIO BUTTON HAS BEEM CLICKED SO THAT i CAN DECIDE WHICH CONVERSION TO USE
                //System.out.println("testing access " + OptionGroup.class);
                //for(int i = 0; i < )
                String currentSelected = single.getValue().toString();
                System.out.println(" test " +single.getValue().toString());
                validateInputs(currentSelected);
            }
        });         
        single.addValueChangeListener(new Property.ValueChangeListener(){           
            @Override
            public void valueChange(ValueChangeEvent event) {
                clearFields();
                /*System.out.println("You chose: " + event.getProperty().getValue().toString() + "\n");
                System.out.println("other line " + event.getProperty() + "\n" + " id is " + single.getId() + " size " + single.size());*/

                //System.out.println("event is " + event.getProperty().getValue());                     

                switch(event.getProperty().getValue().toString()){
                case conversion1:
                    System.out.println(conversion1);
                    break;
                case conversion2:
                    System.out.println(conversion2);
                    break;
                }
                displayFields();

            }
        });
    }   
    public OptionGroup getRadioButtons(){
        return single;
    }
    public TextField getResult(){
        return result;
    }
    public void setConversionResult(double convResult){
        conversionResult = convResult;
    }
    public double getConversionResult(){
        return conversionResult;
    }
    public TextField getUserInput(){
        return userInput;
    }
    public String getUserInputValue(){
        return userInput.getValue();
    }
    public void displayFields(){
        //getResult().setVisible(true);
        getUserInput().setVisible(true);
        getResetButton().setVisible(true);
        getSubmitButton().setVisible(true);
    }
    public Button getResetButton(){
        return reset;
    }
    public Button getSubmitButton(){
        return submit;
    }
    public void clearFields(){
        getResult().setValue("");
        getUserInput().setValue("");
    }
    public void validateInputs(String conversionToValidate){
        try{
            conversionResult = Double.parseDouble(getUserInputValue());//convert user input to double
            System.out.println("conversionResult is " + conversionResult);
            //if successful
            //result.setValue(Double.toString(conversionResult));//convert it back to String
            switch(conversionToValidate){
                case conversion1:
                    result.setValue(Double.toString(kmToMiles(conversionResult)));
                    break;  
                case conversion2:
                    result.setValue(Double.toString(MilesToKm(conversionResult)));
                    break;
            }
        }
        catch(NumberFormatException numberFormatException){//not a parsable double
            System.err.println("not a number");
            createError("Not numbers!");
        }
        catch(NullPointerException nullPointerException){
            System.err.println("String is null");
            createError("String is null");          
        }
    }
    public void createError(String errorString){
        String error = errorString;
        Notification notif = new Notification(
                error,
                Notification.TYPE_ERROR_MESSAGE
            );
            notif.setDelayMsec(20000);
            notif.show(Page.getCurrent());
    }
    public double kmToMiles(double km){
        double miles = km * 0.62137;
        return miles;
    }
    public double MilesToKm(double miles){
        double km = miles * 1.6;
        return km;
    }
}

The thing that stands out about your converter is that apart from the initial disp;ay the whole thing is one big class with GUI, validation, logic all piled up in one big heap. Zero architecture, not even any structure. You can get away with that for such a trivial app, but it will not scale. The very first thing you should consider is separation of user interface from logic.
Because this isn't homework, I can show you a couple of examples to illustarte that if you want; one simple hard-wired centigrate/farenheit converter, and a more interesting generic converter that gets its info from a Units enum.

Thanks, sorry I thought that separation (GUI and component class) was enough.
I've never been lucky enough to have Java homework!
Yes, I would very much appreciate some examples, so I can refactor the converter. Before that though, what do you mean by architecture and structure?

separation of user interface from logic

Right, so you're basically saying that buttons and input fields should be in a class and click handlers and validation in another one?
thanks

OK, here's the first example - separation of logic and UI.
First we code the logic as a class (or package) with no UI.
This temperature converter holds a temperature in degrees Kelvin and lets you set/get its value in F, C, or K

class TemperatureModel {

    // represents a temperature in C F or `K, 
    // allows get/set in Kelvins, Centigrade or Farenheit
    private double kelvin = 293; // Internal representatioun is degrees Kelvin,
    // default value = 20C

    public void setC(int c) {
        setK(c + 273);
    }

    public int getC() {
        return (int) kelvin - 273;
    }

    public void setF(int f) {
        setK(5 * (f - 32) / 9.0 + 273);
    }

    public int getF() {
        return (int) (9 * (kelvin - 273) / 5 + 32);
    }

    public void setK(double k) {
        kelvin = k;
        notifyChangeListeners();
    }

    public double getK() {
        return kelvin;
    }

    ...
}

We can test that by calling the methods and printing the result, or we can use it from a command-line interface, a GUI or a web server.

Note the call to notifyChangeListeners() - this is so GUIs in particular can update them selves when the value changes, regardless of who changed it. The implementation (replaces the ... line above) is simple and standard

    // change listener implementation...
    private final ArrayList<ChangeListener> listeners = new ArrayList<>();

    public void addChangeListener(ChangeListener listener) {
        listeners.add(listener);
        listener.stateChanged(new ChangeEvent(this));
    }

    public void removeChangeListener(ChangeListener listener) {
        listeners.remove(listener);
    }

    private void notifyChangeListeners() {
        ArrayList<ChangeListener> temp = new ArrayList<>(listeners);
        for (ChangeListener c : temp) {
            c.stateChanged(new ChangeEvent(this));
        }
    }

OK, now for a GUI. Labels and entry fields.

class Window1 extends JFrame {

    private final TemperatureModel tm;

    private final JTextField inC = new JTextField(3);
    private final JTextField inF = new JTextField(3);

    Window1(TemperatureModel t) {
        tm = t;

        // create window...
        setLayout(new GridLayout(2, 2));
        add(new JLabel("Centigrade:"));
        add(inC);
        add(new JLabel("Farenheit:"));
        add(inF);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

when the text fields change, update the temperature (with simple validation)...

        // when the text fields change, update the model...
        new TextChangeListener(inC, () -> {
            if (!inC.isFocusOwner()) {
                return; // this was a program update, not the user's
            }
            inC.setBackground(Color.WHITE);
            try {
                int temp = Integer.parseInt(inC.getText());
                tm.setC(temp);
            } catch (java.lang.NumberFormatException e) {
                inC.setBackground(Color.RED);
            }
        });

... and similarly for the inF field

The TextChangeListener is just a little wrapper for Swing's tedious DocumentListener interface - it just runs the code whenever any cahnge is made to the text (I'll post the code for that at the end)

Finally we listen for any changes in the temperature model, and update the display accordingly

       // when the model changes, uodate the text fields
        tm.addChangeListener(
                e -> SwingUtilities.invokeLater(
                        () -> {
                            if (!inC.isFocusOwner()) {
                                // don't mess with the field where the user is
                                inC.setText(tm.getC() + "");
                            }
                            if (!inF.isFocusOwner()) {
                                inF.setText(tm.getF() + "");
                            }
                        })
        );

Why not just update all the fields directly when the user types? Becuase that may not be the only way the temperature changes. This demo continues witha second window that has sliders for C and F. Change any field or slider and they all stay in step.
Here's the whole runnable file for you to paste into your IDE and enjoy...:

import java.awt.Color;
import java.awt.GridLayout;
import java.util.ArrayList;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;

public class TemperatureApp {

    public static void main(String[] args) {
        TemperatureModel tm = new TemperatureModel();
        new Window1(tm).setVisible(true);
        new Window2(tm).setVisible(true);
    }
}

class TemperatureModel {

    // represents a temperature in C F or `K, 
    // allows get/set in Kelvins, Centigrade or Farenheit
    private double kelvin = 293; // Internal representatioun is degrees Kelvin,
    // default value = 20C

    public void setC(int c) {
        setK(c + 273);
    }

    public int getC() {
        return (int) kelvin - 273;
    }

    public void setF(int f) {
        setK(5 * (f - 32) / 9.0 + 273);
    }

    public int getF() {
        return (int) (9 * (kelvin - 273) / 5 + 32);
    }

    public void setK(double k) {
        kelvin = k;
        notifyChangeListeners();
    }

    public double getK() {
        return kelvin;
    }

    // change listener implementation...
    private final ArrayList<ChangeListener> listeners = new ArrayList<>();

    public void addChangeListener(ChangeListener listener) {
        listeners.add(listener);
        listener.stateChanged(new ChangeEvent(this));
    }

    public void removeChangeListener(ChangeListener listener) {
        listeners.remove(listener);
    }

    private void notifyChangeListeners() {
        ArrayList<ChangeListener> temp = new ArrayList<>(listeners);
        for (ChangeListener c : temp) {
            c.stateChanged(new ChangeEvent(this));
        }
    }
}

class Window1 extends JFrame {

    private final TemperatureModel tm;

    private final JTextField inC = new JTextField(3);
    private final JTextField inF = new JTextField(3);

    Window1(TemperatureModel t) {
        tm = t;

        // create window...
        setLayout(new GridLayout(2, 2));
        add(new JLabel("Centigrade:"));
        add(inC);
        add(new JLabel("Farenheit:"));
        add(inF);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // when the text fields change, update the model...
        new TextChangeListener(inC, () -> {
            if (!inC.isFocusOwner()) {
                return; // this was a program update, not the user's
            }
            inC.setBackground(Color.WHITE);
            try {
                int temp = Integer.parseInt(inC.getText());
                tm.setC(temp);
            } catch (java.lang.NumberFormatException e) {
                inC.setBackground(Color.RED);
            }
        });

        new TextChangeListener(inF, () -> {
             if (!inF.isFocusOwner()) {
                return; // this was a program update, not the user's
            }
            inF.setBackground(Color.WHITE);
            try {
                int temp = Integer.parseInt(inF.getText());
                tm.setF(temp);
            } catch (java.lang.NumberFormatException e) {
                inF.setBackground(Color.RED);
            }
        });

        // when the model changes, uodate the text fields
        tm.addChangeListener(
                e -> SwingUtilities.invokeLater(
                        () -> {
                            if (!inC.isFocusOwner()) {
                                // don't mess with the field where the user is
                                inC.setText(tm.getC() + "");
                            }
                            if (!inF.isFocusOwner()) {
                                inF.setText(tm.getF() + "");
                            }
                        })
        );
    }

}

class Window2 extends JFrame {

    private final TemperatureModel tm;

    private final JSlider sliderC = new JSlider(-40, 120);
    private final JSlider sliderF = new JSlider(-40, 240);

    Window2(TemperatureModel t) {
        tm = t;

        // create window...
        setLayout(new GridLayout(2, 2));
        add(new JLabel("Centigrade:"));
        add(sliderC);
        sliderC.setMajorTickSpacing(20);
        sliderC.setPaintTicks(true);
        sliderC.setPaintLabels(true);
        add(new JLabel("Farenheit:"));
        sliderF.setMajorTickSpacing(40);
        sliderF.setPaintTicks(true);
        sliderF.setPaintLabels(true);
        add(sliderF);
        pack();
        setLocationRelativeTo(null);
        setLocation(getX() + 120, getY() + 120); // offset from WIndow1
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // when sliders are changed by the user, update the model...
        sliderC.addChangeListener(
                e -> {
                    if (sliderC.isFocusOwner()) {
                        tm.setC(sliderC.getValue());
                    }
                }
        );

        sliderF.addChangeListener(
                e -> {
                    if (sliderF.isFocusOwner()) {
                        tm.setF(sliderF.getValue());
                    }
                }
        );

        // when the model changes, update the sliders...
        tm.addChangeListener(
                e -> SwingUtilities.invokeLater(
                        () -> {
                            if (!sliderC.isFocusOwner()) {
                                sliderC.setValue(tm.getC());
                            }
                            if (!sliderF.isFocusOwner()) {
                                sliderF.setValue(tm.getF());
                            }
                        })
        );

    }

}

class TextChangeListener implements DocumentListener {
    // little utility class to hide DocumentListener details
    // listens for any changes the text component's document
    // and if it was caused by the user
    // uns the runnable whenever the text changes.

    private final JTextComponent t;
    private final Runnable r;

    public TextChangeListener(JTextComponent t, Runnable r) {
        this.t = t;
        this.r = r;
        t.getDocument().addDocumentListener(this);
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        if (t.isFocusOwner()) {
            r.run();
        }
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        if (t.isFocusOwner()) {
            r.run();
        }
    }

    @Override
    public void insertUpdate(DocumentEvent e) {
        if (t.isFocusOwner()) {
            r.run();
        }
    }
}

Thanks for the code, unfortunately I have hava 7 so it won't run, but nevermind, the main thing is the code and how it is arranged.
So, maybe we should devise a plan then to give a better structure to my converter.
Should I follow your example and do it in steps, like, creating a class that just make the conversion and then test it in the console?

These are the classes I think I can have, let me know if this is reasonable
1)a class doing the conversions, containing the actual conversion plus all the other methods like, clearing the inputs, setters and getters : is the approach I took in my code OK, meaning a kmToMiles method, a MilesToKm one and any other I need?
2)a class with the various components like radio buttons, input fields etc
3)a UI class - which comes as default for each maven project - and which will attach the components above to the UI
4)validation class

OK. If you MUST use Java 7 then so be it, but really it's dangerous to stay on old versions. If there is any possible way to get that up-to-date then you should take it.

To run that under Java 7 you just have to replace the closures with anonymous inner classes. If I get time I'll post some code for you. If not, what you would have seen is that changing any entry field or slider instantly updates all the others, even in separate windows.

For your code:

Should I follow your example and do it in steps, like, creating a class that just make the conversion and then test it in the console?

Yes. Although you don't even need the console. Just code a quick main method that calls the setters and prints the getters with some sensible test values. That's all you need to make sure your logic is working before you start on any user interface. ("Divide and conquer"!)

In this case I think a Validation class may be a bit too much - all you need is to catch the exception when parsing text input to numeric. Moving part of that into a different class/method would make the code longer. The code I posted shows how easy it is.

You may want to re-think your milesToKm and kmToMiles methods. Suppose you have inches, cm, feet, yards and meters. How many conversion methods will you need? It scales On^2
That's why my code used a set/get approach with a set & get method for each unit, that scales On

I can't believe I;m doing this... converting code back to Java 7 so somebody can run it on a machine exposed to unpatched security problems in an obsolete unsupported Java.
eg: I want to have a ChangeListener that executes an updateAll() method on the Swing thread.
In Java that's

        converter.addChangeListener(
                e -> SwingUtilities.invokeLater(this::updateAll)
        );

but in the bad old days that was

        converter.addChangeListener(
                new ChangeListener() {
                    @Override
                    public void stateChanged(ChangeEvent e) {
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                updateAll();
                            }
                        });
                    }
                });

did we really want to live with code that includes this syntatic horror nightmare (yes this is exactly what the relevant part of the code is)...
) ; } } ) ; } } ) ;

yuk

thanks, I will start with the coding then.
About Java 7 vs 8, you're absolutely right, but the company I work for is still using 7, and it will move to 8 apparently at the end of the year. The think is, at home, since they use java 7 I decided to use java 7 too, but maybe I should upgrade to 8 (I think I have the jdk7 installed and not 8)

Yes. You can leave the build settings in Eclipse to be Java 7 for compatibility in the unlikely event of a problem, even if you are using a Java 8 JDK. It has always been a feature of new Java versions that you can set the compile options to be any (recent) earlier version in case of any bizarre incompatibilty. The only exception is things that have had to change to fix a security problem - in which case you should update your code anyway.

Either way, I did a Java 7 version of my second demo app, so it's available in case of need. (The second version is the one that handles unit data dynamically so you can convert between any number of compatible units for any kind of measure). We can look at that soon, if you want.

thanks, I will look at reorganising my code in the first few classes first, and make sure that they work. About the conversion methods then, yes I will be using setters and getters pretty much like I did in my own class. I looked at your code as well of course, but I'm not sure how do you make the application scalable when it comes to converting values? You can get/set the values of the fields but you still need to make the actual conversion, so, for example, in the case of km to miles, you still need the formula, or are you thinking to have one general method taking, for example, the km and the value it needs to be multiplied for in order to get the miles?

Yes. For any given measure (weight, length etc) all the units can be defined in terms of a "base" unit (meter, kilogram etc). So all you need to know is the multiplier to use. Temperature is a bit more interesting because there's also an offset (0 F = -32 C). My second demo uses a UnitDefinition class (unit name, multiplier, offset) and a Conversion class that holds a value in a base unit and a number of UnitDefinitions, and allows you to set/get its value in any of those units.
I can post their code later, but here's how you use them...

        Converter uc = new Converter("Degrees");
        uc.addUnit(new UnitDefinition("K"));  // base unit is degrees Kelvin
        uc.addUnit(new UnitDefinition("C", 1, -273.15)); // multiplier, offset
        uc.addUnit(new UnitDefinition("F", 9.0 / 5.0, -459.67));

        ...

        uc.set("C", 0);
        System.out.println(uc.get("F")); // should be 32
        uc.set("F", 212);
        System.out.println(uc.get("C")); // should be 100
        System.out.println(uc.get("K")); // should be 373.15

OK, so I've done a bit of thinking and started coding. As you might know, when you start a maven project you get automatically a UI class, which I leave on the side for now, but I reckon that class will have the labels and all the GUI fields.
To start with, I created a ConverterModel class, which should contain the conversion logic.
So far it contains setters and getters, but I run into a problem:

package com.vaadin.project.converterRedone;

import com.vaadin.ui.CustomComponent;

public class ConverterModel extends CustomComponent {

    private double unitToConvert;
    private double multiplier;
    public void setUnitToConvert(double unitToConvert){
        this.unitToConvert = unitToConvert;     
    }
    public void setMultiplier(double multiplier){
        this.multiplier = multiplier;
    }
    private void doConversion(){
        //TODO:do the conversion
    }
}

So the problem is that to do the conversion I don't have to multiply all the time, but occasionally divide. So, say, to convert miles to km I multiply miles by 1.6, to convert km to miles I multiply km by 0.62 etc. If it was always a multiplication I could easily do it in the doConversion method, but to convert metres to yards I will have to divide metres by 0.9144: what I'm trying to say is that it's difficult to keep this doConversion a general method because I have to perform divisions and multiplications depending on what I want to convert to. So I will have to have a mechanism in place to decide whether it's going to be a multiplication or division. Does it make sense?

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.