如何将JSpinner的边框更改为圆角可调整的自定义彩色边框i

时间:2019-07-13 00:09:29

标签: java swing jspinner

我想自定义JSpinner,以为其提供一个自定义边框,该边框具有可调整的颜色,可调整的边框厚度以及可调整的半径的圆角。这样我就可以设置微调框的边框并完成操作。

我的微调代码如下:

protected JSpinner createLabelledUpDownControl(JComponent parent, int initialValue, int minVal, int maxVal, String topLabelString, Font topLabelFont, Rectangle topLabelBounds, String topSubLabelString, Font topSubLabelFont, Rectangle topSubLabelBounds,String eachLabelString, Font eachLabelFont, Rectangle eachLabelBounds, String bottomLabelString, Font bottomLabelFont, Rectangle bottomLabelBounds ){
        @SuppressWarnings("serial")
        JSpinner spinner = new JSpinner(new SpinnerNumberModel(initialValue, minVal, maxVal, 1)){
            @Override
            public void paint(Graphics g){
                super.paint(g);
                Graphics2D g2D = (Graphics2D) g.create();
                RenderingHints qualityHints =  new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
                qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
                g2D.setRenderingHints(qualityHints);  
            }
        };
        //spinner.setBorder(BorderFactory.createLineBorder(new Color(37, 54, 142), 4, true));

        //spinner.setBorder(new RoundedColouredBorder(30, new Color(37, 54, 142), 4));

        spinner.setBorder(new RoundedBorder(30, new Color(37, 54, 142), 4));

        spinner.setBounds(0, 0, parent.getWidth(), parent.getHeight());
        spinner.setFont(UI.getRegularArgentumSansFont().deriveFont(Font.BOLD, 88));
        spinner.setUI(new JSpinnerArrow(parent));


        JSpinner.DefaultEditor spinnerEditor = (JSpinner.DefaultEditor)spinner.getEditor();
        spinnerEditor.getTextField().setHorizontalAlignment(JTextField.CENTER);

        JComponent comp = spinner.getEditor();
        JFormattedTextField field = (JFormattedTextField) comp.getComponent(0);
        DefaultFormatter formatter = (DefaultFormatter) field.getFormatter();
        formatter.setCommitsOnValidEdit(true);

    if(parent != null){
            parent.add(spinner);
        }

        return spinner;
    }

,然后给我的微调器自定义箭头类,如下所示:

我设置了箭头的尺寸,以便它们的大小更改为我想要的大小。我认为这一切都非常简单明了。但是,当我尝试为箭头按钮设置自定义边框,并且尝试为整个微调框也自定义边框时,就会发生我的问题。

private class JSpinnerArrow extends BasicSpinnerUI {

        private JComponent parent;

        public JSpinnerArrow(JComponent parent){
            this.parent = parent;
        }

        @Override
        protected Component createNextButton() {
            Component c = createArrowButton("/arrow-upDB.png");
            c.setName("Spinner.nextButton");
            installNextButtonListeners(c);
            return c;
        }

        @Override
        public void paint(Graphics g, JComponent component){
            super.paint(g, component);
            Graphics2D g2D = (Graphics2D) g.create();
            RenderingHints qualityHints =  new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
            qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
            g2D.setRenderingHints(qualityHints);  
        }

        @Override
        protected Component createPreviousButton() {
            Component c = createArrowButton("/arrow-downDB.png");
            c.setName("Spinner.previousButton");
            installPreviousButtonListeners(c);
            return c;
        }

        private Component createArrowButton(String filename) {
            Image icon = UI.loadImage(filename);
            if(icon != null){
                JButton b = createButton(null, "", "", null);
                b.setIcon(new ImageIcon(icon));
                //b.setBorder(BorderFactory.createLineBorder(new Color(37, 54, 142), 4));
                b.setBackground(null);
                b.setBorder(new RoundedBorder(30, new Color(37, 54, 142), 4));
                b.setPreferredSize(new Dimension(65,160));
                return b;
            }
            return createButton(null, "", "", null);
        }
     }

