February 2010 - 文章

【黑暗水電工日記】假戲真做!

樓梯間的天花板燈座年代久遠,塑膠逐漸脆化,前陣子更是開始座盤分離,接著就出現接觸不良的狀態。開燈後常得用雨傘尖輕推燈泡"喬"個幾下才能點亮,上週一,小燈座走到了生命盡頭,再也亮不起來。樓梯少了燈,晚上出入不便,身為黑暗水電工,面對這種險惡情境,當然責無旁貸要扛起拯救世界的重責大任! (遠目)

平日下班都己晚上,樓梯間昏暗難以施工,於是趁著週末,先去附近的"專業水電材料行"(不是一般水電行,是專做"專業水電工"生意的水電材料批發店家,也是上回買到止水墊的地方)進貨買材料。材料行內成天都有"專業水電工"進出,用行話直接跟老板下單,像"LP4來50個"、"有六分管嗎"之類的,我雖然跟水電工前輩們一樣穿著排汗衫牛仔褲,但只要一開口說找"樓梯間天花板用的那種燈泡座",偽水電工的身份馬上穿幫。不過,水電材料行的所有商品都標了價,老板沒打算坑殺外行人(比起上回想賣我高檔汽車大燈燈泡的店家好多了),讓我買起來很安心。以下這兩個燈座,大家猜猜看多少錢?

一個10元,很便宜吧!

搬來木梯,抄起起子卸螺絲、拆舊燈座、裝新燈座、旋緊螺絲,三兩下便換裝完成。看來拆換燈座對我根本是小菜一碟,請叫我【黑暗水電工】!!

完工後站在梯子上,等黑暗女王開關燈檢測功能是否正常。此時發生了一件震憾我幼小脆弱心靈的大事...

樓下傳來鐵門開門聲,原來是住樓上的老先生回來了。木梯卡住狹小的樓梯轉角,要由梯子旁的狹小間隙側身穿過有點為難老人家,於是我趕緊將木梯收合讓出路來。老先生跟我點頭道謝繼續向上走,看到正在家門口控制電燈開關的黑暗女王,隨口說了一句:

妳找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~找水電來換燈哦~~~~~~

但見女王強忍笑意,努力克制不自主抖動的身體,回答老先生: "沒有啦,我先生看燈壞了,就想換一換..."

老大爺,我說"叫我【黑暗水電工】"只是玩笑話,您怎麼當真啦!! 沒想到,我這個"偽.水電工"入戲太深,今天有排汗衫+牛仔褲的加持,全身已開始散放出濃濃的"水電工味",竟順利騙倒鄰居。真不知該為練成"二轉水電工"高興,還是該擔心這會對我繼續從事"專業資訊工作"形成隱憂?

一切都是幻覺,嚇不倒我的。

平復好受驚的心情,在此向大家介紹前陣子在光華商場挖到的寶--八合一折疊起子! 比起過去買過的換頭起子優秀許多,最大的好處是不需額外空間收納起子頭,而且起子前端一體成形跟標準起子一樣細長,不像抽換式起子前端的突出六角套筒座常會卡住螺絲孔,伸不進去轉螺絲。

還有,起子前端的7顆LED燈在照明不足的場合超級好用,大推!!

這麼順手俐落的兵刃,絕對是水電工作人員居家旅遊,不可或缺的良伴,只要100塊大洋,有空要去光華敗家的捧油們可以考慮帶一把回家。

遲來一週的Acer P224W箱文

【註: 文中有標明[p]的連結為噗浪訊息】

故事要從2008年11月說起,家裡有一台LKK的三鳥牌17" LCD,在添購新的22"內建電視盒LCD後,高齡8歲的它在我公司的辦公桌覓得第二春,轉任工作機的第二螢幕。

時間來到半年後,7月的一天早上,17" LCD忽然出現一條血紅垂直線[p]。當時,全黑桌面背景配上紅線,還真有Canon L鏡的高級fu呢! 讓我心生莫名的虛榮感[p]~~~

可惜,代誌嘸像憨人想A哈尼甘單~~~ 兩個月後,第二條紅色線[p]冒出來了,噗友預言,線會愈來愈多,然後不久就會掛點。果不其然,第三條[p]、第四條[p]、第五條[p]陸續出現,還開始不定期出現消失加變色,我想這一定是傳說中稀有的隱藏版LCD--轉吧轉吧七彩霓虹線17"變色龍系列!!

經過一段時間磨合,螢幕線條開始穩定,固定顯示三條。AskaSu提醒我,三條槓意義非凡,這台螢幕肯定是萬中選一、全球僅有,唯一掛上尉階的LCD,從此它博得"上尉LCD[p]"的稱號。

雖然擁有一台掛階LCD挺酷的,但為了防止它無預警退役,我還是展開新LCD的Survey。一開始想衝1920*1080 FullHD解析度,一方面它是市場目前的主流,選擇多競爭大價格優,而超大的可使用桌面也有利於寫程式,只是有個問題--點距!

點距指螢幕上點與點之間的距離。在解析度相同的前題下,用個比喻,就像一模一樣兩千字的文章,印成A4大小的點距會比縮小印成B5來得大,字看起來較大,閱讀較不費力。粗略的點距算法是 吋數 / (水平線數^2+垂直線數^2) ^ 0.5 / 2.54。[參考]

