modèle de composant java Swing personnalisé, UIDelegate, format de composant


On m'a confié la tâche de créer un composant swing personnalisé. J'ai mon composant qui fonctionne correctement dans une application de test qui inclut JSlider qui est utilisé pour zoomer et dézoomer sur une image. Cependant, je suis obligé de présenter mon composant personnalisé dans un modèle, UIDelegate et un format de classe de composant et je suis totalement perdu sur la façon de convertir mon code afin qu'il suive ce format. Voici le code de mon application de test.

package test;

import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.*;

import java.io.File;
import java.net.URL;

import javax.imageio.ImageIO;

public class ZoomDemo extends JComponent implements ChangeListener {

JPanel gui;
/**
 * Displays the image.
 */
JLabel imageCanvas;
Dimension size;
double scale = 1.0;
private BufferedImage image;

public ZoomDemo() {
    size = new Dimension(10, 10);
    setBackground(Color.black);
    try {
         image = ImageIO.read(new File("car.jpg"));
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

public void setImage(Image image) {
    imageCanvas.setIcon(new ImageIcon(image));
}

public void initComponents() {
    if (gui == null) {
        gui = new JPanel(new BorderLayout());
        gui.setBorder(new EmptyBorder(5, 5, 5, 5));
        imageCanvas = new JLabel();
        JPanel imageCenter = new JPanel(new GridBagLayout());
        imageCenter.add(imageCanvas);
        JScrollPane imageScroll = new JScrollPane(imageCenter);
        imageScroll.setPreferredSize(new Dimension(300, 100));
        gui.add(imageScroll, BorderLayout.CENTER);
    }
}

public Container getGui() {
    initComponents();
    return gui;
}

public void stateChanged(ChangeEvent e) {
    int value = ((JSlider) e.getSource()).getValue();
    scale = value / 100.0;
    paintImage();
}

protected void paintImage() {

    int imageWidth = image.getWidth();
    int imageHeight = image.getHeight();
    BufferedImage bi = new BufferedImage(
            (int)(imageWidth*scale), 
            (int)(imageHeight*scale), 
            image.getType());
    Graphics2D g2 = bi.createGraphics();
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
    at.scale(scale, scale);
    g2.drawRenderedImage(image, at);
    setImage(bi);
}

public Dimension getPreferredSize() {
    int w = (int) (scale * size.width);
    int h = (int) (scale * size.height);
    return new Dimension(w, h);
}

private JSlider getControl() {
    JSlider slider = new JSlider(JSlider.HORIZONTAL, 1, 500, 50);
    slider.setMajorTickSpacing(50);
    slider.setMinorTickSpacing(25);
    slider.setPaintTicks(true);
    slider.setPaintLabels(true);
    slider.addChangeListener(this);
    return slider;
}

public static void main(String[] args) {
    ZoomDemo app = new ZoomDemo();
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(app.getGui());
    app.setImage(app.image);

    // frame.getContentPane().add(new JScrollPane(app));  
    frame.getContentPane().add(app.getControl(), "Last");
    frame.setSize(700, 500);
    frame.setLocation(200, 200);
    frame.setVisible(true);
}
}

Le code suivant est le format de classe dont j'ai besoin suivre

Classe de composants

package component;

import javax.swing.JComponent;

import javax.swing.JSlider;
import javax.swing.plaf.ComponentUI;

public class ProgressBar extends JComponent {

public static ComponentUI createUI(JComponent c) {
    return new ZoomUI();
}

public void installUI(JComponent c){



}

public void uninstallUI (JComponent c){

}
}

Classe de modèle

public class ZoomModel extends JSLider  {



}

Classe UIDelegate

public class ZoomUI extends ComponentUI implements ChangeListener{

}

Toute aide sur la façon dont je peux implémenter mon composant personnalisé dans ce format serait grandement appréciée. Je suis très nouveau sur Swing et la documentation que j'ai trouvée sur des composants personnalisés a été très déroutante et de peu d'aide.

Application d'essai

package test;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.event.*;

import java.io.File;
import java.net.URL;

import javax.imageio.ImageIO;

import component.ZoomComponent;

public class ZoomDemo  extends JPanel implements PropertyChangeListener, ActionListener {

ZoomComponent zoomer;
JPanel board;
private BufferedImage image;

public ZoomDemo( ) {
    super(true);  
    setLayout(new BorderLayout( )); 
    board = new JPanel(true); 
    board.setPreferredSize(new Dimension(300, 300)); 
    board.setBorder(new LineBorder(Color.black, 5));

    zoomer = new ZoomComponent();
    add(board, BorderLayout.NORTH);
    add(zoomer, BorderLayout.SOUTH);



}


@Override
public void actionPerformed(ActionEvent arg0) {
    // TODO Auto-generated method stub

}

@Override
public void propertyChange(PropertyChangeEvent arg0) {
    // TODO Auto-generated method stub

}

public static void main(String[] args) {

    UIManager.getDefaults().put("ZoomComponentUI", "component.BasicZoomUI");
    ZoomDemo s= new ZoomDemo();
    JFrame frame = new JFrame("Sample Sketch Application"); 
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    frame.setContentPane(s); 
    frame.pack( ); 
    frame.setVisible(true);
}

}

Author: Wheels99, 2014-11-07

1 answers

D'accord, donc c'était une aventure amusante dans des parties de l'API que je n'utilise pas :), commencez par lire Comment écrire un composant Swing personnalisé et ses liens associés, cela vous donnera le travail au sol pour comprendre ce qui est sur le point de se passer...

Modèle

L'interface

Personnellement, je commence toujours par une interface, la vie est meilleure avec les interfaces et cela vous donne plus de flexibilité. Maintenant, à partir de quel modèle devriez-vous étendre (en fonction de votre exigence)...?

Eh bien, le meilleur choix que j'ai pu trouver était le BoundedRangeModel, qui est également utilisé par le JSlider...cela signifie en fait que je peux non seulement passer ce modèle à la vue, mais à un {[9] } et sans aucun travail supplémentaire, demandez au curseur de changer l'image!! Gagnant-gagnant

import java.awt.Dimension;
import java.awt.Image;
import javax.swing.BoundedRangeModel;

public interface ZoomModel extends BoundedRangeModel {