我对这个结果进行了以下尝试:请注意Spinner Text Area是如何向内裁剪的(我相信它也是被奇怪地拉伸了……并且在Spinner的最右边没有绘制边框。 类的微调结果:RoundedBorder

enter image description here

 public static class RoundedBorder implements Border {
    private int radius;
    private int thickness;
    private Color color;
    public RoundedBorder(int radius, Color color, int thickness) {
        this.radius = radius;
        this.thickness = thickness;
        this.color = color;
    }

    public Insets getBorderInsets(Component c) {
        return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
    }

    public boolean isBorderOpaque() {
        return true;
    }

    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {

        g.setColor(color);
        Graphics2D g2 = (Graphics2D) g;
        RenderingHints qualityHints =  new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON );
        qualityHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
        g2.setRenderingHints(qualityHints);   
        g2.setStroke(new BasicStroke((float)thickness));
        g.drawRoundRect(thickness, thickness, c.getSize().width - 2*thickness, c.getSize().height - 2*thickness, radius, radius);
        g2.setClip(thickness, thickness, width, height);
    }
}

我还尝试了以下方法来绘制边框:这使我得到了以下结果:Spinner来自类:RoundedColouredBorder的结果

enter image description here

这次,由于某种原因,边框不干净,“微调器文本”区域被裁剪到微调器边界中,使它的外部圆形边缘奇数,但在尖锐的边缘内部。 (不是我想要的),再也没有在微调器的右侧绘制边框。

public static class RoundedColouredBorder implements Border {
        private int radius;
        private int thickness;
        private Color color;

        public RoundedColouredBorder(int radius, Color borderColor, int thickness) {
            this.radius = radius;
            this.color = borderColor;
            this.thickness = thickness;
        }

        public Insets getBorderInsets(Component c) {
            return new Insets(this.thickness+1, this.thickness+1, this.thickness+2, this.thickness);
        }

        public boolean isBorderOpaque() {
            return true;
        }

        public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
            Dimension arcs = new Dimension(radius, radius);

            Graphics2D graphics = (Graphics2D) g;
            graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

            //Draws the rounded panel with borders.
            graphics.setColor(color);
            graphics.fillRoundRect(0, 0, width + thickness, height + thickness, arcs.width, arcs.height); //paint background
            graphics.drawRoundRect(0, 0, width - thickness, height  -thickness, arcs.width, arcs.height); //paint border

        }
    }

我要画的是以下内容:

所需结果

enter image description here

因此,基本上,对于我想要的结果,我希望在整个微调器周围使用圆角边框,并在每个箭头按钮周围使用圆角边框,可以调整其颜色,边框的粗细和拐角半径。

在我上面的2次尝试中,使用RoundedBorder和RoundedColouredBorder类使剪辑变得很奇怪,并且边框不像我想要的那样干净。从RoundedBorder类获得的结果似乎将白色微调器文本区域切成较小的大小,并以非常奇怪的方式重新提取它。我在做什么错?

1 个答案:

答案 0 :(得分:1)