以我原來的工作機螢幕組合來說,19" + 17",兩台都設成1280*1024解析度,而二者的點距就有段距離,以髮蒼蒼視茫茫中年人的標準,19"的點距(0.0294)看起來比17"(0.0263)舒適不少。若同樣是1920*1080 FullHD,22"的點距是0.0253,23"是0.0265,24"是0.0276,26"是0.0299,換句話說,要到我偏愛的1280*1024 19"點距水準,FullHD得買到26"才會符合,再不然至少也要24"。這會有兩個問題,1) 我的辦公桌硬要擠下24" or 26" + 19"的組合會猶如阿婆生子--很拼 2) 有點擔心24"/26"大尺寸LCD對近距離長期使用有點太大(一直從左到右轉頭,讀完長篇大論Word文件時不知會不會扭傷脖子 XD)

最後我決定退而求其次選1680,尺寸也退到22"。有兩個選擇: 16:9 (1680*945)或16:10 (1680*1050)? 在辦公室裡鮮少會有看16:9寬螢幕電影的需求,而垂直多出105個pixel高度,足足可以放下三條狀態列,對總是擠滿各式工具小視窗的IDE開發環境來說彌足珍貴。

確定22" 1680*1050的規格後,趁著年假逛了PCHome,發現22" 16:10的選擇並不多,不用在幾十台符合規格中猶豫不決,反而是好事一椿。篩選條件很簡單,一定要有DVI,有HDMI更好,一堆便宜的純D-Sub款式立刻出局,很快地就決定了Acer P224W(剛才發現現在標價比上週調高三百元,頓時又多了賺到的感覺,XD),這台螢幕有24h到貨服務,順便也讓我第一次體驗到"敗家於無形"的奧義--一時意亂情迷,頭腦還來不及清醒,小黑貓就已經叼著東西在門口等您...

敗了家還沒圖沒真相是不被允許的,所以:

講完了。

什麼? 住在開封艾凱香小姐Call In提問,為什麼只有這樣,根本就沒開箱?

咳,請大家仔細看標題,本來就說是"P224W箱文",並不是"P224W開箱文",到此為止也是合情合理。

使用心得還是得提一下: 經一週工作使用實測,各方面感覺良好,這是台好螢幕!

TIPS-Windows 7便利貼(自黏便箋)快速鍵

Windows 7內建了桌面便利貼軟體,或許基於商標專利或其他考慮,名字有些繞口--自黏便箋

相較於市場上其他便利貼軟體,它的功能有點陽春,介面也不怎麼順手。例如: 右鍵選單除了Copy/Paste跟換紙條底色外,找不到任何編輯文字內容的操作,也沒有工具列,一度讓我以為它只能當NotePad用。不過貼上去的內容倒是可以保留字型顏色,表示它的確支援RichText。爬了一下文,發現原來有密技! 雖然沒有按鈕工具列,但有一些快速鍵可以進行"簡單"的文字編輯操作。

  1. Ctrl + Shift + > : 字型變大
  2. Ctrl + Shift + < : 字型變小
  3. Ctrl + B : 粗體
  4. Ctrl + I : 斜體
  5. Ctrl + T : 刪字線
  6. Ctrl + U : 底線
  7. Ctrl + Shift + L : 按一次項目符號(Bulleted)
  8. Ctrl + Shift + L : 按兩次自動編號(Numbered)
  9. Ctrl + L : 靠左對齊
  10. Ctrl + C : 靠中對齊
  11. Ctrl + R : 靠右對齊

很可惜,想改字型跟顏色,還是門都沒有。

如果不介意它的功能過於陽春、介面過於簡陋,Windows 7 User倒可試試這個不用安裝的內建便利貼。不過,我個人把它定位成免費奉送的小菜,針對這類需求,微軟真正的大餐是--OneNote(OneNote是Office 2003/2007的家族成員之一,不過好像知名度並不高),操作順手、功能華麗,用過的人評價都蠻高的,有興趣的人可以試試。

【不小心找到的其他技巧】

POC-以jQuery實作會議室登記狀況顯示

專案有個需求,要在網頁上呈現多間會議室一天的使用登記狀況。'傳統思維"可能會傾向用Table <td>模擬出時段區塊,然後將預約起迄時間範圍內的<td>用colspan併成一塊。不過如果要做到登記時間以10分鐘為單位,意味著每個小時要切割成6個<td>,即便沒有任何預約也是,讓Table充滿一堆無用又囉嗦的廢物Tag。加上計算哪幾個<td>要併在一起,得找出第一個<td>加colspan,並將後面<td>省略,演算法不怎麼單純。分析起來,這就是該用CSS取代Table做法實作Layout的經典情境。

寫了一段jQuery程式做出POC(Proof of Concept)供同事參考,示範使用DIV+CSS達成上述效果,並並盡可能簡化為呼叫函數就可以完成。一魚兩吃,PO出來供大家參考,順便蒐集諸位先進達人們的回饋指教。

