Quantcast
Channel: 黑暗執行緒
Viewing all 2311 articles
Browse latest View live

使用jQuery傳送物件JSON到ASP.NET MVC

$
0
0

過去介紹過用jQuery傳件物件陣列字串陣列到ASP.NET MVC,採取的格式一直都還是application/x-www-form-urlencoded,遇到物件陣列時會編碼成players[0][Id]=...&players[0][Name]=..;JSON是當今傳輸轉換格式的主流,比起Form UrlEncode更直覺易偵錯,如要進行客製有豐富的現成程式庫,是更佳的選擇。

要以JSON格式傳送物件給ASP.NET MVC,除POST內容需為JSON字串,更重要的是HTTP Header的Content-Type必須設為application/json,才能被正確識別並反序列化還原為Server端物件。$.post()並能指定contentType,因此需改用底層的$.ajax(),我習慣仿效$.post()寫成$.postJson(),差別只在於傳送的資料物件會經JSON.stringify()序列化並將contentType指定為appliction/json,其餘呼叫應用方式則與$.post()一致。

用個範例說明,要傳送的Player類別如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
 
namespace Lab1219.Models
{
publicclass Player
    {
publicint Id { get; set; }
publicstring Name { get; set; }
public DateTime RegDate { get; set; }
publicint Score { get; set; }
 
publicstring ToText()
        {
returnstring.Format("I:{0} N:{1} R:{2:yyyy-MM-dd} S:{2:n0}",
                Id, Name, RegDate, Score);
        }
    }
}

View有兩點補充: 1) 先前提過的$.postJson()函式,用起來$.post()一模一樣   2) RegDate宣告日期時用Date.UTC()指定UTC時區。

 
@{
    Layout = null;
}
 
<!DOCTYPEhtml>
 
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>Index</title>
</head>
<body>
<div>
<inputtype="button"id="bSend"value="Test"/>
</div>
<scriptsrc="~/Scripts/jquery-2.0.3.js"></script>
<script>
//以application/json ContentType傳送JSON字串到Server端
        jQuery.postJson = function (url, data, callback, type) {
if (jQuery.isFunction(data)) {
                type = type || callback;
                callback = data;
                data = undefined;
            }
 
return jQuery.ajax({
                url: url,
                type: "POST",
                dataType: type,
                contentType: "application/json",
                data: typeof(data) == "string" ? data : JSON.stringify(data),
                success: callback
            });
        };
</script>
<script>
        $("#bSend").click(function () {
var players = [{
                Id: 1000, Name: "Darkthread",
                RegDate: new Date(Date.UTC(2000, 0, 1)),
                Score: 32767
            }, {
                Id: 1024, Name: "Jeffrey",
                RegDate: new Date(Date.UTC(2000, 0, 1)),
                Score: 9999
            }];
 
            $.postJson("@Url.Content("~/home/send")", players, function (res) {
                alert(res);
            });
        });
</script>
</body>
</html>

Controller方面只有一點補充,參數players前方要加上[FromBody] Attribute,請ASP.NET MVC由POST傳送的內容本體解析參數,餘下的工作細節ASP.NET MVC自會搞定。

using Lab1219.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
 
namespace Lab1219.Controllers
{
publicclass HomeController : Controller
    {
//
// GET: /Home/
public ActionResult Index()
        {
return View();
        }
 
public ActionResult Send([FromBody]Player[] players)
        {
return Content(string.Join("\n",
                players.Select(o => o.ToText()).ToArray()));
        }
    }
}

執行結果如下,這樣就能實現以JSON格式傳送參數給ASP.NET MVC。

檢查傳送內容,驗明POST內容確實為JSON無誤,大功告成。


你是我的巧克力-Chocolatey

$
0
0

在Scott Hanselman的2014開發人員大補帖發現好東西 – Chocolatey。(發音近似"敲可題",字面的意思是顏色或味道像巧克力一樣)

每回新裝機的重要工作之一是安裝一堆常備小工具如: 7-Zip、Notepad++、Sysinternals、Fiddler、Filezilla、LinqPad、KeePass...。之前的SOP是Google關鍵字 -> 找到官方網站 -> 下載 -> 安裝,說來不複雜,但同樣動作重複十來次就是件煩人事兒;另一種做法是自行整理常用工具安裝檔,放在USB碟或網路上備用,但搜羅軟體及持續更新還是得花功夫。相形之下,Linux只需apt-get加軟體簡稱就能下載安裝,不禁令人羡慕。

NuGet的出現改變了"東市買駿馬,西市買鞍韉"的程式庫繁瑣安裝程序;Chocolatey則透過PowerShell Script結合NuGet套件格式,試圖提供單一指令完成Windows的程式下載安裝,營造類似apt-get的便利性,甚至也跟NuGet一樣可處理相依性,例如: 使用軟體A必須先安裝程式B,安裝軟體A時Chocolatey會自動先下載安裝程式B。

【安裝程序】

要安裝Chocolatey,只需要一行指令完成(先決條件是Windows要裝妥PowerShell,Windows 7、Windows 2008 R2之後的OS版本應已內建PowerShell 2.0,舊版OS則需自行安裝),開個DOS命令列視窗執行以下指令:

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin

這樣就搞定了。

如果有Visual Studio及NuGet,也可透過Package Manager Console執行以下三行指令(Chocolatey套件在執行完Initialize-Chocolatey安裝好C:\Chocolatey目錄後就可以移除):

Install-Package chocolatey
Initialize-Chocolatey
Uninstall-Package chocolatey

使用NuGet管理介面也可以下載及移除(但仍需手動透過Package Manager Console執行Initialize-Chocolatey,感覺並沒有簡便太多)

比較之下,使用DOS命令列指令複雜像咒語但步驟最少,NuGet指令明確但程序多,大家可自行選擇喜歡的方式安裝。

【使用方式】

裝好Chocolatey後,預設在系統碟會新增一個Chocolatey目錄(安裝於C:\Chocolatey主要考量共用性及避免動用管理者權限,但使用者亦可自訂目錄),但透過Chocolatey安裝的軟體則視其配置需求,有的會安裝在C:\Chocolatey\lib下,有些則仍得安裝在Program Files,視各軟體包(Package)而定。Chocolate的軟體包裡並不包含軟體安裝檔或程式,實際上仍由該軟體官方網站或github、SourceForge等來源下載,Chocolatey只透過PowerShell Script讓使用者快速由指定位置取得軟體並完成安裝。

就以安裝7-Zip為例。開啟PowerShell視窗後可先使用"clist 7zip"列出7-Zip相關的軟體包,但一般常會遇到一個困擾,出現多個同名軟體包,不知道要裝哪一個? 例如7zip就就有"7zip", "7zip.install", "7zip.commandline"三個項目(有時還會看到.app、.tool、.portable等結尾)。如果同一個軟體包有多個結尾不同的版本,依據FAQ其區別如下:

  • 未加註結尾
    為Virtual Package,主要供相依性管理用途。安裝7zip.install或7zip.commandline後,Chocolatey會認定Virtual Package 7zip已安裝好,若其他軟體依賴7zip,檢查對象為7zip,以涵蓋使用者安裝7zip.install或7zip.commandline的兩種情境。
  • .install(舊名稱為.app,建議一律改用.install)
    指透過MSI、Setup.exe安裝檔安裝,可能會更動Registry、Program Files目錄、系統檔案等,常需升級管理者權限,移除時需透過解除安裝程序。
  • .portable(舊名稱為.commandline、.tool,建議一律改用.portable)
    即所謂綠色軟體,只複製檔案至單一目錄,不需更動Registry、系統檔案,移除時刪除檔案即可,但會損失如副檔名關聯、右鍵選單等系統整合功能。

找到程式包正確名稱,使用"cinst 7zip.install"就可以完成安裝,連安裝程式的操作介面都沒出現,咻咻咻就安裝完成,很酷吧!!

至於Chocolate現在提供哪些軟體,除了用clist猜關鍵字外,也可以透過Chocolatey網站瀏覽尋找:

【關於資安?】

只需下個指令,對來源一無所知就能無聲無息把軟體裝好,興奮之餘也不免擔心: 會不會下載到被植入病毒或木馬的髒東西? 或是被自動安裝一併掛入廣告軟體或間諜軟體等寄生蟲?

依Chocolatey團隊領導者Rob的說法(見Scott文章的Security Issue一節),軟體包在新上線前都經Rob自己審核過,隨著規模擴大未來或許會成立專門的小組負責審核。而依規定,所有軟體包必須符合以下原則(參考):

  • 不包含非法(盜版)軟體、垃圾軟體、惡意程式
  • 軟體需對其他使用者有用,只適用極特定環境的程式就免了
  • 使用無聲安裝模式(Slient Mode,指不需使用者輸入任何選項就完成安裝)時不可包含廣告、間諜軟體或不相干的附贈軟體
  • 不要重複加入已有其他程式包提供過的程式,應利用相依性
  • 宜將軟體包拆解細分以利組合相依
  • 一律使用直覺的小寫名稱

由以上資訊,Chocolatey的軟體包上線前會經過人工審視,並有嚴格的合法及安全性要求。會不會有任何人為疏失的可能性? 我相信是存在的,但應與從github、SourceForge或其他Freeware軟體蒐集網站取得的風險相去不遠,在我可接受範圍內,決定開始享受Chocolatey帶來的便利。順便照慣例高呼: 你是我的巧克力 Chocolatey好威呀!

2013年回顧~

$
0
0

依據一份美國研究(不是英國學者研究,可信度應該高一些)指出:

人類的智力在22歲時達到顛峰,而推理能力、思考速度在27歲就會開始走下坡,但從學習獲得的知識能力到60歲還不會衰退。一般人到37歲記憶力會明顯變差,而所有能力在約42歲開始走下坡路。

掐指一算,今年正式邁入"走下坡元年",人生目標急轉直下--事業名利皆糞土,健康平安才是福。(咳... 老天爺別誤會,樂透還是可以讓我中沒關係,我不會介意) 能力下滑,被後浪逼死在沙灘上是早晚的事,不如早日退休告老還鄉含飴弄孫安養天年,但前陣子去聽了小閃光的國中招生說明會被狠狠打醒: 小孩的升學就業結婚成家的關卡都還沒過,挑戰還多著呢,快醒醒吧!

本以為人生顛峰已過,日子會漸趨黯淡無味,但2013竟還能累積不少有趣經歷,趁著2013最後一天整理備忘,以免老來想兒孫嘴砲當年勇時欠缺題材:

  • 我上報了
    MSN Messenger在今年4月8日吹了熄燈號,無奈繼任的Skype不爭氣,管理成百上千連絡人的分類群組功能竟不能自動移轉,身為程式魔人當然吞不下這口氣,自己寫隻程式解決問題也是很合理滴。
    一開始只是想一併分享給有需要的朋友,便放上FB群組開放下載,結果群組遺失還真是所有MSN老User心中的痛,一傳十十傳百就這麼流傳開,陸續被阿榮福利味海芋小站收錄,被T客邦iThome網路傳真報導,甚至還上了ETToday自由時報,而意義非凡的是 -- 我登上蘋果日報。(而且不是社會版)。
    4月8日當天部落格創下單日超過4萬次點閱的空前記錄,眼看將突破租用主機的流量上限, 嚇得連夜搬家分流,成為有趣的小插曲。
    最後分享一個小祕密: 當初在建立MSN群組匯入工具專案前我在.NET 3.5與.NET 4.0間小小地猶豫,最後做了決定,即便需要額外下載安裝較麻煩,也要讓所有使用工具的朋友們多認識Microsoft .NET Framework(自從有了它,一介平庸大叔也變程式魔人呢!),算是對.NET的致敬。如果今年4月.NET 4.0在台灣的市佔率忽然明顯上升,打點請記在我身上。XD
  • 第一支手機App
    夢想著要寫App程式很久了,遙想當年,公司正狂推iPhone資費方案。由於我深信"身為程式魔人,卻沒能力為自己的手機寫程式將是一生的污點",偏偏要只會.NET的中年人多學一套Objective-C形同逼人尋短。結果辦公室前後左右的同事都換了iPhone,只有我打死不換,壯烈程度直逼死守四行倉庫謝團長~ 但好笑的是,後來雖然拿到HD7,趁著開發者年費優惠註冊後也只寫了個Hello World交差了事,直到今年上半年很奇妙的被派去寫WPF,對XAML有了全新認識,加上學習Knockout.js開始體驗MVVM的美,對於Windows Phone App的開發總算能上手,一時熱血,我完成了人生的第一支手機App-國語辭典。到目前為止,國語辭典App已累積近兩萬次下載,但功勞不是我的,教育部國語辭典本身的專業與權威不言可喻,且多虧黑客松社群高手的努力,省下可觀的資料擷取整理工程,這個App不過在Windows Phone平台有效率且友善地呈現內容,希望它的表現夠稱職。
  • JavaScript跨界之旅
    過去玩過Arduino(連接Wii雙截棍操作LED數字顯示),今年聽到Espruino這個新玩意,類似Arduino但可以用我熟悉的JavaScript直譯操作,門檻更低。熬不過好奇心弄了塊板子來玩,陸續寫了Console小工具、學會操作LCD顯示板、還搞了個LINE通知外接顯示器,少了重學新語言的障礙,Espruino比Arduino好玩,小小彌補我自小想唸電子科卻沒唸到的遺憾。

     
  • 十馬達成
    日子要過馬也要跑, 今年一路跑了萬金石櫻花馬國道馬海山馬葡萄馬艋舺馬田中馬共七場,人生馬數累積到12,跨過十馬門檻,國道馬更是創下4:25:39的個人最佳成績(PB),唯右腳被足底筋膜問題纏上,超過大半年後才逐漸好轉,會不會阻礙我的Sub 4(四小時內完賽)之夢仍是未知數,但我很明確知道: 將會一直跑下去...

祝大家新年快樂,健康平安!!

Coding4Fun-HTML5 Canvas製作翻書動畫格

$
0
0

最近為小閃光做了國小英文1200單字記憶卡,因為每頁右下角有片留白,一時手癢,決定順便加上翻書動畫(Flip Book)[影片]:

簡單來說,只要為每頁準備一個動畫格(Frame)圖檔,逐一安插在每頁固定位置就可搞定。利用程式產生圖檔對我已不是新把戲[GDI+HTML5 Canvas],考量未來工作上用HTML5的機會多一點,決定用HTML5 Canvas來做,Coding for Fun兼練兵磨槍。

程式碼主要修改自利用HTML5 Canvas動態產生文字圖示,要補充的只有幾點:

  1. 跑馬燈文字演算法有點像小時候參加程式設計比賽的考題。近幾年做大型專案、搞一秒幾十萬上下系統的能力毫無長進,但貼身肉博的格鬥倒是練很多,這種小場面嚇不倒我滴~
    (如果程式開發像打仗,我這輩子註定做不了領軍征戰運籌帷幄的將軍之才,能當個精於三行四進擅長近身博擊的老士官長肯定稱職,自己也樂在其中了無遺憾。)
  2. 由於必須等待跑馬燈上方圖檔(即本例中的旋轉天竺鼠)載入,方能取得寬高執行後續動作,故主要動作需安排在load()事件中,形成非同步情境。為確保動畫格(Frame)依序產生,需將非同步作業循序化,幸好匍伏前進之前已練習過,我蓋上jQuery.Deferred()結束這一回合。
  3. 將DataUri轉存圖檔的部分被我省略掉,但滾進的技巧在之前士官長已為大家示範過,單兵們都應該知道如何處置吧!?

其餘細節請看程式註解,另外我也放了一份線上展示給大家玩。稍息後開始動作,稍息!

