點給老張的歌

今天騎車上班的時候,不知不覺哼到這兩首歌,發現跟好友老張的心境相符~
一首是野百合的春天

仿佛如同一場夢
我們如此短暫的相逢
你像一陣春風輕輕柔柔吹入我心中
而今何處是你往日的笑容
記憶中那樣熟悉的笑容
你可知道我愛你想你念你怨你深情永不變
難道你不曾回頭想想昨日的誓言
就算你留戀開放在水中嬌艷的水仙
別忘了寂寞的山谷的角落裡野百合也有春天

另一首是張震嶽作詞作曲的自由

我沒有關係 你可以假裝沒事離開這裡
一切好安靜 我只是想把情緒好好壓抑
到底誰會先說再見 我知道我一定哭泣
走的時候記得說愛我 愛我
說愛我 說愛我 難道你不再愛我
我的淚 滴下來 你從來不曾看過
為什麼 為什麼 愛情讓人變沈重
沒有人 告訴我 原來不是我想像
不要回來 你已經自由了 我也已經自由了

希望是不會走到這個地步:不要回來,你已經自由了,我也已經自由了。

How to monorail by Generator tool(4)

在研究過之後,我決定在 ScaffoldHelper.cs 裡面添加 GetText 這個函數,用來取得字串。

#region Localization helpers
public string GetText( IResource resource, string str )
{
if( resource==null )
return str;
object resStr = resource[ str ];
if( resStr==null )
return str;
return resStr.ToString();
}
#endregion

這樣在 .vm (template)檔案裡面,就可以這麼使用,以取得本地化後的字串:

$ScaffoldHelper.GetText($textres, "Name" )

當然你還是可以用原先的方法:

$textres.Name

所以 PageBrowser() 也可以跟著修正為:

#region Pagination helpers
public string PageBrowser( Page page )
{
return PageBrowser( page, null );
}
public string PageBrowser( Page page, IResource resource )
{
StringWriter output = new StringWriter();
PaginationHelper helper = new PaginationHelper();
helper.SetController(this.Controller);
string firstText = GetText( resource, "First" );
string prevText = GetText( resource, "Previous" );
string nextText = GetText( resource, "Next" );
string lastText = GetText( resource, "Last" );
if (page.HasFirst)
output.Write(helper.CreatePageLink(1, firstText ));
else
output.Write( firstText );
output.Write(" | ");
if (page.HasPrevious)
output.Write(helper.CreatePageLink(page.PreviousIndex, prevText ));
else
output.Write(prevText);
output.Write(" | ");
if (page.HasNext)
output.Write(helper.CreatePageLink(page.NextIndex, nextText ));
else
output.Write( nextText );
output.Write(" | ");
if (page.HasLast)
output.Write(helper.CreatePageLink(page.LastIndex, lastText ));
else
output.Write( lastText );
return output.ToString();
}
#endregion

最後在資源檔裡面加上必要的字串並且修改 .vm (template)以後,就可以得到本土化後的結果。

How to monorail by Generator tool(3)

這一次要講的是 Localization,不幸的是 Generator 沒有考慮到 Localization 問題,所以由 Generator 產生出來的代碼,並不包含這部份,但這卻是一個很好的機會讓我去了解 Generator 與 monorail 底下的機制。

Castle官方網站上已經有介紹該怎麼 Localization:Resources and Localization,但不幸的是,他使用 Visual Studio.Net 作為範例,Visual Studio.Net 已經掩蓋了所有細節。
在研究官方文件與相關文件之後,以下是我的步驟。

首先,你要先在 app/controllers 加上:

[Resource("textres", "ToDo.categories")]

這表示在 view(template) 那兒使用 $textres 就可以存取資源檔的內容。

所以在 app/views/categories/list.vm 裡,就可以把必要的字串改用$textres.字串名稱替代掉,這裡我就不列出整個 list.vm 了,只列出幾行:

<!--略-->
$HtmlHelper.LinkTo( $textres.AddNewCategory, "categories", "new")
<!--略-->
$HtmlHelper.LinkTo( $textres.View, "categories", "view", $category.Id)
<!--略-->
$HtmlHelper.LinkTo( $textres.Edit, "categories", "edit", $category.Id)
<!--略-->
$AjaxHelper.LinkToRemote( $textres.Delete, "delete.aspx", $DictHelper.CreateDict("with='id=$category.Id'", "condition=confirm('Delete?')", "onsuccess=new Effect.Fade('category$category.Id')"))
<!--略-->

然後我選擇在 app/ 下建立一個 resources 目錄,用來放置資源檔:

;Filename: categories.txt
;Language: en-US
View=View
Edit=Edit
Delete=Delete
AddNewCategory=Add a new Category
;Filename: categories.zh-tw.txt
;Language: zh-tw
View=檢視
Edit=編輯
Delete=刪除
AddNewCategory=加新類別

這裡要特別注意,把文字檔的編碼存為 unicode 或是 utf-8,否則會有亂碼出現。

最後修改 default.build,以便把資源檔編譯進去,這裡我也只列出關鍵的幾行:

<!--略-->
<target name="build" description="Compile all source files">
<mkdir dir="${dir.bin}" />
<copy flatten="true" todir="${dir.bin}">
<fileset>
<include name="${dir.lib.castle}/**" />
<include name="${dir.lib.npgsql}/**" />
<include name="${dir.lib.mysql}/**" />
<include name="${dir.lib.migrator}/**" />
<include name="${dir.lib.nunit}/nunit.framework.dll" />
</fileset>
</copy>
<resgen todir="${dir.src.app}/resources/">
<resources>
<include name="${dir.src.app}/resources/*.txt" />
</resources>
</resgen>
<csc target="library" output="${dir.bin}/${project::get-name()}.dll">
<sources>
<include name="${dir.config}/Boot.cs" />
<include name="${dir.src.app}/**/*.cs" />
<include name="${dir.src.test}/**/*.cs" />
<include name="${dir.migrations}/**/*.cs" />
</sources>
<resources basedir="." prefix="ToDo">
<include name="${dir.src.app}/resources/*.resources"/>
</resources>
<references basedir="${dir.bin}">
<include name="System.Web.dll" />
<!--略-->

最重要的就是 resgen 這個區塊與 csc 裡面的 resources 區塊,resgen 是把文字檔轉換為 .resources 資源檔,而 csc 裡面的 resources 則是表示要把產生出來的 .resources 編譯進去。

好了,該修改的都修改了,接下來就是重新建立,輸入 nant 就會自動重新編譯。
有點美中不足的是,下面顯示頁次的部份仍然是英文,這是因為這些字串被寫死在 ScaffoldHelper.cs 裡面,我想下次再來修改這部份。

後記:

  • csc resources 所指定的 prefix 與 [Resource(“textres”, “ToDo.categories”)] 的第二個參數息息相關,不妨修改來試試看。
  • 網站文件有提到 LocalizationFilter 的部份,但是用了卻不行,有待繼續研究。

電影流水帳(2007/12/14~2007/12/24)

  • 博物館驚魂夜Night at the Museum(IMDBWikipedia),就是很適合小朋友看的電影,整個重點還是在於博物館晚上所發生的怪事。
  • 3:10 to Yuma(IMDBWikipedia),中譯:決戰310。在台灣好像不賣座?Yahoo!電影居然查不到,或許根本沒上片也不一定。不過這部西部片很好看,由一個江洋大盜與一個瘸腿父親所交織出來的故事,最後的轉折出人意表之外。

How to monorail by Generator tool(2)

