Modifica dinamica dell'interfaccia utente sulle variazioni di valore nella casella di riepilogo

3

Ho una casella di riepilogo con diversi elementi, che si tratti di server Web (tomcat, iis ecc.). Per ciascun valore della casella di riepilogo, l'interfaccia utente deve avere visualizzazioni diverse. Ad esempio, se scegliamo IIS, vengono visualizzati i campi del nome utente e della password. Se scegliamo tomcat, alcuni campi aggiuntivi appaiono a seconda del sistema operativo utente - IP e porta per linux e percorso per windows.

Il mio approccio attuale è il seguente: - Ho creato due enumerazioni. Il primo per sistemi operativi diversi (windows, linux, mac os x. Il secondo per server: iis, nginx, tomcat ecc. - Ho un metodo che restituisce il valore enum del sistema operativo. - Quando l'utente effettua una scelta, la determino e, in base a questa scelta, controllo il sistema operativo dell'utente. E dopo aver determinato quali elementi dell'interfaccia utente nascondere / aggiungere.

Ecco una sorta di pseudo-codice per quello che ho:

//OS enum
public enum OS {
    WINDOWS, LINUX, MAC_OS_X;
}

//Servers enum
public enum SERVERS {
    TOMCAT, IIS, NGINX;
}

//Server Listbox Handler
private void chooseServer() {
        //...get value from list box
        switch(server) {
            case TOMCAT:
                changeUI(getOS()); //getOS() returns OS enum value
                break;
            case IIS:
                //IIS is for windows only
                changeUI(OS.WINDOWS); 
                break;
            case NGNIX:
                //Temporary support only linux
                changeUI(OS.LINUX);
                break;
        }
    }

//UI changes here
private void changeUI(OS os) {
        switch (os) {
            case WINDOWS:
                userNameField.setEnabled(true);
                userPasswordField.setEnabled(true);
                ipField.setVisible(false);
                portField.setVisible(false);
                serverPathField.setVisible(true);
                break;
            case LINUX:
                userNameField.setEnabled(true);
                userPasswordField.setEnabled(true);
                ipField.setVisible(true);
                portField.setVisible(true);
                serverPathField.setVisible(false);
                break;
            case MAC_OS_X:
                userNameField.setEnabled(false);
                userPasswordField.setEnabled(false);
                ipField.setVisible(false);
                portField.setVisible(false);
                serverPathField.setVisible(false);
                break;
        }
    }

Non mi piace il momento in cui verranno aggiunti altri SO o nuovi server. Questi operatori di switch cresceranno di più e diventeranno più complicati, non parlo di manutenibilità di questo codice. Esistono approcci (modelli) standard per tali compiti che aiutano a evitare switch / if ... else grow e rendere il codice più generico, espandibile, leggibile e supportabile?

Ho provato ad implementare il pattern Adapter suggerito da Neil e ho funzionato in un momento oscuro. Ecco alcuni pseudo-codice:

public abstract class EditorAdapter {
    protected boolean firstInit = true;
    public abstract void adapt(Panel panel);
}

//--------

public class IISWindowsEditorAdapter extends EditorAdapter {
    @Override
    public void adapt(Panel panel) {
        if(firstInit) {
            firstInit = false;

            panel.add(userNameField);
            //add other elements
        }
    }
}

//--------

public class TomcatEditorAdapter extends EditorAdapter {
    private OS os = getOS();

    @Override
    public void adapt(Panel panel) {
        if(firstInit) {
            firstInit = false;

            if(OS == OS.LINUX) {
                panel.add(userNameField);
                //add other elements
            } else if (OS == OS.WINDOWS) {
                //add elements
            } //... else if
        }
    }
}

//--------

public class Editor extends DialogEditor { //extends Dialog with protected Panel field (subPanel)
    public Editor() {
        initialize();
    }

    public void initialize() {
        //...
        main.add(createPanel()); //protected VerticalPanel main;
        //...
    }

    public void createPanel() {
        //listBox initialization
        commonPanel.add(listBox); //panel with common elements
        listBox.addClickHandler(new ChangeHandler() {
            @Override
            public void onChange(ChangeEvent event) {
                selectServer();
            });
        }
        //... add other common elements
    }

    private void selectServer() {
        //... get selection
        switch(server) {
            //Here something should be done in order to control different layouts.
            //At the moment, each choice adds more elements in addition to existing ones
            //As a variant, one can create class fields for each adapter and manipulate with them using lazy initialization
            case IIS:
                new WindowsEditorAdapter().adapt(subpanel);
                break;
            case TOMCAT:
                new TomcatEditorAdapter().adapt(subpanel);
                break;
        }
    }
}

Il problema è che ho diverse tabelle / pannelli nel mio editor che alla fine vengono aggiunti al pannello principale. In generale, sembra:

  • apriamo una finestra di dialogo

  • scegli server web

  • alcuni elementi cambiano all'interno della finestra di dialogo in base alla nostra scelta (e alcune scelte sono influenzate dall'uso dell'OS dell'utente).

