Dialogdesign med Java Swing – del 7

Våra tre komponenter ovan har en hel del gemensamt – de behöver varsin datamodell som baseras på en lista de vid behov hämtar från vår dialogmodell med hjälp av vårt komponentfilter och de ska samtliga lyssna på dialogmodellen för att kunna bli notifierade vid förändringar, samt använda modellens fasad för att välja/välja bort/redigera element.

package com.golcher.tidsintervall.komponenter;

import com.golcher.tidsintervall.dialoger.ITidsintervallModellFasad;
import com.golcher.tidsintervall.dialoger.ITidsintervallModellLyssnare;
import com.golcher.tidsintervall.komponenter.data.KomponentFilter;
import com.golcher.tidsintervall.komponenter.data.TidsintervallNyckel;
import com.golcher.tidsintervall.komponenter.data.TidsintervallVo;
import com.golcher.tidsintervall.komponenter.data.TidsintervallWrapper;
import com.golcher.tidsintervall.komponenter.util.TidsintervallBreddRadgivare;

import javax.swing.*;
import java.util.ArrayList;

public abstract class AbstraktTidsintervallDialogKomponent extends JPanel implements ITidsintervallModellLyssnare
{
    private final ITidsintervallModellFasad _modellFasad;
    private final KomponentFilter _komponentFilter;
    private TidsintervallBreddRadgivare _breddRadgivare;

    public AbstraktTidsintervallDialogKomponent
    (
        ITidsintervallModellFasad modellFasad,
        KomponentFilter filter
    )
    {
        _modellFasad = modellFasad;
        _komponentFilter = filter;
        _breddRadgivare = new TidsintervallBreddRadgivare(_modellFasad);

        // Registrera oss som lyssnare på dialogmodellen
        _modellFasad.registreraLyssnare(this);
    }

    protected ArrayList<TidsintervallWrapper> hamtaKomponentDataFranDialogModell()
    {
        return _modellFasad.hamtaModellFor(_komponentFilter);
    }

    protected void valj(TidsintervallNyckel nyckel)
    {
        _modellFasad.valj(nyckel);
    }

    protected void valjBort(TidsintervallNyckel nyckel)
    {
        _modellFasad.valjBort(nyckel);
    }

    protected void uppdateraEgnaIntervallFranRedigerare(ArrayList<TidsintervallVo> egnaIntervall)
    {
        _modellFasad.uppdateraEgnaIntervallFranRedigerare(egnaIntervall);
        _breddRadgivare = new TidsintervallBreddRadgivare(_modellFasad);
    }

    protected int hamtaLampligBredd()
    {
        return _breddRadgivare.hamtaBredd();
    }

}

Vidare behöver de alla anpassa sin bredd efter det bredaste elementet i dialogmodellen, därav den utbrutna klassen TidsintervallBreddRadgivare.

package com.golcher.tidsintervall.komponenter.util;

import com.golcher.tidsintervall.dialoger.ITidsintervallModellFasad;
import com.golcher.tidsintervall.komponenter.data.KomponentFilter;
import com.golcher.tidsintervall.komponenter.data.TidsintervallWrapper;

import javax.swing.*;
import java.util.ArrayList;

public class TidsintervallBreddRadgivare
{
    private int _bredd = 0;

    public TidsintervallBreddRadgivare(ITidsintervallModellFasad modell)
    {
        int storstaBredd = 0;
        ArrayList<TidsintervallWrapper> allaElement = new ArrayList<>();
        allaElement.addAll(modell.hamtaModellFor(KomponentFilter.Standard));
        allaElement.addAll(modell.hamtaModellFor(KomponentFilter.Egen));
        allaElement.addAll(modell.hamtaModellFor(KomponentFilter.Valda));
        for(TidsintervallWrapper wrapper : allaElement)
        {
            JLabel tmp = new JLabel(wrapper.toString());
            int bredd = tmp.getPreferredSize().width + 20;
            if(bredd > storstaBredd) storstaBredd = bredd;
        }
        _bredd = storstaBredd;
    }

    public int hamtaBredd()
    {
        return _bredd;
    }
}

Vi kan nu börja designa valda-panelen ovanpå basklassen. Först behöver vi skapa den ListModel för den JList som vi ska använda. Notera här att vi är i gott sällskap när vi använder oss av lyssnare, vilket ju även Swings egna klasser gör.

package com.golcher.tidsintervall.komponenter.valda;

import com.golcher.tidsintervall.komponenter.data.TidsintervallWrapper;

import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import java.util.ArrayList;

public class ValdaIntervallListModell implements ListModel<TidsintervallWrapper>
{
    private ArrayList<TidsintervallWrapper> _lista;
    private ArrayList<ListDataListener> _lyssnare;

    public ValdaIntervallListModell()
    {
        _lista = new ArrayList<>();
        _lyssnare = new ArrayList<>();
    }