接下來,就參考這篇有名的 Four Days on Rails 來試試看吧~
首先呢,由於隔了這麼久才寫這篇,所以 Generator 改位置了,你得從這裡:http://svn.castleproject.org:8080/svn/castle/trunk/Experiments/Generator/ Checkout,其他大致都跟上篇一樣。

  1. 先利用 monorail ToDo,建立專案,接著依照上篇文章作必要的設定,同時我們使用 rc2,不使用最新的版本(也就是你不用更動 default.build),設定的部份就不再贅述。
  2. 建立資料庫,這邊我是用MySQL,建立的資料庫名稱是 ToDoDev,不過這只有跟修改連線字串有關係。
  3. 修改資料庫的連線字串,連線字串是放在 config/databases/ 下的 ActiveRecord 設定檔裡面,你可以看到這邊有兩個設定檔,他很貼心的把開發用與測試用的資料庫分開:development.xml、test.xml,事實上,你還可以增加一個 Production (或是其他名字)的 ActiveRecord 設定檔,表示這是正式上線用的資料庫。切換的時候,可以更動 config/boot.cs 以決定使用哪個 ActiveRecord 設定檔,預設是 development。
  4. 建立 Model,首先先產生”Category”,Category 的欄位如下:

    id int autoincrement
    name varchar(255)
    created_on datetime
    updated_on datetime

    ,在專案的目錄下輸入:script\generate model Categories name created_on updated_on,這邊要注意的是,不需要特別寫 id,因為慣例至上,Generator會自動幫你增加 Id 這個欄位。Generator 會幫你作三件事情:產生 Category 這個類別、產生測試 Category 的 Unit test 類別以及建立資料表格的程式,你可以從指令的輸出結果很清楚的看到。

    exists app\models
    create app\models\Categories.cs
    exists test\models
    create test\models\CategoriesTest.cs
    create db\migrations
    create db\migrations01_AddCategoriesTable.cs

  5. 接下來,要去修改 Categories 類別的屬性型態,Generator 預設都是把型態設為字串,db\migrations 那邊則都是建立 varchar(50)。所以除了 app\models\Categories.cs 要修改型態之外,db\migrations01_AddCategoriesTable.cs 下也要改!這邊把 Name 改為 255 個字元,created_on與updated_on 則改為 DateTime。
  6. 都修改完畢之後,就可以執行 nant migrate,這個步驟會幫你去實體資料庫作必要的事情。為甚麼要這樣子作?我想是因為一般資料庫的 script 是沒有作管理的,因此,才刻意把這些要去資料庫建立表格、修改欄位等的動作都放到 db\migrations 下,以便把這些程式也都納入版本控制。
  7. 執行 script\generate scaffold Category,這個步驟會一口氣幫你把 CRUD 的頁面都弄出來。
  8. 那麼,就編譯吧,直接執行 nant,只是卻出現了…

    [csc] app\controllers\CategoriesController.cs(36,72): error CS0246: 找不到型別或命名空間名稱 ‘Category’ (您是否遺漏 using 指示詞或組件參考?)
    [csc] app\controllers\CategoriesController.cs(56,45): error CS0246: 找不到型別或命名空間名稱 ‘Category’ (您是否遺漏 using 指示詞或組件參考?)

    這個,得去修改 CategoriesController.cs,把 Category 都改為 Categories,這看來是 Generator 的問題。接著又再出現

    error CS1501: 方法 ‘CreatePagination’ 沒有任何多載使用 ‘2’ 個引數

    ,唉~這個錯誤訊息實在是太黯然又太銷魂…你必須再次修改 CategoriesController.cs,添加這兩行:

    using System.Collections;
    using System.Collections.Generic;
    

    然後把

    PropertyBag["categories"] = PaginationHelper.CreatePagination( Categories.FindAll(), 10);

    改為

    PropertyBag["categories"] = PaginationHelper.CreatePagination( (IList)Categories.FindAll(), 10);

    ,接下來應該就沒問題了。

  9. 先試試看到目前為止的成果吧,輸入script\server,然後打開你的瀏覽器,在位址列輸入:http://localhost:8080/categories/list.aspx,就能看到結果了。

執行結果:
categories_list_aspx

plugin_syntaxhighlighter

blogger 使用 dp.syntaxhighlighter 的話,你會發現根本無法生效,主要是因為 blogger 把換行符號都替換成 <br/> 了,而且還沒有設定可以決定是否要替換。
很幸運地,有人已經提出解決方法:yehyeh: Blogger dp.SyntaxHighlighter斷行問題解決方法

只是,我已經套用blogger-ext2了,於是想說,是不是可以寫一個blogger-ext2的 plugin 來解決這個問題。
非常感謝jQuery的強大功能,不到半天就完成了,最重要的是,不用像上面解決方法一樣,寫了一堆 code。

// Register dp.SyntaxHighLighter
// Dependency:
//   jQuery-1.2.1
//   blogger-ext2-core (最新版,0.7.x 的樣子)
//   dp.syntaxhighlighter-1.5.1
BloggerExt.SH = function() {
// Plugin 會由此開始
if( dp!='undefined' ) {
// 找到 pre, textarea 下所有 br,然後替換成換行符號,收工。
jQuery("pre > br").each( function() { jQuery(this).replaceWith( "\n" ); } );
jQuery("textarea > br").each( function() { jQuery(this).replaceWith( "\n" ); } );
dp.SyntaxHighlighter.ClipboardSwf = 'http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/clipboard.swf';
dp.SyntaxHighlighter.HighlightAll('code');
}
};
BloggerExt.SH.user_pref = function() {
var prefs = [];
return prefs;
};
BloggerExt.SH.update_pref = function(prefs) {
};
BloggerExt.register('SH', { SH: true} );

使用範例:

<script type="text/javascript" src="jquery-1.2.1.pack.js"></script>
<script src="blogger_ext2.js" type="text/javascript"></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shCore.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushCSharp.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushVb.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushPhp.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushJScript.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushSql.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushXml.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushPython.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushCss.js'></script>
<script type="text/javascript" src='http://syntaxhighlighter.googlecode.com/svn/tags/1.5.1/Scripts/shBrushCpp.js'></script>
<!--假設你已經把上面代碼存為 plugin_syntaxhighlighter.js 了 -->
<script type="text/javascript" src='plugin_syntaxhighlighter.js'></script>

