Wednesday, October 31, 2007 - Posts

Coding Smarter Tips 1 - String

身為程式老鳥,程式寫多了,總會發展出自己處理某些邏輯的一套慣用方法,有些演算法觀念甚至還跨語言: 在VBScript時代想出來的演算法,轉換成C#後,一樣在.NET上虎虎生風。

觸類旁通、得心應手的快意,其實也讓老鳥們喪失了一部份探索新語言、學習新工具的樂趣 --- 其實新語言、新工具已經提供了更快速簡潔的處理方法,當你總能用舊方法打發問題,無形中也忽略進化到用更少程式碼完成相同工作的機會。

以我自己的經驗來說,近年來已少有耐心好好地翻完一本書,新語言、新工具的學習總依賴Google,要用了才臨時去找。沒有靜下心來全面了解熟悉語言工具的後果,即使有能力寫Code解決各式各樣的難題,卻往往不是最更效率、更嚴謹的解題方法。於是我常常在Trace別人的Code時,或是經由旁人的提醒,才知道自己用了幾年的方法,其實是個愚公移山的笨方法,根本可以用一行打死。(一個典型的例子在這裡)

【Coding Smarter系列】搜集了我陸續發現處理常用作業的"最佳".NET實作方法跟大家分享。也歡迎大家來踢館,讓我學到更棒的方法!!

   1:  //需求1: 將CheckBoxList中選取的項目Value用分號串起來
   2:   
   3:  //老方法,跑迴圈,第一筆資料不加;
   4:  StringBuilder sb = new StringBuilder();
   5:  foreach (ListItem item in CheckBoxList1.Items)
   6:  {
   7:      if (!item.Selected) continue;
   8:      if (sb.Length > 0) sb.Append(";");
   9:      sb.Append(item.Value);
  10:  }
  11:  Response.Write(sb.ToString());
  12:   
  13:  //改良法1,不花功夫決定加不加分號,最後再移除多餘的分號
  14:  sb = new StringBuilder();
  15:  foreach (ListItem item in CheckBoxList1.Items)
  16:      if (item.Selected)
  17:          sb.AppendFormat("{0};", item.Value);
  18:  //直接用TrimEnd移掉多餘的分號
  19:  Response.Write(sb.ToString().TrimEnd(';'));
  20:   
  21:  //改良法2,利用Join
  22:  List<string> lst = new List<string>();
  23:  foreach (ListItem item in CheckBoxList1.Items)
  24:      if (item.Selected) lst.Add(item.Value);
  25:  //string.Join會將陣列組裝成字串
  26:  Response.Write(string.Join(";", lst.ToArray()));
  27:   
  28:  //需求2 將數字補零至四位
  29:  int i = 5;
  30:   
  31:  //VBScript常見做法
  32:  string s = "0000" + i.ToString();
  33:  //VB.NET還可用RIGHT$
  34:  Response.Write(s.Substring(s.Length - 4));
  35:   
  36:  //Format會簡單一些
  37:  s = string.Format("{0:0000}", i);
  38:   
  39:  //PadLeft也佷酷,還可以用在非數字上
  40:  s = i.ToString().PadLeft(4, '0'); 
Update 2007-11-13 這裡也可以寫成i.ToString("0000"); 謝謝evakey補充

最後再補充幾個好用的String Member:

  • StartsWith、EndsWith: 比對字串對尾,支援不分大小寫比對。 (Update 2007-11-13 少了s, 謝謝Leem指正)
  • Insert: 插入字串,不用再s.Substsring(0, i) + "AAA" + s.Substring(i)了
  • IndexOf: 不只是VBScript中的Instr而已,支援不分大小寫比對。
Google AdSense,好樣的!

酪梨壽司是我非常崇拜的網路作家,她的文筆犀利流暢,許多傳神的比喻常讓我會心一笑(斷氣的海參? 妙呀!),或許因為幼時長期沈浸於文學作品中,她的文章用字遣詞總多了幾分當下"國文式微世代"所沒有的五彩繽紛與饒富典故。但最讓我折服的是,你永遠可以從她的詼諧中感受到背後的真情流露,這應該是只會跟只敢寫論說文的我,此生都無法觸及的境界吧!

放眼部落格界,雄霸一方的多半是吃喝食記、旅遊記趣,再不就是靠漫畫或網路小說聚集人氣,像壽司靠生活文章走紅,也算是另一種典型吧!!  我印象很深的是,壽司在今年初單靠著Google AdSense在一個月內海削數萬元,讓人不免發起"有為者亦若是"的短暫豪情壯志... (豪情多半會在幾分鐘後因回歸現實而消失殆盡)

今天中午吃便當,配著壽司的出差狂想曲下飯,明明是講外遇,但文裡甜蜜洋溢,閃光閃到我快睜不開眼睛,瞎子都看得出這是不折不扣的炫耀文... 讀到文末,曾經讓壽司發過小財的Google AdSense天外飛來一筆絕妙廣告,實在是太點題了!! 我笑了~~~

Windows Form AutoScaling

寫Windows Form的人應該都會面對這類問題。

