在 mono 上使用 Font 的一些事

昨天碰到的事情,紀錄一下:

  1. mono 1.0 類別庫裡的 Font,有一些 ctor 裡面是空的。如果你發現使用了某個 function 沒作用,或是有些問題,你可以參考一下Mono Class Status以取得類別庫實作的狀況~或者利用Reflector for .NET反組譯看看,免得傻傻在那邊查了半天還不知道原因出在哪。
  2. 字型的底層是 libgdiplus,更底層則是CairoCairo又再使用了FreeType函式庫(好像還有別的,不過這是主要的)。所以字型的設定是取決於fontconfig的設定:/etc/fonts/fonts.conf(用FreeType好像跟這設定沒啥關係,總之我是這樣聯想到的),當你使用某個字型卻無法正常顯示時(例如中文),不妨參考設定然後直接指定字型名稱:

    Font textFont = new Font( “AR PL Mingti2L Big5”, 12, FontStyle.Regular, GraphicsUnit.Pixel, 1, false);

    ,應該就行了。

在.Net如何產生動畫gif (animated gif)

原生的 .Net framework 雖然可以產生 gif 圖檔,但卻無法產生有動畫效果的 gif 圖檔。
找了半天,本來已經萬念俱灰,想說可能要自己來寫了。
但終於還是在SourceForge.net找到合用的library:NGif

下載以後,可以直接參考程式,使用方法很簡單~
這邊摘錄製作的方法:

    /* create Gif */
    //you should replace filepath
    String [] imageFilePaths = new String[]{"G:\1.png"};
    String outputFilePath = "G:\\test.gif";
    AnimatedGifEncoder e = new AnimatedGifEncoder();
    e.Start( outputFilePath );
    e.SetDelay(500);
    //-1:no repeat,0:always repeat
    e.SetRepeat(0);
    for (int i = 0, count = imageFilePaths.Length; i < count; i++ )
    {
        e.AddFrame( Image.FromFile( imageFilePaths[i] ) );
    }
    e.Finish();

NGif同時也提供解開的方法,所以你也可以將 gif 裡面的每個 frame 都個別存為獨立的圖檔。

讀取網頁(3)

WebClient 畢竟還是有一些限制,例如 Cookie,這就沒辦法了,你必須要自行操作 Header。
可是 WebClient 並沒有任何成員可以讓你實現這塊。

而 WebClient 的底層,其實是用 WebRequest 來實現的。這裡的例子使用了
HttpWebRequest,但其實 HttpWebRequest 也是繼承自 WebRequest。
要加上 Cookie 的話,你得指定 HttpWebRequest 的 CookieContainer 屬性。

所以我們很快就可以寫出 wgetInWebRequest()。
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using System.Web;
using System.IO;
using System.Diagnostics;
using System.Collections;