<!DOCTYPEhtml>
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Image Maker</title>
<scripttype='text/javascript'
src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js'></script>
<style>
        img { margin: 2px; }
        body { background-color: #cccccc; }
        .animation { position: absolute; top: 12px; left: 12px; display: none; }
</style>
</head>
<body>
<script>
//利用canvas為圖檔加上跑馬燈文字製作成動畫格(Frame)圖檔
function createFrameImage(idx, text, imgPath, opt) {
//預設參數
var defaultOptions = {
            fontStyle: "bold", //normal, bold, italic
            fontName: "Courier New",
            fontSize: 14, //以Pixel為單位
            fgColor: "black",
            padding: 10
        };
var options = $.extend(defaultOptions, opt);
//建立Canvas,開始幹活兒
var canvas = document.createElement("canvas"),
            context = canvas.getContext("2d");
//評估文字尺寸
var font = options.fontStyle + " " + options.fontSize + "px " +
                   options.fontName;
        context.font = font;
var metrics = context.measureText(text);
 
//Deferred物件, 用來同步圖檔製作完成時間,實現循序執行效果
var dfd = $.Deferred();
 
//建立圖檔物件
var $img = $("<img />"); $img.appendTo("body");
//圖片載入後才能由.with()/.height()取寬高,故等待load後再執行
        $img.one("load", function () {
//由圖檔及文字決定Canvas尺寸
var w = Math.max(metrics.width, $img.width());
var h = $img.height() + options.padding + options.fontSize;
            canvas.width = w; canvas.height = h;
 
//在Canvas放入圖檔,上方置頂,左右置中
            context.drawImage($img[0], (w - $img.width()) / 2, 0);
 
//印出文字
            context.textAlign = "left";
            context.fillStyle = options.fgColor;
            context.font = font;
            context.fillText(text, 0,
                $img.height() + options.fontSize / 2 + options.padding);
 
//移除原始圖檔
            $img.remove();
 
//將圖檔新增至body
            $("<img />", { src: canvas.toDataURL(), "class": "frame" })
            .appendTo("body");
 
//通知執行完畢
            dfd.resolve();
        }).attr("src", imgPath);
 
return dfd.promise();
    }
 
var idx = 0;
var origWord = "darkthread";
var p = origWord.split(""); //拆成字串陣列
//將跑馬燈之文字移動過程展開為字串陣列
var words = [], prefix = "";
//第一張先放入長度與origWord相同的空白字元字串
    words.push(new Array(origWord.length).join(" "));
//做出字母逐一由右移至左堆積的跑馬燈效果
for (var i = 0; i < p.length; i++) {
        prefix = origWord.substr(0, i);
var runLen = p.length - i;
for (var j = runLen - 1; j >= 0; j--) {
var runStr = $.map(new Array(runLen), function (v, k) {
return k == j ? p[i] : " ";
            }).join("");
            words.push(prefix + runStr);
        }
    }
 
//利用jQuery.Deferred()循序產生跑馬燈動畫圖檔
var chain = $.Deferred().resolve();
 
    $.each(words, function (idx, word) {
//將跑馬燈文字套在八張連續動畫格上
var imgPath = "GPA" + ((idx % 8) + 1) + ".png";
//利用.then()進行循序串接
        chain = chain.then(function () {
return createFrameImage(idx, word, imgPath, {});
        });
    });
//待全部執行完畢後模擬翻書動畫效果
    chain.done(function () {
        alert("製作完畢,進行模擬播放!");
//用快速切換各Frame圖檔顯示模擬翻書動畫效果
var $frames = $(".frame");
//透過css將所有Frame移至同一絕對座標並先隱藏
        $frames.addClass("animation");
var idx = 0, lastIdx = -1;
        setInterval(function () {
if (lastIdx > -1) $frames.eq(lastIdx).hide();
            $frames.eq(idx).show();
            lastIdx = idx;
            idx = (idx + 1) % words.length;
        }, 150);
    });
</script>
</body>
</html>

消失的TFS簽入(Check-In)

$
0
0

改用Visual Studio 2013 + TFS一段時間,與同事共同開發專案時不時出現烏龍狀況: Check In修改後的程式碼,請同事取回最新版(Get Latest Version)卻沒有如預期更新。進一步檢查發現TFS上的版本尚未Check In,但明明記得已按下Check In鈕,親眼目睹進度條跑完也沒錯誤訊息呀! 眼睜睜看著Check-In卻從Server上消失,莫非用腦過度產生幻覺,還是年老番癲早該金盆洗手?

同樣的情境上演過好幾次,大家都有過類似經驗,讓人懷疑內情不單純,TFS則被貼上了"不可靠"標籤。印象有過幾次留意到按一次Check In無效,第二次按才完成的經驗,進一步研究找出原因 -- 原來是自動合併(AutoMerge)惹的禍。

我錄了一段示範:

由於TFS不像VSS預設啟用Check-Out鎖定,因此同一份程式允許多個使用者同時Check-Out編輯,再先後Check-In,後Check-In時若系統偵測該程式已被Check-In不同版本,則會啟動自動合併或手動合併程序解決衝突,而VS2013預設會試圖自行排除合併衝突。

在以上示範,PdfJob.cs要Check-In時已被其他人先修改Check-In(修改的是另一個Method),TFS發現此一衝突,但因兩次Check-In版本所修改的地方不同,可以自行合併兩處修改不需人為裁奪,故使用AutoMerge排除衝突。但AutoMerge完成後,並不會接著進行Check-In也不會有明確警示(Output視窗會有訊息),必須再按一次Check In鈕,才能將排除衝突後的程式碼正式簽入。(註: 若因修改範圍重疊無法自動合併時,還是會警告並帶出操作介面要求人為排除)

同事與我遇到的問題,多半是按下Check In時只AutoMerge並未Check In,但看到有跑進度條又沒有警示,很容易誤以為Check In完成,才造成錯誤認知。

知道此一特性,每次Check In時只需特別留意確認有無AutoMerge,必要時再按一次Check In鈕即可避免前述的烏龍狀況。

如果不喜歡這種行為,自動排除衝突的功能也可以被關閉:

關閉後,每次遇到版本衝突都會有明顯警示並需決定衝突排除方式: (檔案一多挺囉嗦的)

相比之下,我覺得自動排除衝突方便多了,只要在Check-In時多留意一些,必要時再按一次Check-In鈕即可。

Knockout.js 3.0

$
0
0

Knockout.js在去年10/25釋出3.0版,手上專案總算由焦頭爛額推進到火燒屁股(好像沒有比較好耶 XD ),便開始著手進行一些零散重構,也順便升級第三方程式庫(有個IE11問題要靠KendoUI升級解決),Knockout.js也想一併升級成3.0。整理Knockout.js 3.0資訊如下:

【Breaking Changes】

俗話說: "先研究不傷身體,再講求效果"。一般升級的首要考量不是有哪些新功能,而是有哪些功能被拿掉,看到酷炫新功能之前網站就先掛點,肯定惹來白眼。

KO RD準備了一份2.x升3.0的注意事項:

  1. Computed改為只有值改變時才觸發通知
    在2.x時代,ko.computed()觀察的observable變數只要被重新給值(即使新值與原值相同亦然)就會觸發重算,許多開發者反應如此會產生無效運算損耗效能,因此3.0的ko.computed()改為只有在值有改變時才觸發重算。
    如果希望維持2.x的行為模式,可透過ko.computed(function() { … }).extend({ notify: 'always' });達成。
  2. 繫結改為獨立更新
    <input type="checkbox" data-bind="visible: showTerms, checked: acceptsTerms" />
    以上範例中有visible及checked兩個繫結,在2.x時代,當acceptsTerms改變會觸發checked狀態更新,也會連帶以showTerms屬性更新visible(即使showTerms完全沒變動),這在某些自訂繫結情境會導致詭異的Bug。
    3.0已改為各繫結獨立更新,一方面改善效能,一方面也避免前述潛在Bug。除非原本程式有偷用"靠任一繫結連動其他繫結重算"密技,否則這個改變不會造成影響。(呵呵,我就中獎了,單選或多選兩用Checkbox清單需要調整,要讀取allBindingsAccessor().options()以形成訂閱關係。)
  3. optionsCaption改為HTML編輯輸出
    下拉選單的optionsCaption參數(就是使用者尚未選取前所顯示的"請選擇..."字樣)原本未提供HTML編碼功能,易造成顯示錯誤(如: < >等符號)及資料漏洞。3.0起則比照text繫結,會一律加上HTML編碼。
    如果原本程式已手動加上HTML編碼,升級後要記得移除。

【新功能】

  1. ko.observableArray加入arrayChange事件(透過trackArrayChanges Extender掛載)
  2. 新增未掛入DOM的元素之繫結支援
  3. 對於由observable所組成陣列(注意: 非observableArray)的處理更完善
  4. Binding Handler可指定繫結順序(value宣告after: [ 'options', 'foreach' ])
  5. Binding Handler新增preprocess可加入進置處理邏輯
  6. Binding Handler新增preprocessNode,在繫結前可對元素節點進行加工
  7. Dynamic Binding Handler – 攔截ko.getBindingHandler(),可以讀入Binding Key(即data-bind="…"中的文字內容),動態決定(或產生)Binding Handler
  8. 新增checkValue,讓checked繫結可以使用字串以外的任意型別內容作為勾選選項時的value。
  9. options change事件: 當options改變且當下已選項目被移除時,將會觸發下拉選單的change事件,連帶更新value、selectedOptions
  10. Observable View Models: 仍屬實驗性質

[KO系列]

http://www.darkthread.net/kolab/labs/default.aspx?m=post

CODE-使用CSS實現按鈕圖檔切換

$
0
0

最近處理到網頁多語系動態切換,部分按鈕採圖檔形式(需為每個語系分別製作圖檔,愛美得多付出些代價),因此切換語系時需變更按鈕圖檔來源,切換效果如下:

若是以前,遇到這類需求不多思索,抄起jQuery上就對了! 訂閱切換選項click事件,取得要切換語系,改變<img> src指向該語系對應的圖檔,再調整語系選項class,使已選取項目呈現不同顏色即大功告成,程式範例如下所示: 線上展示

<!DOCTYPEhtml>
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>按鈕圖檔切換 by JavaScript</title>
<style>
        .lang-sel span {
            color: blue;
            text-decoration: underline;
            cursor: pointer;
            margin-right: 3px;
        }
 
        .img-btn {
            margin: 3px;
            box-shadow: 2px 2px 5px #ccc;
        }
 
            .img-btn:active {
                box-shadow: none;
            }
 
        .lang-sel span.selected {
            color: red;
        }
</style>
</head>
<body>
<divclass='lang-sel'>
<spanclass='tw'data-lang='tw'>正體</span>
<spanclass='cn'data-lang='cn'>简体</span>
<spanclass='en'data-lang='en'>English</span>
<imgid='btn'class="img-btn"src='btn_tw.png'/>
</div>
<scripttype='text/javascript'
src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js'></script>
<script>
function switchLang(lang) {
            $("#btn").attr("src", "btn_" + lang + ".png");
            $(".lang-sel span").removeClass("selected")
            .filter("." + lang).addClass("selected");
        }
        switchLang("tw");
        $(".lang-sel span").on("click", function () {
            switchLang($(this).data("lang"));
        });
</script>
</body>
</html>

近幾年,見識過著名Framework(如Bootstrap、KendoUI)出神入化的CSS用法,不會作詩也會吟(惟功力仍淺,只敢說堪用),學會修改DOM以外的其他解法。

首先,CSS可透過background: url(圖檔網址)指定背景圖,配合不同Selector則可制訂多組規則,例如:

.btn { background: url(img-a.png); }
.tw .btn { background: url(img-b.png); }

以上安排即可實現切換效果: 當.img元素被包在容器中,容器是否加上class="tw"決定了.btn的背景是img-a.png還是img-b.png。以<span class='btn'>當成按鈕,上層容器指定不同class,span的背景圖就會改變;於是,切換語系時只需改變容器class(為求簡便,我通常直接將class設在body),其下所有相關<span>就會自動切換背景圖,達到語系切換,而已選取語系選項的變色效果也可透過類似手法完成:

.tw span.tw, .cn span.cn, .en span.en { color: red; }

除了在body addClass()外不需變更DOM, 用CSS就能一次切換按鈕圖檔及語系選項顯示,jQuery程式簡化到只剩一行: 線上展示

<!DOCTYPEhtml>
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<title>按鈕圖檔切換 by CSS</title>
<style>
        .lang-sel span {
            color: blue;
            text-decoration: underline;
            cursor: pointer;
            margin-right: 3px;
        }
 
        .img-btn {
            margin: 3px;
            display: inline-block;
            width: 45px;
            height: 20px;
            box-shadow: 2px 2px 5px #ccc;
        }
 
            .img-btn:active {
                box-shadow: none;
            }
 
        .tw span.tw, .cn span.cn, .en span.en {
            color: red;
        }
 
        .tw .img-btn {
            background: url(btn_tw.png);
        }
 
        .cn .img-btn {
            background: url(btn_cn.png);
        }
 
        .en .img-btn {
            background: url(btn_en.png);
        }
</style>
</head>
<bodyclass="tw">
<divclass='lang-sel'>
<spanclass='tw'data-lang='tw'>正體</span>
<spanclass='cn'data-lang='cn'>简体</span>
<spanclass='en'data-lang='en'>English</span>
<spanclass="img-btn"></span>
</div>
<scripttype='text/javascript'
src='http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.9.1.js'></script>
<script>
        $(".lang-sel span").on("click", function () {
            $("body").removeClass("tw cn en").addClass($(this).data("lang"));
        });
</script>
</body>
</html>

改用CSS做法後,JavaScript大幅簡化,CSS卻變得較為繁瑣,一來一往,整體說來並未明顯簡潔,但仍有其意義: 關鍵在於分工不同! 圖檔切換的工作由JavaScript移至CSS,意味著苦主工作職掌由程式設計師移向視覺設計師(當然,若JS與CSS都由前端工程師統包,就只是左手移到右手,差異便不大),圖檔變化有時還涉及尺寸變化,常需與排版設計一併考慮,在CSS裡統一協調安排較為便利,也算走CSS處理的優點之一。至於效能,CSS套用規則切換直接由瀏覽器渲染(Render)引擎處理,理應比用JavaScript逐一改變DOM屬性更有效率(尤其是要處理的DOM元素數量較多時),只是差異也有限吧!? (未經實驗,純屬非專業猜測)

未來面對類似應用需求,又多了一種解法選項。

資安筆記-經第三方廠商的社交工程繞道攻擊

$
0
0

在網路上看到Twitter帳號被盜(或者該說被搶刧)的經歷兩則,共通點是受害者本身並無明顯資安過失(例如: 密碼過於簡單、多帳號共用密碼、被植入木馬後門或誤入釣魚網站等),攻擊者是經由社交工程對第三方廠商假冒身分取得敏感資訊或重設身分再進一步盜用帳號。自己沒有犯錯,卻因其他廠商失守而受連累聽來頗悲,讓我格外有興趣了解原委,整理摘要如下:

第一個案例來自Naoki Hiroshima,他擁有一個單字母Twitter帳號"N",曾有人喊價$50,000美金收購。這個珍貴的帳號時常引來覬覦,收到試圖重設密碼的Email猶如家常便飯,但在今年1/20帳號正式被勒索搶走。歹徒的手法是先假冒PayPal員工(acting as an employee)打電話從PayPal人員口中問到Naoki的信用卡號末四碼,接著再打給GoDaddy客服謊稱自己是Naoki想重設DNS管理密碼,信用卡掉了但記得最後四碼,由於GoDaddy需要提供六碼信用卡號驗證身份,結果客服超貼心,允許玩猜猜看補足缺少的兩碼,讓歹徒順利猜中6碼取得DNS管理權。由於Naoki註冊Twitter、Facebook... 等都是用自己網域名稱的Email信箱,DNS被刧,Email Server(MX)被改指向對方控制的主機,意味著對方已能在各網站重設密碼並接收郵件修改密碼。Naoki雖然及時改掉Twitter的註冊信箱,但對方經由控制DNS掌握住其餘帳號的密碼修改權,已具有摧毁Naokia網站的能力,藉此要脅Naoki讓出@N。Naoki打電話向GoDaddy報告此一緊急狀況,GoDaddy要求提供信用卡最後6碼驗證身分,但卡號及個資早被歹徒竄改,主人反而無法證明是本人,替代解決方案是遞交政府簽發文件證明身分,伹流程需要48小時。最後,Naoki屈服,讓出Twitter @N帳號(將@N改成@N_is_stolen),換回DNS及其他帳號的管理權,並保住經營網站的線上資料。

Naoki的心得: 1) 不要用自訂網域Email當作註冊信箱,一旦DNS被偷,身分也會連帶被偷走 2) 把DNS MX的TTL設久一點,如果當初設成一週而非1小時,DNS被偷走後就還有七天寬限期足以應變(當然,此舉會造成DNS更新延遲生效的副作用) 3) 盡可能使用兩階段式登入(密碼+簡訊),雖然在本案例中不見得管用。