    public Image getImage();

    public Dimension getScaledSize();

}

Le résumé

Ensuite, j'aime faire une version abstraite, c'est là que je mets la fonctionnalité "commune", qui est susceptible d'être la même pour la plupart des implémentations, dans ce cas, ce ne sera peut-être pas le cas requis, mais je suis finckle comme ça...

import java.awt.Dimension;
import java.awt.Image;
import javax.swing.DefaultBoundedRangeModel;

public abstract class AbstractZoomModel extends DefaultBoundedRangeModel implements ZoomModel {

    public AbstractZoomModel() {
        super(100, 0, 0, 200);
    }

    @Override
    public Dimension getScaledSize() {
        Dimension size = new Dimension(0, 0);
        Image image = getImage();
        if (image != null) {
            double scale = getValue() / 100d;
            size.width = (int) Math.round(image.getWidth(null) * scale);
            size.height = (int) Math.round(image.getHeight(null) * scale);

        }
        return size;
    }

}

Donc, vous pouvez voir ici, j'ai défini quelques propriétés de base, un niveau de zoom de départ de 100, un niveau max de 200 et un niveau minimum de 0, plus j'ai implémenté le getScaledSize, qui est un peu utilisé et facilite la vie...

La valeur par défaut...

Maintenant, parce que nous aimons être gentils, nous fournissons une implémentation "par défaut" du modèle. C'est assez basique en ce sens que tout ce qu'il fait prend une référence à un image...

import java.awt.Image;

public class DefaultZoomModel extends AbstractZoomModel {
    Image image;

    public DefaultZoomModel(Image image) {
        this.image = image;
    }

    @Override
    public Image getImage() {
        return image;
    }

}

Vous pouvez créer des implémentations qui téléchargent des images à partir d'un URL par exemple...

La vue

Ok, c'est le composant lui-même, qui est ajouté à votre interface utilisateur. Il contient les fonctionnalités de base nécessaires pour construire et préparer le délégué de l'interface utilisateur et gérer le modèle. La principale chose d'intérêt ici est l'utilisation du support de changement de propriété pour fournir une notification de la modification du modèle, c'est important car vous le ferez voir...

import java.awt.Color;
import java.awt.Dimension;
import javax.swing.JComponent;
import javax.swing.UIManager;

public class ZoomComponent extends JComponent {

    private static final String uiClassID = "ZoomComponentUI";
    private ZoomModel model;

    public ZoomComponent() {
        setBackground(Color.black);
        setFocusable(true);
        updateUI();
    }

    public void setModel(ZoomModel newModel) {
        if (model != newModel) {
            ZoomModel old = model;
            this.model = newModel;
            firePropertyChange("model", old, newModel);
        }
    }

    public ZoomModel getModel() {
        return model;
    }

    @Override
    public Dimension getPreferredSize() {
        ZoomModel model = getModel();
        Dimension size = new Dimension(100, 100);
        if (model != null) {
            size = model.getScaledSize();
        }
        return size;
    }

    public void setUI(BasicZoomUI ui) {
        super.setUI(ui);
    }

    @Override
    public void updateUI() {
        if (UIManager.get(getUIClassID()) != null) {
            ZoomUI ui = (ZoomUI) UIManager.getUI(this);
            setUI(ui);
        } else {
            setUI(new BasicZoomUI());
        }
    }

    public BasicZoomUI getUI() {
        return (BasicZoomUI) ui;
    }

    @Override
    public String getUIClassID() {
        return uiClassID;
    }
}

Le délégué de l'interface utilisateur

Maintenant, les autres trucs amusants...Si nous suivons la convention standard, vous fourniriez normalement un concept abstract du délégué de l'interface utilisateur, par exemple...

import javax.swing.plaf.ComponentUI;

public abstract class ZoomUI extends ComponentUI {       
}

À partir de là, d'autres délégués vont grandir...

Délégué de base de l'interface utilisateur

La convention suggérerait normalement de fournir une implémentation "de base", en faisant beaucoup de travail, mais en permettant à d'autres implémentations de sauter dans le changement probablement

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;

public class BasicZoomUI extends ZoomUI {

    private ZoomComponent zoomComponent;
    private MouseAdapter mouseHandler;
    private ChangeListener changeHandler;

    private Action zoomIn;
    private Action zoomOut;
    private PropertyChangeListener propertyChangeHandler;

    protected ChangeListener getChangeHandler() {
        if (changeHandler == null) {
            changeHandler = new ChangeHandler();
        }
        return changeHandler;
    }

    protected void installMouseListener() {
        mouseHandler = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                zoomComponent.requestFocusInWindow();
            }