//輸入起/迄整點、寬、高及是否顯示時間標題列,傳回顯示全日時段狀態的<div>
function genTimeline(startHour, endHour, width, height, incHeader) {
    $.Darkthread.tools.addDefaultStyles(".dttl_BookingBlock", {
        ".dttl_BookingBlock": {
            float: "left", position: "absolute",
            margin: "0px", padding: "0px", height: "100%",
            "font-size": "9pt", "background-color": "#dddddd",
            "border-left": "solid #444444 1px",
            "border-right": "solid #444444 1px"
        },
        ".dttl_Timeline": {
            border: "solid black 1px", position: "relative"
        },
        ".dttl_TimelineHeader": {
            border: "solid black 1px",
            backgroundColor: "#dddddd",
            "font-size": "8pt", position: "relative"
        },
        ".dttl_SplitLine": {
            float: "left", position: "absolute",
            "border-left": "solid gray 1px", 
            width: "1px", height: "100%"
        }
    });
 
    var $tl = $("<div class='dttl_Timeline' />");
    $tl.height(height).width(width);
    var hourWidth = width / (endHour - startHour);
    var $h = $("<div />");
    if (incHeader)
        $h.width(width).height(15).addClass("dttl_TimelineHeader");
    for (var i = startHour; i < endHour; i++) {
        var vLine =
            "<div class='dttl_SplitLine' style='left:" +
            hourWidth * (i - startHour) + "px;' />";
        $tl.append(vLine);
        if (incHeader)
            $h.append("<div>" + i + "</div>").append(vLine);
    }
    if (incHeader) {
        $h.find("div").css({
            float: "left", margin: "0px", width: hourWidth + "px", 
            textAlign: "center"
        });
    }
    var $container = $("<div class='dttl_Container' />");
    $container.append($h).append($tl)
    .data("hourWidth", hourWidth)
    .data("startHour", startHour)
    .data("endHour", endHour);
    return $container;
}
 
//display會直接顯示在方格中,可支援HTML語法
//detail以title="..."方式處理,請給純文字
//cssOption可為css屬性物件,例如: { border:"...", color:"..." }或是className
//會傳回<div> jQuery物件供後續應用
function addBooking(timeline, startTime, endTime, display, detail, cssOption) {
 
    if (cssOption == undefined || typeof cssOption == "string") 
        cssOption = { };
 
    var $tl = $(timeline);
    var sh = $tl.data("startHour"), eh = $tl.data("endHour");
    //將時間先轉成小數
    var p = startTime.split(':');
    var st = parseInt(p[0]) + (parseFloat(p[1]) / 60);
    //小於startHour, 以startHour為準
    if (st < sh) st = sh;
    
    p = endTime.split(':');
    var et = parseInt(p[0]) + (parseFloat(p[1]) / 60);
    //大於endHour, 以endHour為準
    if (et > eh) et = eh;
 
    //算出起始位置及寬度
    var hw = $tl.data("hourWidth");
    var w = (et - st) * hw;
    var x = (st - sh) * hw;
 
    //加入顯示元素
    var $container = $tl.children("div:last");
    
    var $dv = $("<div class=\"dttl_BookingBlock\"/>");
    $dv.css(cssOption).css({ 
        left: x + "px", width: w + "px",
        opacity: 0.7
    });
 
    $dv.html("<span style='padding: 2px;'>" + display + "</span>")
    .attr("title", startTime + "-" + endTime + "\n" + detail);
    if (typeof cssOption == "string")
        $dv.addClass(cssOption);
 
    $container.append($dv);
    
    return $dv;
}

以下是應用範例:

<script src="jquery-1.4.2.js" type="text/javascript"></script>
<script src="jquery.darkthread.tools.js" type="text/javascript"></script>
<script src="TimelineUtil.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function() {
        var $d = genTimeline(9, 21, 800, 30, true);
        $("body").append($d);
 
        var $booking;
        //truncText可幫忙取前10個字            
        var longText = "超長文字測試,利用truncDetail()取前10個字元";
        $booking = addBooking($d, "14:30", "17:00",
            $.Darkthread.tools.truncText(longText, 10), longText);
        //允許重疊顯示
        $booking = addBooking($d, "16:30", "20:00", "來亂的", "咬我呀!");
  //cssOption可指定CSS
        var cssOption = { backgroundColor: "green", color: "yellow" };
        $booking = addBooking($d, "09:30", "10:00", "Jeffrey", 
                              "重要會議", cssOption);
 
        //cssOption傳入字串時,視為className
        $booking = addBooking($d, "10:30", "12:00", 
                                "Meeting", "MouseOver特效", "booking");
        //addBooking會傳回<div>的jQuery物件,可進行加上hover/click事件等操作
        $booking.hover(function() {
            $(this).addClass("inverse");
        }, function() {
            $(this).removeClass("inverse");
        }).css("cursor", "pointer").click(function() {
            alert("Hi!");
        });
 
        //產生100條時間軸各有12筆登錄,測試速度
        var $tbl = $(
        "<table cellspacing='0' cellpadding='0' style='margin-top: 10px;' />");
        for (var i = 0; i < 100; i++) {
            $tr = $("<tr><td width='10%' /><td width='90%' /></tr>");
            $tr.find("td:first").text("Room" + i);
            var $timeline = genTimeline(9, 21, 800, 30, i == 0);
            $tr.find("td:last").append($timeline);
            $tbl.append($tr);
            for (var j = 9; j < 21; j++) {
                $booking = addBooking($timeline, j + ":00", j + ":30", 
                    "Test-" + i + "-" + j, "Test");
            }
        }
        $("body").append($tbl);
    });