第二個案例來自HACKTICOOL,他在Twitter及Instagram用的@jb帳號更值錢(號稱值$500,000,主要是沾Justin Bieber小賈斯汀的光)。歹徒的手法是先由DNS註冊資訊取得郵寄地址,憑著這一丁點資料向Amazon線上購物客服佯稱遺失密碼且無法用原信箱收發信,結果客服信了,在電話裡就依要求人工重設密碼。接著歹徒登入Amazon取得信用卡號後四碼、住家地址等資訊,再打電話向Apple客服要求重設密碼。幸運的是,HACKITCOOL及時以電話通知Amazon及Apple客服鎖住帳號,並將帳號註記為不允許透過電話變更,阻止進一步攻擊才保住Twitter帳號。

以上兩個案例都是走社交工程攻擊路線,PayPal員工被人假冒員工騙走客戶信用卡末四碼、GoDaddy客服允許只記得四碼信用卡號的客戶玩猜猜看試出六碼、Amazon客服只憑著郵寄地址個資就誤信歹徒,透過電話幫忙重設密碼。這裡面沒看到精巧的駭客程式或技巧,純粹瞄準人性弱點及不夠嚴謹的SOP加以突破,就算個人防毒防駭做得再精實,資安防線因為其他人(豬隊友?)的漏洞照樣全盤皆輸。說實在的,除了提高警覺遇異常訊號要儘速反應(一刻都不得鬆懈,好累!),我也想不到一勞永逸的解決方法。懷念以前Internet初起,民風純樸到SMTP不必防垃圾信、FTP密碼用明碼傳也無妨的年代~


【茶包射手日記】jQuery.css()傳入數字或字串值的差異

$
0
0

KendoUI由2013.2.716升級到2013.3.1119後,出現kendoWindow指定位置無效的狀況。問題程式碼為$("#w").kendoWindow({ position: { top: 6, left: 6 } }),跟官方文件範例相同,原本運作良好,元件升級後失效。推測與元件版本有關,先使用JS偵錯工具找出kendoWindow的position相關邏輯,再比較新舊版kendo.web.js,發現以下差異:

KendoUI 2013.3.1119新加入的position.left = position.left.toString()的寫法看來很可疑,傳入的數字在新版時會先被轉成字串,才當成jQuery .css()的參數值,莫非.css("top", …) 傳入數字或字串的結果? 做個實驗吧!

<!DOCTYPEhtml>
<html>
<head>
<metacharset=utf-8/>
<title>CSS value type test</title>
</head>
<body>
<divid='x'style='position: absolute'></div>
<scriptsrc="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>
var $x = $("#x");
    $x.css("top", "10"); //set string type value
    alert($x.css("top"));
    $x.css("top",  10); //set number type value
    alert($x.css("top"));
</script>
</body>
</html>

果不其然,以上測試會先跳出"auto",再跳出"10px",意味著傳入字串"10"無效,傳入數字10才有作用。換句話說,當傳入數字,jQuery會加上"px",才被視為有效的top值,在jQuery函數中可以找到以下邏輯證實這點:

// If a number was passed in, add 'px' to the (except for certain CSS properties)
if ( type === "number"&& !jQuery.cssNumber[ origName ] ) {
    value += "px";
}

至此可推論是新版KendoUI加入的toString()闖禍。既知原因就好辦,kendoWindow宣告時以top: "6px"取代top: 6就能解決問題。無法理解加入.toString()的理由,但此一改版會導致API文件的範例失效,故可認定為Bug,就一併回報給原廠囉!

【笨問題】在Visual Studio解決方案直接執行特定專案

$
0
0

大家有沒有遇過這種情境? Visual Studio解決方案(Solution, .sln)中有多個專案,其中有一個以上可直接執行(如Windows Form、Console Application、Web)。我最常遇到的情境是 -- 以Web為主的解決方案裡有轉檔用Console Application程式(要在App_Data建立資料檔)或是依Schema自動建立ViewModel的程式產生器。Web專案才是主角,故解決方案的起始專案(Startup Project)會設成Web專案,方便按下F5就能測試及偵錯。但如果需要重新建立資料檔或更新ViewModel類別,便得改執行工具程式專案。過去我都是笨笨地先將工具程式專案設定成起始專案,按F5或Ctrl+F5執行,跑完再起始專案改回Web專案。直到有一天,無意間看到同事露一手,這才發現在專案的右鍵選單裡有個Debug項目,其中的Start new instance(開始新執行個體)可以直接執行該專案! 想到以前把起始專案切過來切過去,只能說自己又笨了好久... (背景音樂: 我是條大笨牛哦 大笨牛哦哦哦哦~~)

PS: 我發現每次看別人操作Windows或IDE,總會學到一些新技巧。如果不是怕被當成偷窺狂扭送法辦,好想搬個小椅子輪流坐在每個同事背後一下午偷學招式。 XD

人懶又沒耐性如我,覺得嫌"選取專案 -> 按右鍵 -> 移到Debug -> 移到Start new instance –> 點擊執行"的過程還是太繁瑣,決定更上一層樓: 在Visual Studio鍵盤選項輸入startnew,可以查到Start new instance對應的命令,Use new shortcut下拉選單先選取Solution Explorer,指定Ctrl+K, Ctrl+E為快速鍵,再按下"Assign"。如此,步驟可以簡化成"選取專案 –> Ctrl+K, Ctrl+E"搞定, 懶得很帥氣吧? 哈!

GPS心跳錶不專業評比~

$
0
0

老GPS錶(GH-625M)於田中馬一役,苦撐至終點錶帶斷裂,為了感念它的壯烈事蹟,買支新錶也是很合理滴! (喂) 其實,625M換完錶帶後又是一條好漢,但考慮黑暗女王也開始慢跑,再買支GPS錶每個人都有專屬Perfromance Counter,便於進行精準效能調校以求最佳化。(理由一大堆,但"欲敗之物,何患無辭?",讀者們都是明眼人,這段就當沒說吧!)

入手前爬文蒐集網友討論,我設立的條件: GPS夠準、可測心跳、游泳等級防水、電池續航力至少能撐一場全馬、C/P值佳且低於萬元(最好像GH-625M一樣物超所值),同時使用者夠多在網路有口碑,至於造形酷炫及輕薄度則在其次。經過初審嚴選,入圍的只有XT馬拉松錶、Bryton Cardio 40H、60H(對Garmin神往已久,但其價位顯然在另一檔次,感覺以我的實力難以高攀,決定待Sub 4挑戰成功後再考慮它!)。