            @Override
            public void mouseWheelMoved(MouseWheelEvent e) {
                int amount = e.getWheelRotation();
                ZoomModel model = zoomComponent.getModel();
                if (model != null) {

                    int value = model.getValue();
                    model.setValue(value + amount);

                }
            }

        };
        zoomComponent.addMouseListener(mouseHandler);
        zoomComponent.addMouseWheelListener(mouseHandler);

    }

    protected void installModelPropertyChangeListener() {

        propertyChangeHandler = new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                ZoomModel old = (ZoomModel) evt.getOldValue();
                if (old != null) {
                    old.removeChangeListener(getChangeHandler());
                }
                ZoomModel newValue = (ZoomModel) evt.getNewValue();
                if (newValue != null) {
                    newValue.addChangeListener(getChangeHandler());
                }
            }
        };

        zoomComponent.addPropertyChangeListener("model", propertyChangeHandler);

    }

    protected void installKeyBindings() {

        zoomIn = new ZoomInAction();
        zoomOut = new ZoomOutAction();

        InputMap inputMap = zoomComponent.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "zoomIn");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "zoomOut");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "zoomIn");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "zoomOut");

        ActionMap actionMap = zoomComponent.getActionMap();
        actionMap.put("zoomIn", zoomIn);
        actionMap.put("zoomOut", zoomOut);
    }

    protected void installModelChangeListener() {

        ZoomModel model = getModel();
        if (model != null) {
            model.addChangeListener(getChangeHandler());
        }

    }

    @Override
    public void installUI(JComponent c) {

        zoomComponent = (ZoomComponent) c;

        installMouseListener();
        installModelPropertyChangeListener();
        installKeyBindings();
        installModelChangeListener();

    }

    protected void uninstallModelChangeListener() {

        getModel().removeChangeListener(getChangeHandler());

    }

    protected void uninstallKeyBindings() {

        InputMap inputMap = zoomComponent.getInputMap(JComponent.WHEN_FOCUSED);
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0), "donothing");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0), "donothing");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0), "donothing");
        inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0), "donothing");

        AbstractAction blank = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
            }
        };

        ActionMap actionMap = zoomComponent.getActionMap();
        actionMap.put("zoomIn", blank);
        actionMap.put("zoomOut", blank);

    }

    protected void uninstallModelPropertyChangeListener() {

        zoomComponent.removePropertyChangeListener(propertyChangeHandler);
        propertyChangeHandler = null;

    }

    protected void uninstallMouseListener() {

        zoomComponent.removeMouseWheelListener(mouseHandler);
        mouseHandler = null;

    }

    @Override
    public void uninstallUI(JComponent c) {

        uninstallModelChangeListener();
        uninstallModelPropertyChangeListener();
        uninstallKeyBindings();
        uninstallMouseListener();

        mouseHandler = null;
        zoomComponent = null;

    }

    @Override
    public void paint(Graphics g, JComponent c) {
        super.paint(g, c);
        paintImage(g);
    }

    protected void paintImage(Graphics g) {
        if (zoomComponent != null) {
            ZoomModel model = zoomComponent.getModel();
            Image image = model.getImage();
            Dimension size = model.getScaledSize();
            int x = (zoomComponent.getWidth() - size.width) / 2;
            int y = (zoomComponent.getHeight() - size.height) / 2;
            g.drawImage(image, x, y, size.width, size.height, zoomComponent);
        }
    }

    public static ComponentUI createUI(JComponent c) {
        return new BasicZoomUI();
    }

    protected ZoomModel getModel() {

        return zoomComponent == null ? null : zoomComponent.getModel();

    }

    protected class ChangeHandler implements ChangeListener {

        @Override
        public void stateChanged(ChangeEvent e) {
            zoomComponent.revalidate();
            zoomComponent.repaint();
        }

    }

    protected class ZoomAction extends AbstractAction {

        private int delta;

        public ZoomAction(int delta) {
            this.delta = delta;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            ZoomModel model = getModel();
            if (model != null) {
                model.setValue(model.getValue() + delta);
            }
        }

    }

    protected class ZoomOutAction extends ZoomAction {

        public ZoomOutAction() {
            super(-5);
        }

    }

    protected class ZoomInAction extends ZoomAction {

        public ZoomInAction() {
            super(5);
        }

    }

}

De là, vous pouvez aller concevoir des implémentations spécifiques à la plate-forme, mais j'ai décidé de rester avec le délégué de base...

Tout mettre ensemble

Si cela ne suffisait pas, avant de pouvoir l'utiliser, vous devez installer le délégué...

UIManager.getDefaults().put("ZoomComponentUI", "your.awesome.package.name.BasicZoomUI");

Nb: Changez your.awesome.package.name pour refléter votre nom de paquet réel...

Exemple exécutable

 import java.awt.BorderLayout;
 import java.awt.Dimension;
 import java.awt.EventQueue;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.io.File;
 import java.io.IOException;
 import javax.imageio.ImageIO;
 import javax.swing.JFrame;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
 import javax.swing.JSlider;
 import javax.swing.UIManager;
 import javax.swing.UnsupportedLookAndFeelException;

 public class TestZoom100 {

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

      public TestZoom100() {
           EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                     try {
                          UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                     } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                          ex.printStackTrace();
                     }

                     UIManager.getDefaults().put("ZoomComponentUI", "your.awesome.package.name.BasicZoomUI");

                     try {
                          DefaultZoomModel model = new DefaultZoomModel(ImageIO.read(new File("/your/awesome/image.jpg")));
                          model.setValue(50);
                          ZoomComponent zoomComp = new ZoomComponent();
                          zoomComp.setModel(model);

                          JSlider slider = new JSlider(model);

                          JFrame frame = new JFrame("Testing");
                          frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                          frame.add(new JScrollPane(zoomComp));
                          frame.add(slider, BorderLayout.SOUTH);
                          frame.pack();
                          frame.setLocationRelativeTo(null);
                          frame.setVisible(true);
                     } catch (IOException exp) {
                          exp.printStackTrace();
                     }
                }
           });
      }

 }

N'oubliez pas de changer le nom du paquet pour le BasicZoomUI au nom du paquet que vous avez stocké dans et spécifie réellement un fichier image;)

 6
Author: MadProgrammer, 2014-11-07 09:17:01