自己编写的一个Windows风格按钮控件
下面是我自己编写的一个Window风格的按钮控件,已经基本上完成,在对话框上所有的风格和动作都与标准Windows按钮无异,唯一的遗憾是我无法在控件内完成快捷键事件触发ActionEvent事件来响应快捷键,当然快捷键事件可以在父窗口触发,但是不能把快捷键事件封装在按钮内,则违反了面向对象的程序设计原则。另外对于不了解这个按钮内部实现的用户来说,使用也不方便,也不可能在父窗口内实现响应KeyEvent来响应按钮的快捷键,所以还得在控件内部实现,我尝试过在构造函数内添加父窗口的侦听器(用getParent().addKeyListener()方法),代码中红色的注释掉的一行,结果运行时抛出NullPointerException异常,不知道为什么,是否在按钮构造时父窗口尚未构造完成,如果这个方法不能使用,布置有什么方法可以实现这一目的。异常的全部信息如下:
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at MenuTestUI.CButton.<init>(CButton.java:58)
at MenuTestUI.CFileDialog.<init>(CFileDialog.java:33)
at MenuTestUI.CMenuTestFrame.OnFileOpen(CMenuTestFrame.java:160)
at MenuTestUI.CMenuTestFrame.actionPerformed(CMenuTestFrame.java:117)
at java.awt.MenuItem.processActionEvent(Unknown Source)
at java.awt.MenuItem.processEvent(Unknown Source)
at java.awt.MenuComponent.dispatchEventImpl(Unknown Source)
at java.awt.MenuComponent.dispatchEvent(Unknown Source)
// 按钮空间的源代码
package MenuTestUI;
import java.awt.*;
import java.awt.event.*;
import java.util.Set;
public class CButton extends Component implements MouseListener,FocusListener,KeyListener{
/**
*
*/
private static final long serialVersionUID = 1959233040601280332L;
private static int m_nCountDefault = 0;
private static CButton m_buttonDefault = null;
private String m_strLabel = new String();
private boolean m_bIsMouseInside = false;
private boolean m_bIsMousePressed = false;
private boolean m_bIsDefaultButton = false;
private ActionListener m_listenerAction = null;
//private Container m_windowParent;
// 构造函数
/** 构造一个空的按钮控件
*
*/
public CButton(){
super();
m_strLabel = "";
addMouseListener(this);
setFocusable(true);
//getParent().addKeyListener(this);
}
/** 构造一个带有标签字符串的按钮
*
* 参数:
* @param label:String字符串,用于在按钮上显示的文本。
*
* 返回值:本方法无返回值。
*/
public CButton(String label){
super();
m_strLabel = label;
addMouseListener(this);
setFocusable(true);
addFocusListener(this);
addKeyListener(this);
//getParent().addKeyListener(this);
}
/** 构造方法
* CButton(String label,boolean bIsDefault);
*
* 参数:
* @param label :String类型的字符串,其值为按钮上显示的文本。
* @param bIsDefault:布尔型,若其值为true,则表示该按钮为默认按钮,否则为非默认按钮。
* 按钮为默认按钮时,其黑框内绘制一道蓝色粗框, 若在容器中键入回车键,即可触发该按钮事件。
* 在同一个容器中只允许出现一个默认按钮,若用户代码中设置了多个默认按钮,则最 后一个被设置
* 的按钮为默认按钮。
*
* 返回值:本方法无返回值。
*/
public CButton(String label,boolean bIsDefault){
super();
m_strLabel = label;
SetAsDefault(bIsDefault);
addMouseListener(this);
setFocusable(true);
addFocusListener(this);
getParent().addKeyListener(this);
}
/** void paint(Graphics g)
* 绘制按钮,子类可通过覆盖本方法实现自绘按钮控件。
*
* 参数:
* g:用于绘制按钮的图形上下文。
*
* 返回值:本方法无返回值。
*/
public void paint(Graphics g){
DrawMyButton(g);
DrawMyString(g);
// 在按钮上绘制标签文字
}
// 绘制按钮标签文字,包括热键的下划线
private void DrawMyString(Graphics g){
g.setColor(this.getForeground()); // 将图形上下文的绘制颜色设置为当前按钮的前景色
//g.setFont(new Font("宋体",Font.PLAIN,30));
FontMetrics ft = g.getFontMetrics(); // 获取按钮的当前字体规格
int nIndexOfShortcut = m_strLabel.indexOf('&');
int nWidth = 0,nHeight = ft.getHeight(),nCenterY = getHeight()/2,nCenterX = getWidth()/2;
if(nIndexOfShortcut != -1){
String str = m_strLabel.substring(0, nIndexOfShortcut)+m_strLabel.substring(nIndexOfShortcut+1);
nWidth = ft.charsWidth(str.toCharArray(), 0, str.length());
int nStartX = nCenterX-nWidth/2;
g.drawString(str, nStartX, nCenterY+nHeight/4);
int nLineStartX = nStartX + ft.charsWidth(str.substring(0, nIndexOfShortcut).toCharArray(), 0, nIndexOfShortcut);
int nLineEndX = nStartX + ft.charsWidth(str.substring(0, nIndexOfShortcut+1).toCharArray(), 0, nIndexOfShortcut+1);
g.drawLine(nLineStartX,nCenterY+nHeight*3/10,nLineEndX,nCenterY+nHeight*3/10); // 绘制下划线。
}
else {
nWidth = ft.charsWidth(m_strLabel.toCharArray(), 0, m_strLabel.length());
g.drawString(m_strLabel, nCenterX-nWidth/2, nCenterY+nHeight/4);
}
}
// 绘制按钮本身的图案,并根据不同的鼠标情况绘制不同的图案
private void DrawMyButton(Graphics g){
Color colorBackGround = this.getBackground(); // 取得按钮的当前背景颜色
// 设置当前绘制颜色为黑色并绘制按钮边框
g.setColor(new Color(0,45,255));
g.drawRoundRect(0, 0, getWidth()-1, getHeight()-1,
getHeight()>getWidth()?getWidth()/4:getHeight()/4,
getHeight()>getWidth()?getWidth()/4:getHeight()/4);
// 在按钮边框以不同颜色深度绘制线条以呈现立体效果
int red = colorBackGround.getRed();
int green = colorBackGround.getGreen();
int blue = colorBackGround.getBlue();
//System.out.println("The RGB of background color is R = "+red+", G = "+green+", B = "+blue);
// 绘制左边与顶部的高亮边
g.setColor(new Color(255,255,255));
g.drawLine(2, 2, 2, getHeight()-2);
g.drawLine(2, 2, getWidth()-2,2);
g.setColor(new Color(red+5,green+5,blue+5));
g.drawLine(3, 3, 3, getHeight()-3);
g.drawLine(3, 3, getWidth()-3,3);
// 绘制底部与右边的深色边
g.setColor(new Color(red-10,green-10,blue-10));
g.drawLine(getWidth()-2, 2, getWidth()-2, getHeight()-3);
g.drawLine(2,getHeight()-2,getWidth()-3,getHeight()-2);
g.setColor(new Color(red-5,green-5,blue-5));
g.drawLine(getWidth()-3, 3, getWidth()-3, getHeight()-4);
g.drawLine(3,getHeight()-3,getWidth()-4,getHeight()-3);
// 鼠标指针位于按钮之内时的图形
if(m_bIsMouseInside && !m_bIsMousePressed){
g.setColor(new Color(241,206,7));
g.drawRoundRect(1, 1, getWidth()-4, getHeight()-4,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1);
g.drawRoundRect(2, 2, getWidth()-5, getHeight()-5,
getHeight()>getWidth()?getWidth()/4-2:getHeight()/4-2,
getHeight()>getWidth()?getWidth()/4-2:getHeight()/4-2);
g.drawRoundRect(2, 2, getWidth()-6, getHeight()-6,
getHeight()>getWidth()?getWidth()/4-3:getHeight()/4-3,
getHeight()>getWidth()?getWidth()/4-3:getHeight()/4-3);
//DispFocus(g);
}
// 按钮处于焦点时的情形
if(isFocusOwner())
DispFocus(g);
if(m_bIsDefaultButton && !m_bIsMousePressed && !m_bIsMouseInside){
g.setColor(new Color(92,152,202));
g.drawRoundRect(1, 1, getWidth()-3, getHeight()-3,
getHeight()>getWidth()?getWidth()/4:getHeight()/4,
getHeight()>getWidth()?getWidth()/4:getHeight()/4);
g.drawRoundRect(2, 2, getWidth()-4, getHeight()-4,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1);
g.drawRoundRect(2, 2, getWidth()-5, getHeight()-5,
getHeight()>getWidth()?getWidth()/4-2:getHeight()/4-2,
getHeight()>getWidth()?getWidth()/4-2:getHeight()/4-2);
}
// 鼠标处于按下时的情形
if(m_bIsMousePressed){
// 用深灰色背景填充按钮区域
g.setColor(new Color(red-10,green-10,blue -10));
g.fillRoundRect(1, 1, getWidth()-2, getHeight()-2,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1,
getHeight()>getWidth()?getWidth()/4-1:getHeight()/4-1);
// 绘制高亮底边和深色上边呈现立体效果
// 绘制深色上边和左边
g.setColor(new Color(red-20,green-20,blue-20));
g.drawLine(3, 1, getWidth()-3, 1);
g.drawLine(1, 3, 1, getHeight()-3);
g.setColor(new Color(red-10,green-10,blue-10));
g.drawLine(4, 2, getWidth()-4, 2);
g.drawLine(2, 4, 2, getHeight()-4);
// 绘制高亮底边和右边
g.setColor(new Color(255,255,255));
g.drawLine(3, getHeight()-2, getWidth()-3, getHeight()-2);
g.drawLine(getWidth()-2, 3, getWidth()-2, getHeight()-3);
g.setColor(new Color(235,235,235));
g.drawLine(4, getHeight()-3, getWidth()-4, getHeight()-3);
g.drawLine(getWidth()-3, 4, getWidth()-3, getHeight()-4);
DispFocus(g);
}
}
private void DispFocus(Graphics g){
// 绘制虚线以显示焦点
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
int nStep = 2;
// 绘制横线
Color color = g.getColor(); // 保存当前绘图上下文的绘图颜色
g.setColor(new Color(0,0,0)); // 设置当前绘图颜色为黑色
x1 = x2 = 5;
y1 = 3;
y2 = getHeight()-4;
for(int i = 0; i < (getWidth()-9)/(nStep+1); i++){
x2 += 1;
if(x2 > getWidth()-5)
x2 = getWidth()-5;
g.drawLine(x1, y1, x2, y1);
g.drawLine(x1, y2, x2, y2);
x2 += nStep;
x1 = x2;
}
// 绘制竖虚线
y1 = y2 = 5;
x1 = 3;
x2 = getWidth()-4;
for(int i = 0; i < (getHeight()-9)/(nStep+1); i++){
y2 += 1;
if(y2 > getHeight()-5)
y2 = getHeight()-5;
g.drawLine(x1, y1, x1, y2);
g.drawLine(x2, y1, x2, y2);
y2 += nStep;
y1 = y2;
}
g.setColor(color); // 将绘图颜色设置为还原为原来的绘图颜色
}
public String toString(){
return getClass().getName() + "[CButton" + ((Container)getParent()).getComponentZOrder(this)
+ "@" + Integer.toHexString(this.hashCode()) + ", "+getX() + ", " + getY() + ", " + getWidth()
+ "x" + getHeight() + ","+ "label=" + m_strLabel + "]";
}
public String getLabel(){
return m_strLabel;
}
public void setLabel(String label){
m_strLabel = label;
}
@Override
public void mouseClicked(MouseEvent arg0) {
if(arg0.getButton() != MouseEvent.BUTTON1)
return;
// TODO Auto-generated method stub
m_listenerAction.actionPerformed(new ActionEvent(this,
ActionEvent.ACTION_PERFORMED,"CountDown"));
//System.out.println("确定按钮");
}
@Override
public void mouseEntered(MouseEvent arg0) {
// TODO Auto-generated method stub
m_bIsMouseInside = true;
repaint();
}
@Override
public void mouseExited(MouseEvent arg0) {
// TODO Auto-generated method stub
m_bIsMouseInside = false;
repaint();
}
@Override
public void mousePressed(MouseEvent arg0) {
// TODO Auto-generated method stub
if(arg0.getButton() != MouseEvent.BUTTON1)
return;
m_bIsMousePressed = true;
m_bIsDefaultButton = true;
requestFocus();
// 将其他按钮控件的m_bIsDefaultButtons属性设置为false(非默认按钮)
/*Component com[] = getParent().getComponents();
for(int i = 0; i<com.length;i++){
if((com[i].getClass()).toString().contains("CButton")){
((CButton)com[i]).SetAsDefault(false);
}
}*/
// 设置本控件为默认按钮
//SetAsDefault(true);
repaint();
}
@Override
public void mouseReleased(MouseEvent arg0) {
// TODO Auto-generated method stub
m_bIsMousePressed = false;
repaint();
}
@Override
public void focusGained(FocusEvent arg0) {
// TODO Auto-generated method stub
//arg0.
m_bIsDefaultButton = true;
repaint();
}
@Override
public void focusLost(FocusEvent arg0) {
// TODO Auto-generated method stub
m_bIsDefaultButton = false;
repaint();
}
/** void SetAsDefault(boolean bIsDefault)
* 设置/取消按钮控件的默认属性,按钮为默认按钮时,其黑框内绘制一道蓝色粗框, 若在容器中键入回车键,即可触发
* 该按钮事件。
* 在同一个容器中只允许出现一个默认按钮,若用户代码中设置了多个默认按钮,则最 后一个被设置的按钮为默认按钮。
* 参数:
* @param bIsDefault:如为true,则设置为默认,否则为非默认
*
* 返回值:本方法无返回值
*/
public void SetAsDefault(boolean bIsDefault){
// 将当前按钮设置为默认按钮
m_bIsDefaultButton = bIsDefault;
// 若bIsDefault为true,则将上一个默认按钮设置为非默认按钮,并将m_buttonDefault设置为当前按钮
if(bIsDefault){
if(m_buttonDefault != null && m_buttonDefault != this)
m_buttonDefault.SetAsDefault(false);
m_buttonDefault = this;
}
repaint();
}
public boolean IsDefault(){
return m_bIsDefaultButton;
}
public void addActionListener(ActionListener listenerNew){
m_listenerAction = AWTEventMulticaster.add(m_listenerAction, listenerNew);
}
public void removeActionListener(ActionListener listenerOld){
m_listenerAction = AWTEventMulticaster.remove(m_listenerAction,listenerOld);
}
@Override
public void keyPressed(KeyEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void keyReleased(KeyEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent arg0) {
// TODO Auto-generated method stub
//System.out.print("Parent Key Event");
}
}