摘要: 很久沒有回來這里寫技術BLOG了,這里的氛圍還行,大家都對一個問題積極的思考(至少之前這里給我的感覺是這樣的),2年里面自己也忙著做些事情,沒有寫,最近有空也就寫寫,偶爾會去oschine.net看看新聞,然后就在那里看到了一個人提出的問題很有意思,就是怎么表達式求解,例如(1 + 2) / 3 - 1 * 2 + 5 / (3 + 2)這樣的字符串輸入,怎么樣解析之后輸出結果。說來也好笑,對于我...
閱讀全文
posted @
2011-11-09 10:36 ruislan 閱讀(1761) |
評論 (6) |
編輯 收藏
摘要: 很久沒來了,不是一位朋友給我發郵件問我關于swing的問題,才想起,然后翻看了之前的代碼,發現當年還實現了一個Vista風格的按鈕沒有放出來,現在補上,也許現在人們的swing水平對我這代碼不屑一顧,不過還是依然拋磚引玉,給未知的人們一個啟發。還是老慣例,上效果圖:
正常情況下:(和vista一樣具有焦點的按鈕的顏色漸深漸淺的循環變化)
鼠標在區域內時:
鼠標按下去:
然后是代...
閱讀全文
posted @
2009-09-12 12:54 ruislan 閱讀(2486) |
評論 (3) |
編輯 收藏
認為自己是達人的就不用看了。只是一點小技巧,不敢班門弄斧,做個總結,為那些還不知道的解解惑,隨便告訴大家我還活著。
最近客戶提了個小改動,客戶網站上圖片存放的目錄需要改動一下。例如在網上訪問是www.tkk7.com/images/*.*,在服務器上的目錄是D:/<webroot>/images/*.*,客戶想把這個images目錄下的資源全部移動到E:/data/里面去,但是在網上www.tkk7.com/images/*.*還是同樣可以訪問得到,我剛開始犯了形式主義的錯誤,老是想用程序解決,一會filter,一會servlet/action,后來我配置程序的時候突然看到了server.xml,于是我想到了選擇用映射的方式。正好,server.xml中的<Context>就是做這個事情的。于是乎我們在<Host></Host>中增加了一個<Context docBase="E:/data/images" path="/images">,OK,重啟之后,所有檢索www.tkk7.com/images路徑下的資源實際上都由E:/data/images下的資源提供了。
posted @
2008-02-15 16:41 ruislan 閱讀(1271) |
評論 (3) |
編輯 收藏
在寫多線程程序的時候,你就像個經理,手下有那么或多或少的職員,你負責協調職員之間的工作,如果你稍不留神,職員之間就陷入了相互等待的尷尬狀態。還好,大多數時候多線程都還在我們掌控之內,即便是遇到這樣的deadlock情況,我們也能夠去修正,但是有的時候生活就是那么不盡人意,特別是NIO這種你不能掌控的時候,且看下面的代碼:
/**
* @(#)DeadLock.java v0.1.0 2007-12-13
*/
package ruislan.rswing.test;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Executors;
/**
* NIO DeadLock
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class DeadLock {
public static void main(String[] args) throws Exception {
Service service = new Service();
Executors.newSingleThreadExecutor().execute(service);
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress("http://www.tkk7.com", 80));
service.addChannel(channel);
}
static class Service implements Runnable {
Selector selector;
public Service() {
}
public void run() {
try {
selector = Selector.open();
while (true) {
selector.select();
System.out.println(selector.selectedKeys().size());
}
} catch (Exception e) {
}
}
public void addChannel(SocketChannel channel) {
try {
channel.register(selector, SelectionKey.OP_CONNECT
| SelectionKey.OP_READ);
System.out.println("can reach here?when pigs fly!");
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
}
乍看之下,我們的代碼沒有問題,但是運行之后你會發現,這句System.out.println("can reach here?when pigs fly!");永遠無法執行,也就是說register()方法被阻塞了!Oh god bless,讓我們看看JavaDoc是怎么說的:
...
可在任意時間調用此方法。如果調用此方法的同時正在進行另一個此方法或 configureBlocking 方法的調用,則在另一個操作完成前將首先阻塞該調用。然后此方法將在選擇器的鍵集上實現同步,因此如果調用此方法時并發地調用了涉及同一選擇器的另一個注冊或選擇操作,則可能阻塞此方法的調用。
...
看這句“可在任意時間調用此方法。”,也就是說我們調用的時間沒有任何限制,而阻塞的情況只會出現在“如果調用此方法的同時正在進行另一個此方法或 configureBlocking 方法的調用”的情況下,即便是阻塞了,我相信“正在進行另一個此方法或configureBlocking”也不會花掉太多的時間,況且這里沒有上面這樣的情況出現。那register()是被誰擋住了?或者是BUG?
我們來分析一下程序,程序有兩個線程主線程和Service線程,主線程啟動后啟動了Service線程,Service線程啟動Selector然后Service線程陷入select()的阻塞中,同時,主線程調用Service的addChannel()方法來添加一個SocketChannel,嗯,兩個線程之間唯一的聯系就是selector,看來要從selector尋找線索,很可惜,selector的實現沒有源代碼可查,不過可以肯定是channel的register()會調用selector的register(),雖然此時持有selector的Service線程被select()方法所阻塞,但是并不影響其他線程對其操作吧?那么,剩下的解釋就是Selector的select()方法和register()方法公用了一個鎖,select()方法阻塞住了,所以register()拿不到這個鎖了,那么這樣一來我們就只能保證讓select()或者register()不能同時調用或者register()調用的時候select()不持有這個鎖,也就是說我們要用Service線程自己來執行addChannel()方法,所以改進如下:
/**
* @(#)DeadLock.java v0.1.0 2007-12-13
*/
package ruislan.rswing.test;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* NIO DeadLock
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class DeadLock {
public static void main(String[] args) {
Service service = new Service();
new Thread(service).start();
for (int i = 0; i < 5; i++) {
new Thread(new ChannelAdder(service)).start();
}
}
static class ChannelAdder implements Runnable {
private Service service;
public ChannelAdder(Service service) {
this.service = service;
}
@Override
public void run() {
try {
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false);
channel.connect(new InetSocketAddress(
"http://www.tkk7.com", 80));
service.addChannel(channel);
} catch (Exception e) {
e.printStackTrace();
}
}
}
static class Service implements Runnable {
private Selector selector;
private Queue<SocketChannel> pendingRegisters;
public Service() {
pendingRegisters = new LinkedBlockingQueue<SocketChannel>();
}
public void run() {
try {
selector = Selector.open();
while (true) {
selector.select();
System.out.println(selector.selectedKeys().size());
handlePendingRegisters();
}
} catch (Exception e) {
}
}
public void handlePendingRegisters() {
while (!pendingRegisters.isEmpty()) {
SocketChannel channel = pendingRegisters.poll();
try {
channel.register(selector, SelectionKey.OP_CONNECT);
System.out.println("can reach here?yeah!");
} catch (ClosedChannelException e) {
e.printStackTrace();
}
}
}
public void addChannel(SocketChannel channel) {
pendingRegisters.offer(channel);
selector.wakeup();
}
}
}
新的代碼,我們在Service的線程提供了一個待處理Channel隊列,然后在添加一個SocketChannel到隊列中時喚醒這個selector,取消阻塞,然后在Service的循環中處理這個pendingChannel,這樣就避免這個Deadlock的發生了。當然我們亦可以在那個代碼上將select的超時時間設置非常的短,然后讓兩個線程去競爭,這樣做有太多的不可控性,不推薦了。
posted @
2007-12-13 18:31 ruislan 閱讀(1349) |
評論 (3) |
編輯 收藏
UI作為用戶與電腦的交互界面,如何更好的服務于人,讓人們用起來方便、簡單、快捷一直是UI開發者應該有的覺悟,作為開發人員的我們來說,不應該只是把UI推給電腦平面設計人員,更不應該一手包辦了(如果你不是一個人的話)。我們開發人員常常在開發UI的時候避重就輕,基本上都在強調code的美學,模式的應用而忽略了真實用戶的感受。我們常常得意于自己技術的美麗,而將一些比自己水平低的應用嗤之以鼻。但是用戶卻從來不關心代碼是如何寫的,他們關心這個應用是否對他們有用,順手乎?聰明乎?所以如果我們只是美麗于自己的設計,太關注軟件的本身而忽略了用戶的感受,就跟某些象牙塔里拿著錢做些無用的研究的人沒什么兩樣,或許有個美麗的名詞,為了科學。
那么如何才能算是好的UI人性化的設計呢?這個得看針對的用戶主要是哪些。我們熟知的操作系統Windows XP,Windows Vista,Vista是微軟最新的操作系統,包含了很多開發人員辛苦的結晶,但是在我身邊的很多人都不愿意裝它,也包括一些新聞的調查也說Vista不如當年XP出世那般火爆,他們大多數不愿裝的都說了同樣的話,XP都還有很多不懂,怕Vista更搞不懂,說實話我用過Vista,就我這么一個算是業內人士用起來當然駕輕就熟,再加上我們都有勇于創新的精神,所以常常去用新的東西,而普通客戶就不這么想了,我問了幾個不懂電腦才安裝了Vista的用戶的感受,“開始菜單的‘開始’兩個字沒有了,我還以為換了位置”,“界面比XP漂亮啊,但是我的機器好像有點慢,是不是要設置個什么啊”……再來我們熟知的AJAX,我已經接到過很多次不同的人給我的電話,說“為什么網頁打開的時候突然好卡了,以前不這樣啊?”,“網頁瀏覽不了,老說請稍候,數據加載中,等了很久,就是不出現”……面對這些電話或許我們會說,你們怎么那么笨啊,它卡是因為在下東西,在執行JS,寫JS的人太垃圾,浪費了資源,不出現就刷新啊,不要瀏覽那個不專業的網站了,等等就OK了等等回答,其實很多時候我們可以避免用戶的問題出現,例如你的AJAX的JS太大的時候,可以先提示用戶說,數據量較大,請稍后,如果長時間無反應,請按瀏覽器的刷新按鈕,或者嘗試按下F5鍵。
我還見過許多軟件鼓吹自己的功能如何強大,如何厲害,多么的人性化,但是我打開他們的軟件,居然發現只能用鼠標操作!!這是多么大的UI設計失敗!在舉一個例子,MSN和QQ兩個IM,如果你用MSN,在聯系人框里按上下的話,MSN會很聰明的明白你是要選擇上一位或者下一位聯系人,而QQ會很聰明的明白你是要拖動滑動條!@#$,還有很多軟件記憶力太差,不管我如何操作,它就是記不住,關閉軟件重新啟動后又回到了最初的模樣,還有的軟件自信心不足,一再問我“你確定嗎?”,“真的要這樣做嗎?”,“或許您不小心點了?”而我只是在點關閉這個娛樂性質的軟件而已,而有些軟件又特膽肥,做了一個不可恢復的操作盡然連提示都沒有,還有的軟件文化太差,常常把一個按鈕或者圖標該表達的含義弄得模棱兩可,以至于常常讓我們會錯意,做錯操作,或者把一些高風險的操作放在常用操作的旁邊,很容易點錯,還有的把不常用的操作也放到常用操作區,還不告訴用戶怎么去掉,這樣的例子不勝枚舉。
出現這些問題的原因在于我們與用戶之間的思維方式有著很大的不同。例如在寫文章之前我才將老爸從我的電腦椅上請下來,請下來之前他正在看我吃飯前的網頁——“界面九宮格”,我說您能看得懂嘛,他說“不懂,不過這軟件的界面不都這樣嘛?再說了,一張紙就8個方位,加上中間正好九個,你的東西不擺這里擺哪里啊?”,我正要解釋一下這與軟件設計的關系,但是突然一想,是啊,有道理啊,要是我給他老人家再解釋一下可以放在上面和下面,那不就是3D的了。再比如我一直都很不屑一顧的網絡實名,但是當它被我在很多人的機器里面消滅之后,很多人都打電話問我,怎么在地址欄里面輸入漢字,跳出搜索界面了,不是那個漢字的網站了,以前是有的,原來我只看到了它流氓的一面,忽略了普通用戶是根本記不住網址在哪里,甚至有些用戶不懂英文,你怎么讓他記得住全是英文的網址呢?不過過了幾天,他們都說不用了,有一個網站導航網址做了他們的主頁,他們平日想去的網站都在上面列著的,我后來才知道,就是被我以前同寢室的刪除了半天的hao123。所以我們必須充分考慮我們的應用是針對哪些用戶,他們是哪一類人,習慣是什么,當然還有就是UI設計的一些基本的東西,例如鼠標能夠完成的動作,同樣鍵盤也能完成等等。
posted @
2007-11-11 13:36 ruislan 閱讀(1427) |
評論 (5) |
編輯 收藏
Swing作為一個完整的UI解決方案,包含了一個GUI程序所擁有的方方面面,當然包括作為普通程序也好,作為GUI程序也好,作為Web程序等等程序都共有的線程概念。
Swing中的線程有三種:初始線程,事件線程,工作線程
這三種線程基本上包括了讓一個GUI完美工作的方方面面,首先,初始線程被用來創建GUI組件、資源加載和啟動GUI組件,眾所周知,Swing是事件驅動的,所以當UI出現了之后,初始線程就完成了它的使命,并將接力棒交給了事件線程,Event Dispatch Thread,這個時候所有組件的事件行為都交給了這個線程去處理,當然我們自己也要需要用線程來運行許多任務,優秀的GUI程序是絕不能讓界面被卡死不動的,那會讓用戶崩潰,所以這個時候就需要工作線程了,也可以說是在背后運行的線程,這種線程是勞動階級,任勞任怨的執行者長時間的工作。
初始線程的寫法很簡單,這樣就可以了:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
initGUI();
showGUI();
}
}
但是Applet中,你可能需要調用SwingUtilities.invokeAndWait這個方法,要是init方法返回了,瀏覽器開始展現Applet,但是GUI的創建還在thread中,出錯也是可想而知的。
至于invokeLater和invokeAndWait這兩個線程的簡單點的區別就是invokeLater是異步的,你不知道它什么時候會開始執行,invokeAndWait則是同步的,它會等到動作執行完成之后才返回。
Event Dispatch Thread不是線程安全的,所以要用線程來與它打交道要注意了,同步問題總是讓人頭痛。
在1.5之前應該說工作線程都是由開發人員自己去定義的,但是現在Swing推薦了SwingWorker這個類,包括Swing最新的符合JSR標準的Swing AppFramework也使用了SwingWorker這個類來處理所有在GUI背后做的事情。
了解了Swing中的線程定義,能夠讓我們更好的寫出優美的基于Swing的GUI程序。
posted @
2007-11-04 12:40 ruislan 閱讀(1269) |
評論 (2) |
編輯 收藏
很多人都說我們這行的人是偏執狂,我也覺得我是有一點倔脾氣,就像看連續劇,從第一集開始一直到最后一集才會關上電腦,一旦一個研究開始,就一定要有一個結果或者令自己滿意的結果才結束,但是當我看到這位“偏執狂”之后,我覺得我只是稍微有一點偏執而已。
研究UI繪制的時候很容易陷入另外一個領域,圖像領域,或者是游戲領域,我不喜歡做游戲是因為我愛玩游戲,如果玩和工作綁在一起了的話,那么工作之后的休閑就也是工作了。
好了,說了點廢話,下面是對FengGUI的介紹.
FengGUI是一個建立在OpenGL上的GUI的API,FengGUI提供了很多標準的UI組件,像Button,TextField,Panel之類的,下面先看看截圖:

GridLayout的截圖

可分割的面板
要說到最大的特色,莫過于FengGUI基于OpenGL,并且可以在組件里面直接使用OpenGL,可以輕松的集成jME(java Monkey Engine,一個非常棒的Java 3D游戲引擎),jogl(Java OpenGL API),lwjgl(輕量級Java游戲庫),jPCT(同樣非常棒的Java 3D游戲引擎),但是在跑它的demo時我也感受到了CPU 80% 工作率的壓力,所以就目前的我膚淺的了解,用它來做普通的GUI程序估計還為時有點早,但是如果是游戲中的組件的話確實是與上述引擎和API的非常好的補充。
posted @
2007-10-27 20:34 ruislan 閱讀(1707) |
評論 (2) |
編輯 收藏
以前或許大家對一個UI組件是否透明沒有那么關心,但是自從Vista的毛玻璃出現后,UI透明就成了大家非常關注的一個話題,于是Java陣營開始了鋪天蓋地的討論如何實現透明的效果,但是很不幸的是無論組件如何透明和變換,但是能夠放置于屏幕任何位置的Window一族就是沒法透明和變形,原生代碼的問題還是交給原生代碼來解決吧。
自己寫原生代碼是可怕的,特別是對我這種只知道Java的平凡程序員,所以我們得借助一個非常方便的跨平臺的調用OS function方便的Lib,JNA便是最佳選擇(那個誰,這里不是討論JRuby&JPython的)。
so, all robots, transform!
下面我們要做一個界面是圓角四邊形的,中間有一個滑動條來滑動調節透明度。先看看做好的截圖。