</script>
<style type="text/css">
    .booking
    {
        background-color: Green;
        color: Yellow;
    }
    div.inverse 
    {
        background-color: black;
        color: White;
    }
</style>

【註】只是POC,防呆、檢核及彈性都很薄弱,不足處請自理!

黑暗jQuery工具包1-動態加入預設Style設定

對我來說,Javascript在處理CSS上一直有一個小困擾...

一般在習慣上,為了方便開發人員修改,多半會將Style設定獨立放在.css檔案裡,應用者可視需要覆寫或修改之。但有蠻多時候,應用者根本不打算更動CSS內容,只想直接沿用預設CSS設定,但部署時還是少不了要Copy相對應的.css檔案,網頁也必須多加上<style src="...">宣告。

我總覺得,最理想的方式是將預設css與js融和在一個檔案裡。當開發者想自行定義CSS樣式,可將其寫在.css中加以引用,或在HTML中以<style>逕行宣告;若只想用預設內容,則只需參照.js就大功告成。

今天遇到類似需求,索性寫了幾個小函數,試著實現上述想法。

我的設計概念是這樣的: 先透過document.styleSheets.cssRules比對某個CSS Selector(例如: div.cCountryPicker),若已被定義過,表示網頁已透過參照.css檔或直接宣告<style type="text/css">方式定義了div.cCounterPicker,此時就不需加入預設的div.cCountryPicker樣式規則;反之,若沒有定義該規則,則透過$("head").append("<style type=\"text/css\">....")的方式加入Style宣告。

程式碼如下:
(PS: 我規劃用jQuery.Darkthread.*蒐集我陸續開發出來的工具函數,最前面的$.extend用來將多個.js中宣告的jQuery.Darkthread.*函數彙整在一起)

/*
** Darkthread's Tools for jQuery ver 1.0 **
by Jeffrey, 2010-02-25, http://blog.darkthread.net
=======================================================
checkStyle: to check if specific style selector is defined
addStyles: add <style type="text/css"> dynamically
addDefaultStyles: if specific style selector isn't defined, add style setting
 
*/
jQuery.extend(jQuery, { Darkthread: {} });
jQuery.extend(jQuery.Darkthread, {
    tools: {
        //ruleSelector sample: a:hover, .myClass, #someId
        checkStyle:
        function(ruleSelector) {
            //REF: http://www.javascriptkit.com/dhtmltutors/externalcss3.shtml
            var cssCol = document.styleSheets;
            ruleSelector = ruleSelector.toLowerCase();
            for (var i = 0; i < cssCol.length; i++) {
                var rules = cssCol[i].cssRules || cssCol[i].rules;
                for (j = 0; j < rules.length; j++)
                    if (rules[j].selectorText.toLowerCase() == ruleSelector)
                    return true;
            }
            return false;
        },
        /*styleDictionary sample:
        { 
        "a:hover": { "background-color": "red", "color":"yellow" },
        "#dvTest": { "border", "solid blue 1px" }
        }
        Note: it's for <style> declaration, not for jQuery.css, so don't use 
        backgourndColor instead of "background-color"
        */
        addStyles:
        function(styleDictionary) {
            var sb = [];
            for (var selector in styleDictionary) {
                sb.push(selector + " {");
                var props = styleDictionary[selector];
                for (var p in props) {
                    sb.push(p + ":" + props[p] + ";");
                }
                sb.push("}");
            }
            $("head").append("<style type=\"text/css\">\n" +
                sb.join("\n") + "\n</style>");
        },
        addDefaultStyles:
        function(ruleSelector, styleDictionary) {
            if (!$.Darkthread.tools.checkStyle(ruleSelector))
                $.Darkthread.tools.addStyles(styleDictionary);
        }
    }
});

應用方式挺簡單的,將以下的程式寫進.js,等於將預設的.css一起藏進.js裡,就不必再多搞出一個預設用途的.css檔案囉!

    $.Darkthread.tools.addDefaultStyles(".dttl_BookingBlock", {
        ".dttl_BookingBlock": {
            float: "left", position: "absolute",
            margin: "0px", padding: "0px", height: "100%",
            "font-size": "9pt", "background-color": "#dddddd",
            "border-left": "solid #444444 1px",
            "border-right": "solid #444444 1px"
        },
        ".ddtl_Timeline": { border: "solid red 1px" }
    });

(以上的程式碼,會在網頁本身沒有宣告.dttl_BookingBlock且所有參照的.css中也沒有該宣告時,加入.dttl_BookingBlock及.dttl_Timeline兩條CSS規則,賦與預設Style設定。)

Outlook 2007無法預覽PDF附件

Outlook 2007在x64平台上無法預覽信件的PDF附件檔,會出現:

無法預覽此檔案,因為下列預覽器發生錯誤:
PDF Preview Handler for Vista
若要在本身所屬的程式中開啟此檔案,請按兩下檔案。

找到這篇文章,才知道這是Adobe PDF Preview Handler的Bug,網友們已經抱怨了兩年多,Adobe還是沒處理,於是該文章的作者就自力救濟寫出修補程式,修改Registry裡錯誤的AppID後,PDF預覽器馬上就可以使用囉~~

