且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

自定义java Swing组件模型、UIDelegate、组件格式

更新时间:2023-01-11 16:02:59

好的,这是对我不使用的 API 部分的一次有趣冒险:),首先阅读 如何编写自定义Swing 组件 及其相关链接,这将为您提供了解即将发生的事情的基础工作...

Okay, so that was a fun adventure into parts of the API I don't use :), start by having a read through How to Write a Custom Swing Component and it's associated links, this will give you the ground work to understand what is about to happen...

就我个人而言,我总是从一个界面开始,界面让生活更美好,它给你更多的灵活性.现在,您应该从哪个模型扩展(根据您的要求)...?

Personally, I always start with an interface, life is better with interfaces and it gives you more flexibility. Now, which model should you extend from (based on your requirements)...?

嗯,我能找到的***的选择是 BoundedRangeModel,它也被 JSlider 使用......这实际上意味着我不仅可以通过这个模型到视图,但是到 JSlider 并且没有任何额外的工作,让滑块更改图像!双赢

Well, the best choice I could find was the BoundedRangeModel, which is also used by the JSlider...this actually means that I can not only pass this model to the view, but to a JSlider and without any extra work, have the slider change the image!! Win-Win

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

public interface ZoomModel extends BoundedRangeModel {

    public Image getImage();

    public Dimension getScaledSize();

}

摘要

接下来,我喜欢制作一个抽象版本,这是我放置通用"功能的地方,对于大多数实现来说这可能是相同的,在这种情况下,它可能不是必需的,但我很喜欢这...

The Abstract

Next, I like to make an abstract version, this is where I put "common" functionality, which is likely to be the same for most implementations, in this case, it might not be required, but I'm finckle like this...

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;
    }

}

所以,你可以在这里看到,我已经定义了一些基本属性,100 的起始缩放级别,200 的最大级别和 200 的最小级别code>0,另外我已经实现了 getScaledSize,它使用了一点,让生活更轻松......

So, you can see here, I've defined some basic properties, a starting zoom level of 100, a max level of 200 and a minimum level of 0, plus I've implemented the getScaledSize, which is used a bit and makes life easier...

现在,因为我们喜欢友好,所以我们提供了模型的默认"实现.这是非常基本的,因为它所做的一切都需要对图像的引用......

Now, because we like been nice, we provide a "default" implementation of the model. This is pretty basic in that all it does it takes a reference to an 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;
    }

}

例如,您可以创建从 URL 下载图像的实现...

You could create implementations that download images from an URL for example...

好的,这是实际的组件本身,它被添加到您的 UI 中.它包含构建和准备 UI 委托以及管理模型所需的基本功能.这里最重要的事情是使用属性更改支持来提供模型更改的通知,正如您将看到的,这很重要...

Okay, this is the actually component itself, which gets added to your UI. It contains the basic functionality need to construct and prepare the UI delegate and manage the model. The key thing of interest here is the use of the property change support to provide notification of the change to the model, this is important as you will see...

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;
    }
}

UI 代理

现在其他有趣的东西...如果我们遵循标准约定,您通常会提供 UI 委托的 abstract 概念,例如...

import javax.swing.plaf.ComponentUI;

public abstract class ZoomUI extends ComponentUI {       
}

由此,其他代表将成长...

From this, other delegates will grow...

约定通常会建议您提供一个基本"实现,完成很多繁重的工作,但允许其他实现有机会跳入可能的变化

Convention would normally suggest you provide a "basic" implementation, doing a lot of the heavy lifting, but allowing other implementations the opportunity to jump in change things to there likely

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);
        }

    }

}

从这里你可以去设计特定于平台的实现,但我决定坚持使用基本委托......

From here you could go and devise platform specific implementations, but I've decided to stick with the basic delegate...

如果这还不够,在您使用任何一个之前,您必须安装委托...

If that wasn't enough, before you can use any of it, you must install the delegate...

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

nb:更改 your.awesome.package.name 以反映您的实际包名称...

nb: Change your.awesome.package.name to reflect your actual package name...

 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();
                     }
                }
           });
      }

 }

不要忘记将 BasicZoomUI 的包名更改为您存储它的包名并实际指定一个图像文件;)

Don't forget to change the package name for the BasicZoomUI to the package name you have it stored in and actually specify a image file ;)