一直跑得好好的程式傳來噩耗,在某某大官的機器上圖歪字斜,最要命的是老爺子找不到送出鈕可按,正暴怒中。戒慎恐懼地前往"命案現場"(沒處理好的話,馬上就是了)查看,載著厚重老花眼的層峰長官,怒指著LCD控訴你寫的什麼鬼表單,居然沒有送出鈕。定神一看,媽呀! 設成800X600還加大字型(120DPI),原本精巧可愛、手帕大小的表單,現在大如床單,一個個國字猶如魚丸,按此比例推算,原本在右方的送出鈕現在應該位於隔壁祕書Partition隔板的分機表上...

Windows彈性化的桌面解析度及大小字型設計方便了許多髮蒼蒼視茫茫的"資深"使用者(相信很快我也會用到),但這顯然是許多在慣用高解析度的開發者較難想像的情境。雖然我們可以透過基本解析度要求的方式排除部分差異太大的作業環境,但使用大字型是一些眼力不佳使用者不得不的選擇。幸好,.NET程式很貼心地提供了自動縮放的功能,在.NET 1.1可以設定Form.AutoScale,.NET 2.0則有Form.AutoScaleMode更進一步可以選擇跟著Font大小或螢幕DPI大小調整Form的大小(例如: 希望表單佔螢幕一定的比例)。

底下是一個用.NET 1.1寫的簡單範例:

其中(1)是在我1280x1024+96DPI Font本機桌面的顯示,(2)是AutoScale=false在1024x768+120DPI Font桌面的顯示,字變大了,但表單寬度沒變大(但Form Title有變高),所以右方的"高度"兩個字就看不到了。 (3)是AutoScale=true(預設值)的情形,可以看到表單隨著字型變寬也變高,文字可以完整展現。

但問題來了,並不是所有的配置元素都可以等比例放大(例如: 圖檔),或是放大後有可能超出原本預估的螢幕寬度,於是畫面配置錯亂,有些按鈕則飛到九天之外。要解決這個困擾,我想到的幾種解決辦法是:
1) 善用Panel等Container的Scroller功能,允許使用者捲動可視範圍玩一下尋寶遊戲。(操作起來會很累)
2) 針對不同解析度設計多組版型。(使用者很爽,開發者很累)
3) 將字型大小設為固定的Pixel單位(預設為Point),則字的大小將不隨使用者桌面的DPI而有所變化。(使用者可能抱怨字型過小)

每個解決辦法都有其缺點,就視不同的情境運用吧!

另外,今天發現VS.NET 2003有個Bug,使用96DPI環境設計的專案,在另一台120DPI的機器上以VS.NET 2003開啟時,PictureBox等物件會被偷改尺寸加以放大,這個雞婆的舉動糟透了。要避免此一問題,可以將Form.AutoScale先設為false。參考資料如後: http://www.dotnet247.com/247reference/msgs/37/186063.aspx

KB-UTF8Encoding And BOM
static void TestXMLWriter()
{
    MemoryStream ms = new MemoryStream();
    XmlTextWriter xtw = new XmlTextWriter(ms, Encoding.UTF8);
    xtw.Formatting = Formatting.Indented;
    XmlDocument xd = new XmlDocument();
    xd.LoadXml("<Group><User>Jeffrey</User></Group>");
    xd.Save(xtw);
    xtw.Flush();
    xtw.Close();
    string xml = Encoding.UTF8.GetString(ms.ToArray());
    Console.WriteLine(xml);
    xd.LoadXml(xml);
}

XmlTextWriter設為Fomatting.Indented時,可以把XML整成美美的縮排格式,於是我寫了以上的Code。但是這段程式有點問題,明明是XmlTextWriter輸出的Byte Array轉成字串後,再被另一個XmlDocument.LoadXml()卻會發生Data at the root level is invalid. Line 1, Position 1.的錯誤!

問題出在BOM!! Byte Order Mark,相信有很多人在使用文字編輯軟體都有發現Save UTF-8 with BOM這類的選項,BOM是加在文字檔前的幾個位元,可以協助應用程式識別文字的儲存格式(多用於Windows,許多被這幾個位元搞暈頭的開發者視之為微軟的餘毒),這裡有篇好文章

Byte order mark Description
EF BB BF UTF-8
FF FE UTF-16, little endian
FE FF UTF-16, big endian
FF FE 00 00 UTF-32, little endian
00 00 FE FF UTF-32, big-endian

先前的程式寫法,XmlTextWriter在寫入MemoryStream時,預設就會加上BOM。若我們將Byte Array寫入檔案,再用StreamReader或XmlDocument.Load讀取,BOM被用來識別編碼格式後將被丟棄不出現在內容字串中;當我們直接用Encoding.GetString轉成字串時,BOM則會被帶入,造成LoadXml()解析錯誤,雖可以設法Trim掉,但我覺得更好的方法是利用UTF8Encoding建構式所支援的encoderShouldEmitUTF8Identifier參數,設為false,輸出結果即不會加註BOM。因此程式要改成:

XmlTextWriter xtw = new XmlTextWriter(ms, new System.Text.UTF8Encoding(false));

That's all, folks.

Search

Go

<October 2007>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
 
RSS
【工商服務】


BlogLook Score and Rank

Syndication