PS: 另外,該Fix還做了一個"32/64-bit thumbnail bridge",一併修正了x64下檔案總管檢視時無法產生PDF內容縮圖的問題(Adobe沒出x64版的縮圖產生器),真神人也!

升級Win2008R2後Hyper-V VM無法啟動

年前將作業系統由Windows 2008升級到Windows 2008 R2,已有年後要"打地鼠"的心理準備(每日一詞: 【打地鼠】意指針對疑難雜症或系統問題,採取見一個解一個,遇到再說的鄉愿策略),而今天打了第一隻地鼠...

啟動Hyper-V VM時出現以Microsoft Emulated IDE Controller: Failed to Power on with Error 'The system cannot find the file specified.'錯誤,導致VM無法開機。

本以為會很棘手,幸好在檢視VM設定時很快就發現可疑之處: Physical CD/DVD drive:選單出現Drive 'Not found',而下拉選單中另有一筆Drive 'D:'應該才是正確的選項。

重設為Driver 'D:'後,VM即可順利啟動,而之後再開啟VM Setting,Driver 'Not found'選項也消失了,我的推測是升級R2後,DVD光碟機被賦與了新識別碼,而VM設定仍指向舊識別碼,導致上述問題,理論上重新設過就可解決囉。

【茶包射手手札】比蝸牛還慢的HD

前一代工作機XPC SB81P自從退休後,輾轉流落到客廳,擔任起"偽家電"兼娛樂PC的角色。(老讀者們可能己見過,它在這篇文章中曾露過臉,照片左側音箱上方,巴黎鐵塔下方,我想它能二次就業多少跟黑色鏡面長相脫不了關係,沒想到俊男美女較吃香的定律在硬體界也適用)

前些時候,周遭的機器接連發生某廠牌HD集體暴斃的慘劇,它也在中鏢之列,便換了顆新硬碟WD 6400AAKS,順便把OS由XP換裝成Windows 7。這台機器我裝完機後幾乎未再碰過,平日多由黑暗女王及兩位王室小成員操作使用。在換HD升OS後,陸續接獲一些抱怨: 放YouTube影片聲音會斷斷續續、某些程式執行過程會停頓一下、跑大型遊戲程式時會當機...

趁著年假時間較有空,便在客廳扮演一下茶包射手,著手修理這台不乖的電腦。

實測播放YouTube影片,360p順暢,到480p起就偶有斷續雜音(感覺是CPU跟不上),720p更是慘不忍睹。有點狐疑,同一台機器,之前裝XP用得還不錯,即便機器老了點(Pentium-D 935 3.2GHz + 2G RAM) ,Windows 7對硬體要求稍高,但直覺上應該不至於連YouTube影片都跑不動。

Vista/Windows 7起有所謂的效能分數檢測,是很不錯的初步評量。執行了一次,發現頗意外的結果,這顆新HD居然只有2.7分,是所有分數中最差的。啥? 最新買的裝置居然是整台機器的罩門,太不合理了!

X光照出異常,接著就該做斷層掃瞄了。找來HD專屬的測速工具--HD Tune,診斷報告一出,我花容失色大吃一驚!!

嚇! 3MB/s!! 大家評評理,SATA HD都在拼100MB/s的時代,這種速度能看嗎能看嗎?

經過一番嘗試,發現如果我把BIOS裡的On-Chip Serial ATA Setting/SATA Mode設定由AHCI調回IDE,HD速度就會改善,一舉飆上86MB/s,才是WD 6400AAKS合理的效能表現。

為何AHCI模式會導致如此不堪的HD效能數字,噗友鐵衛在噗浪提供了一些情資,不過似乎於我的狀況不太相同,由HD Tune資訊顯示,HD狀態為UDMA Mode 5。但回想起來,因為XP要換AHCI工程很大,所以AHCI倒真是安裝Windows 7時才調過來的,跟遇到HD效能問題的時點相當,坐實HD問題與AHCI設定有關的推論。

雖然未知AHCI模式導致HD效能低落的真實原因,但畢竟這是台高壽五年的老機器,若是追到最後,問題出在BIOS卻又不可能再有Update,將情何以堪? 於是我很果斷地決定"面對它、接受它、處理它(調成IDE不就好了)、放下它",啦啦啦啦~~~

在福虎開春之際,茶包射手就用馬馬虎虎的結案態度應景一下(喂!),順道給大家拜個晚年囉!

簡易版記憶體用量觀察工具

找資料時發現GC.GetTotalMemory()這個方法,看到有些人用它來測量記憶體使用狀況,我也好奇玩了一下,包成一個MemWatch Class(比照Stopwatch的概念):