注意圖中的JFrame邊角已經變成了圓弧,滑動滑塊,JFrame開始透明,桌面的圖標顯現出來,下面是代碼。
/**
* @(#)TransparentFrame.java v0.1.0 2007-10-21
*/
package ruislan.rswing.test;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.border.LineBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import com.sun.jna.examples.WindowUtils;
/**
* Transparent JFrame use JNA
*
* @author ruislan <a href="mailto:z17520@126.com"/>
* @version 0.1.0
*/
public class TransparentFrame {
private JFrame frame;
private JPanel container;
private JSlider slider;
private JPanel titleBar;
private JLabel titleLabel;
private JButton closeButton;
public static void main(String[] args) {
new TransparentFrame().launch();
}
private void launch() {
createUI();
launchUI();
}
protected void launchUI() {
frame.setVisible(true);
}
protected void createUI() {
System.setProperty("sun.java2d.noddraw", "true");
frame = new JFrame();
frame.setSize(200, 150);
frame.setAlwaysOnTop(true);
frame.setUndecorated(true);
container = new JPanel();
frame.setContentPane(container);
container.setLayout(new BorderLayout());
container.add(new JLabel("Ubunto vs Vista, I like both."),
BorderLayout.CENTER);
container.setBorder(new LineBorder(Color.BLACK));
titleBar = new JPanel();
titleBar.setLayout(new BorderLayout());
titleLabel = new JLabel("JNA is great!");
titleBar.add(titleLabel, BorderLayout.CENTER);
titleBar.setBorder(new LineBorder(Color.GRAY));
closeButton = new JButton("X");
closeButton.setFocusPainted(false);
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
titleBar.add(closeButton, BorderLayout.EAST);
container.add(titleBar, BorderLayout.NORTH);
slider = new JSlider(0, 100);
slider.setValue(100);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
float value = slider.getValue();
WindowUtils.setWindowAlpha(frame, value * 0.01f);
}
});
container.add(slider, BorderLayout.SOUTH);
RoundRectangle2D.Float mask = new RoundRectangle2D.Float(0, 0, frame
.getWidth(), frame.getHeight(), 20, 20);
WindowUtils.setWindowMask(frame, mask);
centerWindow(frame);
}
public static void centerWindow(Container window) {
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
int w = window.getSize().width;
int h = window.getSize().height;
int x = (dim.width - w) / 2;
int y = (dim.height - h) / 2;
window.setLocation(x, y);
}
}
利用JNA來制作透明效果非常簡單,只需要調用
WindowUtils.setWindowAlpha(window, alpha)就可以了。當然這是JNA特別為UI寫的工具代碼。如果要改變形狀,用WindowUtils.setWindowMask(window, shape)或者WindowUtils.setWindowMask(window, icon)就可以了,但是要注意一點必須設置System.setProperty("sun.java2d.noddraw", "true"),否則JNA會告訴你這個程序不支持透明。當然要運行起來,還得需要兩個Jar,jna.jar 和 examples.jar ,都不是很大,只有200多K。
雖然這篇文章只是介紹了一下JNA關于Swing的簡單用法,但是有了這個我最先到的便是可以做類似于Yahoo Widget和Google Widget之類的東西了,還可以做好看的fishEye,SideBar,JNA的JAR兩個合起來不過400K,卻能讓這么多復雜的事情簡單化,真是精湛的藝術啊,嗯。
posted @
2007-10-21 13:43 ruislan 閱讀(4385) |
評論 (12) |
編輯 收藏
很久沒有上來更新了,因為一些原因。但是,我又回來了,套用MASK的話,小子們,想我嗎?
回來了先報告一個Blogjava的BUG,就是用Opera9瀏覽器寫文章時不能用鼠標點擊編輯區,還好用TAB可以切換過來,然后就是用Google輸入法的話,標點符號和數字輸入一次,編輯區會出來兩個,最后就是編輯完之后好像內容也保存不了,所以我又換回FF來編輯了。
回來了卻不知從何開始了,組件的改造也算拋磚引玉了(當然如果你們喜歡,我還可以繼續改造),冰封的程序要繼續改造的話就要開一個工程了大家集體參與了,所以那就還是先從上次哪位仁兄提到的組件透明的問題,從那個開始吧!show time。
posted @
2007-10-20 16:30 ruislan 閱讀(433) |
評論 (1) |
編輯 收藏
昨天我們改進了選擇區,今天我們來繼續為選擇區選定之后添加一個操作框
下面是改進后的截圖:

正常的情況,右下角出現了一個操作框

在三個(左、右、底)邊際的情況,我們重新計算了位置。
三個按鈕還沒有功能,也不是圖片,但是我們離QQ的截屏程序又進一步了,嗯!
posted @
2007-09-14 18:48 ruislan 閱讀(1160) |
評論 (10) |
編輯 收藏
“千里冰封” 兄弟的截屏程序酷斃了,但是好像9月4日之后就沒有繼續更新了,我們來繼續為他的程序改進,順便也把我們這幾天都在講的2D繪制用進來,我們的目標是讓冰封的截屏程序成為截屏程序里的王!
今天先改進一下截圖時候的選框,還是先放上截圖的截圖(*o*):

這是原來的圖片,下面是改進后的

和改進的代碼部分:
這部分代碼插入 Temp類的paintComponent方法中的 if (showTip) 這句的前面
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.3F));
g2d.setColor(Color.RED.brighter().brighter());
int sX = Math.min(startX, endX);
int sY = Math.min(endY, startY);
g2d.fillRect(sX, sY, Math.abs(endX - startX), Math.abs(endY
- startY));
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 1F));
boolean drawCTip = endX - startX != 0 && endY - startY != 0;
if (drawCTip) {
String cTip = String.format("%dX%d", Math.abs(endX - startX),
Math.abs(endY - startY));
int cTipH = 20;
Font cTipFont = new Font("system", Font.BOLD, 16);
g2d.setFont(cTipFont);
int cTipW = SwingUtilities.computeStringWidth(
getFontMetrics(cTipFont), cTip);
g2d.setPaint(Color.BLACK);
int cStartY = sY - cTipH > 0 ? sY - cTipH : sY;
g2d.fillRect(sX, cStartY, cTipW, cTipH);
g2d.setPaint(Color.WHITE);
g2d.drawString(cTip, sX, cStartY == sY ? sY + cTipH - 3
: sY - 3);
}
g2d.dispose();
怎么樣,比起QQ的截圖程序,我們又近一步了,嗯。
posted @
2007-09-13 13:28 ruislan 閱讀(1488) |
評論 (10) |
編輯 收藏
摘要: 嗯,BeanSoft的話很有道理,令我敬佩,也許是昨天在下對那個“更好的”三個字感到一時憤慨,所以看到UI就自己擴大了問題,想到迎合LookAndFeel上面去了,在此說句對不起了。你的回帖里面偏重于從整個組件的設計和重用性上,我的文章主要講的是如何將2D繪制和組件的繪制結合起來,看客如果既了解了如何繪制自己想要的組件,又能設計得體,重用性高的話也算是對我拋磚引玉的欣慰了。...
閱讀全文
posted @
2007-09-12 13:36 ruislan 閱讀(2440) |
評論 (11) |
編輯 收藏
看來我們的JTextField之旅也到了一個階段,已經很不錯了,現在我們來改造JButton,讓那個呆板的Swing看起來舒服一些。
還是先放上完成后的效果圖:

普通的狀態

鼠標滑過

鼠標按下
和代碼:
1 /**
2 * @(#)RJButton.java 0.1.0 2007-9-11
3 */
4 package ruislan.rswing;
5
6 import java.awt.AlphaComposite;
7 import java.awt.Color;
8 import java.awt.Font;
9 import java.awt.GradientPaint;
10 import java.awt.Graphics;
11 import java.awt.Graphics2D;
12 import java.awt.RenderingHints;
13 import java.awt.Shape;
14 import java.awt.event.MouseAdapter;
15 import java.awt.event.MouseEvent;
16 import java.awt.geom.RoundRectangle2D;
17
18 import javax.swing.JButton;
19
20 /**
21 * Custom JButton
22 *
23 * @version 0.1.0
24 * @author ruislan <a href="mailto:z17520@126.com"/>
25 */
26 public class RButton extends JButton {
27 private static final long serialVersionUID = 39082560987930759L;
28 public static final Color BUTTON_COLOR1 = new Color(205, 255, 205);
29 public static final Color BUTTON_COLOR2 = new Color(51, 154, 47);
30 // public static final Color BUTTON_COLOR1 = new Color(125, 161, 237);
31 // public static final Color BUTTON_COLOR2 = new Color(91, 118, 173);
32 public static final Color BUTTON_FOREGROUND_COLOR = Color.WHITE;
33 private boolean hover;
34
35 public RButton() {
36 setFont(new Font("system", Font.PLAIN, 12));
37 setBorderPainted(false);
38 setForeground(BUTTON_COLOR2);
39 setFocusPainted(false);
40 setContentAreaFilled(false);
41 addMouseListener(new MouseAdapter() {
42 @Override
43 public void mouseEntered(MouseEvent e) {
44 setForeground(BUTTON_FOREGROUND_COLOR);
45 hover = true;
46 repaint();
47 }
48
49 @Override
50 public void mouseExited(MouseEvent e) {
51 setForeground(BUTTON_COLOR2);
52 hover = false;
53 repaint();
54 }
55 });
56 }
57
58 @Override
59 protected void paintComponent(Graphics g) {
60 Graphics2D g2d = (Graphics2D) g.create();
61 int h = getHeight();
62 int w = getWidth();
63 float tran = 1F;
64 if (!hover) {
65 tran = 0.3F;
66 }
67
68 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
69 RenderingHints.VALUE_ANTIALIAS_ON);
70 GradientPaint p1;
71 GradientPaint p2;
72 if (getModel().isPressed()) {
73 p1 = new GradientPaint(0, 0, new Color(0, 0, 0), 0, h - 1,
74 new Color(100, 100, 100));
75 p2 = new GradientPaint(0, 1, new Color(0, 0, 0, 50), 0, h - 3,
76 new Color(255, 255, 255, 100));
77 } else {
78 p1 = new GradientPaint(0, 0, new Color(100, 100, 100), 0, h - 1,
79 new Color(0, 0, 0));
80 p2 = new GradientPaint(0, 1, new Color(255, 255, 255, 100), 0,
81 h - 3, new Color(0, 0, 0, 50));
82 }
83 g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
84 tran));
85 RoundRectangle2D.Float r2d = new RoundRectangle2D.Float(0, 0, w - 1,
86 h - 1, 20, 20);
87 Shape clip = g2d.getClip();
88 g2d.clip(r2d);
89 GradientPaint gp = new GradientPaint(0.0F, 0.0F, BUTTON_COLOR1, 0.0F,
90 h, BUTTON_COLOR2, true);
91 g2d.setPaint(gp);
92 g2d.fillRect(0, 0, w, h);
93 g2d.setClip(clip);
94 g2d.setPaint(p1);
95 g2d.drawRoundRect(0, 0, w - 1, h - 1, 20, 20);
96 g2d.setPaint(p2);
97 g2d.drawRoundRect(1, 1, w - 3, h - 3, 18, 18);
98 g2d.dispose();
99 super.paintComponent(g);
100 }
101 }
102
注意代碼中的幾個部分:
首先是paintComponent方法中最后一行,我們調用了父類的paintComponent方法,這是因為我們要靠父類來繪制字符,但是父類的這個方法除了繪制字符之外還會繪制其他的,所以我們需要關閉掉其他的(當然我們也可以自己來繪制字符,但是JButton提供了方法為什么不用呢),所以我們在構造方法那里調用了:
setBorderPainted(false);
setFocusPainted(false);
setContentAreaFilled(false);
告訴父類不用繪制邊框,不用繪制焦點,不用繪制內容部分,這部分我們自己來搞*o*。
然后就是這一句了g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)告訴繪制API我們需要平滑一點,否則繪制出來會有很多鋸齒喲。
接下來的這一句g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,tran))告訴繪圖API我們需要繪制一個有透明度的,tran就是透明度(0-1)。
然后就是將邊框的邊角變直角為圓角,我們繪制一個RoundRectangle2D,這個就是邊角都為圓角的方形,然后我們根據這個方形來clip我們的方形,這樣方形就被RoundRectangle2D的圓角方形包裹,從而變成了圓角方形。
最后就是繪制外邊線和內邊線,通過改變內邊線和外邊線的色變從而造成陷入或者突出效果。
整個JButton改造完畢,如果你能夠活用clip的話,你也可以做一個五角星的JButton喲。
整個源代碼我連同Eclipse工程文件已經打包成zip放在我的文件里面,下載鏈接如下:
http://www.tkk7.com/Files/ruislan/rswing-0.1.0.zip
posted @
2007-09-11 12:24 ruislan 閱讀(5553) |
評論 (20) |
編輯 收藏
昨天我們給JTextField增加了一個泡泡提示窗口,今天我們繼續昨天的,首先處理在顯示泡泡的時候忽略輸入的Backspace、Enter、Delete、Esc按鍵,然后加上錯誤的時候的聲音提示,最后再給JTextField換裝備,讓它看起來像MSN 8.5beta的輸入框,還是先放上圖片:

這幅圖是MSN的輸入框,輸入框的內部到光標有一部分是有點毛玻璃的感覺,不過這個style是死的,我們改進一下,我們的JTextField在輸入之前是看似普通的,在鼠標放上去之后,看起來就與MSN的輸入框類似了,而且我們還給這個輸入框加入一個淡黃色的背景。
好了,現在很明確要做的事情了
1. 加入鼠標事件監聽器,監聽MouseEnter和MouseExit事件,根據這個兩個事件設置不同的背景色和邊框
2. 做一個能夠顯示毛玻璃效果的邊框
以下是邊框的代碼和部分MyJTextField的代碼,完整的代碼待我會打包傳上來的
MyJTextField.java
在初始化組件的時候加上Border的初始化:
hoverBorder = new CoolBorder(HOVER_BORDER_COLOR, 3);
border = BorderFactory.createCompoundBorder(new LineBorder(
BORDER_COLOR, 1), new EmptyBorder(new Insets(2, 2, 2, 2)));
setBackground(BACKGROUND_COLOR);
setBorder(border);
和事件的初始化:
addMouseListener(new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
setBorder(hoverBorder);
setBackground(HOVER_BACKGROUND_COLOR);
repaint();
}
@Override
public void mouseExited(MouseEvent e) {
setBorder(border);
setBackground(BACKGROUND_COLOR);
repaint();
}
});
以及屏蔽功能性按鈕和發出聲音提示的代碼:
addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
char input = e.getKeyChar();
// ESC27 ,Backspace 8 ,Enter 10, Del 127, must ignore
boolean ignoreInput = input == (char) KeyEvent.VK_ESCAPE
|| input == (char) KeyEvent.VK_BACK_SPACE
|| input == (char) KeyEvent.VK_ENTER
|| input == (char) KeyEvent.VK_DELETE;
if (ignoreInput) {
limitTip.setVisible(false);
numberTip.setVisible(false);
return;
}
if (getText().length() + 1 > limit) {
Toolkit.getDefaultToolkit().beep();
deleteInputChar(e);
limitTip.setVisible(true);
return;
} else {
limitTip.setVisible(false);
}
if (numberOnly) {
if (!Character.isDigit(input)) {
numberTip.setVisible(true);
Toolkit.getDefaultToolkit().beep();
deleteInputChar(e);
} else {
numberTip.setVisible(false);
}
}
}
private void deleteInputChar(KeyEvent source) {
source.setKeyChar((char) KeyEvent.VK_CLEAR);
}
});
下面是Border的完整代碼:
1 /**
2 * @(#)CoolBorder.java 0.1.2 2007-9-10
3 */
4 package ruislan;
5
6 import java.awt.Color;
7 import java.awt.Component;
8 import java.awt.Dimension;
9 import java.awt.GradientPaint;
10 import java.awt.Graphics;
11 import java.awt.Graphics2D;
12 import java.awt.Insets;
13
14 import javax.swing.border.Border;
15
16 /**
17 * Custom Border.
18 *
19 * @version 0.1.2, 2007-9-10
20 * @author ruislan <a href="mailto:z17520@126.com"/>
21 */
22 public class CoolBorder implements Border {
23 private int thickness;
24 private Insets insets;
25 private Dimension lastComponentSize;
26 private Color color;
27 private Color color2;
28
29 public CoolBorder(Color color, int thickness) {
30 this.color = color;
31 if (color == null) {
32 this.color = color = Color.gray;
33 }
34 color2 = new Color(210, 210, 210, 0);
35 this.thickness = thickness;
36 }
37
38 @Override
39 public Insets getBorderInsets(Component c) {
40 Dimension currentComponent = c.getSize();
41
42 if (currentComponent.equals(lastComponentSize)) {
43 return insets;
44 }
45
46 insets = new Insets(thickness, thickness, thickness, thickness);
47 lastComponentSize = currentComponent;
48 return insets;
49 }
50
51 @Override
52 public boolean isBorderOpaque() {
53 return true;
54 }
55
56 @Override
57 public void paintBorder(Component c, Graphics g, int x, int y, int width,
58 int height) {
59 Graphics2D g2d = (Graphics2D) g.create();
60 // 畫上邊緣
61 GradientPaint gp = new GradientPaint(x, y, color, x, y + thickness,
62 color2);
63 g2d.setPaint(gp);
64 g2d.fillRect(x, y, width, thickness);
65 // 畫下邊緣
66 gp = new GradientPaint(x, y + height - thickness - 1, color2, x, y
67 + height, color);
68 g2d.setPaint(gp);
69 g2d.fillRect(x, y + height - thickness - 1, width, thickness);
70 // 畫左邊緣
71 gp = new GradientPaint(x, y, color, x + thickness, y, color2);
72 g2d.setPaint(gp);
73 g2d.fillRect(x, y, thickness, height);
74 // 畫右邊緣
75 gp = new GradientPaint(x + width - thickness - 1, y, color2, x + width,
76 y, color);
77 g2d.setPaint(gp);
78 g2d.fillRect(x + width - thickness - 1, y, thickness, height);
79 // 畫外框
80 g2d.setPaint(color);
81 g2d.drawRect(x, y, width - 1, height - 1);
82 g2d.dispose();
83 }
84
85 }
86
然后來欣賞我們的結果吧:

鼠標放上去之前

鼠標放上去之后

我們輸入了不是數字的字符
至此,我們的JTextField又向前走了一步,下次我們還能如何改進呢?把JTextField這個死板的長方形改造成云狀的或者其他形狀的?
附源代碼下載地址:http://www.tkk7.com/Files/ruislan/myjtextfield.zip
posted @
2007-09-10 13:21 ruislan 閱讀(4347) |
評論 (3) |
編輯 收藏
摘要: 接著昨天的MyJTextField,我們繼續為JTextField增強,今天我們為MyJTextField增加一個泡泡提示。先看圖片:
當輸入第三個字符'a'時,由于昨天我們的MyJTextField做了處理,所以'a'不能被輸入,而且彈出泡泡提示你下次不要了喲!
同樣的,下一張圖片:
為MyJTextField增加泡泡提示功能要做以下幾件事情:
1. 泡泡窗口
2. 顯示的...
閱讀全文
posted @
2007-09-09 13:32 ruislan 閱讀(5179) |
評論 (2) |
編輯 收藏
在網上Google了一下,基本上的做法有兩種,第一種是JFormattedTextField;另外一種是自己繼承PlainDocument,
Override
insertString方法,然后用JTextFiled.setDocument的方法放入自己繼承的對象實例。但是最終我都沒有采用這兩種方法,首
先我對JFormattedTextField的方式不太舒服,然后感覺繼承PlainDocument有點太重,所以自己考慮了一種方案,也許有人跟我
的想法雷同,那就純屬巧合了。
下面放上代碼:
public class MyJTextField extends JTextField {
private int limit = Integer.MAX_VALUE;
private boolean numberOnly;
public MyJTextField() {
addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if (getText().length() + 1 > limit) {
deleteInputChar(e);
return;
}
if (numberOnly) {
char input = e.getKeyChar();
if (!Character.isDigit(input)) {
deleteInputChar(e);
}
}
}
private void deleteInputChar(KeyEvent source) {
source.setKeyChar((char) KeyEvent.VK_CLEAR);
}
});
}
public void setMaxTextLength(int limit) {
if (limit < 0) {
return;
}
this.limit = limit;
}
public int getMaxTextLength() {
return limit;
}
public void setNumberOnly(boolean numberOnly) {
this.numberOnly = numberOnly;
}
public boolean isNumberOnly() {
return this.numberOnly;
}
}
整個思路很簡單,就是檢查Type事件,如果超出了限制就刪除新增加的字符,如果設置了只是數字,那么不是數字的就刪除新增加的字符。使用的時候只需要
MyJTextField textField = new MyJTextField();
textField.setLimit(8);
textField.setNumberOnly(true);
結果就是最多輸入8個字符而且只能是數字。
posted @
2007-09-09 00:41 ruislan 閱讀(1285) |
評論 (2) |
編輯 收藏