程式碼高𠅙

2012/09/14

你所想像不到的 Javascript

/**
 * 這是一篇我在 N 年前所寫的 Javascript 教材,近來隨著 Node.js 及 Mobile web 
 * 的熱潮興起,Javascript 又再度走紅,因此再把它重新發佈,希望能對用到的人
 * 有所幫助。
 */
 
這不是一篇教你如何在網頁中應用 JavaScript 的教材。這是一篇讓你明瞭看似簡單的 JavaScript,其實其核心語法功能強大。我將在這篇文章中說明 JavaScript 物件導向的特性,並說明如何透過這些特性,達到傳統程式語言 (C++/Java) 所難以完成的功能。
 
Written by Edward Hsieh/謝鎮澤
 
January 04, 2002 -- 1st Edition
January 04, 2009 -- 2nd Edition
 
Object and JavaScript
 
一般談到 Object-Oriented,皆會提到物件導向語言必須具有的三個特性,即封裝、繼承及多型。這三種特性中,若缺少了繼承的特性、則稱為 Object-Based。
 
Javascript 與其說是物件導向語言,倒不如說它是以物件為基礎的程式語言,但它極為動態的程式設計風格,卻能模擬出物件導向的特性。這種作法與大家所熟悉的,以類別 (class) 為基礎的靜態物件導向語言大不相同。
 
在 Javascript 中有五種基礎的物件型態,包含 undefined, object, boolean, number, 以及 string。以下的範例可供驗證這五種基礎型態:
 
alert (typeof(X));
alert (typeof(null));
alert (typeof(true));
alert (typeof(123));
alert (typeof('abc'));
 
除了上述五種基礎類型,使用者還可自訂型態,而這些自訂的型態,都是「以物件為基礎」的。
 
Create Objects in JavaScript
 
在 Javascript 中建立物件的方法相當簡單。直接看個例子:
 
var main = new Object;     // 建立新物件
main.x = 123; // 設定物件成員變數(屬性)之一
main["y"] = "XYZ"; // 設定物件成員變數(屬性)之二
alert(main["x"]); // 取得物件屬性並輸出
alert(main.y);    
 
可以看到在 JavaScript 中,main.x 與 main["x"] 這兩種語法是通用的。其實在其他語言中,這兩種表示法的語意並不相同。我稍後再作說明。
 
List All Members in an Object
 
這是 JavaScript 的必殺技,使用 JavaScript 的人務必要學會這個技巧。底下函式可以傳回一個物件的所有成員的字串表達式,包括物件中的屬性及方法。在物件導向程式設計中,這種技術叫 reflection。
 
function listMember(main) {
   var s = "";
   for( key in main ) // 使用 in 運算子列舉所有成員
   s += key + ": " + main[key] + "\n";
   return s;
}
 
範例碼中的 key 會對應到物件中的屬性名稱,如 "x" 或 "y",而 main[key] 則對應到屬性值。
 
說這項技巧是必殺技的原因是,你可以透過這項技巧,將物件封裝的黑箱打開來,看看裡面藏有什麼東西。我常用這項技巧來看看 IE 與 Mozilla 的 DOM 物件模型有何不同。試試看下面呼叫範例,就可以知道這項技巧的強大了:
 
var ary = [123, "abc"];
alert (listMember(ary));
alert (listMember(document.location));
 
Construct Object with Initial Value
 
要在建立物件的同時指定物件初始值,必須先透過 function 建立一個「原型物件」(或稱為 constructor),再透過 new 運算子建立新物件。例如以下程式碼會建立一個二維陣列的原型,再產生一個新的二維物件。
 
function Array2DVar(x,y) {     // 定義二維陣列原型
   this.length = x;
   this.x = x;        // x 維度長度
   this.y = y;        // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
   this[i] = new Array(y);    // this 代表物件本身
}
 
var a2dv = new Array2DVar(10, 10);   // 建立新的 10*10 的二維陣列
a2dv[1][3] = "ABC";       // 設定二維陣列元素值
a2dv[2][6] = "XYZ";
a2dv[9][9] = 1000;
 
alert( a2dv[1][3]);  // 取得二維陣列元素值,並顯示出來
alert( a2dv[2][6]);
alert( a2dv[9][9]);
 
Initial Array Object
 