    public void uppdateraModellen(ArrayList<TidsintervallWrapper> nyLista)
    {
        _lista = nyLista;
        for(ListDataListener l : _lyssnare)
        {
            l.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, _lista.size()-1));
        }
    }

    @Override
    public int getSize()
    {
        return _lista.size();
    }

    @Override
    public TidsintervallWrapper getElementAt(int index)
    {
        if(index < 0 || index > _lista.size()-1) throw new ArrayIndexOutOfBoundsException(index);
        return _lista.get(index);
    }

    @Override
    public void addListDataListener(ListDataListener l)
    {
        _lyssnare.add(l);
    }

    @Override
    public void removeListDataListener(ListDataListener l)
    {
        _lyssnare.remove(l);
    }
}

Notera också att modellen ovan skapas tom i konstruktorn. Det är först vid anropet uppdateraModellen som den tilldelas data. Detta är med avsikt, eftersom vi ska låta panelen med vår JList lyssna direkt på vår dialogmodell och vid uppdateringar förmedla informationen till komponentmodellen.

Nu kan vi äntligen färdigställa valda-panelen (till höger i vår dialogskiss överst på sidan)

package com.golcher.tidsintervall.komponenter.valda;

import com.golcher.tidsintervall.dialoger.ITidsintervallModellFasad;
import com.golcher.tidsintervall.komponenter.AbstraktTidsintervallDialogKomponent;
import com.golcher.tidsintervall.komponenter.data.KomponentFilter;
import com.golcher.tidsintervall.komponenter.data.TidsintervallWrapper;

import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;

public class ValdaIntervallPanel extends AbstraktTidsintervallDialogKomponent
{
    private static final String ramRubrik = "Valda intervall";
    private static final String rensaRubrik = "Rensa";
    private static final String taBortRubrik = "Ta bort markerade";

    private ValdaIntervallListModell _komponentModell;
    private JList<TidsintervallWrapper> _valda;

    public ValdaIntervallPanel
    (
        ITidsintervallModellFasad modellFasad
    )
    {
        super(modellFasad, KomponentFilter.Valda);
        this.setBorder
        (
            BorderFactory.createTitledBorder
            (
                BorderFactory.createEtchedBorder(),
                ramRubrik,
                TitledBorder.LEFT,
                TitledBorder.TOP
            )
        );

        this.setLayout(new BorderLayout());

        _komponentModell = new ValdaIntervallListModell();
        _valda = new JList(_komponentModell);
        _valda.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        _valda.setPreferredSize(new Dimension(hamtaLampligBredd(), 100));
        ScrollPane scrollPane = new ScrollPane();
        scrollPane.add(_valda);
        scrollPane.setPreferredSize(new Dimension(hamtaLampligBredd(), 100));
        this.add(scrollPane, BorderLayout.CENTER);

        byggKnappPanel();

        uppdateraLokalModell();
    }

    private void byggKnappPanel()
    {
        JButton rensaKnapp = new JButton(rensaRubrik);
        rensaKnapp.addActionListener(new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                ValdaIntervallPanel.this.rensa();
            }
        });

        JButton taBortKnapp = new JButton(taBortRubrik);
        taBortKnapp.addActionListener(new AbstractAction()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                ValdaIntervallPanel.this.taBort();
            }
        });

        JPanel knappPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        knappPanel.add(rensaKnapp);
        knappPanel.add(taBortKnapp);

        this.add(knappPanel, BorderLayout.SOUTH);
    }

    private void uppdateraLokalModell()
    {
        ArrayList<TidsintervallWrapper> sorteradLista = hamtaKomponentDataFranDialogModell();
        _komponentModell.uppdateraModellen(sorteradLista);
        _valda.setPreferredSize(new Dimension(hamtaLampligBredd(), 100));
        _valda.invalidate();
    }

    public void rensa()
    {
        // Vi kommer uppdatera modellen direkt när vi kör metoden valjBort, så vi måste först lägga över
        // de element som finns i listan i en egen array
        ArrayList<TidsintervallWrapper> elementAttTaBort = new ArrayList<>();
        for(int index=0; index < _komponentModell.getSize(); index++)
        {
            TidsintervallWrapper wrapper = _komponentModell.getElementAt(index);
            elementAttTaBort.add(wrapper);
        }

        // Nu kan vi anropa modellen och förändra, utan att vi snubblar
        // på index som ändras medan vi itererar över innehållet
        for(TidsintervallWrapper wrapper : elementAttTaBort)
        {
            valjBort(wrapper.nyckel());
        }
    }

    public void taBort()
    {
        int index = _valda.getSelectedIndex();
        if(index>=0)
        {
            TidsintervallWrapper wrapper = _komponentModell.getElementAt(index);
            valjBort(wrapper.nyckel());
        }
    }

    @Override
    public void modellenHarUppdaterats()
    {
        uppdateraLokalModell();
    }
}

Dags att ägna nästa pass åt våra valbara-paneler, vi ses!