//簡易版的記憶體用量觀察工具
//  透過觀察Managed Heap的總使用量變化
//  粗略推估某段程式所耗用的記憶體大小 
class MemWatch
{
    //比較記憶體使用量變化的基準值
    private long _lastTotalMemory = 0;
    //記憶體使用量變化
    public long MemorySizeChange = 0;
    //是否強制GC再測量記憶體用量
    private bool _forceGC = false;
    //可指定測量前是否要先做GC
    //(可排除己不用但尚未回收的記憶體)
    public MemWatch(bool forceGC)
    {
        _forceGC = forceGC;
    }
    public MemWatch() : this(false) { }
    //保留測量開始之基準
    public void Start()
    {
        _lastTotalMemory = 
            GC.GetTotalMemory(_forceGC);
    }
    //測量從Start()至今的記憶體變化
    public void Stop()
    {
        MemorySizeChange =
             GC.GetTotalMemory(_forceGC) - _lastTotalMemory;
    }
    //記憶體使用量變化(以KB計)
    public string MemorySizeChangeInKB
    {
        get
        {
            return string.Format("{0:N0}KB", 
                MemorySizeChange / 1024);
        }
    }
    //記憶體使用量變化(以MB計)
    public string MemorySizeChangeInMB
    {
        get
        {
            return string.Format("{0:N0}MB", 
                MemorySizeChange / 1024 / 1024);
        }
    }
}

測試時,故意宣告指定大小的byte[],看看結果是否如預期:

class Program
{
    static void Main(string[] args)
    {
        //測試1-宣告byte陣列記憶體使用
        //利用MemWatch觀察Heap記憶體使用狀況
        MemWatch mw = new MemWatch();
        //宣告前先記錄使用前的記憶體用量
        mw.Start();
        byte[] b1 = new byte[512 * 1024];
        //宣告後再測一次
        mw.Stop();
        //會得到二次的記體變化差距為512KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
 
        //測試2-這次宣告byte陣列後立即棄用,但不做GC
        mw.Start();
        byte[] b2 = new byte[256 * 1024];
        b2 = null;
        mw.Stop();
        //b2佔用記憶體已釋出,但尚未回收,結果為256KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
        
        //進行下一階段測試前先強制GC
        GC.Collect();
 
        //測試3-同2,但換成先GC才測記憶體大小的測量模式
        mw = new MemWatch(true);
        mw.Start();
        byte[] b3 = new byte[256 * 1024];
        b3 = null;
        mw.Stop();
        //MemWatch測量時會先將b2釋出的記憶體回收,故結果為0KB
        Console.WriteLine(mw.MemorySizeChangeInKB);
 
        Console.Read();
    }
}

補充:

  1. GC.GetTotalMemory()只能取得Managed Heap部分的大小,若變數耗用的是Stack部分的記憶體(例如: Value Type),則不在量測範圍內。
  2. GetTotalMemeory()只能量測總耗用量,無法精準指出特定變數/物件所耗用記憶體大小。
  3. MemWatch預設會包含配置後棄用但尚未被GC機制回收的記憶體量,若要強制回收可自行呼叫GC.Collect()或用MemWatch(true)建構式啟用強制回收模式。注意,這裡是因測試需要才自行操作GC回收,實務上應避免在程式中自行呼叫GC.Collect(),最好交由.NET Runtime自行決定何時進行回收,較有利於整體效能。
  4. Compiler常會針對程式碼進行最佳化(例如: 忽略只宣告但完全未使用的物件、改變程式執行順序以改善效率...等等),因此當執行結果與預期出入很大時,可研究看看是否與最佳化有關。
升級Windows 2008 R2!!

Windows 2008 R2已上市好一段時日,我現在工作桌機的OS是Windows 2008 x64(翻了一下日記,是一年多前裝的),雖然蠻想升級R2嘗鮮,但礙於更換OS茲事體大,若安裝升級過程發生不測,重新安裝/還原工作環境工程浩大,手上工作就都得停擺了。

趁著有一星期年假可以做為緩衝,前陣子我就偷偷策劃,打算趁過年把OS升級到R2,一併除舊佈新一番。過去升級OS我都是重新安裝再搬檔案,砍掉重練感覺比較乾淨俐落。但這回想偷懶,決定第一次試試"就地升級"的OS安裝法。

昨天部門吃尾牙,出發前我啟動了安裝程式(注意: 要先將所有Hyper-V VM Shutdown),接著下一步下一步地進入升級程序(畫面上還很貼心地提供了進度跟處理檔案數),不打算坐在電腦前枯等,就開開心心吃尾牙去~~

尾牙結束回到辦公室,電腦已裝好重開完畢,等待我登入的已經是Windows 2008 R2 x64了,登入後系統環境大致如常,真是讓人愉悅的安裝經驗呀!!

今天小小實地用了一下,微調了幾個小地方:

  1. IE8會復原成原始設定(但我的最愛會保留),首頁設定、Cookie等也會被清除
  2. 預設沒有聲音,要先Enable Audio Service
  3. 發現不能使用Windows 7 Aero Theme,爬了文發現是因為Theme Service被Disabled的原因。
  4. 我還是很習慣在工作列直接點小圖示啟動程式,所以花了點功夫Add Quick Launch Toolbar

感覺上原來的工作環境已完整被搬來R2,其他問題就等遇到再用打地鼠法解決吧!

CODE-Convert Text to PDF in C#

透過程式直接將Text內容轉換成PDF的程式範例,寫來給其他組同事做為系統整合模組開發參考,順便PO文備忘。

要在.NET轉PDF,當然少不了大家都說讚的iTextSharp,程式很簡單,我還順手加了一個遇到"\f" (0x0C) Form Feed符號就強制換新頁的功能。