在 JavaScript 中陣列也是物件 (其實近代多數語言中陣列也都是物件,只有像 C 或 Assembly 這類古老的語言才不把陣列看成物件),因此也可以用 constructor 的語法來建構。當然 JavaScript 還提供了 [] 語法,以更方便建構陣列,範例如下:
 
a = new Array("abc", "xyz", 1000); // constructor 語法,或
a = ["abc", "xyz", 1000]; // 陣列標準語法
 
陣列的元素可以是簡單的資料、其他物件,或是函數。舉個例子來在陣列裡面放函式:
 
b = [ // 使用函式作為陣列元素
   function () { alert("這個好玩!") },
   function () { alert("再按一次離開!") },
   function () { alert("再來一次!") },
   function () { alert("最後一次!") }
];
 
for (var i = 0; i < b.length ; i++)
   b[i]();
 
最後一個 for 迴圈是個有趣的應用。由於 b 陣列中現在存放的所有元素都是函式,因此我們可以對 b 的每個元素進行呼叫。
 
Object as Association Array
 
關連陣列 (Assocation Array) 又稱作 Map 或 Dictionary,是一種物件容器,其中可以放置許多的 key-value pair,以存取子物件。在 JavaScript 中,物件本身就可以作為關連陣列。以關連陣列的方式初始化物件的範例如下:
 
obj1 = {"a" : "Athens" , "b" : "Belgrade", "c" : "Cairo"};
alert(obj1["a"]); // 顯示 Athens
 
obj2 = {
   name: "Edward",
   showName: function() { alert(this.name); }  // 使用函式作為物件屬性
}
 
obj2.showName();  // 顯示 Edward
obj2.age = 23; // 屬性可以動態加入
 
其中 obj1 儲存了三個子元素,其鍵 (key) 為 "a", "b" 與 "c",而值 (value) 為 "Athens", "Belgrade" 與 "Cairo"。obj2 中 showName 鍵所對應的值為 function,因此 obj2.showName() 即為函式呼叫。
 
Object as Return Value
 
雖然 Javascript 的函式只能傳回一個變數,但你卻可以將傳回值設定為物件,達到傳回 1 個以上變數值的效果:
 
function a () {
   return [32, 17];
}
 
b = a();
alert( b ); // 或
alert(a());
 
function pixel () { return {"x": 32, "y":17}; }
 
point = pixel (); alert (point.x + "n" + point.y); // 或 alert (pixel().x + "n" + pixel().y);
 
Delegation Function Object
 
函式也是物件,只是其中包含的是程式的邏輯。這項特性可拿來作為委任式的程式設計,亦即使用委任函式當作另一函式的參數:
 
function doloop(begin, end, func) { // 這個函式是個 iterator
   for (var i = begin; i < end; i++) {
       func(i);
   }
}
 
function func1(i) { // 印出 ** n **
   document.writeln("** " + i + " **");
}
 
doloop(1, 10, func1); // 印出 1o 行 ** n **
 
doloop(20, 29, function(i) { // 印出 10 行 ## n ##
   document.writeln("## " + i + " ## ");
});
 
Object = Properties + Behaviors
 
古有明訓:程式 = 資料結構 + 演算法。而物件是建構程式的基本單位,自然的具有相同的性質。物件除了有屬性 (property),也可具有操作 (behavior),也就是函式。
 
假如我們要使用一維陣列來模擬二維陣列,那麼就無法使用 ary[x][y] 這種表示法來設定或取得陣列成員。不過我可以定義一個 set 方法來設定成員變數,而以 get 方法來取得成員變數值。原型函式定義如下:
 
function Array2D(x,y){      // 以一維陣列模擬二維陣列的原型物件
   this.length = x * y;      // 陣列總長
   this.x = x;         // x 維度長度
   this.y = y;         // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
       this[i] = null;
   this.get = function(x,y){      // 成員函式:取得陣列第 [x,y]個元素值
      return this[x*this.x + y];
   }
    
   this.set = function(x,y,value){  // 成員函式:設定陣列第 [x,y] 個元素值
   this[x*this.x + y] = value;
}
}
 
我們接著來使用它:
 
var a2d = new Array2D(10, 10);    // 建立新的「二維」陣列
 