public class Network
{
    public static string wgetInWebRequest( string url, CookieContainer cookies, Encoding encoding )
    {
        string responseData = "";
            
        try
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create( url );

            // 加上 Cookie
            webRequest.CookieContainer = cookies;

            // 如果你有 Proxy 的話~
            // webRequest.Proxy = new WebProxy( "your_proxy", 3128 );

            // 加上 User Agent,用來模擬瀏覽器~
            //webRequest.Headers.Add( "User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)");

            StreamReader responseReader;
            responseReader = new StreamReader(webRequest.GetResponse().GetResponseStream(), encoding );

            // and read the response
            responseData = responseReader.ReadToEnd();
        }
        catch( Exception ex )
        {
            Debug.WriteLine( ex.ToString() );
        }
        finally
        {
            Debug.WriteLine( responseData );
        }
        return responseData;
    }
}
我想,也許你會問這有什麼用?
一般網站應用系統登入以後,都會設置 Cookie 以表示你已經登入了,換句話說,你只要先設法模擬登入,然後取得 WebResponse 所得到的 Cookie,接著再把得到的 Cookie 放到 WebRequest.CookieContainer,你接下來所有對該網站應用程式的 Request 就已經是有特定使用者身份的了~
讓我舉個 ASP.Net 網站的例子吧~
    public static void Login( string user, string pwd)
    {
        string loginPage = wgetInWebRequest( "http://your_web_app/login.aspx", null, Encoding.Default );

        // ASP.Net 會在頁面埋一個 __VIEWSTATE 隱藏表單變數,先取得!
        Regex rx = new Regex(@"\<input\ type=""hidden""\ name=""__VIEWSTATE""\ value=""(?<viewstate>.+)""\ /\>");
        string viewstate = "";

        try {
            // Find matches.
            MatchCollection matches = rx.Matches( loginPage );
                
            if( matches.Count == 1 )
            {
                // 要作 UrlEncode
                viewstate = HttpUtility.UrlEncode( matches[0].Groups["viewstate"].Value );

                // 用來收 cookie 的容器
                CookieContainer cookies = new CookieContainer();

                // now post to the login form
                HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create( "http://your_web_app/login.aspx" );

                // 模擬瀏覽器
                //webRequest.Headers.Add( "User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)");

                // POST
                webRequest.Method = "POST";
                webRequest.ContentType = "application/x-www-form-urlencoded";

                // 收到的 cookies 會放到 cookies 變數
                webRequest.CookieContainer = cookies;

                // 如果你有 proxy 的話
                // webRequest.Proxy = new WebProxy( "your_proxy", 3128 );

                // 這邊要看 login 表單有哪些變數而定,請自行變化
                string postData = string.Format( "__VIEWSTATE={0}&user={1}&password={2}&Submit=Submit", viewstate, user, pwd );
                StreamWriter requestWriter = new StreamWriter(webRequest.GetRequestStream());
                requestWriter.Write(postData);
                requestWriter.Close();

                // 收到內容啦,但是我們不需要他的結果,只要 cookies
                webRequest.GetResponse().Close();
                    
                // now we can send out cookie along with a request for the protected page
                string responseData = wgetInWebRequest( "http://your_web_app/default.aspx", cookies, Encoding.Default );
                Debug.WriteLine( responseData );
            }
            else
                Debug.WriteLine( "Internal error, too many ViewState." );
        }
        catch( Exception ex ) {
            Debug.WriteLine( ex.ToString() );
        }
        finally {
        }
    }

大致上就是這樣子,我不作太多的說明囉~
想要作更多的話,可以再研究HTTP Protocal並配合Sniffer之類的軟體去監看網路封包,來了解詳細的流程。

讀取網頁(2)

上一篇最後留下 Encoding 的問題還沒解決,事實上已經有解了:你可以改用 DownloadString() 函數。
DownloadString() 是怎麼做到的呢?
他是先看 WebRequest 得到的 Header 裡面是否有 Content-Type ,如果有指定的話,再看他裡面是否指定 charset,如果有,就以此編碼去讀取 stream。
如果沒有指定的話呢?.Net 2.0 的 WebClient 多提供了 Encoding 屬性,換句話說,就會以 Encoding 屬性指定的編碼去讀取 stream。
事實上,大部分的 browser 並沒這麼笨~他還會先去偷看網頁內容是否有指定 Content-Type 這個 meta tag:

<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″ />

如果有的話,就會以此 charset 所指定的編碼來讀取。

讀取網頁(1)

如果你要搞個 spider 或是 bot,需要讀取網頁的話,用 WebClient 就綽綽有餘了~
using System.Net;

public class Network
{
    public static string wget( string url )
    {
        WebClient _client=new WebClient();
        string result="";
            
        try {
            // 藉著修改 Header,可以用來模擬某特定 Browser,以下是模擬 IE 6
            _client.Headers.Add("Accept","image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*");
            _client.Headers.Add("Accept-Language","zh-tw");
            _client.Headers.Add("User-Agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)");

            // 表示支援壓縮,也就是說,你可以丟壓縮過的資料過來,我來解~
            //_client.Headers.Add("Accept-Encoding","gzip, deflate");

            // read
            System.IO.Stream objStream=_client.OpenRead( url );

            // 要知道正確的編碼,再去讀取~.Net會幫我們自動轉為 unicode 字串。這邊預設都是 UTF8
            System.IO.StreamReader _reader=new System.IO.StreamReader(objStream,System.Text.Encoding.UTF8);
            //System.IO.StreamReader _reader=new System.IO.StreamReader(objStream,System.Text.Encoding.GetEncoding(950));
            result = _reader.ReadToEnd();
        }
        catch( Exception ex ) {
            throw ex;
        }
        finally {
        }
            
        return result;
    }
}

所以我們就可以這麼用
Console.WriteLine( Network.wget("http://www.google.com.tw") );

目前有個決定性的缺點:必須先知道網頁編碼,我們才能讀到正確的文字~有辦法先知道網頁的編碼,再去決定要怎麼讀取嗎?