Ecco perché la prima inizializzazione dell'interfaccia utente con adattatore non funziona qui. Almeno non ho capito come implementare questo adattatore per la logica sopra.

    
posta Dragon 23.04.2013 - 10:50
fonte

3 risposte

3

Nota : Modificato per riflettere i requisiti modificati di Dragon per farlo funzionare sulla selezione.

Un modello comune per generalizzare questo tipo di cose è un adattatore. Avere una classe astratta chiamata GUIAdapter che può armeggiare con i singoli aspetti del tuo programma in base alle impostazioni:

public abstract class GUIAdapter {
    public abstract void adapt(GUIFrame frame);
    public abstract void remove(GUIFrame frame);
}

Quindi sovrascrivi GUIAdapter con adattatori adatti per ogni tipo di situazione:

public class WindowsGUIAdapter extends GUIAdapter {
    public void adapt(GUIFrame frame) {
        // Things to do when windows operating system
    }
    public void remove(GUIFrame frame) {
        // Remove trace of WindowsGUIAdapter
    }
}

public class LinuxGUIAdapter extends GUIAdapter {
    public void adapt(GUIFrame frame) {
        // Things to do when linux operating system
    }
    public void remove(GUIFrame frame) {
        // Remove trace of LinuxGUIAdapter 
    }
}

public class TomcatGUIAdapter extends GUIAdapter {
    public void adapt(GUIFrame frame) {
        // Things to do when using tomcat server engine
    }
    public void remove(GUIFrame frame) {
        // Remove trace of TomcatGUIAdapter 
    }
}

// et cetera ...

Questi adattatori hanno il controllo più o meno come si lascia loro avere sopra la cornice. Quando si conosce il sistema operativo o il server, si chiama "remove" su tutti gli adattatori esistenti, si aggiunge l'adattatore appropriato per la selezione corrente, quindi si chiama "adatta" per applicare la configurazione per la selezione corrente. Ti consiglio di aggiungere un pannello al frame e di rimuoverlo quando viene chiamato "remove". Tieni un riferimento a JPanel come membro privato della tua classe estesa di GUIAdapter per poterlo rimuovere facilmente in seguito.

Si noti che non ho creato un WindowsTomcatGUIAdapter. L'idea è che la tua classe GUIFrame (presumo che sia ciò che viene chiamato) ospita un elenco di adattatori che vengono richiamati per modificare lo stato del frame in base alla selezione senza sapere come funzionano.

public class GUIFrame extends JFrame {
    // Other protected variables here
    private List<GUIAdapter> adapters = new ArrayList<GUIAdapter>();

    private JList serverList, osList;

    private SERVERS selectedServer = null;
    private OS selectedOS = null;


    public GUIFrame() {
        initialize();
    }

    private void initialize() {
        // Do things common to all here

        // Operating system select event
        ListSelectionListener refreshOptionsListener = new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent listSelectionEvent) {
                selectedOS = (OS)osList.getSelectedValue();
                selectedServer = (SERVERS)serverList.getSelectedValue();

                refreshAdapters();
            }
        };

        // Instantiate osList
        osList = new JList();
        // ...
        osList.addListSelectionListener(refreshOptionsListener);

        // Instantiate serverList
        serverList = new JList();
        // ...
        serverList.addListSelectionListener(refreshOptionsListener);
    }

    private void refreshAdapters() {
        for(GUIAdapter adapter : adapters) {
            adapter.remove(this);
        }
        adapters.clear();

        if(os == OS.WINDOWS) {
            adapters.add(new WindowsGUIAdapter());
        } else if (os == OS.LINUX) {
            adapters.add(new LinuxGUIAdapter());
        } // else if () ...

        if(server == SERVERS.TOMCAT) {
            adapters.add(new TomcatGUIAdapter());
        } // else if () ...

        for(GUIAdapter adapter : adapters) {
            adapter.adapt(this);
        }    
    }

    // Other class logic here
}

Ora hai disaccoppiato tutta la logica relativa al sistema operativo o al motore con le classi che eseguono questa logica. Ogni volta che viene selezionato un nuovo sistema operativo o un nuovo server, gli adattatori correnti vengono rimossi e vengono aggiunti nuovi adattatori. Se nessun sistema operativo o server è selezionato, non viene aggiunto alcun adattatore (nessuna condizione è soddisfatta se / else catena poiché selectOS o selectedServer sarebbe null), che va bene. Il telaio non è adattato poiché non è richiesto alcun adattamento come ci si aspetterebbe.