a2d.set(1, 3, "ABC");   // 設定「二維」陣列元素值
a2d.set(2, 6, "XYZ");
a2d.set(9, 9, 1000);
 
alert( a2d.get(1,3) );  // 取得「二維」陣列元素值,並顯示出來
alert( a2d.get(2,6) );
alert( a2d.get(9,9) );   
 
Member Function Outside of Constructor
 
我們也可以將物件成員函式寫於原型物件之外。以下的 Array2D 物件與上一個範例中的 Array2D 物件有相同的作用,只不過這次是寫在原型物件之外。
 
function Array2D(x,y){      // 以一維陣列模擬二維陣列的原型物件
   this.length = x * y;      // 陣列總長
   this.x = x;         // x 維度長度
   this.y = y;         // y 維度長度
   for(var i = 0; i < this.length; i++)  // 初始各元素值為 null
       this[i] = null;
   this.get = Array2DGet; // 用這種方式把成員函式掛進來
   this.set = Array2DSet;
}
 
function Array2DGet(x,y){      // 成員函式:取得陣列第 [x,y] 個元素值
return this[x*this.x + y];
}
 
function Array2DSet(x,y,value){    // 成員函式:設定陣列第 [x,y] 個元素值
this[x*this.x + y] = value;
}
 
Dynamic Object Function
 
這裡說明如何為一個已定義物件,動態的加上其他操作的方法。
 
如果一物件已定義完成,而您也使用它來建立了新的物件,這時候您想為原型物件增加新的操作 (而不修改原型物件的原始碼),讓所有該物件的複本都能使用該操作,該如何達成呢?方法是使用物件的 prototype 屬性。以下這個例子,為 Array 這類 Object 在執行期加入一個 max 方法,以取得陣列元素之最大值 (修改自微軟 jscript.chm之範例):
 
function array_max(){      // 定義求取 Array 最大值之函式
   var i, max = this[0];
   for (i = 1; i < this.length; i++){
       if (max < this[i])
           max = this[i];
   }
   return max;
}
Array.prototype.max = array_max;   // 在 Array 原型中加入 max 函式
 
上面的程式碼,首先建立一個 array_max 方法,以求取陣列之最大元素。接著將這個方法設定給 Array 原型物件。
 
var x = new Array(1, 2, 3, 4, 5, 6);  // 透過 Array 建構子建立一陣列
                                 // 想求取 x 中某一元素之最大值
var y = x.max( );       // 取得 x 之最大元素
 
Dynamic Mix In
 
假如物件 source 有 mathod1, method2 兩個函式;而另一物件 target 有 methodA 及 methodB 兩個函式。現在我想把 source 的所有特性 (feature) 匯入到 target 中,我們可以撰寫一個 mixin 函式如下,來達到這裡所說的功能:
 
function mixin(target, source) {
   if( typeof source == "object")
       for( value in source )
           target[value] = source[value];
}
 
這個 mixin 函式可以動態的為 target 加上另一物件的所有操作。這種 Mix in 的功能可是 C++/Java 的 static type 語言所望塵莫及的。完整的應用範例如下:
 
function listMember(main) {
   var s = "";
   for( key in main ) // 使用 in 運算子列舉所有成員
       s += key + ": " + main[key] + "\n";
   return s;
}
 
function main(){              // main 之建構子
   this.name = "main";
   this.methodA = function() {};
   this.methodB = function() {};
}
var dynamic = {
   method1: function() {},
   method2: function() {}
}
function mixin(target, source) {
   if( typeof source == "object")
       for( value in source )
           target[value] = source[value];
}
obj = new main();
mixin(obj, dynamic);   // 匯入 dynamic 物件之所有功能
alert(listMember(obj));
 
Singleton Object
 
用 associate array 的語法來建立 singleton, 用 function contructor 的語法來建立可多重複製的原型物件
 
Inheritance Through Functions
 
function superClass() {
 this.supertest = superTest; //attach method superTest
}
 
function subClass() {
 this.inheritFrom = superClass;
 this.inheritFrom();
 this.subtest = subTest; //attach method subTest
}
 
function superTest() {
 return "superTest";
}
  
function subTest() {
 return "subTest";
}
 
 
var newClass = new subClass();
 
alert(newClass.subtest()); // yields "subTest"
alert(newClass.supertest()); // yields "superTest"