雞尾酒排序

早上翻譯了這篇:雞尾酒排序,翻譯的不是很好~以後再求改進吧。
順手再寫了 c# 的版本。
public static void cocktailSort( int[] theList )
{
    int bottom = 0;
    int top = theList.Length-1;
    bool swapped = true;
    
    while( swapped == true )
    {
        swapped = false;
        for( int i=bottom; i<top; i++ )
        {
            if( theList[i] > theList[i+1] )
            {
                int tmp = theList[i+1];
                theList[i+1]=theList[i];
                theList[i]=tmp;
                swapped = true;
            }
        }
            
        top = top-1;
        for( int i=top; i>bottom; i– )
        {
            if( theList[i]<theList[i-1] )
            {
                int tmp = theList[i-1];
                theList[i-1]=theList[i];
                theList[i]=tmp;
                swapped = true;
            }
        }
            
        bottom = bottom+1;
    }
}

SharpDevelop2 + IronPython + PythonBinding

現在你可以用 SharpDevelop2 開發 IronPython 應用程式了。
1.首先下載PythonBinding,目前最新的版本是 0.5Beta。
2.解開,這裡我是將解開後的檔案放到 c:\tools\pythonbinding
3.打開 SharpDevelop2
4.[Tools][AddInManager],選 Install AddIn
5.指向剛剛 c:\tools\pythonbinding\PythonBinding.AddIn,接著按下 Close。
6.關閉 SharpDevleop2,重新啟動。
接下來,你可以 New Solution,就會看到 Python 的部份
PythonBinding-1
所以請新增一個 console application,這裡我命名為 hellopython。
這個新的專案裡面已經有個 Program.py 了,他只簡單的印出 “Hello world”。
這時候很直覺的按下綠色的三角形,也就是執行,SharpDevelop2 會很直接的告訴你有問題。

The “IronPythonCompilerTask” was not given a value for the required parameter “Mainfile”. (MSB4044)

你需要修改一下專案的屬性:以滑鼠右鍵點選左邊的專案,然後選 Property,依照下圖將 Start Object 修改為 Program.py
PythonBinding-2
再次按下執行,SharpDevelop2 很順利地 build 完成,但卻仍然無法執行,SharpDevelop2 會這麼告訴你:

Exception System.ArgumentException was thrown in debuggee:
The path is not of a legal form.
NormalizePathFast()
GetFullPathInternal()
GetFullPath()
ExecuteCompiled()
Main()

可是我以 console 模式去執行編譯出來的 HelloPython.exe,卻可以執行。
後來仔細看了一下工具列,原來按下紅色的驚嘆號就可以順利執行了。
PythonBinding-3
我想應該是 SharpDevelop2 還不支援 IronPython 的除錯模式的關係。
此外,目前也還不支援 Auto completion(自動完成),我想這部份應該很快就會被 release 出來了吧。
參考資料:

IronPython(4) – 類別、繼承、模組化

基本上還是跟上次的一樣,只是我們要重新寫過,直接繼承 Form 類別,然後模組化。
Python 定義類別的方法相當簡單:

class classname( parentclass ):
  def __init__( self ):
    # initialize
  def doSomething( self ):
    # do something

他是以左邊的縮排來決定範圍,所以同樣縮排就表示是同一區塊。
self 則是表示自己,也就類似 c# 的 this。
所以我們把昨天的程式碼重新寫過:

import clr
clr.AddReferenceByPartialName(“System.Windows.Forms”)
clr.AddReferenceByPartialName(“System.Drawing”)
from System.Windows.Forms import *
from System.Drawing import *
class OptionForm( Form ):
  def __init__(self):
    self.InitializeComponent()
  def InitializeComponent(self):
    self.Text = “RSSMSN – customize your msn personal message”
    # Initialize Label
    lblHello = Label(Text=”Hello world!” )
    lblHello.Visible=True
    lblHello.Location=Point( 10, 10 )
    lblHello.AutoSize=True
    self.Controls.Add( lblHello )
    # Initialize NotifyIcon
    notifyIcon1 = NotifyIcon( Visible=True, Text=”RSSMSN”, Icon=Icon(“Matrix.ico”) )
    # Initialize Form
    self.StartPosition=FormStartPosition.CenterScreen

大致上就會是這樣子,我們將這個檔案命名為 fmOption.py。
主程式則另外放到 main.py:

import clr
clr.AddReferenceByPartialName(“System.Windows.Forms”)
clr.AddReferenceByPartialName(“System.Drawing”)
from System.Windows.Forms import *
from System.Drawing import *
from fmOption import *
f = OptionForm()
Application.Run(f)

你可以看到主程式變得簡單許多。這邊只需要將剛剛寫好的 fmOption.py 引用進來,再將 OptionForm 實體化,請 Application 類別去把窗開出來就好了。
Python 引用其他檔案的方法,統一都是 from your_module import classname。
這個 your_module 可以是 assembly name 也可以是 module name。
大致上就是這樣子了。
下次的目標是加上 destructor 以確保 System Tray 的 Icon 會在程式結束時消失。
參考資料:

IronPython(3) – 第一個窗

基本上參考 tutorial/wfdemo.py 就可以很快的寫出來第一個窗。

import clr
clr.AddReferenceByPartialName(“System.Windows.Forms”)
clr.AddReferenceByPartialName(“System.Drawing”)
from System.Windows.Forms import *
from System.Drawing import *
f = Form()
f.Text = “RSSMSN – customize your msn personal message”
# Initialize Label
lblHello = Label(Text=”Hello world!” )
lblHello.Visible=True
lblHello.Location=Point( 10, 10 )
lblHello.AutoSize=True
f.Controls.Add( lblHello )
# Initialize NotifyIcon
notifyIcon1 = NotifyIcon( Visible=True, Text=”RSSMSN”, Icon=Icon(“Matrix.ico”) )
# Initialize Form
f.StartPosition=FormStartPosition.CenterScreen
Application.Run(f)

他是直接產生 Form 與 Label 的實體,然後丟給 Application,就可以很順利的產生第一個窗。
這裡有個有趣的用法:

lblHello = Label( Text=”Hello world!” )

所以產生實體的時候,可以直接以 PropertyName=value 來指定 Property 的初始值。
但是這樣的寫法,在後期大概會寫到暈倒。
所以還是要搞清楚如何撰寫類別並且繼承才會是比較好的解法。
下次的目標:撰寫類別與繼承。

IronPython(2) – 使用 rsstoolkit

世界上總是有許多人願意提供他們的心血,我們只需要站在他們的肩膀上,就可以快速的發展出我們自己的東西來。
ASP.NET RSS Toolkit 是一個存取 RSS 的 library,其他相關的 Library 還有 RSS.Net,但是目前RSS.Net還沒有釋出 for .Net 2.0 的版本,所以我們選用 ASP.NET RSS Toolkit
首先下載並解開作者所提供的壓縮檔案,將 bin 目錄下的 RssToolKit.dll 複製到 IronPython 目錄下的 lib,以方便 IronPython 取用。
執行 IronPython,要在 IronPython 使用別人寫好的 library(Assembly),有兩個步驟,首先要載入 library,然後再 import 裡面的類別。

import clr
clr.AddReferenceToFile(“RssToolkit.dll”)
from RssToolkit import *

話說回來 ASP.NET RSS Toolkit的說明文件真的是可以用 Poor 來形容,裡面附的一份 Word 文件,就僅描述如何在 Visual Studio 2005 下使用而已,完全沒提及ASP.NET RSS Toolkit裡面有什麼類別,類別又有什麼屬性… Orz…
還好ASP.NET RSS Toolkit有附原始碼,Trace 一下就能瞭解一二。
所以接下來先 initialize 變數。
Python處理變數也蠻像 BASIC 的,隨寫即用:

r = RssDataSource()

連 new 都不用寫。
RSS 的構造主要是以 Channel 為主,所以 r.Channel 就表示 Channel,r.Channel.Items 就表示 Channel 裡面的各個項目。
有了這些資訊,就可以寫出取 RSS 資訊的程式了。
可是Python的迴圈該怎麼寫呢?因為我們要取出 Items 裡的每個項目。
for…next 的寫法

for i in range(5):
print i

foreach 的寫法

for item in r.Channel.Items:
print item[“title”]

總結這些,我們就可以寫出一個取出 RSS 內各項目的程式了:

import clr
clr.AddReferenceToFile( “RssToolkit.dll” )
from RssToolkit import *
r = RssDataSource()
r.Url = “http://tw.news.yahoo.com/rss/realtime”
print r.Channel[“title”]
for item in r.Channel.Items:
print item[“title”]
print item[“link”]

下次的目標,一個簡單的視窗 + 縮小到 System tray。
參考資料: