java - 如何将 JSpinner 的边框更改为具有可调节半径 i 的圆角的自定义彩色边框
问题描述
我想自定义我的 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);
}
}
我已经尝试了以下结果:注意微调器文本区域是如何被向内剪裁的(我相信它也被奇怪地拉伸了......并且边框没有绘制在微调器的最右侧边缘。微调器结果来自类:圆角
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 Result from class: RoundedColouredBorder
这次由于某种原因边框不干净,微调器文本区域剪辑到微调器边框中,使其具有奇怪的外圆边缘但位于尖角边缘内。(不是我想要的)并且再次没有在微调器的右侧绘制边框。
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
}
}
我要画的是以下内容:
期望的结果
所以基本上为了我想要的结果,我想要整个微调器周围的圆形边框和每个箭头按钮周围的圆形边框,我可以调整颜色、边框的厚度和角的半径。
在我使用 RoundedBorder 和 RoundedColouredBorder 类的上述 2 次尝试中,我得到了非常奇怪的剪辑,并且边框不像我想要的那样干净。从 RoundedBorder 类获得的 Result 似乎将白色微调器文本区域切割成更小的尺寸,并以一种非常奇怪的方式重新拉伸它。我做错了什么?
解决方案
在这篇文章的中间部分之后是我的答案的代码,但我想先与你分享找出答案的努力,试图让你相信这是我(至少)能找到的最佳解决方案. 所以这里是:
- 您需要在按钮和微调器周围绘制一个可调节圆弧半径的边框。因此,您需要定义自己的边界,因为没有其他边界可以这样做。实际上这
LineBorder
是我能找到的最接近的,因为有一个构造参数roundedCorners,它以不可调整的值对边界角的圆弧半径进行四舍五入。在研究了 的实现之后LineBorder
,我认为您可以安全地将其子类化为始终具有圆角以及可调节的圆弧半径。所以你只需要覆盖paintBorder
总是画圆角,然后为圆弧半径做一个设置器和吸气器。 您需要一个具有自定义形状的按钮(例如
RounRectangle2D
根据您的问题)。这意味着不仅将使用自定义形状绘制它,而且该自定义形状还将用于定义鼠标光标是否位于按钮上方。我发现(这意味着我可能错了,但这是我最好的尝试)第二部分有两种选择:- 覆盖
ComponentUI.contains
以定义按钮内的点。这意味着子类化ComponentUI
(或更恰当地子类ButtonUI
化以便能够设置按钮的 UI)。 - 覆盖
Component.contains
以定义按钮内的点。这意味着子类化Component
(或更恰当地说,JButton
在这种情况下是子类化)。
您可能想知道这两个选项中的哪一个实际上用于定义按钮内部的点。好吧,两者都有,因为
ComponentUI.contains
委托的默认实现是Component.contains
. 尽管如此,第二种选择似乎已经更好了,因为它看起来更像是独立于解放军的。但是,对于第一部分,您还需要仅在您定义的形状内而不是在其(正方形)边界内绘制按钮。这意味着覆盖paint
和update
按钮(这意味着子类化JComponent
,甚至更恰当地JButton
)来设置自定义剪辑。因此,这导致我们对子类JButton
化并同时解决这两个问题(加上可能独立于 PLAF)。- 覆盖
- 您需要一个具有自定义形状的微调器。继我们需要对类进行子类化的原因之后
JButton
,我们还需要对类进行子JSpinner
类化以提供我们的自定义形状。 - 我还注意到,在您想要的结果中,两个按钮之间存在间隙。我还从搜索
JSpinner
其 UI 的实现中知道,有 3 个组件被添加到JSpinner
(因为它是正常的Container
):编辑器、下一个按钮和上一个按钮。那么谁负责设置添加到容器中的组件的位置和大小?... 它的LayoutManager
. 因此,您还需要对此进行自定义LayoutManager
,这将增加按钮之间的间隙,同时将它们放置在微调器中。的当前实现LayoutManger
可以JSpinner
在BasicSpinnerUI
类中找到Handler
。如果您想使用自己的自定义扩展其操作,我只是让您知道LayoutManager
。在这篇文章的代码中,我还实现了一个自定义LayoutManager
基于Handler
类。 在搜索了更多关于它的实现
JSpinner
及其 UI 之后,我发现微调器的 3 个组件创建如下:- 编辑器是在
JSpinner
自身内部创建的,具体取决于SpinnerModel
. 然后微调器的 UI 获取(使用JSpinner.getEditor
)微调器的编辑器并对其进行初始化。 - 下一个按钮实际上是在微调器的 UI (
BasicSpinnerUI
) 中创建的,然后添加到微调器中。 - 上一个按钮也在微调器的 UI 中创建,然后添加到微调器。
所以这就需要子类化
BasicSpinnerUI
和覆盖BasicSpinnerUI.createPreviousButton
,并BasicSpinnerUI.createNextButton
返回JButton
我们创建的具有自定义形状的自定义子类。- 编辑器是在
- 最后,我将微调器按钮的创建从微调器
BasicSpinnerUI
移至微调器。这将允许我们在自定义微调器中使用 getter 和 setter 导出按钮,就像在默认实现中已导出编辑器一样JSpinner
。只需修改 custom以从andBasicSpinnerUI
中的自定义微调器中获取自定义按钮,就像使用's 编辑器所做的那样。这将允许对按钮进行更轻松的后期创建(例如即时)定制。createPreviousButton
createNextButton
BasciSpinnerUI.createEditor
JSpinner
请注意,我使类尽可能独立,这意味着:
- 自定义
BasicSpinnerUI
子类可用于常规JSpinner
s。 - 自定义
LayoutManager
将布置任何具有 3 个组件的容器,这些组件的名称分别为“Editor”、“Previous”和“Next”... - 自定义
JSpinner
可以自行正常工作,无需任何自定义BasicSpinnerUI
。 - 自定义
LineBorder
工作正常。 - 自定义
JButton
工作正常。它只是JButton
一个自定义形状。
但是以上所有内容都需要结合起来才能创建您想要的结果。
最后观察:
根据JComponent.isOpaque
“一个不透明的组件在其矩形边界内绘制每个像素。 ”的文档。好吧,矩形关键字是个问题,因为我们需要微调器(以及它的两个按钮、它的编辑器和它的文本字段)具有自定义形状。因此,请确保调用setOpaque(false)
微调器、其按钮、微调器编辑器以及微调器编辑器的文本字段,因为我们在每种情况下都像自定义形状一样进行绘制和操作。
总而言之,一些工作代码:
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);
}
}
请注意,我研究的是弧宽和高度而不是半径。
和样本输出:
显然,您需要背景颜色为白色,而不是图片中的红色,但我将其保留为红色只是为了演示它的外观。
我在这个答案中没有解决的问题:
好吧,微调器的编辑器的文本字段以及编辑器本身,由于所需的形状而变成了一种奇怪的形状。所以,你需要左边的圆角,右边的方角!这意味着一个自定义Shape
(可能是形状的联合,例如一个Area
),它将定义哪些点位于文本字段/编辑器内。在这种情况下,默认编辑器JSpinner
使用 aJFormattedTextField
作为文本字段,使用 instanceofJSpinner.DefaultEditor
作为编辑器。如前所述,对于自定义微调器和自定义按钮,您有两个选择:子类化JFormattedTextField
(and ),或为/JSpinner.DefaultEditor
创建自定义 UI 。这些解决方案存在一些问题:JFormattedTextField
JSpinner.DefaultEditor
- 中没有setTextField(要使用)或createTextField(要被覆盖),
JSpinner.DefaultEditor
唯一的方法是用自定义的替换它是在编辑器中找到并删除相应的组件并添加具有相同特征的新组件. - 如果您喜欢子类化
JSpinner.DefaultEditor
,那么您还需要子类化JSpinner.ListEditor
,JSpinner.NumberEditor
和JSpinner.DateEditor
, 还要JSpinner
重写其createEditor
方法以根据模型返回新类型。
他们(尽可能简单)的解决方案似乎是:
JComponent
通过子类化(并覆盖其contains
方法)从头开始创建新类型的编辑器。- 为编辑器的文本字段创建自定义
BasicFormattedTextFieldUI
(并覆盖其contains
方法)。
或者
- 微调器的默认编辑器的子类
BasicPanelUI
(并覆盖其contains
方法)。 - 为编辑器的文本字段创建自定义
BasicFormattedTextFieldUI
(并覆盖其contains
方法)。
这些问题似乎可以解决,但会进一步扩大解决方案(并且已经有 400 多行代码和注释)。所以我选择不解决这些问题,结果是文本字段和编辑器在圆形微调器内是方形的。这意味着如果用户单击文本字段可以在某些区域(角)获得焦点,而用户实际上单击了圆形微调器的边框。文本字段和编辑器不会在角落的圆形微调器上绘制自己,因为我们将它们设置为不透明!背景颜色由微调器本身处理。
推荐阅读
- sql-server - 根据目的表为Sp添加计算规则
- javascript - 反应自动完成组件,每次输入字母时调用端点
- mysql - 查询所需的输出
- apache - 如何显示单个页面的 503 错误
- amazon-ec2 - 如何使用 bitbucket 管道将图像从 Docker 集线器拉到 EC2
- xamarin - Xamarin Tap 模拟 (ADB)
- python - 使用键将行从 dataframeB 插入 DataframeA 且不合并
- matlab - 为什么我的代码无法与 Mex 一起使用但与 cl 一起使用?
- flutter - 无法添加或更新列表
- c# - 删除activemq消费消息/s