一則關於思考的小故事--心裡掛鳥籠

一則關於思考的小故事,出自《換個思考,換種人生》第 3 章「精通思考流程」,找個地方思考:
偉大發明家兼德科公司創辦人凱特林,擁有 140 多項專利,及近 30 多所大學的榮譽博士頭銜,我曾經聽他談起專門闢室思考的事情,他比喻那是在心裡掛鳥籠。
這種說法似乎很怪異,然而當你聽過他以 100 美元跟人打睹的事,就會比較有概念了。
有一回凱特林向朋友說,他有辦法讓對方在來年買一隻鳥當寵物。那位朋友心想,誰也無法「逼」他做這種事,就和他打賭了。
不久之後,凱特林送了一個瑞士手工精製的昂貴鳥龍給這位朋友。朋友把鳥籠帶回家,覺得鳥籠實在美極了,於是掛在飯廰裡。可是他發現每當客人來訪時,總有人問:「你的鳥什麼時候死的?」
「我從沒養過鳥。」他會這麼說,然後把整件事解釋一遍。重複幾次之後,他終於去買了一隻鸚哥,再把輸掉的 100 美元付給凱特林。凱特林後來說:「如果你把鳥籠掛在心裡,終究會放點什麼東西進去的。」
正如凱特林的鳥籠能引來一隻鳥,特定的思考地點也能引來好的想法。如果你來到思考的地點希望能產生好的想法,那麼終究會想出一些好點子。
這則故事也讓我回想起個人兒時的生活經歷:小學一二年級前,我並不是特別喜歡讀書。有一天父親竟然買了一架簡易式的折疊式書桌,說是給我當禮物。當時我對那書桌的設計感到好奇,也覺得它的樣式精巧。每天吃完晚飯後,非得「享受」一下坐在它上面的「樂趣」不可。就這樣,也養成我至今為止尚稱喜歡閱讀的習慣。

如果這樣的人也能成功的扭轉她的生命

在我生活周遭,免不了有些因為生活過得並不很好,而怪罪生活環境的朋友。雖然我認為他們應該停止抱怨,做些更有利於生命的事,但是總是少了一個強而 有力的案例,來說明每個人的確可以因為信念及努力,成功的改變他們的人生,直到我讀到《換個思考,換種人生》,「技巧五:釋放策略思考的力量」,「策略持 家」一節:
萊恩於 1900 年代中期住在俄亥俄州的迪凡斯。她一直沒有學開車,自從有了孩子以後,也從沒出外工作過。在她那個年代,身為母親就必需留在家裡。這本來也沒有什麼問題,只不過她家裡很缺錢,而且她有 10 個小孩!
她的丈夫凱利在一家機械廠工作,收入十分微薄。更糟的是,凱利是個酒鬼,每星期拿回家的錢,1/3 都被他喝掉了。
任何人在街上遇見萊恩,恐怕都看不出她會運用策略思考,但她的確是。她必需撫養 10 個小孩,打理家務,還要另外賺到足夠的錢養活全家。
在那個時間,製造廠商經常贊助徵稿競賽…〈略過部分〉
萊恩的文字能力不錯,婚前在地方報社工作又鍛鍊了不少,因此她想了一個順理成章的計劃。既然她不能外出賺錢 (即使有人願意照顧 10 個小孩,你能想像這筆褓姆費有多可觀嗎?) 那就參加競賽來賺取金錢與家用品。
想要照顧一家 12 口,洗這麼多人的衣服,還要同時寫數百首詩。廣告歌曲和宣傳短文,需要高度的策略。萊恩做到了。
首先,她苦心建立了一些關係網,用來蒐集參賽表格和購物證明,諸如盒蓋、罐頭標籤等等。其次,她必須在做家事的時候寫東西。每年她都買一本活頁筆記薄,把點子記錄下來,並追蹤一切參賽後的相關資訊。
接下來的挑戰,是找出時間寫作。她處理家務時,總是會把筆記本翻開就近放著,但是她發現燙衣服是最佳時機。
萊恩的策略思考並不限於書寫的準備工作,她也運用策略挑選書寫內容。每一次徵文比賽,她都慎選字眼。她的女兒泰莉回想萊恩準備競賽的情形,說道:
「她 總是會說競賽不只是收集盒蓋和表現聰明才智,還要考慮格式 (有些競賽會要求使用特定字眼,有時若是使用產品想關字眼可以加分)、產品對象 (針對家庭、年輕人或小孩) 和評審。比起贊助商和產品,參賽者更應該重視作為評審的廣告代理商。每個廣告商都有他們的偏好,可能是韻文或散文,可能是幽默或中規中矩的文章。」
萊恩打聽出每家廣告商的喜好,這個策略幫了大忙。在幾年當中,她為這個大家庭贏了幾台洗衣機和烘乾機,數十件大大小小的電器用品、兩輛新車 (車子賣了)、數面筆小額獎金,和兩筆大額獎金,分別是 5000 元與 3400 元。
她把第一筆大額獎金做為頭期款,買下一棟房子,一家 12 口終於搬出 2 房的出租屋。第二筆獎金則用來償還丈夫偷偷申請的二胎房貸 (她一直到償還日前一個月才發現)。
當情況不允許失敗,策略思考就是你最好的選擇。萊恩本來可以偶爾寫首詩,投稿到地方報社去,但她需要做點事來幫忙家人的生計。「像我爸爸這樣的丈夫和父親,永遠不可能改變,」她的女兒說:「我們家唯一的希望就是看媽媽怎麼改變,怎麼撫養一群快樂健康的孩子。」
萊恩成功了。她不僅讓家人活下來,還幫助他們成功。她擁有 7 個小孩大學畢業,1 個拿到博士學位,還有 1 個法學院畢業。
這則故事給了我很深的感觸。如果在那樣環境下的女人都能因為策略思考而成功,那麼我相信大都數人成功的條件都比她優越的多。我把這樣的故事告訴一個酒鬼朋友,想聽聽看他有何看法。
他說:嗯!能討到這樣的老婆就好了。
嗯,這……也算是策略思考的第一步吧。