經評審團(由我跟謎之聲組成,陣容慘不忍睹)剖析:

  • XT馬拉松錶
    它的型號是GH-625XT,為GH-625M的下一代。我對GH-625M的GPS精準確非常滿意,XT承襲了它的棈準跟"雄壯威武"(很多人受不了它的體積與外型,但這是GPS精準的代價),採用新一代晶片、改為軟式心跳帶(比625M的硬式心跳帶舒適準確)、防水30米,電池號稱達18小時。客服口碑不錯,與馬拉松世界網站的無縫整合也是優勢。
  • Cardio 40H
    與XT相比,Cardio 40非常輕薄,造型較為好看,甚至可以當手錶戴。軟式心跳帶走ANT+通訊協定(據說較不易受干擾)、內建步頻計算功能、防水30米,韌體更新頻率頗高(入手沒多久就有更新),但電池續航力稍差只有8小時(跟625M的10小時相近)。Bryton有自己的網站,運動數據分析比馬拉松世界多樣化,但社群互動極少,不過只要數據可轉存成GPX/TCX,上傳到馬拉松世界不是難事。
  • Cardio 60H
    規格與40H幾乎相同,瞄準鐵人玩家,多了自行車固定架及踏頻感應器,電池續航力則長達16小時。由於短時間內不會朝鐵人三項發展,60H對我的意義只有電池續航強,個人連續跑個6, 7小時都快跑到奈何橋了,16小時應該會直攻地府刀山主峰,不構成關鍵因素。

思索後,XT與40H價位功能相近,而XT是625M的改版與舊機相去,決定給Cardio 40H一個機會,試試不同品牌、不同走向的產品增廣見聞。另一方面,步頻功能讓我很感興趣,練跑到一個階段,藉提高步頻增速幾乎是必要途徑,加大步伐對肢體撞擊性高,容易受傷;高步頻雖然對心肺負荷較重,但有利動作經濟性(花較少的力氣移動相同距離),專業跑者步頻幾乎都在180步/分鐘以上。40H去年11月就入手,算算也用了3個月,整理心得如下:

  1. 步頻功能
    練步頻蠻有趣的,換新錶後才知在沒刻意衝步頻之下,若跑Pace 540,我的步頻會落在80-85之間。刻意拉高到90,速度也會上升逼近5分速,但是 非-常-喘!! (註: 40H算的是單腳步頻,前述的180步/分換算後為90步/分)
    入手後沒多久就有韌體更新,意外又多了"節拍器"功能,例如: 設定步頻90,按下開始計時,就會有每分鐘90次的"嗶! 嗶! 嗶! 嗶!..."聲提醒你跟上腳步,粉刺激! (令我回想起當兵跑步的背景聲 "衣噁 衣噁 衣噁 衣痾" XD )
  2. 心跳偵測
    不確定是軟式心跳帶的功勞還是ANT+真有神效,40H測到的心跳值硬是比625M的硬式心跳帶精準許多。以前有時會出現心跳忽然降至90一陣子再跳回1x0的明顯落差,偶爾數字會破200甚至飆上220讓我懷疑自己根本是美國隊長而不自知。40H ANT+心跳帶量測的值倒是很符合預期,8分速12x、7分速13x、6分速15x、530跳16x、5分速就直逼177極限,確認我不是美國隊長是年過40的中年大叔無誤。
  3. GPS精準度
    40H很輕薄精巧,事前在網路上看過別人分享的軌跡圖還不差。如果這是第一支GPS錶,我應該會滿足,但體驗過625M,"曾經滄海難為水",40H的GPS軌跡偏差比預期大,但,仍在可接受範圍。
    身為茶包射手,準不準當然不能只靠感覺,要進行科學實驗才具說服力。GPS收訊受天候、地形地物影響,不同時空取得的結果直接相比會有疑義,於是我當了一回個瘋子,左手戴625M、右手戴40H跑了一趟河濱實測,40H的線圖(右圖)有幾次飄到河裡去,625M(左圖)明顯精確許多,但40H表現也不差。(看過手機App的河濱道道軌跡,還飄到對岸過馬路哩) 
         

    若搬到附近有高樓的政大400米跑道,40H大輸: (左邊是625M的完美弧形、右邊是40H,三不五時就會切西瓜)
         



    說句良心話,要求二支錶具備相同GPS精確度並不公平,畢竟625M號稱"大頭錶","雄偉"又"樸實"的外觀讓許多人打死不戴(請參考上圖),但以上評測還625M一個公道: 如果不想在體積重量讓步,只能犧牲GPS精準度。625M胖得有道理,腫得有價值呀!
    不確定是否因省電考量,40H的GPS取樣為4秒一次且不能調整,低於625M的每秒一次。依我個人的外行看法,提高取樣頻率輔以演算法校正,40H的GPS精準度或許能再提升。
  4. 計時操作
    40H的起跑、計圈、暫停後繼續 都集中於正面主鈕,按壓的回饋感稍嫌模糊,而停止需要連按兩次停止鈕小小不便。我個人偏好625M的操作,按一次開始,按一次暫停,計圈及結束歸檔則由另一顆按鈕負責,清楚分明。40H的計時概念區分跑步時間(偵測你真正在"跑步"的時間)跟總時間,好不好用見仁見智;625M則忠實扮演碼表功能,何時開始何時結束由跑者全權決定。
  5. 訓練課程
    40H有所謂的Bryton訓練及Bryton測驗,可以下載設計好的訓練課程(例如: 先輕鬆跑十分鐘、用最高強度跑五分鐘,再用有氧區期跑十分鐘)按表操課,對於想有計劃訓練的人來說很有用。我還沒進化到訂菜單、有計劃訓練的境界,暫時用不上,將來或許能派上用場。關於這部分,運動筆記裡有篇文章值得參考。
  6. 匯入軟體
    40H插上USB後會變成Windows的外接儲存裝置,在其中可以找到資料檔案,但屬自訂二進位格式,需安裝BrytonBrige軟體才能解讀匯出,並有兩種選擇: 存檔至本機(支援BDX、TCX、GPX等格式)或直接上傳到BrytonSport網站。模擬成儲存裝置的點子挺好,比透過RS-232傳輸簡便,可惜非公開格式,否則自己寫程式讀取應能搞出不少有趣應用。
    625M與XT除了將檔案上傳至網站,還有官方軟體Training Gym Pro的選擇,能將資料儲存在本機管理及檢視,方便備份或進行其他整合應用。這點大概只吸引程式魔人,大部分的使用者應該覺得資料直接上雲端更方便。
  7. 網站功能
    比較馬拉松世界BrytonSport,二者性質差距頗大。馬拉松世界強調社群化,改版快,迅速成為跑友匯集,寫跑步日記、分享心得及賽事記錄的便利平台,人氣甚旺。BrytonSport雖然針對每次跑步結果提供較詳細多樣的分析,但跑友間的互動功能甚少,改版速度較慢,較傾向個人記錄用途。
    順道提醒,無意發現BrytonSport的FB帳號整合機制有資安漏洞,曾向站方反應過但尚未修正,用FB帳號登入BrytonSport的朋友宜留意資料隱私問題。[此點是本文唯一敢稱專業的意見 :P]

綜合評論,40H在外型上大勝625M(甚至XT),輕巧美觀,心率偵測較精準(純屬個人經驗,朋友使用XT就沒有心率不準問題,當然,美國隊長體質也不是人人都有滴),且內建步頻功能(及節拍器),但GPS精準度明顯輸給625M或XT(尤其在附近有高樓的跑道時飄移嚴重),電池續航力則小輸625M和大輸XT(要60H才能與XT抗衡)。XT與40H的售價相近,若跑者注重美觀大方,跑量較低,少在運動場繞圈,對測速精確度要求較低,需要步頻功能及訓練計劃輔助,則40H勝出;若跑者在意GPS及測速準確性,跑量高,不需要時尚帥氣的裝備也能散發自信(誤),則XT會是好選擇。

VS2013 Browser Link導致網頁onbeforeunload在Firefox失效

$
0
0

在開發機測試專案網頁,Firefox測試不正常,IE/Chrome則沒問題。同事使用相同版本Firefox於其本機測試,並無異常,讓我感到萬分沮喪,甚至開始懷疑人生...

歷經一番追蹤比對找到凶手,是VS2013 Browser Link惹的禍! 將問題重現案例簡化如下:

@{
    Layout = null;
}
 
<!DOCTYPEhtml>
 
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>Index</title>
</head>
<body>
<div>
        Last Leave Time = <spanid="sLvTime"></span>
</div>
<scriptsrc="~/Scripts/jquery-1.8.2.min.js"></script>
<script>
        $(function () {
            window.onbeforeunload = function () {
                localStorage.lvtime = "TIME:" + new Date().getTime();
            }
            $("#sLvTime").text(localStorage.lvtime);
        });
</script>
</body>
</html>

在window.onbeforeunload事件中將目前時間存入localStorage,每次載入網頁時顯示上次所儲存的時間字串,反覆重新整理網頁應可看到數字不斷改變。啟用VS2013 Browser Link後,網頁在Firefox下數字不變,在IE/Chrome下則正常;關閉Browser Link後,IE/Chrome/Firefox盡皆正常。

開啟FireBug,可看到啟動BrowserLink時,網頁加掛的BrowserLink Script(下圖黃底處)會置換window.onbeforeunload事件內容。

同樣的狀況並不會發生於IE及Chrome:

外掛事件影響原有網頁功能,且僅發生於特定瀏覽器,研判為Bug。二話不說,回報到Connect~

【茶包射手日記】滑鼠游標錯亂(變梳子)