Gtk# UI設計隨便聊

前一陣子用MonoDevelop來寫 Gtk# 應用程式玩,發現真的是很簡單。
幾乎可以跟 SharpDevelop 或 Visual Studio.Net 設計 Windows form 應用程式一樣了,同樣也是拖拉放,就可以完成。
對於初入門的人來說,最痛苦的可能還是對於 Gtk# 的物件模型不熟悉,因為不熟悉,所以也就不知道該去處理哪些屬性與事件。
好在Mono官方網站有提供入門文件:

用力唸一唸這些文件跟自己去嘗試,大致上應該是夠了。但這些文件大部分都是自己去寫 Layout code。
如果你正在使用目前版本的 MonoDevelop 在 Layout 的話,你會發現世界全然不同,因為MonoDevelop引進了 Stetic
現在使用 MonoDevelop 拉出來的 Form,實際上都會被存放在專案目錄下的 gtk-gui 目錄下的某個 xml 檔案,在每次 build 之前,MonoDevelop 會先把 xml 轉換為一個存放於 gtk-gui 目錄下的 partial 類別(.cs),然後 build 的時候,再 build 到一起。而在專案目錄下所看到的代碼,只會有一行 Build()。
這真的是方便很多,開發者只需要專注於如何處理程式邏輯就可以,而不需要去管 UI 代碼的生成。
MonoDevelop 加入 Stetic 之前,大部分的 Gtk# 應用程式還是用手動加代碼或是用 Glade# 來設計介面(詳細的 Glade# 用法,可以參考 Your First Glade# Application 的說明)。
Glade# 其實也是把整個 UI 存成一個 xml 檔案,所以你得先使用 Glade 應用程式設計出 UI,得到 .glade 檔案,然後在程式裡面:

  1. 載入 .glade 檔案
  2. 為控制項(Widget)加上 Attribute
  3. 撰寫事件處理並繫結


這當然是比手動寫代碼方便許多,但比起 Stetic,還是要多作事,往往用 Glade 修改 UI 後,可能會忘記加必要的 Widget 宣告,或是對應的事件繫結。

Gtk.TreeView(3)

在 Linux 裡面,所謂的”事件”,多半是用 Signal 來表示,所以你看到 Signal 時,可以概略地當作”事件”來看。
而這些事件跟 Windows Form 的事件命名法差異相當大。
以下這些是TreeView比較常用到的:

    protected virtual void OnTreeview1RowActivated (object o, Gtk.RowActivatedArgs args)
{
// double click
Console.WriteLine( String.Format("[{0}]: {1}", o.GetType().ToString(), "row activated") );
}
protected virtual void OnTreeview1RowCollapsed (object o, Gtk.RowCollapsedArgs args)
{
// collapse
Console.WriteLine( String.Format("[{0}]: {1}", o.GetType().ToString(), "row collapsed") );
}
protected virtual void OnTreeview1RowExpanded (object o, Gtk.RowExpandedArgs args)
{
// expand
Console.WriteLine( String.Format("[{0}]: {1}", o.GetType().ToString(), "row expanded") );
}
protected virtual void OnTreeview1CursorChanged (object sender, System.EventArgs e)
{
// click
Console.WriteLine( String.Format("[{0}]: {1}", sender.GetType().ToString(), "Cursor changed") );
}

我是怎麼查到的呢?
坦白說,我是用笨方法,看到那些 Console.WriteLine 了沒?
我先試著在可能的事件裡面放置這些 Console.WriteLine,接著執行程式,試著去 Click、Expand,然後看 Console 輸出就知道了…

電影流水帳(2007/11/29~2007/12/13)

只看了兩部片。不知道今年我到底看了多少片子?到年底的時候來算算看。

  • Transformer(Wikipedia,IMDB),中譯:變形金剛,受到台灣很多人喜愛的片子,特效炫,故事緊湊,女主角也很正,完全符合好萊塢作風,也難怪會賣座。個人唯一覺得不好的地方就是,金剛們的金屬味很重,沒有卡通裡面那種色彩強烈的感覺。
  • Silent Hill(Wikipedia,IMDB),中譯:沉默之丘。第一次挑戰英文字幕,除了很多單字以外,其實看來不會很吃力。整個裡面的氣氛鋪陳的很好,片中等於有兩個世界,兩個世界交錯地發生事情,不會讓人有搞不懂的情況。總之,我覺得拍的不錯。不過片尾並沒有所謂的結局,看來應該是有打算要拍續集,希望可以看到最後這兩個世界是怎麼再次接軌起來。(謎之聲:世界末日與冷酷異境書末最後兩個世界也沒接起來啊,你要期待什麼?)