2011/10/14

Google+ Bookmarklet



Google+  <== 這是 Bookmarklet 連結
使用方式:
  1.  將上面這個 Bookmarklet 連結,拉到瀏覽器的網址列,或加入書籤。
  2. 在 網址列 或 書籤 中點選剛加入的 Google+ Bookmarklet,就能將目前瀏覽的網頁,分享到 Google+ 上,以下是分享頁面抓圖。
  3.  按下 Share 鈕即可分享。


2011/09/06

用 Java Annotation 簡化命令列參數處理

撰寫 Java 命令列程式,免不了要處理命令列參數。常見的作法是跑個 for loop, 裡面用 if ... else 去判斷要執行的選項。這樣的作法當然可以達到目的,不過比較不具有程式的美感。

其實在 Web 上,同樣的問題也存在,就是怎麼把 URL 對應到特定的 servlet, action 或 controller 的方法上。這不管是在 Spring Framework 3 Web MVC, Servlet 3 或是 Jessey 中,都有一套近似的解法,就是使用 @Annotation。

OK, 那我們何不如法泡製,也用 @Annotation 來處理命令列參數呢? 我設計的命令列規範如下:
  • 將命令列參數區分成「選項」及「選項參數」。選項如 -start, -stop;選項參數如 -start localhost 18080 中,未加 dash 符號的字串,即紅字部分。
  • 一個命令列可以接受多個選項及多個選項參數。
  • 支援將選項綁定到主程式裡面的方法,以下我稱之為「選項常式」。
  • 支援將選項參數綁定到選項常式的參數上。
  • 支援自動列出所有選項及其說明。
  • 自動檢查選項參數是否正確。 
  • 支選整數型態的選項參數。
基於這個規範,我設計出的這組 Command line API,可以透過類似以下方式使用:

package com.isong.cmdexe;

/**
 * Usage example:<pre>
 * java -jar ds.jar -start localhost 2011
 * java -jar ds.jar -start localhost 2011 -sm
 * java -jar ds.jar -stop
 * </pre>
 * @author edwardsayer
 */
public class DummyServer {
    /**
     * @param args
     */
    public static void main(String[] argv) {
        new CmdExecutor(new DummyServer()).exec(argv);
       
    }
   
    // execute: java -jar ds.jar -start localhost 2011
    @CommandArg(value = "-start ip port", desc = "start the server instance.")
    public void doStart(String ip, int port){
        System.out.println("Starting server at " + ip + ":" + port + "...");
    }
   