$
0
0

工作機滑鼠游標偶爾會錯亂,由箭頭圖示變成奇怪圖案,每次的怪圖示不盡相同,最常見是下圖左起第二個(網路上有人形容像把梳子):


圖片出處

這回遇到則是黑方塊+白色垂直虛線(車縫線?):

有趣的是,在我的三螢幕上,滑鼠只有移到某個螢幕才會錯亂,其餘兩個螢幕則正常,出問題的通常是遠端桌面(Terminal Service Client開全螢幕)專用螢幕。有時關閉遠端桌面問題便消失,有時則需要重開機解決。心血來潮爬文,在superuser找到一篇回答,才知這是ATI顯卡驅動程式的有名Bug(似乎也跟當下運行的程式有關聯),據說從2001就存在,至今仍未徹底消失。工作機剛好用的就是ATI HD5450顯卡,而問題螢幕接的也正是ATI顯卡,試著取消問題螢幕延伸桌面,問題游標跑會到ATI所接的另一枚螢幕 XD。問題螢幕移除後,Windows變成偵測不到該螢幕型號,無法加回延伸桌面,感覺驅動程式或顯卡已些微失控了。試著更新驅動程式到新版(但未重新開機),無法加入延伸桌面問題排除了,但游標錯亂依舊。

評估或測試討論區所提"不需重新開機的解決方法":

  • Unplugging mouse
    移除並重插滑鼠鍵盤組的USB接收器,無效
  • Open magnifier and zoom to 100%, leaving the magnifier open
    有效! 但必須一直開著放大鏡程式才能發揮效力
  • Activating an eyefinity profile (for those with multiple screens)
    ATI Eyefinity技術跟Windows的延伸桌面多螢幕概念不同,暫不考慮。
  • Changing resolution
    修改解析度 ,無效。
  • Put Windows into sleep mode
    工作機為Windows 2008R2,有啟用Hyper-V,無法進入睡眠模式
  • As soon as you see the stripes/comb pointer, move the mouse to the edge of your screen (top right was mentioned) and shake the mouse a few times
    一看到游標壞掉馬上移到右上角搖一搖? (呃,來不及了,下次遇到再試 XD)

測完一趟,終於甘心重新開機,一如預期,問題消失。由於無法主動重現問題反覆驗證,本案調查只能就此打住,暫時結論: 唯一不重開機解決滑鼠錯亂的方法只有啟用放大鏡(其他第三方游標工具應該也可適用)。

400米跑道配速對照表

$
0
0

最近在400米跑道練配速及間歇,開始在意每圈的配速精準度。消費型GPS裝置誤差大於10公尺,但測試數據時而偏東時而偏西,累計下來截長補短,倒也不致偏差里程太遠。依經驗,GH-625M一圈400米平均有10-15米誤差,訊號不佳時甚至多到50米;Bryton Cardio 40H的心率頗準,但GPS誤差較大(由軌跡來看,靠近綜合教學大樓時飄得特別凶,高樓建築遮蔽訊號應是主因),有時甚至一圈多出90米近25%,6分速當場變成5分速。

在操場繞圈,測速不必捨近求遠。跑道距離是已知的,用每圈所耗時間除以單圈距離就能換算精確配速(Pace, 分'秒"/KM),GPS錶沒內建這類功能,但只要默記圈時與Pace對照,一圈2'00"=5分速,2'12"=530,2'24"=6分速... 由每圈時間就掌握配速區間並非難事。但有個問題 -- 400米的8條跑道距離長短不一,內圈短外圈長,第1與第8跑道相差達52公尺:

標準400公尺跑道,跑道的彎道為半圓形,最內圈的半徑是36公尺,每條跑道寬1.2公尺,有8個跑道.所謂400公尺跑道,是指最內圈跑道的長度,最內圈的半徑為36公尺,故需先求出最內圈跑道的兩個半圓彎道的周長和,再用400公尺減去兩個彎道的周長和,就得直道的長度.400-(3.14362)=173.9,173.9/2=87,這可以算出:
第1道(872)+{ 23.14(36+ 1.20)} =400
第2道(872)+{ 23.14(36+ 1.21)} =407.6
第3道(872)+{ 23.14(36+ 1.22)} =415
第4道(872)+{ 23.14(36+ 1.23)} =422.6
第5道(872)+{ 23.14(36+ 1.24)} =430
第6道(872)+{ 23.14(36+ 1.25)} =437.7
第7道(872)+{ 23.14(36+ 1.26)} =445
第8道(872)+{ 23.14(36+ 1.27)} =452.8
參考資料來源: YAHOO知識+

若不想傷腦筋,永遠跑第一跑道最省事。但在第1跑道常需超車(很多人都愛跑第一 :P),加上內圈轉彎急較傷腳,不少老鳥偏好第8跑道,或第1到第8隨意跑以求均衡(有時還要切換順逆時針方向)。50公尺差異(12.5%)足讓Pace 630變6分速,如果配速想抓得精準也得斤斤計較。如果有張不同配速對各跑道計圈時間對照表,就能快速換算。不囉嚓,就動手做一張吧: (使用範例: 想在第8跑道跑Pace 530,查表可知每圈時間應為2分29秒)

慢跑不忘練功,這張配速表是用jQuery + Knockout.js做的,附上線上展示與程式碼,有興趣的朋友請自取參考或修改應用。(如果要改成200米或300米跑道對照,修改distances陣列內容即可,只是小圈跑道轉彎比例太高,繞多了會暈車吧? :P)

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>400米跑道配速對照表</title>
<style>
    thead td { background-color: #ff6a00; color: white; }
    td { 
      border: 1px solid #ccc; padding: 3px; 
      font-size: 10pt; font-family: courier new;
      text-align: center;
    }
    tr:hover { background-color: #ccc; }
</style>
</head>
<body>
<tablecellspacing=0cellpadding=0>
<thead>
<tr>
<tdstyle='width: 75px'>Pace</td>
<!-- ko foreach: trackNames -->
<tddata-bind = "text: $data"></td>
<!-- /ko -->
</tr>
</thead>
<tbodydata-bind="foreach: data">
<tr>
<tddata-bind="text: name"class='hdr'></td>
<!-- ko foreach: paces -->
<tddata-bind="text: $data"></td>
<!-- /ko -->
</tr>
</tbody>
</table>
<scriptsrc="http://ajax.aspnetcdn.com/ajax/knockout/knockout-3.0.0.js "></script>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script>
var distances = [400,407.6,415,422.6,430,437.7,445,452.8];
var res = [];
function d2(v) { return v < 10 ? "0" + v : v; }
function pace(s) {
var mm = parseInt(Math.floor(s / 60));
var ss = s - mm * 60;
return d2(mm) + ":" + d2(ss);
    }
//從3分速到8分速, 換算秒數為180到480
for (var secs = 180; secs <= 480; secs+=10) {
var paces = [];
      $.each(distances, function(i, v) {
        paces.push(pace(Math.round(secs * v / 1000)));
      });
      res.push({ name: pace(secs), paces: paces });
    }
var vm = {
      trackNames: $.map(distances, function(v,i) { return"跑道" + (i+1); }),
      data: res
    }
    ko.applyBindings(vm);
</script>
</body>
</html>

CODE-客製kendoGrid小計標題

$
0
0

專案需求: kendoGrid需依指定欄位分群加總並依預設格式顯示小計。

例如,資料有Color、Size、Count三欄,報表需呈現SUM(Count) GROUP BY Color的小計結果。

kendoGrid支援群組化(Grouping),參考API文件在DataSource定義group,並為Color及Count column定義groupFooterTemplate,便可完成分群統計並加入小計列(即上圖所示)。[Live Demo]

var items = [
      { Color: "Blue", Size: "L", Count: 1024 },
      { Color: "Blue", Size: "M", Count: 256 },
      { Color: "Red", Size: "M", Count: 16 },
      { Color: "Red", Size: "S", Count: 8 }
    ];
var ds = new kendo.data.DataSource({
      data: items,
      group: {
        field: "Color", aggregates: [
          { field: "Count", aggregate: "sum" }
        ]
      }
    });
  $("#grid").kendoGrid({
    columns: [
      { 
        field: "Color",
        groupFooterTemplate: "Subtotal"
      },
      { field: "Size" },
      { 
        field: "Count", 
        groupFooterTemplate: "#=kendo.format('{0:n0}', sum)#"
      }
    ],
    dataSource: ds
  });

下一步挑戰來了: 規格書要求在Subtotal字樣需加註該筆小計所屬顏色,例如: Subtotal (Blue),並取消上方的群組收合功能。groupFooterTemplate除了指定範本字串,還可提供自訂函式function(data) { … },data參數物件將包含宣告於aggregates的群組項目,例如: data.Count.sum即為計數加總結果。本想如法炮製,在aggregates增加{ field: "Color", aggregate: "max" },以為能像SQL MAX(Color)一樣取得Blue/Red,Kendo UI只支援average, count, max, min, sum五種彙總運算,不支援自訂彙總函式,而且一律當數字處理,發現data.Color.max的結果為0而非顏色字串內容。

爬文後,找到Telerik RD在討論區示範的替代解法: 定義Color的groupHeaderTemplate函式,可由data.field可得欄位名稱"Color"、data.value則可取得顏色字串"Blue"或"Red",將結果保存於全域變數,稍後於groupFooterTemplate便可將顏色字串放入小計標題,程式如下: [Live Demo]

  $("#grid").kendoGrid({
    columns: [
      { 
        field: "Color",
        groupHeaderTemplate: function(data) {
//data.field = "Color", data.value = color_value
          groupColor = data.value; //store in global variable 
return data.value;
        },
        groupFooterTemplate: "#= 'Subtotal (' + groupColor + ')'#"
      },
      { field: "Size" },
      { 
        field: "Count", 
        groupFooterTemplate: "#=kendo.format('{0:n0}', sum)#"
      }
    ],
    dataSource: ds
  });

搞定小計列,隱藏群組收合功能相對簡單,透過幾行CSS就搞定囉~[Live Demo]

<style>
    .k-grouping-row { display: none; }
    .k-grid .k-group-col { display: none; }
    th.k-group-cell, td.k-group-cell { display: none; }
</style>


【水電工日記】省水閥漏水記

$
0
0

家裡的省水閥用了近五年,最近開始漏水。原本想是超出使用年限,已有買新品替換的心理準備,上網比價查資料發現: 依官網的說法,其耐用度超過20萬次,理論上十年都不會壞,加上看到有網友遇到故障連絡客服可以換新。上網查詢客服連絡資料,發現客服FAQ網頁有這條:

Q13 省水閥有漏水的狀況,要如何處理?
...
若您是使用一段時間後發現有漏水的狀況,可能管路裡有雜質水垢造成堵塞,請您定期依照下列步驟清洗省水閥:

  1. 先將省水閥從水龍頭上拆下,分別取出墊片,拆下濾網 (45%省水閥:藍色 / 84%省水閥:綠色 / 可調式起泡器:橘色),並可使用少許牙膏或洗碗精等清潔劑幫助清潔。
  2. 濾網可用尖刺物(例如針或大頭釘)挑起,注意:勿遺失濾網。
  3. 使用牙刷清潔內部及濾網,之後再將濾網蓋上後壓緊即可。注意:濾網應正確裝妥,確認整個邊緣都有裝進去。
  4. 請勿任意再拆解其餘部份,以免造成內部零件損壞,導致無法提供售後服務。

省水閥五年都沒清過(當年Blog貼文的說明書照片還有提到要定期清除哩,但誰鳥它呀! RTFM魔咒再次上演),推測是阻塞造成漏水的可能性極高。

變身黑暗水電工的時候到了,衝進電話亭換上連身工作服,用扳手卸下省水閥,先移除橡膠墊圈,找來別針小心取下瀘網,有種探索真相的興奮感:

省水閥中間是一個灰色桶狀塑膠結構,能整個取出。灰色結構下方是平時用來開關水的控制桿,連動上方塑膠止水栓控制進水,進水面有一層土黃色陳年水垢,刷也刷不掉。猜想漏水是髒東西影響水栓密合所致,故清洗內部才是重點,無法繼續拆解(FAQ也警告再拆會破壞保固),只能設法透過空隙用水沖洗內部,我還動用沖牙機高壓水柱沖了兩輪才甘心。

清洗完畢重新安裝,開關水測試一下,不漏了耶!

大家準備好跟我一起慶祝了嗎? 黑暗水電工再次漂亮出擊,完美達成任務!!

滴! 滴! 滴! 滴! 一陣滴水聲讓我跌坐在地,瓶摔杯碎灑了一地香檳,前來獻花的正妹"呸"了一聲轉頭而去。這... 不能接受這個事實,又反覆拆裝沖洗兩次,仍無法中止漏水,黯然吞下苦澀茶包。

所幸,網頁提到若清洗後仍漏水不止,請洽客服處理。撥了080專線,客服小姐問過狀況記下電話,沒多久工程師來電,簡單詢問款式、購買時間、漏水狀況後,請我們將省水閥寄到試驗中心,他們會檢查,如果真有損壞將負責更換零件修復寄回。三天後,省水閥就回家囉! 看起來還是原零件,但漏水問題已徹底解決。

熬不過好奇心驅使,決定拆開研究一番。觀察進水孔的膠塑栓換了新品(上面沒有土黃色水垢),原本舊水栓會低陷落入洞中,更換後的水栓則不會,由此推測漏水應是止水栓下方結構損壞無法密合所致。原廠工程師進一步拆解更換零件才修復問題,不算我的責任失分,本場無關勝負。

解開了心中謎團,水龍頭也不再漏水,黑暗水電教室我們下次再見~ (揮手下降)

忘了,要為3M貼心的客服FAQ與售後服務按個"讚"!

2014渣打公益馬拉松~

$
0
0

我的第13場馬拉松,成績不突出,也沒落馬被回收,卻是跑馬生涯的轉捩點!

幾週前,四十多年的老爺車進廠保養,修車技師醫生給了一些忠實建議,讓我警覺: 自己過去常以奮發向上自豪,常憑一股不服輸的蠻勁做事,就連跑步也是。跑到後來,往往全靠一股真氣,管他體力心跳呼吸,拼就對了。這種拼勁展現在年輕人身上是可貴特質,搬到老爺車上只會險象環生,旁人也要捏把冷汗;先不談什麼養生,根本上就不健康。深思之後,我在這場比賽決定洗心革面,照舊全程猛看手錶,但不再是盯著Pace不要往下掉,而是注意心跳不要往上飆,心跳設定上限,能跑多快就跑多快唄。六分速也好,八分速也罷,Sub5很好,落馬也沒差。不同的心境,同樣是42公里,硬是有全新的體驗。

渣打公益馬拉松是我第一場起跑與終點不同的賽事。清早到了會場,兩排大型貨車在凱道一字排開,事先已公告號碼布對應的車號,如果有留意找起來蠻快的,但現場才想到查車號的人就手忙腳亂了。尤其是主持Dennis一直恐嚇,車子5:20就要開走引擎已經發動囉! 沒寄物的人動作要快,不然得揹衣包袋跑完42K,搞得跑友一陣驚慌,一堆人在衣保區衝來衝去。我腦中浮現新兵報到那天,值星班長高聲宣布: 所有人背包就定位,三分鐘後著甲種服裝載小帽帶板凳連集合場集合完畢,話說完還有2分55秒...

   
(另外想起一事,主辦單位昨天有個外行IT決定: 臨時通知大家上網下載最新版賽事公告[為何不直接寫靜態網頁? 可能是想省工吧!];而要下載的PDF檔案也"不大","才"幾十MB。在眾跑友合力DDoS之下,伺服器當場暈過去。我直接放棄回頭開通知信確定寄物車號,有網友抱怨花一個小時還沒下載完。)

天氣很好,氣溫15-20度左右,多雲時晴,是很好的跑馬天。心境已轉,PB不PB已與我無關,今天帶著新GPS錶-Bryton Cardio 40H,也第一次掛ANT+心跳帶跑全馬,全程盯緊心跳,超過上限就降速,降回一定幅度就再加速。另外,喝水也謹慎些,抓住22原則(一位體重約60公斤的人,建議每20分鐘補充200c.c.左右的水,依當時天氣、體能情況調整。),差不多是每次過水站喝一杯(最多兩杯),但前幾倍人太多又不怎麼渴就跳過(後來有點後悔,應該還是要喝的)。相較之前一口渴牛飲狂喝,搞得一肚子水,愈跑愈慢愈痛苦,控制補水量策略是有效的。賽後檢討,補水量應該可以再調高一點(畢竟我的流汗量較常人高很多),而且跑完應該馬上補一瓶,今天算是喝得不足,賽後心跳降得很慢,可能與缺水有關。

心中無Pace,不急著趕路,一路胡亂拍:

   
剛出發,太陽還沒露臉。台北市沒有辦城市馬的條件,跑仁愛路接基隆路後上麥帥橋,接著就被趕到高架+河濱道。渣打馬安排起點跟終點不同,應該是為了市區交管可以更早撤掉吧!


幾千人一起在高架橋上奔跑,停下來拍照才發現橋還晃得真凶,幻想會不會橋被震斷,大家全掉進河裡? XD

 
還沒跑完前半馬,第一名的黑人選手已經折回衝最後5K了。話說,部分的賽道過窄,選手領先群折返後被迫與後方大軍團對衝,常被逼到跑路緣...


本場最大成就: 碾過一名黑人選手(上圖紅頭巾者) [謎之聲: 人家在輕鬆跑,你是在自嗨啥鬼?]


一舉超越美國隊長! 我果然有美國隊長體質(誤)


可愛的Cosplay造型,春麗?


太陽出來了! 雲多不太曬,氣溫不高且風大涼爽,感謝老天爺的好天氣。

盯住不要讓心跳過高,探索到不一樣的跑馬境界。隨著距離與時間的增力,可以明顯感受體力與身體機能下滑,同樣的心率一開始可以跑六分速,後來只有七分速,到最後階段,連跑七分半、八分速也逼近上限,推測與乳酸堆積以及水分吸收追不上流失速度有關(身體缺水時心率會偏高)。雖然放棄了Pace,跑到後期,卻如倒吃甘蔗,開始享受不過度壓迫身體的好處。

之前要求自己前半馬要抓2小時,代價是把身心操過頭,每到最後15K往往疲累不堪,跑不動就當步兵,到最後咬牙還不一定守得住5小時。

善待自己,身體也會對你微笑,這場是我有史以來痛苦指數最低的比賽吧! 第一次沒有臉孔猙獰就完成比賽,不重視Pace速度卻比想像快,最後10K,發現我已追上5小時配速員,而且氣力有餘能輕鬆超車。以前總會追到咬牙切齒氣喘吁吁,不在意成績時,成績卻不差,氣定神閒,這才是適合我這種中年大叔的健康跑馬態度吧!

 

 


最後,說好不再逞凶鬥狠,卻還是忍不住,硬是要搶在5小時配速員前幾步通過終點(謎: 很幼稚!)
成績4:53:08,大約排在全部跟分組選手的前60%,及格囉! 重點是輕鬆多了。


領完東西後,得要走去"遠得要命"嘉年華會場旁的水門搭接駁車(看到好多人一拐一拐的拖著腳去搭車),11:30左右還在頒42K全馬的前幾名獎項,但許多攤子都收了,旁邊有大型的氣墊遊戲區,我到的時間已經打烊準備消氣。


我搭車的時間是11點半,人不算多,由於分圓山及總統府兩個方向,排隊隊伍後方有人舉牌指示隊伍的目的,很聰明的設計。以常在大排長龍的場合,常常要問隊伍末端的人"這條是在排什麼?" "其實我也不知道" XD (後來看網路討論,8.5K離場時許多人等車等到地老天荒,搞得一肚子火坐計程車離開)


本回戰利品,大毛巾、帽子、水、雞精、運動飲料、仙草蜜、橘子、能量棒、背袋、Pizza兌換券(代替餐盒,我喜歡)。


設計有點Boring的獎牌...

CSS筆記-依視窗寬度自動隱藏

$
0
0

專案需求,需實現以下效果:

資訊列有三個元素,藍色部分為主要顯示內容,綠色區塊(Block 1)及紅色區塊(Block 2)靠緊右側為次要資訊。當視窗寬度不足,綠色及紅色區塊需自動隱藏,避免遮蔽藍色區塊的文字。

JavaScript熟手會優先想到攔截window.onresize事件,再依視窗寬度決定Block1或Block2是否隱藏,其實CSS也有武器應付這類情境,不用寫半行程式,靠瀏覽器內建功能就搞定。

過去介紹過的CSS @media,其支援max-width、min-width條件參數,讓某段CSS宣告只在特定視窗寬度生效。此一彈性讓設計師得以針對不同螢幕解析度加上額外設定,讓同一網頁的排版依視窗尺寸自動調整,維持可讀性,而這也是RWD(Responsive Web Design)很常用的技巧。(延伸閱讀: CSS Media Queries 介紹與基礎應用, Media Query 小撇步)

@media用在這個案例只是牛刀小試。將Block 1, Block 2先設為display:none,利用@media (max-width: …px)加入兩個區塊,限定視窗寬度大於等於465px時Block 2 display: block,大於等於560px時Block 1 display: block。如此,寬度大於等於560會顯示Block 1 & 2,介於560到465則只顯示Block 2,小於465時兩個Block都會被隱藏。

程式範例如下: [線上展示]

<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>利用CSS在寬度不足時自動隱藏右側元素</title>
<style>
    body { font-size: 9pt; }
    .main {
      position: absolute; top: 0px; left: 0px; right: 0px;
      height: 20px; background-color: blue; color: white; padding: 6px;
    }
    .block { 
      position: absolute; border: 1px solid gray; top: 0px; 
      height: 18px; color: yellow; padding: 6px;
      text-align: center;
    }
    .b1 { 
      display: none;
      background-color: green;
      right: 150px;
      width: 100px;
    }
    .b2 { 
      display: none;
      background-color: brown;
      right: 0px;
      width: 150px;
    }
    @media only screen and (min-width:465px) {
      .b2 { display: block; }
    }
    @media only screen and (min-width:560px) {
      .b1 { display: block; }
    }
</style>
</head>
<body>
<divclass='main'>
我達達的馬蹄,是美麗的錯誤,我不是歸人,是個過客 </div>
<divclass='b1 block'>Block 1</div>
<divclass='b2 block'>Block 2</div>
</body>
</html>

Big5-HKSCS編碼初探(上)

$
0
0

身為中文編碼解析工具的開發者,一直以為自己"略懂"中文編碼,這兩天再度見證"學海無涯",默默收回"略懂"二字...

從以下畫面說起:

五種瀏覽器(IE, Opera, Chrome, Firefox, Safari)一次排開檢視同一個網頁,IE10再度成為瀏覽器家族的黑羊,只有它無法顯示"滙"及"衞"兩個中文字。

開啟notepad++看看hkscs-test.htm有何奧妙之處?

值得注意的地方有三處:

1) charset=Big5-HKSCS 
2) 檔案為ANSI編碼 
3) notepad++跟IE一樣,無法解析ANSI編碼的"滙"跟"衞"