大约在这篇文章的中间是我的答案的代码,但我想先与您分享找出答案的工作,以使您确信这是我的最佳解决方案(至少) 能找到。这样就可以了:

  • 您需要在圆弧半径可调的按钮和微调器周围绘制边框。因此,您需要定义自己的边框,因为没有其他边框可以这样做。实际上,LineBorder是我能找到的最接近的数字,因为存在一个构造参数 roundedCorners ,该参数以不可调节的值对边界角的圆弧半径进行倒圆。研究了LineBorder的功能之后,我认为您可以放心地将其子类化为始终具有圆角并且圆弧半径也可调整。因此,您只需要覆盖paintBorder即可始终绘制圆角,然后为弧半径创建一个setter和getter。
  • 您需要一个具有自定义形状的按钮(例如,根据您的问题为RounRectangle2D)。这意味着不仅将使用自定义形状进行绘制,而且还将使用该自定义形状来定义鼠标光标是否位于按钮上方。正如我所发现的(这意味着我可能错了,但这是我的最佳尝试),第二部分有两个选择:

    1. 覆盖ComponentUI.contains以定义按钮内的点。这意味着子类化ComponentUI(或更恰当地子类化ButtonUI以便能够设置按钮的UI)。
    2. 覆盖Component.contains以定义按钮内的点。这意味着子类化Component(或更合适的是,在这种情况下子类化JButton)。

    您可能想知道这两个选项中的哪个实际上用来定义按钮内的点。很好,两者都是,因为ComponentUI.contains的默认实现将委托给Component.contains。尽管如此,第二种选择似乎已经更好,因为它看起来更像是与PLAF无关的。但是,对于第一部分,还需要仅在定义的形状内而不是在其(正方形)范围内绘制按钮。这意味着覆盖按钮的paintupdate(这意味着将JComponent子类化,或更恰当地JButton进行子类化)来设置自定义剪辑。因此,这导致我们继承JButton的子类,并立即解决了这两个问题(可能还加上了PLAF)。

  • 您需要一个自定义形状的微调器。根据我们需要对JButton类进行子类化的原因,我们还需要对JSpinner类进行子类化以提供自定义形状。
  • 在您期望的结果中,我还注意到两个按钮之间存在间隙。通过搜索JSpinner及其UI的实现,我还知道JSpinner中添加了3个组件(因为它是普通的Container):编辑器,下一个按钮和上一个按钮。那么,谁来负责设置添加到容器中的组件的位置和大小?...其LayoutManager。因此,您还需要为此使用自定义LayoutManager,这将在将按钮放置在微调器中时增加按钮之间的间隙。 LayoutManger的{​​{1}}的当前实现可以在BasicSpinnerUI中作为JSpinner类找到。如果您想使用自己的自定义Handler扩展其操作,我只是想告诉您。在本文的代码中,我还基于LayoutManager类实现了自定义LayoutManager
  • 在对Handler及其UI的实现进行了更多搜索之后,我发现微调器的3个组件的创建如下:

    1. 根据SpinnerModel,在JSpinner本身内部创建编辑器。然后,微调器的UI(使用JSpinner.getEditor)获得了微调器的编辑器并对其进行了初始化。
    2. 下一个按钮实际上是在微调器的用户界面(JSpinner)中创建的,然后添加到微调器中。
    3. 上一个按钮也是在微调器的UI中创建的,然后添加到微调器中。

    因此需要创建BasicSpinnerUI的子类,并覆盖BasicSpinnerUI.createPreviousButtonBasicSpinnerUI.createNextButton以返回我们创建的具有自定义形状的自定义BasicSpinnerUI子类。

    < / li>
  • 最后,我将微调器按钮的创建从JButton移到了微调器。这将使我们能够在自定义微调器中使用getter和setter导出按钮,就像在默认实现BasicSpinnerUI中已经导出了编辑器一样。只需修改自定义JSpinner即可从BasicSpinnerUIcreatePreviousButton中的自定义微调器中获取自定义按钮,就像BasciSpinnerUI.createEditorcreateNextButton的编辑器一样。这将使按钮的创建后(例如即时)自定义更加容易。

注意,我使类尽可能独立,这意味着:

  1. 自定义JSpinner子类可用于常规BasicSpinnerUI
  2. 自定义JSpinner将对具有3个名称分别为“编辑器”,“上一个”和“下一个”的三个组件的容器进行布局。
  3. 自定义LayoutManager本身可以很好地工作,而无需任何自定义JSpinner
  4. 自定义BasicSpinnerUI可以很好地运行。
  5. 自定义LineBorder可以正常运行。它只是具有自定义形状的JButton

但是上述所有内容都需要结合起来以创建所需的结果。

最终观察:

根据JComponent.isOpaque的文档“ 不透明组件在其矩形边界内绘制每个像素。”。很好, rectangular 关键字是一个问题,因为我们需要微调器(及其两个按钮,其编辑器及其文本字段)具有自定义形状。因此,请确保在微调器上,其两个按钮上,微调器的编辑器上以及微调器的编辑器的文本字段上调用setOpaque(false),因为在每种情况下我们都绘制并像自定义形状一样进行操作。

总结一下,一些有效的代码:

JButton

请注意,我研究的是弧的宽度和高度,而不是半径。

和示例输出:

Sample output

很明显,您需要将背景颜色设置为白色,而不是图片中的红色,但是我将其保留为红色只是为了演示它的外观。

我在此答案中未解决的问题