<%@ Page Language="C#" %>
<%@ Import Namespace="iTextSharp.text" %>
<%@ Import Namespace="iTextSharp.text.pdf" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">
    protected void btnConvert_Click(object sender, EventArgs e)
    {
//REF: http://www.codeproject.com/KB/graphics/iTextSharpTutorial.aspx
        Document doc = new Document(PageSize.A4.Rotate());
        
        using (MemoryStream ms = new MemoryStream())
        {
 
            try
            {
                PdfWriter.GetInstance(doc, ms);
                doc.Open();
//中文字型問題REF 
//http://renjin.blogspot.com/2009/01/using-chinese-fonts-in-itextsharp.html
                string fontPath = 
                    Environment.GetFolderPath(
                    Environment.SpecialFolder.System) +
                    @"\..\Fonts\kaiu.ttf";
                BaseFont bfChinese = BaseFont.CreateFont(
                    fontPath,
                    BaseFont.IDENTITY_H, //橫式中文
                    BaseFont.NOT_EMBEDDED
                );
                Font fontChinese = new Font(bfChinese, 8f, Font.NORMAL);
                
                StringReader sr = new StringReader(txtInput.Text);
                string line = null;
                while ((line = sr.ReadLine()) != null)
                {
                    string[] p = line.Split('\f');
                    foreach (string s in p)
                    {
                        doc.Add(new Paragraph(s, fontChinese));
                        if (p.Length > 1) //表示有換頁符號
                            doc.NewPage();
                    }
                }
            }
            catch (DocumentException de)
            {
                Response.Write(de.Message);
                Response.End();
            }
            catch (IOException ioe)
            {
                Response.Write(ioe.Message);
                Response.End();
            }
            doc.Close();
            //Output PDF
            Response.Clear();
            Response.ContentType = "application/octet-stream";
            Response.AddHeader("content-disposition", 
                "attachment;filename=output.pdf");
            Response.BinaryWrite(ms.ToArray());
            Response.End();
        }
                    
    }
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Text to PDF demo</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:TextBox ID="txtInput" runat="server" Height="600px" TextMode="MultiLine" 
            Width="800px"></asp:TextBox>
    <br /><asp:Button ID="btnConvert" runat="server" onclick="btnConvert_Click" 
            Text="Convert to PDF" />
    </div>
    </form>
</body>
</html>
CODE-SetTimeout/ClearTimeout in C#

正在從事以休閒為目的Coding活動時,忽然有個衝動想在C#中也用一下Javascript裡常用的setTimeout/clearTimeout。

setTimeout說穿了就是透過另一條Thread執行程式產生非同步效果,用.NET實作是小菜一碟,而我想挑戰的是如何用最簡潔的方法實作出來。

剛好這陣子陸續玩過Action<T> and Func<T>Closure in C#,加上研究Parallel.For()時被迫反覆寫了十來次,現在已經練就信手就可掰出一段Thread配Lambda範例的境界。這個題目拿來作為隨堂考試再適合也不過了。

在Action加Lambda加Closure的加持之下,只要不到30行程式就可以在C#中使用SetTimeout/ClearTimeout囉~~~ C#真是簡潔有力的好語言呀!

先看程式碼,後面再提一下重點:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Threading;
 
namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            //No UI thread issue, use SetTimeout(cb, delay)
            SetTimeout(() =>
            {
                MessageBox.Show("First");
            }, 2000);
            SetTimeout(() =>
            {
                listBox1.Items.Add("Second");
            }, 4000, this);
            Guid hnd = SetTimeout(() =>
            {
                listBox1.Items.Add("Third");
            }, 6000, this);
            SetTimeout(() =>
            {
                listBox1.Items.Add("Forth");
            }, 8000, this);
            ClearTimeout(hnd);
        }
 
        #region SetTimeout/ClearTimeout Simulation
        //Dictionary for running setTimeout
        static Dictionary<Guid, Thread> _setTimeoutHandles =
            new Dictionary<Guid, Thread>();
        //SetTimeout for no UI Thread issue
        static Guid SetTimeout(Action cb, int delay)
        {
            return SetTimeout(cb, delay, null);
        }
        //Javascript-style SetTimeout function
        //remember to set uiForm argument when there cb is trying
        //to change UI controls in window form
        //it will return a GUID as handle for cancelling
        static Guid SetTimeout(Action cb, int delay, Form uiForm)
        {
            Guid g = Guid.NewGuid();
            Thread t = new Thread(() =>
            {
                Thread.Sleep(delay);
                _setTimeoutHandles.Remove(g);
                if (uiForm != null)
                    //use Invoke() to avoid threading issue
                    //Ref: http://tinyurl.com/yjckzhz
                    uiForm.Invoke(cb);
                else
                    cb();
            });
            _setTimeoutHandles.Add(g, t);
            t.Start();
            return g;
        }
        //Javascript-style ClearTimeout
        static void ClearTimeout(Guid g)
        {
            if (!_setTimeoutHandles.ContainsKey(g))
                return;
            _setTimeoutHandles[g].Abort();
            _setTimeoutHandles.Remove(g);
        }
        #endregion
    }
}