回頭檢視瀏覽器網頁編碼狀態,很快找出IE無法正確顯示的原因。IE將網頁識別為BIG5編碼,其餘瀏覽器則正確地依網頁宣告採用Big5-HKSCS。

由此推論,IE問題出在未正確採用Big5-HKSCS解析網頁。若將網頁宣告改為chartset=big5,則其他瀏覽器也會出現異常,且出現的亂碼不同。

關鍵在於Big5-HKSCS,過去摸過Big5、GB2312、UTF-8、UCS,就是沒跟它交手過。查了資料後才正式認識它,Big5-HKSCS的正式名稱是香港增補字符集(Hong Kong Supplementary Character Set,簡稱HKSCS)是香港政府基於繁體中文電腦操作環境中最流行的大五碼(Big-5)之上擴展的字符集標準,是現時香港的中文資訊交換內碼標準。

用中文編碼解析工具檢查,可查出"滙"的Big5-HKSCS編碼是FA C0,"衞"則是8F C0。

找到一個日本網站提供Big5與Big5-HKSCS完整對照表,驗證這兩個字碼就是Big5-HKSCS中的"滙"及"衛"無誤,而其在Big5無對應字元。

開啟Windows 7 VM做測試,若將System Locale設成Chinese (Traditional, Hong Kong, S.A.R.),notepad++及Notepad便能正確顯示HKSCS字集的額外文字,但IE11仍不行。網站上的討論傾向IE只認得標準Big5,不支援Big5-HKSCS(但我沒能找到官方文件證實),如真要看charset=Big5-HKSCS的網頁,請改用其他瀏覽器。

心得: 開啟Big5-HKSCS網頁請使用非IE瀏覽器,如果你是網頁開發人員,請回歸Unicode,使用UTF-8編碼才是王道!

Big5-HKSCS編碼初探(下)

$
0
0

昨天介紹了Big5-HKSCS,初步心得是: Big5-HKSCS跟Big5一樣是歷史的眼淚,新一代Unicode標準已能涵蓋其字元範圍又能同時兼容各國語系。因此,拋棄ANSI規格,回歸Unicode才是王道!!

但這衍生一個需求 -- 若既存文字檔或其他老系統仍採有Big5-HKSCS編碼內容,是否能用.NET程式轉換為Unicode呢?

拿這種問題來問傳說中偉大的中文編碼解碼工具程式作者,是一種羞辱吧? 只見那個其貌不揚的中年男子,右手收在腰後,不屑地伸出左手在鍵盤上飛快地敲了一段程式:

File.ReadAllText("d:\\hkscs-test.htm", Encoding.GetEncoding(951));

接著用鄙夷的口吻說道: "Encoding.GetEncoding(950)解析Big5的寫法都示範過幾百遍,Big5-HKSCS的CodePage是951,照著用Encdoing.GetEncoding(951)不就好了? 這也要問? 笨!!"

實測程式。哐噹! 不知哪裡飛來一塊鐵板,砸在剛才還盛氣凌人的中年男子頭上...

.NET掌了一個NotSupportedException: {"No data is available for encoding 951."} !!

查了MSDN討論區文章,發現一件殘酷的事實 -- Vista之後,Windows已經不再提供HKSCS編碼語言包了!

Starting with Windows Vista, HKSCS-2004 characters are only be supported as Unicode 4.1 or later. All characters are assigned standard, non-PUA codepoints. The characters are displayed with the MingLiU font, and these characters can be entered via the keyboard. The patch that provides Big5 encoding of HKSCS is unsupported in Windows Vista and later. A utility provided by Microsoft is available to convert HKSCS and Unicode PUA-encoded characters to Unicode 4.1 version. 951版本中的Big5編碼字在Vista之後的版本是不兼容的。在Windows XP之前的版本,你可以安裝語言包支持它。

所幸,文中提到微軟有個轉換工具能將HKSCS轉成Unicode。找到一個程式庫 -- Microsoft Character Code Conversion Routines For HKSCS-2004,它是個Unmanaged Library(hkscs04.dll),只有C++範例,而我只會寫C#,得透過DllImport外部函式庫的方式呼叫它。

可能因為涉及字串內容語系轉換,我發現先前在其他PInvoke函式用StringBuilder接收結果字串的技巧不管用,只好胡亂試些繞道做法。最後,C/C++麻瓜花了點時間試出用IntPtr當成結果字串參數、用Marshal.AllocHGlobal()取得記憶體空間、再用Marshal.Copy()將內容搬至byte[]的做法,總算順利取回轉換結果。最後,再把邏輯包成string Convert(string) .NET方法方便使用。

using System;
using System.Runtime.InteropServices;
using System.Text;
 
class HkscsHelper
{
constuint HKSCS_ERR_INVALID_CHARS = 0x00000001;
 
    [DllImport("hkscs04.dll", CharSet = CharSet.Ansi, SetLastError = true)]
publicstaticexternint HKSCS_Big5ToUnicode41(
uint dwFlags, string lpBig5Str, int cbBig5,
        IntPtr lpUnicode41Str, int cchUnicode41);
 
publicstaticstring Convert(string srcString)
    {
int srcLen = Encoding.GetEncoding(950).GetByteCount(srcString);
int len = HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS, 
                  srcString, srcLen, IntPtr.Zero, 0);
        IntPtr ipDst = Marshal.AllocHGlobal(len);
try
        {
int resLen = 2 * HKSCS_Big5ToUnicode41(HKSCS_ERR_INVALID_CHARS, 
                             srcString, srcLen, ipDst, len);
byte[] dst = newbyte[resLen];
            Marshal.Copy(ipDst, dst, 0, resLen);
return Encoding.Unicode.GetString(dst);
        }
finally
        {
            Marshal.FreeHGlobal(ipDst);
        }
    }
}

用前篇文章的"滙豐銀行 警衞室"來驗證看看,成功!!

聲明: 我整合Unmanaged Library的經驗有限,不確定文中採用的做法是否夠嚴謹有效率,尚請高手前輩們不吝指正。

Viewing all 2311 articles
Browse latest View live