2013年9月13日 星期五

[ Java 代碼範本 ] 如何為 Java UI 添加 Key Listener

Preface: 
在平常我們常常使用 Windows 的 Ctrl+C (複製) , Ctrl+P (貼上) etc, 那我們有沒有辦法在 Java UI 上添加類似的快捷鍵呢? 答案是使用 KeyAdapter 類別. 底下我們透過一個簡單範例說明如何為 UI 添加快捷鍵與剪貼簿的使用. 

CalcUI 範例: 
考慮我們有個只有加法的 Calculator 如下, 在按下 "=" 按鈕後會將計算結果顯示於最右邊的 TextField: 
 

而如果我們要將計算結果取出, 則需要到最右邊的 TextField 選取所有內容後, 再按下 Ctrl+C 的複製快捷鍵. 但我們希望一旦按下 "=" 鍵, 接著直接按下 Ctrl+C 的快捷鍵便可以將計算結果置於剪貼簿中. 因此下面將說明如何完成這個需求. 

Step1. 第一步就是要先讓 UI 可以監聽到鍵盤的 Event, 此因我們新增另一個類別來處理 Key event 並監聽 Ctrl+C 的事件: 
  1. class MyKeyListener extends KeyAdapter{    
  2.     public void keyPressed(KeyEvent e){    
  3.         if ((e.getKeyCode() == KeyEvent.VK_C) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {  
  4.             // Copy calculation result to clipboard  
  5.            }  
  6.     }    
  7. }  
Step2. 接著為了要能操作 clipboard, 最外層的 JFrame 需要實作 ClipboardOwner 介面; 並加入剛剛建立的 KeyEvent 監聽類別建立的物件: 
  1. public class CalcUI extends JFrame implements ClipboardOwner{  
  2. ...  
  3.     public CalcUI() {  
  4.         super();  
  5.         initGUI();  
  6.         clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();    
  7.         addKeyListener(new MyKeyListener());  
  8.         setFocusable(true); // 確保自己能得到 focus 已收到監聽事件  
  9.         frame = this; // 方便後續 reference 到自己.  
  10.     }  
  11. ...  
  12. }  
Step3. 然後因為按下 "=" 按鈕後外面的 JFrame 會失去 focus (導致不能監聽鍵盤事件), 因此要稍作修改: 
  1. jButtonEq.addActionListener(new ActionListener() {  
  2.     public void actionPerformed(ActionEvent evt) {  
  3.         int sum = Integer.valueOf(jTextFieldA.getText()) + Integer.valueOf(jTextFieldB.getText());  
  4.         jTextFieldSum.setText(String.valueOf(sum));  
  5.         frame.requestFocus(); // 讓外面的 JFrame 獲得 focus 以繼續監聽事件  
  6.     }  
  7. });  
Step4. 最後修改原先的 MyKeyListener 讓使用者鍵入 Ctrl+C 後將計算結果置於剪貼簿: 
  1. class MyKeyListener extends KeyAdapter{    
  2.     public void keyPressed(KeyEvent e){    
  3.         if ((e.getKeyCode() == KeyEvent.VK_C) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {  
  4.             StringSelection contents = new StringSelection(jTextFieldSum.getText());  
  5.             clipboard.setContents(contents, frame);    
  6.            }  
  7.     }    
  8. }  
完整代碼如下: 
  1. package swing.demo;  
  2. import java.awt.Toolkit;  
  3. import java.awt.datatransfer.Clipboard;  
  4. import java.awt.datatransfer.ClipboardOwner;  
  5. import java.awt.datatransfer.StringSelection;  
  6. import java.awt.datatransfer.Transferable;  
  7. import java.awt.event.ActionEvent;  
  8. import java.awt.event.ActionListener;  
  9. import java.awt.event.KeyAdapter;  
  10. import java.awt.event.KeyEvent;  
  11.   
  12. import javax.swing.JButton;  
  13. import javax.swing.JFrame;  
  14. import javax.swing.JLabel;  
  15. import javax.swing.JTextField;  
  16. import javax.swing.SwingConstants;  
  17. import javax.swing.SwingUtilities;  
  18. import javax.swing.WindowConstants;  
  19.   
  20. public class CalcUI extends JFrame implements ClipboardOwner{  
  21.     private JTextField jTextFieldA;  
  22.     private JTextField jTextFieldSum;  
  23.     private JButton jButtonEq;  
  24.     private JTextField jTextFieldB;  
  25.     private JLabel jLabelPlus;  
  26.     public CalcUI frame;  
  27.     private Clipboard clipboard;    
  28.   
  29.     /** 
  30.     * Auto-generated main method to display this JFrame 
  31.     */  
  32.     public static void main(String[] args) {  
  33.         SwingUtilities.invokeLater(new Runnable() {  
  34.             public void run() {  
  35.                 CalcUI inst = new CalcUI();  
  36.                 inst.setLocationRelativeTo(null);  
  37.                 inst.setVisible(true);  
  38.             }  
  39.         });  
  40.     }  
  41.       
  42.     public CalcUI() {  
  43.         super();  
  44.         initGUI();  
  45.         clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();    
  46.         addKeyListener(new MyKeyListener());  
  47.         setFocusable(true);  
  48.         frame = this;  
  49.     }  
  50.       
  51.     private void initGUI() {  
  52.         try {  
  53.             setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);  
  54.             getContentPane().setLayout(null);  
  55.             {  
  56.                 jTextFieldA = new JTextField();  
  57.                 getContentPane().add(jTextFieldA);  
  58.                 jTextFieldA.setText("10");  
  59.                 jTextFieldA.setBounds(12, 12, 83, 24);  
  60.                 jTextFieldA.setHorizontalAlignment(SwingConstants.CENTER);  
  61.             }  
  62.             {  
  63.                 jLabelPlus = new JLabel();  
  64.                 getContentPane().add(jLabelPlus);  
  65.                 jLabelPlus.setText("+");  
  66.                 jLabelPlus.setBounds(101, 15, 17, 17);  
  67.                 jLabelPlus.setFont(new java.awt.Font("Arial",1,12));  
  68.                 jLabelPlus.setHorizontalAlignment(SwingConstants.CENTER);  
  69.             }  
  70.             {  
  71.                 jTextFieldB = new JTextField();  
  72.                 getContentPane().add(jTextFieldB);  
  73.                 jTextFieldB.setText("10");  
  74.                 jTextFieldB.setBounds(125, 12, 20, 24);  
  75.                 jTextFieldB.setSize(83, 24);  
  76.                 jTextFieldB.setHorizontalAlignment(SwingConstants.CENTER);  
  77.             }  
  78.             {  
  79.                 jButtonEq = new JButton();  
  80.                 getContentPane().add(jButtonEq);  
  81.                 jButtonEq.setText("=");  
  82.                 jButtonEq.setFocusable(false);  
  83.                 jButtonEq.setBounds(214, 12, 42, 24);  
  84.                 jButtonEq.setFont(new java.awt.Font("Arial",1,12));  
  85.                 jButtonEq.addActionListener(new ActionListener() {  
  86.                     public void actionPerformed(ActionEvent evt) {  
  87.                         int sum = Integer.valueOf(jTextFieldA.getText()) + Integer.valueOf(jTextFieldB.getText());  
  88.                         jTextFieldSum.setText(String.valueOf(sum));  
  89.                         frame.requestFocus(); // 讓外面的 JFrame 獲得 focus 以繼續監聽事件  
  90.                     }  
  91.                 });  
  92.             }  
  93.             {  
  94.                 jTextFieldSum = new JTextField();  
  95.                 getContentPane().add(jTextFieldSum);  
  96.                 jTextFieldSum.setText("?");  
  97.                 jTextFieldSum.setBounds(267, 12, 105, 24);  
  98.                 jTextFieldSum.setHorizontalAlignment(SwingConstants.CENTER);  
  99.             }  
  100.             pack();  
  101.             this.setSize(400, 86);  
  102.         } catch (Exception e) {  
  103.             //add your error handling code here  
  104.             e.printStackTrace();  
  105.         }  
  106.     }  
  107.       
  108.     class MyKeyListener extends KeyAdapter{    
  109.         public void keyPressed(KeyEvent e){    
  110.             if ((e.getKeyCode() == KeyEvent.VK_C) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) {  
  111.                 StringSelection contents = new StringSelection(jTextFieldSum.getText());  
  112.                 clipboard.setContents(contents, frame);    
  113.             }  
  114.         }    
  115.     }  
  116.   
  117.     @Override  
  118.     public void lostOwnership(Clipboard arg0, Transferable arg1) {}   
  119. }  
Supplement: 
* [blog] java Gui 键盘监听事件 
* [ Java 代碼範本 ] 用 java 設定、取用剪貼簿內容 
* stackoverflow: Java detect CTRL+X key 
* Java Tutorial : How to write key listener

2013年9月12日 星期四

[ Java 代碼範本 ] 擷取 Web page 的影像

Preface:
假設裡有個 URL, 你希望得到該 URL 連到的 Web page 的影像, 硬幹的方法是:
1. 打開瀏覽器
2. 輸入 URL
3. 影像擷取

但如果你要 automation 的話, 則可以利用 Java 的 Deskop 類別上的方法 browse(URI uri) 幫你打開瀏覽器並輸入 URL; 並利用類別 Robot 上面的方法 createScreenCapture(Rectangle screenRect) 幫你擷取桌面上的截圖. 然而這樣的方法必須在有 UI 的環境下才生效, 如果你是在 Linux runlevel<=2 (沒有 UI) 的情況下, 則會有 exception 發生. 此時就麻煩點要採用第二種方法.

方法一:
使用 Java 自帶類別就可以辦到, 但是前提必須待在有 UI 的環境下 (這樣才打得開瀏覽器 orz), 底下為範例代碼:
  1. /** 
  2. * BD: Take Image of web page from given URL. 
  3. * REF: 
  4. *  - http://stackoverflow.com/questions/2746289/converting-webpage-into-jpeg-image-using-java 
  5. *  - http://stackoverflow.com/questions/602032/getting-java-gui-to-open-a-webpage-in-web-browser 
  6. *  - http://stackoverflow.com/questions/7032556/how-to-take-a-webpage-screenshot 
  7. * @param url: URL to browse 
  8. * @param output: Output image file (.png) 
  9. * @param st: Sleep time which should exceed browsing time to have correct image. 
  10. * @throws Exception 
  11. */  
  12. public static void TakeImageOfWebPage(String url, File output, long st) throws Exception  
  13. {         
  14.     Desktop.getDesktop().browse(new URI(url));  
  15.        Robot robot = new Robot();  
  16.        Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();  
  17.        Rectangle rect = new Rectangle(screenDim);  
  18.        Thread.sleep(st);  
  19.        BufferedImage img = robot.createScreenCapture( rect );          
  20.        ImageIO.write(img, "png", output);  
  21. }  
假設你要 take 雅虎的 web page image, 則可以如下使用上面 API:
  1. public static void main(String[] args) throws Exception{          
  2.     TakeImageOfWebPage("http://www.kimo.com.tw", new File("yahoo.png"), 1000);            
  3. }  
此時你會發現你的瀏覽器被打開, 而輸出的 "yahoo.png" 即是當前 yahoo 的首頁 image. 這會有個困擾就是當你執行幾次, 你的瀏覽器就會產生多少個分頁....

方法二:
第二個方法不會有第一個方法造成瀏覽器產生多個分頁的問題, 而且是使用在 Linux runlevel=2 沒有 UI 的環境下. 但是在使用這個方法前有些 library 要去下載:
* selenium: Selenium browser automation framework
* Apache Commons IO: Commons IO is a library of utilities to assist with developing IO functionality.
* google-collections: Guava is a fully compatible superset of the old Google Collections Library.
* java-json.jar

除此之外, 你還需要安裝 xvfb, 如果是在 Ubuntu, 下面的命令會幫你搞定:
# apt-get install xvfb

接著下面是範例代碼:
  1.    private static int      DISPLAY_NUMBER  = 99;  
  2.    private static String   XVFB            = "/usr/bin/Xvfb";  
  3.    private static String   XVFB_COMMAND    = XVFB + " :" + DISPLAY_NUMBER;      
  4. public static void TakeImageOfWebPageViaFirefox(String url, String fn) throws Exception  
  5. {  
  6.     // http://stackoverflow.com/questions/7032556/how-to-take-a-webpage-screenshot  
  7.     // https://code.google.com/p/selenium/  
  8.     Process p = Runtime.getRuntime().exec(XVFB_COMMAND);  
  9.        FirefoxBinary firefox = new FirefoxBinary();  
  10.        firefox.setEnvironmentProperty("DISPLAY", ":" + DISPLAY_NUMBER);  
  11.        WebDriver driver = new FirefoxDriver(firefox, null);  
  12.        driver.get(url);  
  13.        File scrFile = ( (TakesScreenshot) driver ).getScreenshotAs(OutputType.FILE);  
  14.        FileUtils.copyFile(scrFile, new File(fn));  
  15.        driver.close();  
  16.        p.destroy();  
  17. }  
這次我們來 take google web page 的 image 並存成檔案 "google.png":
  1. TakeImageOfWebPageViaFirefox("http://www.google.com.tw", "google.png");   
Supplement:
* stackoverflow: How to take a webpage screenshot?
* ConvertWebpage: 工具幫你自動轉換
* CutyCapt
CutyCapt is a small cross-platform command-line utility to capture WebKit's rendering of a web page into a variety of vector and bitmap formats, including SVG, PDF, PS, PNG, JPEG, TIFF, GIF, and BMP...

Using CutyCapt without X server 執行範例:
# xvfb-run --auto-servernum ./CutyCapt --url=http://cutycapt.sourceforge.net --out=cutycapt.png


[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...