好吧,微调器编辑器的文本字段以及编辑器本身,由于需要的形状而变成怪异的形状。因此,您将需要在左侧提供圆角,在右侧提供方形角!这意味着自定义Shape(可能是形状的结合,例如Area),它将定义哪些点位于文本字段/编辑器内。在这种情况下,import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.Shape; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JSpinner; import javax.swing.JSpinner.DefaultEditor; import javax.swing.JTextField; import javax.swing.SpinnerModel; import javax.swing.SpinnerNumberModel; import javax.swing.border.LineBorder; import javax.swing.plaf.basic.BasicSpinnerUI; public class Main { //This is a LineBorder only that it always paints a RoundRectangle2Ds instead of Rectangle2Ds. public static class CustomLineBorder extends LineBorder { private double arcw, arch; public CustomLineBorder(Color color, int thickness, double arcw, double arch) { super(color, thickness); this.arcw = arcw; this.arch = arch; } //Note: the implementation of this paintBorder is inspired by the superclass. @Override public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { if ((thickness > 0) && (g instanceof Graphics2D)) { Graphics2D g2d = (Graphics2D) g; Color oldColor = g2d.getColor(); g2d.setColor(lineColor); Path2D path = new Path2D.Double(Path2D.WIND_EVEN_ODD); path.append(new RoundRectangle2D.Double(x, y, width, height, thickness, thickness), false); path.append(new RoundRectangle2D.Double(x + thickness, y + thickness, width - 2 * thickness, height - 2 * thickness, arcw, arch), false); g2d.fill(path); g2d.setColor(oldColor); } } public void setArcWidth(double arcw) { this.arcw = arcw; } public void setArcHeight(double arch) { this.arch = arch; } public void setLineColor(Color lineColor) { this.lineColor = lineColor; } public double getArcWidth() { return arcw; } public double getArcHeight() { return arch; } } public static class CustomJButton extends JButton { private double arcw, arch; public CustomJButton(double arcw, double arch) { this.arcw = arcw; this.arch = arch; } public void setArcWidth(double arcw) { this.arcw = arcw; revalidate(); //Not sure if needed. repaint(); } public void setArcHeight(double arch) { this.arch = arch; revalidate(); //Not sure if needed. repaint(); } public double getArcWidth() { return arcw; } public double getArcHeight() { return arch; } @Override public Dimension getPreferredSize() { //Here you set the preferred size of the button to something which takes into account the arc width and height: Dimension sz = super.getPreferredSize(); sz.width = Math.max(sz.width, Math.round((float) getArcWidth())); sz.height = Math.max(sz.height, Math.round((float) getArcHeight())); return sz; } //Note that the width/height/arcw/arch of the component are not constant. Thats why we create a new instance of RoundRectangle2D.Double every time... protected Shape createShape() { return new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), getArcWidth(), getArcHeight()); } //Paint only inside the createShape's Shape: @Override public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setClip(createShape()); super.paint(g2d); g2d.dispose(); } //Update only inside the createShape's Shape: @Override public void update(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setClip(createShape()); super.update(g2d); g2d.dispose(); } //Tell which points are inside this button: @Override public boolean contains(int x, int y) { return createShape().contains(x, y); } } //The implementation of this subclass is inspired by the private static class Handler of the BasicSpinnerUI: public static class CustomJSpinnerLayout implements LayoutManager { private final int gap; //You can make this non-final and add setter and getter, but remember //to call revalidate() on the spinner whenever you change this gap of this class... private Component nextButton; private Component previousButton; private Component editor; public CustomJSpinnerLayout(int gap) { this.gap = gap; nextButton = null; previousButton = null; editor = null; } //Only recognizes 3 components ("Next", "Previous" and "Editor"). Others are not layed out. @Override public void addLayoutComponent(String constraints, Component c) { switch (constraints) { case "Next": nextButton = c; break; case "Previous": previousButton = c; break; case "Editor": editor = c; break; } } @Override public void removeLayoutComponent(Component c) { if (c == nextButton) nextButton = null; else if (c == previousButton) previousButton = null; else if (c == editor) editor = null; } @Override public Dimension preferredLayoutSize(Container parent) { return minimumLayoutSize(parent); } //Only recognizes 3 components ("Next", "Previous" and "Editor"). Others are not taken into account. @Override public Dimension minimumLayoutSize(Container parent) { Dimension next = nextButton.getPreferredSize(); Dimension prev = previousButton.getPreferredSize(); Dimension edit = editor.getPreferredSize(); Insets pari = parent.getInsets(); int totalHeight = Math.max(edit.height, next.height + prev.height + gap); int buttonMaxWidth = Math.max(next.width, prev.width); return new Dimension(buttonMaxWidth + edit.width + pari.left, totalHeight + pari.top + pari.bottom); } //Only recognizes 3 components ("Next", "Previous" and "Editor"). Others are not layed out. @Override public void layoutContainer(Container parent) { if (editor != null || nextButton != null || previousButton != null) { //Warning: does not account for component orientation (eg leftToRight or not). Dimension prnt = parent.getSize(); Dimension next = nextButton.getPreferredSize(); Dimension prev = previousButton.getPreferredSize(); Insets i = parent.getInsets(); int maxButtonWidth = Math.max(next.width, prev.width); int buttonHeight = Math.round((prnt.height - gap) / 2f); editor.setBounds(i.left, i.top, prnt.width - i.left - i.right - maxButtonWidth, prnt.height - i.top - i.bottom); nextButton.setBounds(prnt.width - maxButtonWidth, 0, maxButtonWidth, buttonHeight); previousButton.setBounds(prnt.width - maxButtonWidth, prnt.height - buttonHeight, maxButtonWidth, buttonHeight); } } } public static class CustomBasicSpinnerUI extends BasicSpinnerUI { //Works like createEditor() of BasicSpinnerUI, in that it gets the spinner's button from the spinner itself. @Override protected Component createPreviousButton() { if (spinner instanceof CustomJSpinner) { CustomJButton prev = ((CustomJSpinner) spinner).getButtonPrevious(); prev.setInheritsPopupMenu(true); //Inspired by the code of the private BasicSpinnerUI.createArrowButton(). prev.setName("Spinner.previousButton"); //Required by the code of BasicSpinnerUI.createPreviousButton(). installPreviousButtonListeners(prev); //Required by the code of BasicSpinnerUI.createPreviousButton(). return prev; } return super.createPreviousButton(); //If this UI is added to a non CustomJSpinner, then return default implementation. } //Works like createEditor() of BasicSpinnerUI, in that it gets the spinner's button from the spinner itself. @Override protected Component createNextButton() { if (spinner instanceof CustomJSpinner) { CustomJButton next = ((CustomJSpinner) spinner).getButtonNext(); next.setInheritsPopupMenu(true); //Inspired by the code of the private BasicSpinnerUI.createArrowButton(). next.setName("Spinner.nextButton"); //Required by the code of BasicSpinnerUI.createNextButton(). installNextButtonListeners(next); //Required by the code of BasicSpinnerUI.createNextButton(). return next; } return super.createNextButton(); //If this UI is added to a non CustomJSpinner, then return default implementation. } //Creates the default LayoutManager for the JSpinner. //Could be replaced by a call to setLayout on the custom JSpinner. @Override protected LayoutManager createLayout() { return new CustomJSpinnerLayout(8); } } public static class CustomJSpinner extends JSpinner { private CustomJButton next, prev; //Maintain a reference to the buttons, just like the JSpinner does for the editor... private double arcw, arch; public CustomJSpinner(SpinnerModel model, double arcw, double arch) { super(model); this.arcw = arcw; this.arch = arch; next = new CustomJButton(arcw, arch); prev = new CustomJButton(arcw, arch); } public void setButtonPrevious(CustomJButton prev) { this.prev = prev; revalidate(); repaint(); } public void setButtonNext(CustomJButton next) { this.next = next; revalidate(); repaint(); } public CustomJButton getButtonPrevious() { return prev; } public CustomJButton getButtonNext() { return next; } public void setArcWidth(double arcw) { this.arcw = arcw; revalidate(); //Not sure if needed. repaint(); } public void setArcHeight(double arch) { this.arch = arch; revalidate(); //Not sure if needed. repaint(); } public double getArcWidth() { return arcw; } public double getArcHeight() { return arch; } //Note that the width/height/arcw/arch of the component are not constant. Thats why we create a new instance of RoundRectangle2D.Double every time... protected Shape createShape() { return new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), arcw, arch); } //Paint only inside the createShape's Shape: @Override public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setClip(createShape()); Color old = g2d.getColor(); g2d.setColor(getBackground()); g2d.fillRect(0, 0, getWidth(), getHeight()); g2d.setColor(old); super.paint(g2d); g2d.dispose(); } //Update only inside the createShape's Shape: @Override public void update(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setClip(createShape()); Color old = g2d.getColor(); g2d.setColor(getBackground()); g2d.fillRect(0, 0, getWidth(), getHeight()); g2d.setColor(old); super.update(g2d); g2d.dispose(); } //Tell which points are inside this spinner: @Override public boolean contains(int x, int y) { return createShape().contains(x, y); } } private static void initCustomJButton(CustomJButton cjb, String text, Color nonRolloverBorderColor, Color rolloverBorderColor, int borderThickness) { cjb.setOpaque(false); //Mandatory. cjb.setText(text); //Could be setIcon... //All the folllowing steps of this method are optional (remove them, edit them, etc as you like). //Add a CustomLineBorder to the CustomJButton (upon your request): CustomLineBorder clb = new CustomLineBorder(nonRolloverBorderColor, borderThickness, cjb.getArcWidth(), cjb.getArcHeight()); cjb.setBorder(clb); //Create the mouse rollover effect of changing the color of the border of the button when the mouse hovers over the button: cjb.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent mevt) { clb.setLineColor(rolloverBorderColor); cjb.repaint(); } @Override public void mouseExited(MouseEvent mevt) { clb.setLineColor(nonRolloverBorderColor); cjb.repaint(); } }); } public static void main(String[] args) { //Setup parameters: double arcw = 50, arch = 50; int borderThickness = 2; Color borderMainColor = Color.CYAN.darker(), buttonRolloverBorderColor = Color.CYAN; //Create the spinner: CustomJSpinner spin = new CustomJSpinner(new SpinnerNumberModel(), arcw, arch); //Customizing spinner: spin.setUI(new CustomBasicSpinnerUI()); //Mandatory first step! spin.setOpaque(false); //Mandatory. spin.setBorder(new CustomLineBorder(borderMainColor, borderThickness, spin.getArcWidth(), spin.getArcHeight())); //Upon your request. spin.setPreferredSize(new Dimension(200, 200)); //Optional. spin.setBackground(Color.RED); //Obviously needs to be changed to "Color.WHITE", but for demonstration let it be "Color.RED". //Customizing spinner's buttons: initCustomJButton(spin.getButtonNext(), "Next", borderMainColor, buttonRolloverBorderColor, borderThickness); initCustomJButton(spin.getButtonPrevious(), "Prev", borderMainColor, buttonRolloverBorderColor, borderThickness); //Customizing spinner's editor: JComponent editor = spin.getEditor(); editor.setOpaque(false); //Mandatory. if (editor instanceof DefaultEditor) { JFormattedTextField jftf = ((DefaultEditor) editor).getTextField(); jftf.setOpaque(false); //Mandatory. jftf.setHorizontalAlignment(JTextField.CENTER); //Upon your request. //jftf.setFont(new Font(Font.MONOSPACED, Font.ITALIC, 25)); } JFrame frame = new JFrame("Customized JSpinner"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(spin); frame.pack(); frame.setLocationRelativeTo(null); frame.setVisible(true); } } 的默认编辑器使用JFormattedTextField作为文本字段,并使用JSpinner.DefaultEditor的实例作为编辑器。如前所述,对于自定义微调器和自定义按钮,您有两个选择:子类JSpinner(和JFormattedTextField),或为JSpinner.DefaultEditor / { {1}}。这些解决方案存在一些问题:

  1. JFormattedTextField中没有 setTextField (将被使用)或 createTextField (将被覆盖),并且唯一的方法是将其替换为习惯之一是在编辑器中查找并删除相应的组件,然后添加具有相同特征的新组件。
  2. 如果您想继承JSpinner.DefaultEditor的子类,则还需要继承JSpinner.ListEditorJSpinner.NumberEditorJSpinner.DateEditor的子类,还需要继承JSpinner.DefaultEditor的子类,以覆盖其{ {3}}方法可根据模型返回新类型。

他们(尽可能简单)的解决方案似乎是:

  • 通过子类化JSpinner.DefaultEditor(并覆盖其JSpinner方法)从头开始创建新型编辑器。
  • 为编辑器的文本字段创建自定义createEditor(并覆盖其JComponent方法)。

  • 微调器的默认编辑器的子类BasicFormattedTextFieldUI(并覆盖其contains方法)。
  • 为编辑器的文本字段创建自定义contains(并覆盖其contains方法)。

这些问题似乎可以解决,但将扩大解决方案的范围(并且已经有400多行代码和注释)。因此,我选择不解决这些问题,结果是文本字段和编辑器在圆形微调器内是正方形的。这意味着,如果用户单击文本区域,则存在某些区域(角),文本字段将在该区域中获得焦点,而用户实际上单击的是例如圆形微调框的边框。文本字段和编辑器不会在角落处的圆形微调器上绘制自身,因为我们将它们设置为非透明!背景颜色由微调框本身处理。