    // execute: java -jar ds.jar -sm 
    @CommandArg(value = "-sm", desc = "start session monitor.")
    public void startSessionMonitor(){
        System.out.println("Starting session service...");
    }
   
    // execute: java -jar ds.jar -stop 
    @CommandArg(value = "-stop", desc = "stop the server instance.")
    public void doStop(){
        System.out.println("Stoping...");
    }
}

從以上的程式碼片段,可以知道這組 Command line API 提供了一個 CmdExecutor 類別, 以及一個 @CommandArg annotation。

@CommandArg 會在主程式的選項常式上,加上標注訊息,包括選項名稱,以及選項描述。

而CmdExecutor 類別則依據 @CommandArg 所提供的資訊,綁定使用者輸入的參數。另外,若使用者未輸入任何參數,CmdExecutor 則顯示參數提示訊息,這些訊息,其實是我們透過 @CommandArg 加諸在每個選項常式上的訊息。範例如下:

Available options:
-start ip port
    start the server instance.
-sm
    start session monitor.
-stop
    stop the server instance.

接下來,就來了解 CmdExecutor 及 @CommandArg 的實作方式。先看看 @CommandArg 的程式碼:

@Retention(RetentionPolicy.RUNTIME)
public @interface CommandArg {
    String value();
    String desc() default "";
}

可以看到 @CommandArg 包含兩個屬性。value 用來設定選項名稱,而 desc 用來設定選項描述。

CmdExecutor 較為冗長,程式碼如下:

public class CmdExecutor {
    private Map cmdMap;
    private StringBuffer cmdDocs;
    private Object target;
    
    public CmdExecutor(Object target){
        this.target = target;
    }
    
    public int exec(String[] args){
        System.out.println("args = " + Arrays.asList(args));
        initCommands();
        if (args.length == 0) {
            showCommands();
            return 0;
        }
        
        for (int i = 0; i < args.length; i++) {
            if (args[i].startsWith("-")) {
                String option = args[i];
                Method method = cmdMap.get(option);
                if (method == null) {
                    System.out.println("Unknown options: " + option);
                    showCommands();
                    return 1;
                }
                try {
                    Class[] types = method.getParameterTypes();
                    Object[] paras = new Object[types.length];
                    for (int j = 0; j < types.length; j++) {
                        if ( ++i >= args.length || args[i].startsWith("-")) {
                            System.out.println("Invalid option parameter count: " + option);
                            showCommands();
                            return 1;
                        }
                        if(types[j].isAssignableFrom(String.class))
                            paras[j] = args[i];
                        else if(types[j].isAssignableFrom(int.class) 
                                || types[j].isAssignableFrom(Integer.class))
                            paras[j] = Integer.valueOf(args[i]);
                        else
                            throw new IllegalArgumentException("Invalid command parameter type: " + method.getName());
                    }
                    method.invoke(target, paras);
                } catch (Exception e) {
                    e.printStackTrace();
                } 
            }
        }
        return 0;
    }
    
    private void initCommands(){
        if (cmdDocs != null)
            return;
        
        cmdDocs = new StringBuffer("Available options:\n");
        cmdMap = new HashMap();
        Method[] methods = target.getClass().getMethods();
        for (Method method: methods) {
            CommandArg argCmd = method.getAnnotation(CommandArg.class);
            if (argCmd != null) {
                cmdDocs.append(argCmd.value() + "\n    " + argCmd.desc() + "\n");
                cmdMap.put(argCmd.value().split(" ")[0], method);
            }
        }
    }
    
    private void showCommands(){
        System.out.println(cmdDocs);
    }
}

以上主要程式邏輯放在 exec 這個方法上,它會透過呼叫 initCommands 方法,建立所有選項常式之描述 (cmdDocs) 及對應 (cmdMap)。完成後,就是實際去解析命令列參數。如果沒有任何參數,就透過 showCommands 顯示說明。如果有,就進行選項與主程式方法的對應。
 

2010/02/28

用 AutoHotkey 啟用雲端字典及翻譯

Google 字典一直是我相當喜愛使用的網路服務,簡潔的版面及清晰的解釋及例句,都是我喜歡它的原因…硬要說它有什麼缺點的話,就是沒有看到日文的支援。固然透過 Firefox 或 Chrome,我們可以很容易的瀏覽器中啟用翻譯服務,但若是想要在其他應用程式中也使用到 Google 字典,就必須借助其他方法了。

AutoHotKey 是一個 Windows 下的桌面工具,可以讓我們透過自訂的快速鍵,啟動特定的應用程式、開啟文件、或指定的 URL 等。我們可以利用 AutoHotKey 的功能,在桌面上一鍵啟動 Google 字典及翻譯。

  1. 下載 AutoHotKey
  2. 執行安裝
  3. 安裝完成後,執行 AutoHotKey
  4. 在 Windows 的 System Tray 中,在 AutoHotKey 的 [H] 按鈕上按右鍵,帶出右鍵功能表,選擇「Edit This Script」
  5. 此時 AutoHotKey 預設會以 Windows 內建的「記事本」(Notepad) 開啟 AutoHotKey.ahk,供我們編輯
  6. 接下來就得編輯 AutoHotKey 的指定碼了,所幸相當簡單。其 Help 中有詳細的說明,我把常用的網頁服務設定如下的代碼區塊。
  7. 網友們可複製以下程式碼,貼到 AutoHotKey.ahk 檔案最後,然後存檔,關閉記事本。
  8. 使用 AutoHotKey 的右鍵功能表,執行「Reload This Script」。

至此大功告成,以後在任何應用程式中,只要先選定 (反白) 要查詢的文字,然後按下 Win + X 組合鍵,就可將選定文字帶至 Google 字典中查詢;此外,你也可以使用 Win + S 啟用 Google 搜尋,或使用 Win + Z 啟用 Google 翻譯。

另外,如果你想要讓 Windows 啟動時就自動執行 AutoHotKey.ahk,可以為 AutoHotKey.ahk 建立一個捷徑,並將捷徑放置在開始功能表裡面的「啟動」目錄即可。

Ahk 代碼:

;; 定義按 Win + S 鍵,啟用 Google 搜尋
#s::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://www.google.com.tw/search?hl=zh-TW&q=%clipboard%
return

;; 定義按 Win + X 鍵,啟用 Google 字典
#x::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://www.google.com.tw/dictionary?aq=f&oq=breed&langpair=en|zh-TW&hl=zh-TW&q=%clipboard%
return

;; 定義按 Win + Z 鍵,啟用 Google 翻譯
#z::
clipboard = ; Empty the clipboard
Send, ^c
ClipWait, 2
Run http://translate.google.com.tw/translate_t#auto|zh-TW|%clipboard%
return

Technorati : , , , ,

2009/09/15

WinForm: 如何判斷表單是否開啟在 Visual Studio 的 Designer 中

最近工作上用到比較多微軟的開發工具,像是 Visual Studio, Reporting Servoces, ASP.NET 等。

我們採用了一套 UI Framework,可以在 ASP.NET 上,以 WinForm 的程式設計模式來開發 Web 表單。在表單開發上,可完全免除自行撰寫 Client 端 JavaScript 的需要。簡言之,這種開發模式具有 WinForm 的優點,相對的,開發 WinForm 時所需要面對的問題,這裡同樣要面對。

例如,在開發 WinForm 表單時,我們常會在元件的 Load 事件載入資料來源,進行資料綁定。在執行環境這樣的邏輯沒有問題,但在開發環境,以 Visual Studio 的 Designer 開啟元件時,就可能會遇到無法讀取設定檔、資料連結錯誤等等問題,而造成 Designer 無法順利開啟表單。

我查詢了一下,在 MSDN Windows Developer Center 上也有人詢問這個問題,其中有個開發者已經給了一個可用的 solution,但因為他的描述過於複雜,且要求改寫基底類別,所以我擷取了他的解法,加以簡化。

首先,我們撰寫一個 GuiUtil 類別,其中包括下列方法:

public static bool InDesignMode() 
{
    try 
    { 
        return System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv";
    }
    catch
    {
        return false;
    }
}

如此一來,我們就可以採用以下的程式碼,在 Client 端程式的 Load 事件中進行呼叫,而不會產生設計畫面出不來的狀況了。

if (!GuiUtil.InDesignMode())
{
        InitDataBinding();
}

-- 完 --