Questo schema funziona bene solo se gli adattatori del server non hanno realmente bisogno di sapere quale sistema operativo viene utilizzato o inversamente, se gli adattatori del sistema operativo non hanno realmente bisogno di sapere quale server viene utilizzato. Tuttavia, se ti trovi in quella situazione, dovresti creare un WindowsTomcatGUIAdapter che si occupi solo di quel particolare caso piuttosto che tentare di far funzionare bene WindowsGUIAdapter e TomcatGUIAdapter (in quanto ciò creerebbe ancora una volta l'accoppiamento). Le aggiunte future sono letteralmente semplici come aggiungere una nuova classe e collegarle all'inizializzazione.

Presumibilmente, è possibile estendere questa logica per includere la convalida o qualsiasi altro tipo di logica particolare per quel particolare adattatore.

    
risposta data 23.04.2013 - 12:37
fonte
1

Immagino che quello che stai cercando sia una specie di tabella delle decisioni . Per ogni possibile combinazione di sistema operativo, server e elemento GUI memorizzano un flag booleano che indica la visibilità. Potresti semplicemente utilizzare un array booleano tridimensionale ( bool[][][] uiVisibility ) per quell'attività, indicizzato da enumerazioni indicanti il sistema operativo, il server e l'elemento dell'interfaccia utente specifico. E il codice di inizializzazione della GUI sarà simile a questo:

    userNameField.setEnabled(uiVisibility[osIndex][serverIndex][UIelem.userNameField.getValue()]);
    userPasswordField.setEnabled(uiVisibility[osIndex][serverIndex][UIelem.userPasswordField.getValue()]);

Come vedi, nessun interruttore / caso più e il codice di impostazione della visibilità si trova in un unico punto.

Si noti che è molto più probabile che l'elenco degli elementi della GUI cresca rispetto all'elenco dei sistemi operativi o dei server, almeno fino a quando non è necessario distinguere tra diverse versioni del sistema operativo o versioni del server.

EDIT: se questo è lo strumento giusto per il lavoro dipende. In primo luogo, una tabella decisionale è sicuramente molto meglio gestibile per duplicare lo stesso codice setEnabled 12 volte, con piccole modifiche tra questi blocchi (si noti che il suggerimento di Neil non risolve questo problema, lo nasconde semplicemente perché non mostra la // Things to do parte nella sua risposta)

La chiave è come inizializzare la tabella. Sono disponibili diverse opzioni, ad esempio utilizzando l'inizializzazione di array standard. Se hai solo piccole differenze tra le 12 combinazioni, c'è anche la possibilità di creare un'inizializzazione predefinita per tutti i blocchi nella tabella delle decisioni (o un'inizializzazione predefinita per ciascun sistema operativo) e aggiungere qualche piccolo codice scrivendo solo le modifiche per combinazioni specifiche di Server / OS.

    
risposta data 23.04.2013 - 12:45
fonte
1

Se la tua configurazione è composta da valori booleani, penso che quello che potresti avere è la seguente struttura che comprende un BitSet :

class OSConfiguration {
  private BitSet configurationFields;

  public OSConfiguration() {
    configurationFields = new BitSet();
  }

  // you can allow checking if a given bitset index is set or not
  public boolean isFieldEnabled(int index) { return configurationFields.get(index); }

  // ... or create proper getter/setters
  public boolean isUserNameFieldEnabled() { return configurationFields.get(3); }
  public void setUserNameFieldEnabled(boolean value) { configurationFields.set(3, value); }
  // all other fields could be done like that
}

E la seguente TreeMap :

TreeMap map = new TreeMap<SERVERS, Map<OS, OSConfiguration>>();

// to check if an OS configuration is available for a given server:
boolean isAvailable = map.get(SERVERS.TOMCAT).containsKey(OS.WINDOWS);

// to get an entire configuration:
OSConfiguration configuration = map.get(SERVERS.TOMCAT).get(OS.WINDOWS);
// to check if a given configuration is enabled:
boolean isEnabled = map.get(SERVERS.TOMCAT).get(OS.WINDOWS).isUserNameFieldEnabled();

Ora, anche se la configurazione del sistema operativo deve avere più di un semplice valore booleano, dato che hai nascosto quello nella classe OSConfiguration, puoi fare quello che vuoi.

In ogni caso, ti suggerisco di mantenerlo semplice.

    
risposta data 24.04.2013 - 15:16
fonte

Leggi altre domande sui tag