/*===============================================================================
* SlightBevelBorder.java
*===============================================================================
* auth: Jason
* CSDN ID: Unagain
* Email: tl21cen@hotmail.com
* date: 2006-4-11
*===============================================================================
*/
package tl.util;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import javax.swing.border.SoftBevelBorder;
public class SlightBevelBorder extends SoftBevelBorder {
private static final long serialVersionUID = 1L;
public SlightBevelBorder(int bevelType) {
super(bevelType);
}
public SlightBevelBorder(int bevelType, Color highlight, Color shadow) {
super(bevelType, highlight, shadow);
}
/**
* Paints the border for the specified component with the specified
* position and size.
* @param c the component for which this border is being painted
* @param g the paint graphics
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param width the width of the painted border
* @param height the height of the painted border
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width,
int height) {
Color oldColor = g.getColor();
g.translate(x, y);
if (bevelType == RAISED) {
g.setColor(getHighlightOuterColor(c));
g.drawLine(0, 0, width - 2, 0);
g.drawLine(0, 1, 0, height - 2);
g.setColor(getShadowOuterColor(c));
g.drawLine(0, height - 1, width - 1, height - 1);
g.drawLine(width - 1, 0, width - 1, height - 1);
} else if (bevelType == LOWERED) {
g.setColor(getShadowOuterColor(c));
g.drawLine(0, 0, width - 2, 0);
g.drawLine(0, 0, 0, height - 2);
g.setColor(getHighlightOuterColor(c));
g.drawLine(0, height - 1, width - 1, height - 1);
g.drawLine(width - 1, 0, width - 1, height);
}
g.translate(-x, -y);
g.setColor(oldColor);
}
}
/*===============================================================================
* StatusbarBuilder.java
*===============================================================================
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*===============================================================================
* auth: Jason
* CSDN ID: Unagain
* Email: tl21cen@hotmail.com
* date: 2006-4-11
*===============================================================================
*/
package tl.util;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Hashtable;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.AbstractBorder;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MouseInputAdapter;
import javax.swing.text.JTextComponent;
//class StatusBar extends JComponent {
public class StatusbarBuilder {
//final static Dimension XGAP = new Dimension(2, 0);
private JPanel bar;
/**
* matains all instances created, each instance associate
* with an existing window object.<br>
* you can create and to obtain a instance using newInstance
* method with a specified window object.
*/
private static
Hashtable<Window, StatusbarBuilder> instances =
new Hashtable<Window, StatusbarBuilder>();
private Window window;
final public static int PLAIN = 0;
final public static int LOWERED = 1;
/**
* @deprecated
*/
final public static int RAISED = 2;
// for builder
private static int commonBarStyle = LOWERED;
// for instance
private AbstractBorder border = null;
// test
private int style;
/**
* only two style can be used for the bar, PLAIN or
* LOWERED. I try to make cell's border to be RAISED
* earlier, but it looks ugly.
* <br><i>WARNING: </i>result of this method can only
* effect the instance created next but not an instance
* has been created prior. And you also be caution that
* the effect will be acting for any instance created
* subsequently until you invoked it again to set other
* value. <br>
* call reset method can reset all properties changed to
* be default.
* @param style
*/
public static void setBarStyle(int style) {
if (style < PLAIN || style > LOWERED) {
commonBarStyle = PLAIN;
} else {
commonBarStyle = style;
}
}
// for builder
private static Color commonBkColor = null;
// for instance
private Color bkColor = null;
/**
* set background color of bar.
* <br><i>WARNING: </i>result of this method can only
* effect the instance created next but not an instance
* has been created prior. And you also be caution that
* the effect will be acting for any instance created
* subsequently until you invoked it again to set other
* value. <br>
* call reset method can reset all properties changed to
* be default.
* @param color
*/
public static void setBackground(Color color) {
commonBkColor = color;
}
final static int DEFAULT_HEIGHT = 23;
private static int commonHeight = 0;
private int height = 0;
/**
* set the height of cell in bar
* <br><i>WARNING: </i>result of this method can only
* effect the instance created next but not an instance
* has been created prior. And you also be caution that
* the effect will be acting for any instance created
* subsequently until you invoked it again to set other
* value. <br>
* call reset method can reset all properties changed to
* be default.
* @param height
*/
public static void setHeight(int height) {
commonHeight = height;
}
final static int DEFAULT_GAP_WIDTH = 2;
private static int gapWidth = DEFAULT_GAP_WIDTH;
private int gw = 0;
/**
* In fact, it is used to set the width of gap.
* <br><i>WARNING: </i>result of this method can only
* effect the instance created next but not an instance
* has been created prior. And you also be caution that
* the effect will be acting for any instance created
* subsequently until you invoked it again to set other
* value. <br>
* call reset method can reset all properties changed to
* be default.
* @param width
*/
public static void setGap(int width) {
gapWidth = width;
}
private static JComponent commonNotice = null;
private JComponent notice;
/**
* I think perhaps you've got a custom component that
* can show help message more effective.
* I hope it can be used on this bar directly.
* <br><i>WARNING: </i>result of this method can only
* effect the instance created next but not an instance
* has been created prior. And you also be caution that
* the effect will be acting for any instance created
* subsequently until you invoked it again to set other
* value. <br>
* call reset method can reset all properties changed to
* be default.
* @param comp a custom component can show help message.
*/
public static void setNotice(JComponent comp) {
commonNotice = comp;
}
public static void reset() {
commonBarStyle = LOWERED;
commonBkColor = null;
commonHeight = DEFAULT_HEIGHT;
gapWidth = DEFAULT_GAP_WIDTH;
commonNotice = null;
}
/**
* send a message to bar, so can be appeared in bar.<br>
* <i>be cauthion</i> that it will do nothing if it is a custom
* component you applied, and it isn't an instance of JLabel
* and any other subclass of JTextComponent.
* @param s a message
*/
public void notice(String s) {
if (notice instanceof JLabel) {
((JLabel)notice).setText(s);
} else if (notice instanceof JTextComponent) {
((JTextComponent)notice).setText(s);
}
}
/**
* By default, notice cell hasn't border. so this method
* can be use to set whether notice cell can has a border.
*
* @param style @see @link
*/
public void setNoticeStyle (int style) {
if (style == PLAIN) {
if (border instanceof EmptyBorder) {
notice.setBorder(border);
} else {
notice.setBorder(
new EmptyBorder(1,1,1,1));
}
} else if (style == LOWERED) {
if (border instanceof BevelBorder) {
notice.setBorder(border);
} else {
notice.setBorder(
new SlightBevelBorder(BevelBorder.LOWERED));
}
}
}
/**
* Create a status bar for the window given. by default, the
* window use BorderLayout as its layout mananger, and the
* status bar created will reside on bottom of the window.
*
* @param window
* @return
*/
public static StatusbarBuilder getInstance(Window window) {
return getInstance(window, BorderLayout.PAGE_END);
}
/**
* Create a status bar for a window given. User apply
* constraints for status to reside in the window.
*
* @param window
* @param constraints
* @return
*/
public static StatusbarBuilder getInstance(Window window,
Object constraints) {
// get instance from pool.
StatusbarBuilder instance = instances.get(window);
// create a new instance if there isn't a instance associate
// the window.
if (instance == null) {
instance = new StatusbarBuilder();
// pool the new instance.
instances.put(window, instance);
window.add(instance.bar, constraints);
instance.window = window;
// create a monitor on window, so that statusbar can be
// released before window wiil be deactivate.
window.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
Window window = e.getWindow();
System.out.println(window);
StatusbarBuilder inst = instances.get(window);
System.out.println(inst);
inst = null;
instances.remove(window);
System.out.println("gfgfff");
}
});
}
return instance;
}
private RBCorner corner;
/**
* create a inew instance of status bar. Each status bar created
* belones to a window specified.
*/
private StatusbarBuilder() {
bar = new JPanel();
bar.setLayout(new BoxLayout(bar, BoxLayout.LINE_AXIS));
bar.setBorder(new EmptyBorder(2, 0, 1, 1));
// initialize instance variable.
height = commonHeight == 0 ? DEFAULT_HEIGHT : commonHeight;
bkColor = commonBkColor == null ? bar.getBackground() : commonBkColor;
// prepare border
style = commonBarStyle;
if (commonBarStyle == LOWERED) {
border = new SlightBevelBorder(BevelBorder.LOWERED);
} else if (commonBarStyle == RAISED) {
border = new SlightBevelBorder(BevelBorder.RAISED);
} else {
border =
//new LineBorder(bkColor);
new EmptyBorder(2, 2, 2, 2);
}
// initialize a gap used for separate cells.
// gw = gapWidth == 0 ? DEFAULT_GAP_WIDTH : gapWidth;
gw = gapWidth;
bar.setBackground(bkColor);
//bar.setMinimumSize(new Dimension(50, height));
//bar.setMaximumSize(new Dimension(5000, height));
bar.add(Box.createRigidArea(new Dimension(0, height)));
corner = new RBCorner();
corner.setBorder(border);
bar.add(corner);
// At first, it used to show notice such as help message.
// In addition, it used to fill increased space if bar
// was stretched.
if (commonNotice == null) {
notice = new JLabel();
} else {
notice = commonNotice;
}
notice.setMinimumSize(new Dimension(50, height));
notice.setMaximumSize(new Dimension(5000, height));
//notice.setBorder(border);
bar.add(notice);
bar.add(createGap());
}
// 增加显示格。在添加到Container之前,设置其Border。
/**
* add a cell to the bar, and its height will be
* changed to predefined height.
*
* @param comp
*/
public void add(JComponent comp) {
Dimension minSize = comp.getMinimumSize();
Dimension prefSize = comp.getPreferredSize();
Dimension maxSize = comp.getMaximumSize();
add(comp, minSize.width, prefSize.width, maxSize.width);
}
/**
* add a cell with a specified solid width.
*
* @param comp
* @param solidWidth
*/
public void add(JComponent comp, int solidWidth) {
add(comp, solidWidth, solidWidth, solidWidth);
}
/**
* add a cell with specified minimum width and max width.
* @param comp
* @param minWidth
* @param maxWidth
*/
public void add(JComponent comp, int minWidth, int maxWidth) {
add(comp, minWidth, minWidth, maxWidth);
}
/**
* add a component to the bar with specified minimum
* width, preffered width and maximum width.<br>
* The effect doesn't observable. It seens that width
* of other cell won't change until notice cell reduced
* to its minimum size when the window's width reduced.
*
* @param comp
* @param minWidth
* @param prefWidth
* @param maxWidth
*/
public void add(JComponent comp, int minWidth, int prefWidth, int maxWidth) {
if (comp != corner) {
bar.remove(corner);
}
comp.setBorder(border);
comp.setMinimumSize(new Dimension(minWidth, height));
comp.setPreferredSize(new Dimension(prefWidth, height));
comp.setMaximumSize(new Dimension(maxWidth, height));
// 按默认的添加方式,显示格会挤在一起,所以之间填充一个水平的刚性支撑。
//System.out.println(bar.add(gap));
//bar.add(gap);
bar.add(comp);
bar.add(createGap());
if (comp != corner) {
bar.add(corner);
}
}
private Component createGap() {
Component gap;
if (gw == 0) {
gap = Box.createHorizontalStrut(0);
} else {
if (commonBarStyle == PLAIN) {
gap = new Gap(gw);
} else {
gap = Box.createHorizontalStrut(gw);
}
}
return gap;
}
private class SolidCell extends JComponent {
int cellWidth;
SolidCell(int width) {
super();
this.cellWidth = width;
}
public Dimension getMinimumSize() {
return new Dimension(cellWidth, height);
}
public Dimension getMaximumSize() {
return new Dimension(cellWidth, height);
}
public Dimension getPreferredSize() {
return new Dimension(cellWidth, height);
}
}
class Gap extends SolidCell {
public Gap(int width) {
super(width);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(bkColor.darker());
g.drawLine(0, 0, 0, height-1);
g.setColor(bkColor.brighter().brighter());
g.drawLine(1, 0, 1, height-1);
}
}
final static int DEFAULT_CORNER_WIDTH = 18;
/**
* Do you take notice of that there is a manner of
* status bar in many other programs, that you can drag
* the right-bottom corner to resize the window? <br>
* Okey, I just want to achieve it. I've spent many
* more hours on it, but it isn't ideal yet.<br>
* Be it so.
*
* @author Jason
*/
class RBCorner extends SolidCell {
Rectangle resizeArea;
public RBCorner() {
super(DEFAULT_CORNER_WIDTH);
ResizeAdapter mouseAdapter =
new ResizeAdapter();
addMouseMotionListener(mouseAdapter);
addMouseListener(mouseAdapter);
}
/**
* this class in response to monitor several mouse
* events, as response, it changes Cursor when mouse
* over and leave it, and it can also resize the
* window that it resides in when you drag it.
*
* @author Jason
*/
class ResizeAdapter
extends MouseInputAdapter{
Cursor oldCursor;
boolean entered;
boolean holded;
Point p;
public void mouseDragged(MouseEvent e) {
Point p1 = e.getPoint();
window.setSize(
window.getWidth() + (p1.x - p.x),
window.getHeight() + (p1.y - p.y)
);
p = p1;
}
public void mousePressed(MouseEvent e) {
p = e.getPoint();
holded = true;
}
public void mouseReleased(MouseEvent e) {
p = null;
holded = false;
window.validate();
window.repaint();
}
public void mouseEntered(MouseEvent e) {
entered = true;
oldCursor = getCursor();
setCursor(
Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
}
public void mouseExited(MouseEvent e) {
if (entered) {
setCursor(oldCursor);
entered = false;
}
}
}
public void paint(Graphics g) {
super.paint(g);
Rectangle r = g.getClipBounds();
Color c = g.getColor();
for (int i = 0; i < 3; i++) {
g.setColor(Color.GRAY);
g.drawLine(r.width - i * 4 - 4, r.height - 1, r.width - 1,
r.height - i * 4 - 4);
g.drawLine(r.width - i * 4 - 5, r.height - 1, r.width - 1,
r.height - i * 4 - 5);
g.setColor(Color.WHITE);
g.drawLine(r.width - i * 4 - 6, r.height - 1, r.width - 1,
r.height - i * 4 - 6);
}
g.setColor(Color.LIGHT_GRAY);
g.drawLine(r.width - 13, r.height - 1, r.width - 1, r.height - 1);
g.drawLine(r.width - 1, r.height - 13, r.width - 1, r.height - 1);
}
}
}
/*===============================================================================
* 说明:
* 这是StatusbarBuilder的一个测试。StatusbarBuilder是我新做的一个类,也可以说是
* 我学java以来第一个比较满意的作品,虽然确实还存在一些问题。但时间不允许,所以暂告
* 一段落。希望这个小东东能给你开发带来帮助。
*===============================================================================
* auto: Jason
* csdn: Unagain
* email: tl21cen@hotmail
* date: 2006-4-13
*/
package tl.util.test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.BevelBorder;
import tl.util.StatusbarBuilder;
public class TestStatusbar {
public static void main(String[] args) {
StatusbarBuilder bar1, bar2;
BevelBorder border =
new BevelBorder(BevelBorder.LOWERED);
JFrame fr1 = new JFrame();
JFrame fr2 = new JFrame();
fr1.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
bar1 = StatusbarBuilder.getInstance(fr1);
bar1.add(new JLabel("Main Window"), 100);
bar1.add(new JTextField("JTextField"), 100);
JButton btn = new JButton("JButton" );
bar1.add(btn);
bar1.notice("create bar using default setting.");
final StatusbarBuilder bar = bar1;
final JFrame fr = fr2;
btn.addActionListener(
new ActionListener(){
public void actionPerformed(ActionEvent e){
fr.dispose();
if (fr == null) {
bar.notice("fr2 disposed.");
return;
}
bar.notice("show fr2.");
fr.setVisible(true);
}
});
JTextArea t1 = new JTextArea();
t1.setBorder(border);
fr1.add(t1,BorderLayout.CENTER);
JTextArea t2 = new JTextArea();
t2.setBorder(border);
fr2.add(t2,BorderLayout.CENTER);
fr2.setDefaultCloseOperation(
JFrame.DISPOSE_ON_CLOSE);
final JTextField notice = new JTextField();
notice.addComponentListener(
new ComponentListener(){
public void componentResized(ComponentEvent e) {
notice.setText(e.paramString());
}
public void componentMoved(ComponentEvent e) {
notice.setText(e.paramString());
}
public void componentShown(ComponentEvent e) {
notice.setText(e.paramString());
}
public void componentHidden(ComponentEvent e) {
notice.setText(e.paramString());
}
});
StatusbarBuilder.setNotice(notice);
StatusbarBuilder.setBackground(Color.LIGHT_GRAY);
StatusbarBuilder.setBarStyle(StatusbarBuilder.PLAIN);
StatusbarBuilder.setGap(5);
StatusbarBuilder.setHeight(30);
bar2 = StatusbarBuilder.getInstance(fr2);
bar2.add(new JLabel("Sub Window."));
bar2.add(new JLabel("Hello"), 60);
fr1.setPreferredSize(new Dimension(400, 300));
fr2.setPreferredSize(new Dimension(300, 200));
fr1.pack();
fr2.pack();
fr1.setVisible(true);
fr2.setVisible(true);
}
}
一、接口说明
接口方法有4类,包括Builder设置,创建实例,添加显示格和显示提示信息。
1. 设置builder
设置状态条的背景颜色
public static void setBackground(Color color);
设置状态条中显示格的风格。有2种风格,默认是StatusbarBuilder.LOWERED,这是大多数状态条常见的。另一个是StatusbarBuilder.PLAIN,显示格式为平面。
public static void setBarStyle(int style);
设置显示窗格之间的距离。
public static void setGap(int width) ;
设置窗格的高度。
public static void setHeight(int height);
设置显示提示信息的部件。如果不设置,则使用默认的JLabel作为显示。
public static void setNotice(JComponent comp);
一旦对builder做了设定,以后创建的状态条实例都会使用这些设置。调用这个函数可以恢复到默认设置。
public static void reset();
2. 创建实例
为一个已存在的窗口创建一个status bar。状态条不检查窗口的布局,直接认定窗口使用BorderLayout布局管理器,并且 把工具条置于窗口下方,适用用大多数应用。
public static StatusbarBuilder getInstance(Window window)
如果你用了别的布局管理器,可以使用这个方法。通过传递一个constraints来确定工具条位置。
public static StatusbarBuilder getInstance(Window window, Ojbect constraints)
3. 添加显示格
添加一个显示格到状态条
public void add(JComponent comp);
添加一个显示格到状态条,并指出固定宽度。这个方法应该比较常用。
public void add(JComponent comp, int solidWidth);
添加一个显示格到状态条,指定最小宽度和最大宽度
public void add(JComponent comp, int minWidth, int maxWidth);
添加一个显示格到状态条,指定最小宽度、初始宽度和最大宽度
public void add(JComponent comp, int minWidth, int prefWidth, int maxWidth);
4. 显示提示信息
显示一条提示信息。比如:notice("准备完毕。");
public void notice(String s);
提示信息部件的窗格样式是单独设置的,可以设置成是平的或凹陷的。
public void setNoticeStyle (int style);
二、主要设计思想
1. 封装
其实这个状态条只是一个使用BoxLayout作为布局管理的JPanel,使用add(JComponent)修饰窗格并添加到JPanel。我最初的设计就是这样。但是由于JPanle从Container继承而来,所以,如果开发过程中使用add(new Label()),Label是一个Component对象,那么这个Label就会在没有经过add(JComponent)方法的修饰的情况下添加到状态条,导致布局混乱。而add(Component)是public的,后继的继承中不能降低其可见度。所以,我使用了一个非Component子类的包装类来屏蔽外部对JPanel的访问。
2. 实例管理
我想我在这里犯了一个错误。看过代码的知道我用一个Hashtable来管理维护window及其申请的实例,对于初次申请的window,为其创建一个状态条,并贮存window和新实例的引用。以后该window再申请时,直接返回已创建的该window相应的实例。同时,我还为每个提出申请的window注册了一个监听器,当window关闭时,消除window和实例的引用。我的意图是在保证一个窗口只能创建一个状态条的同时,随时可以使用getInstance(Window window)获得引用。
开始我觉得是个不错的想法,但是到测试时发现问题了(参考我的例子)。当SubWindow关闭之后(关闭模式是DISPOSE_ON_CLOSE),StatusbarBuilder清除了对SubWindow及其对应StatusBar的引用。但是,当点击MainWindow状态条上的“haha”按钮时,SubWindow又恢复了显示。这时,如果再调用getInstance(referenceOfSubWindowInstance)就会出错。这个测试我当时没想到,所以没写到在例子里。窗口重新激活的原因我想是因为jvm的收集机制,只要还有指向该对象的引用,这个对象就不会被回收。我在“haha”按钮的执行代码中就有窗口的引用,并且该引用是final类型。在实际应用中,不排除这种情况的可能,尽管少,并且注意之后会避免,但毕竟是个缺陷,留待后续版本解决。
3. SlightBevelBorder
这个类继承了jsdk中的SoftBevelBorder,代码其实很简单。当我有这个想法时,我看了BevelBorder的代码,“原来如此”。所以有时间的话看看jsdk的源代码对提高编程水平是很有帮助的。虽然有些结构很难,但并不都是高不可攀。
三、一点心得
学了这么长时间,总算有点成就感了。通过作为一个练习,从中又学到一些设计方法和思路。现在网上有很多实现界面的类,其实,我想说,我们也可以做到,甚至更好。
继续努力。
引用自:Unagair的专栏 http://blog.csdn.net/unagain/
关于StatusbarBuilder的说明: 介绍API说明、设计思路以及当前存在的问题。
- SlightBevelBorder.java :SlightBevelBorder类源代码。
- StatusbarBuilder.java:StatusbarBuilder类源代码。
- TestStatusbar.java:测试程序