重點補充:

  1. SetTimeout的原理是呼叫端傳入Action參數及延遲微秒(ms)數後,立即新增一條Thread執行"先Thread.Delay()指定時間長度再呼叫所傳入的Action",並將控制權交回呼叫端,邏輯十分單純。
  2. 由於SetTimeout傳入要延遲執行的Action程式實際上會由另一條Thread執行,若在該邏輯中變動Window Form上的元素,就會違背不可透過非UI Thread去更動UI元素的規則。因此,我們再多增加一個Form參數,當有需要時,透過Form.Invoke()間接執行才可避開UI Thread限制。
  3. 要能ClearTimeout,就必須保留Thread變數,必要時將其Abort()掉。
    我用了一個Diction<Guid, Thread>來保存Thread,SetTimeout時Dictionary.Add()並傳回一個GUID,ClearTimeout時可憑該GUID去中止尚未執行的排程。
  4. 理論上,ClearTimeout只能中止"仍在等待執行的工作",因此Thread.Delay一結束時,就立刻Dictionary.Remove(GUID) (這裡剛好展現了Closure的美妙之處),不再允許ClearTimeout,否則程式都跑了一半還胡亂Thread.Abort()會出人命的。

寫完再回頭看,短短30行Code還真扯到不少進階的東西(Action, Closure, Threading),看懂的人C# Coding能力應該都在中階以上了吧!(純個人意見,勿戰) 對這一段還不熟的人可以參考以前PO過的幾篇文章當起頭,但我想還需要參考官方文件、網路範例配合實地動手寫過才容易完全理解。

【延伸閱讀】

  1. Lambda演算式
  2. 如何透過Lambda精簡Threading程式
  3. Action<T> and Func<T>
  4. UI Thread限制
  5. Closure in C#
MEMO-用Javascript RegExp將<x>置換成<span class="x">

每次都記不太住Javascript RegExp要怎麼做複雜的Replace(例如: 將比對相符的字串內容變成新置換文字的一部分,標題說的"將<x>換成<span class='x'>"就是典型案例),特別記錄一下供未來年老回憶之用。

我遇到的實際需求是想將Sharepoint查詢結果中的高亮註記<c1>, <c2>分別轉成<span class='hl1">, <span class="hl2">。

例如:

、<c2>晶圓</c2>雙雄... 有利<c1>台灣</c1>出口業加速復甦,而<c1>台灣</c1>程式魔人族群亦在持續擴大中,符合投顧的預期...

要轉成

、<span class='hl2'>晶圓</span>雙雄... 有利<span class='hl1'>台灣</span>出口業加速復甦,而<span class='hl1'>台灣</span>程式魔人族群亦在持續擴大中,符合投顧的預期...

程式說穿了不值一文錢,但也好歹讓我試了十來分鐘,不寫下來恐怕下回又得要再花十分鐘,所以有了這篇備忘文。

var s = "、\u003cc2\u003e晶圓\u003c/c2\u003e雙雄... 有利\u003cc1\u003e台灣\u003c/c1\u003e出口業加速復甦,而\u003cc1\u003e台灣\u003c/c1\u003e程式魔人族群亦在持續擴大中,符合投顧的預期...";
var regex = /[<]c(\d)>/g;
s = s.replace(regex, "<span class='hl$1'>");
regex = /[<]\/c\d>/g;
s = s.replace(regex, "</span>");

【後記】這是我目前唯一一個在一顆蕃茄(25min)內搞定的工作項目--研究如何用Javascript置換SPS搜尋結果的高亮CSS,然後拿剩下的10分鐘跟偷偷佔用的休息時間寫了這篇文章 XD

在.NET 3.5中使用Parallel.For()

網友KENCHAO問到"好威的Parallel.For可以用在.NET 3.5上"嗎?

微軟在Task Parallel Library CTP版本時代,曾提供過相容於.NET 3.5的Microsoft Parallel Extensions for .NET Framework 3.5。但找了一下,官方似乎已不再提供該版本的下載... 但是別氣餒,依據MS Parallel Programming RD小組在2009年11月的PO文,有一個來自個DevLabs的替代解決方案--Reactive Extensions to .NET (Rx),其中有個System.Threading.dll可向前相容Parallel Extensions for .NET Framework 3.5(但不提供技術支援),靠著它,我們就可讓Parallel.For在.NET 3.5中也大顯神威~~~

Rx首頁下載【Rx for .NET Framework 3.5 SP1】,安裝後你可以在C:\Program Files\Microsoft Reactive Extensions\Redist\DesktopV2 找到System.Threading.dll。

用VS2008開啟一個.NET 3.5專案,新增參考指向剛才提到的System.Threading.dll,在程式開端加個using System.Threading.Tasks宣告,接著在程式碼中就可以大大方方地使用Parallel.For囉!

不過,要注意幾點:

  1. 在.NET 3.5使用TPL所能展現的效能會遜於.NET 4.0,理由是.NET 4.0在ThreadPool及一些效能調校上還做了額外增強。
  2. 不提供官方支援。
  3. 透過.NET 3.5使用TPL時,無法享用VS2010的Parallel Task/Parallel Stack/Concurrency Visualizer等平行運算專屬新工具。
  4. OperationCanceledException的使用與.NET 4.0的版本有些差異。

雖然有以上限制,但能在.NET 3.5裡用Parallel.For()簡化原本囉嗦的多執行緒寫法,畢竟還是件爽快的事,值得一試!

搜尋

Go

<February 2010>
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
28123456
78910111213
 
RSS
【工商服務】
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication