February 2009 - 文章

jQuery-1.3.2-vsdoc.js上線了

VS2008支援jQuery Intellisense,方法是透過引用jquery-*-vsdoc.js,在編輯期間載入一個提供額外加註說明的js版本。

不過在1.3推出時,jquery-1.3-vsdoc.js並未同步釋出,但有強者自己DIY做出vsdoc可以暫代。之後1.3.1, 1.3.2推出時,因為都只側重Bug Fixing及效能強化,API規格未變,我就直接將先前DIY版jquery-1.3-vsdoc.js更名為1.3.1-vsdoc及1.3.2-vsdoc,一樣用得很愉快。

前幾天Visual Web Developer Team宣告了1.3*-vsdoc的官方版終於出爐了! 現在大家可以直接由jQuery官網的下載網頁直接下載囉。

 
(圖片來源: Visual Web Developer Team Blog)

關於PDF JBIG2漏洞零時差攻擊

Adobe Reader及Acrobat最近傳出有漏洞(Adobe也已證實),同時已經有人利用此漏洞製作出黑心PDF檔,算是標準的零時差攻擊(漏洞發布的同時,病毒/木馬程式跟著一起上市)。

使用Acrobat或Adobe Reader開啟黑心PDF檔時,其中包藏的惡意程式會利用類似溢位攻擊的方式取得控制權,進而執行夾帶的程式碼下載木馬及幹些見不得人的勾當。

先前的認知是這波攻擊要透過Javascript才能生效,因此停用Javascript應具備阻擋攻擊的效果。很不幸地,既然是溢位攻擊,能搞的名堂可多了,幾乎可以為所欲為,已經有人研發出就算PDF停用Javascript也能發動攻擊的版本,大家可別覺得停用PDF Javascript功能就可以高枕無憂。

依Adobe的說法,Acrobat及Acrobat Reader 9要3/11才會釋出修正版,7,8版更要等到3/18,在此之前,大家最好避免開啟來路不明的PDF檔案。再不然考慮用非Adobe的PDF Reader,例如: FoxItXChange

【延伸閱讀】

x64: 找不到MSDAORA Provider

在Windows 2008 x64上使用Query Express,選擇Mcirosoft Oracle driver時,出現以下錯誤訊息:

Unable to connect: The 'MSDAORA' provider is not registered on the local machine.

前往Google大廟參拜,得到的結論懷疑因程式預設以x64模式執行,試圖讀取x64版本OLE DB Driver時失敗(沒安裝或該Driver根本沒出x64版)。網上建議的解決方法多半是重Build Project,將Target由Any CPU改為x86。

雖然手上在用的QueryExpress是自己修改過的版本,要重Build不是難事。但我不禁在在想,如果手上只有exe沒有Source Code? 就只能攤手嗎?

再做了一下功課,發現有個工具corflags可以透過修改執行檔檔頭(CLR Header)的方式達到類似的效果。開啟Visual Studio 2008 Command Prompt(裡面有額外設定PATH,才能找到.NET SDK的工具檔),先用corflags QueryExpress.exe看設定,再用corflags /32BIT+ QueryExpress.exe,標註強迫在WoW64下以x86方式執行。

C:\QueryExpress>corflags QueryExpress.exe
Microsoft (R) .NET Framework CorFlags Conversion Tool.  Version  3.5.21022.8
Copyright (c) Microsoft Corporation.  All rights reserved.

Version   : v2.0.50727
CLR Header: 2.5
PE        : PE32
CorFlags  : 1
ILONLY    : 1
32BIT     : 0
Signed    : 0

C:\QueryExpress>corflags /32BIT+ QueryExpress.exe
Microsoft (R) .NET Framework CorFlags Conversion Tool.  Version  3.5.21022.8
Copyright (c) Microsoft Corporation.  All rights reserved.

改過設定後再跑一次,這回就可以順利執行了。

CODE-用jQuery翻修HTML內容

處理一個案子,同事給了我一個HTML的樣版(其他ASPX產生的),我要借它的排版用在其他地方,數字及文字欄位的部分要轉成[$FieldName$]的格式,去掉不必要的id以及style設定,範例如下:

<!-- 原始網頁 -->
<tr id="OrderUserControl1_tr_ORDER_PRZ" style="DISPLAY:block;">
    <td class="title">委託價格</td>
    <td><span id="OrderUserControl1_lab_ORDER_PRZ">123.45</span></td>
</tr>
<tr id="OrderUserControl1_tr_TRADE_CRNCY" style="DISPLAY:block;">
    <td class="title">交易幣別</td>
    <td><span id="OrderUserControl1_lab_TRADE_CRNCY">USD</span></td>
</tr>
<tr id="OrderUserControl1_tr_CLEAR_CRNCY" style="DISPLAY:block;">
    <td class="title">交割幣別</td>
    <td><span id="OrderUserControl1_lab_CLEAR_CRNCY">USD</span></td>
</tr>
 
<!-- 想要翻成以下的樣版 -->
<TR>
<TD class=title>委託價格</TD>
<TD>[$ORDER_PRZ$]</TD></TR>
<TR>
<TD class=title>交易幣別</TD>
<TD>[$TRADE_CRNCY$]</TD></TR>
<TR>
<TD class=title>交割幣別</TD>
<TD>[$CLEAR_CRNCY$]</TD></TR>
<TR>

欄位筆數不超過五十筆,手工改不會花太久時間,但心想HTML操弄是jQuery最擅長的領域,決定讓它露兩手瞧瞧!

$(function() {
    $("span[id^=OrderUserControl1_lab_]").each(function() { 
        var spn = $(this);
        if (spn.is(":parent")) 
            spn.text("[$" + spn.attr("id")
            .replace("OrderUserControl1_lab_", "") + "$]");
        spn.replaceWith(spn.text()); 
    });
    $("*[id]").removeAttr("id");
    $("*").css("display", "");
    var xwin = window.open("about:blank"); 
    xwin.document.write($("table:first").html());
});

從另外新開的視窗View Source就可以取回HTML Code了。程式中用了is(), [id^=...] Selector, replaceWith(), 10行搞定。

jQuery 1.3.2

jQuery 1.3.2已於2/20釋出,跟1.3.1差不多,以修Bug為主,但也有一些改變,整理以下:

  • $("h1,h2,h3")的寫法,原本得到的群組會先列出所有的h1, 再來是所有的h2,再來是h3;1.3.2起則會依照元素在DOM中的出現順序排列。這個修改是為了符合W3C的Selector API規範
  • .live()可以支援event.stopPropagation()或return false,防止事件浮到上層元素。
  • :visible/:hidden改變做法,過去以display:none或visibility:hidden判定,1.3.2+改用offsetWidth/offsetHeight是否為0判別,一方面速度變快N倍,另一方面,被藏在hidden中的元素也能被判為hidden。
  • height(), width(), innerHeight(), innerWidth(), outerHeight(), outerWidth()調整過,速度快了N倍。
  • Sizzle selector engine在IE6下的表現加快22%。
  • appentTo(), pretendTo(), insertBefore(), insertAfter(), replaceAll()改為傳回所有加入後的元素,而非原本的元素。如果若元素被複製多份加在多處,之後串接API的處理對象會包含全部新加入的分身。

另外,在Bug Tracker的1.3.2修復清單中,也可以查到我先前通報的#3254 Defective cloned object也在本版中修復了。這樣我就算是參與了jQuery的發展史,哇哈哈哈~~~ (自High中...)

測試DTC設定-使用Mini C# Lab

每次安裝Web測試環境的一項重要檢查工作,是要確認Web與SQL間的DTC連線暢通,在經驗裡這是件眉眉角角很多的任務,光從我過去針對它寫過的KB、文章數量就可見一斑。

測試DTC通不通的方法,除了直接執行引用DTC的程式之外,還可以用MS的DTCPing工具。不過,這次介紹如何使用我寫的小工具Mini C# Lab來測試DTC。

測試原理請大家參考".NET 分散式交易程式開發FAQ(PDF)"一文,用Mini C# Lab跑以下的小程式,在TransactionScope中對SQL建立兩條連線會觸發使用DTC,因此若沒設好,第二個查詢會傳回錯誤。

using System;
using System.Data;
using System.Data.SqlClient;
using System.Transactions;
//REFDLL System.Data;System.Xml;System.Transactions
 
public class CSharpLab
{
    private static void querySqlServer() 
    {
        string cnStr =  "Data Source=theSql;User Id=theUser;Password=thePasswd;";
        using (SqlConnection cn = new SqlConnection(cnStr))
        {
            SqlCommand cmd = new SqlCommand("SELECT getdate() as D", cn);
            cn.Open();
            SqlDataReader dr = cmd.ExecuteReader();
            dr.Read();
            Console.WriteLine(dr["D"]);
            cn.Close();
        }
    }
 
    public static void Test()
    {
        using (TransactionScope tx=new TransactionScope()) 
        {   
            querySqlServer();
            querySqlServer();
            tx.Complete();
        }
    }
}

以本次測試為例,因為"Network DTC Access"沒啟動,得到以下錯誤:

意外發現,錯誤訊息還挺明確的,本機Network DTC Access沒開時長這樣:

ERROR: Runtime error!
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool

如果是遠端那台沒開啟長這樣:

ERROR: Runtime error!
The partner transaction manager has disabled its support for remote/network transactions. (Exception from HRESULT: 0x8004D025)

常被雞同鴨講聲東擊西指鹿為馬不知所云的錯誤訊息氣到吐血,看到這麼精確的訊息,就甘心A。

可凍結式GridView強化版: 隱藏、跳列、尋找

自從前幾天幫同事Survey了可凍結列、欄的GridView的解決方案後,反應出奇熱烈... 大家熱烈地提出許多希望能"更方便使用"的"好點子"。啊! 腦海中浮現經典的場景: User看完Prototype後,興奮地提出更多的規格需求,一片歡樂中,唯有一人悶悶不樂、背脊發涼、冷汗直流 --- 負責Coding的那個可憐蟲...

看人口渴,貼心奉上一杯茶,誰知,張三問能不能少糖去冰多加檸檬,李四則嚷著想改成加蛋不加槓丸。

不過,愈是複雜的Scenario,就愈能突顯jQuery的威力,害不服輸的我又熱血起來,所以...

有興趣的人可以看一下線上展示,打勾並按【隱藏選取列】可以把部分資料列藏起來、也可以輸入列號後移至指定的表格列,最後還實作了尋找會自動捲動到所在位置並反白顯示關鍵字的功能(一直按尋找會自動找下一筆吻合者)。

多虧jQuery,程式寫起來比想像中簡短,而且用到的API還挺多的: after, attr, remoteAttr, addClass, css, evt.target, toggle, find, end, $.browser.ie, offset, position, val, scrollTop, scrollLeft, text, html, is, filter, each, click...

    <script type="text/javascript">
        $(function() {
            //在表格裡塞入選取欄位
            $("#GridView1 th:nth-child(1)")
            .after("<th><input type='checkbox' id='cbxSelAll' /></th>");
            $("#GridView1 td:nth-child(1)")
            .after("<td><input type='checkbox' class='clsHideCbx' /></td>");
            //配合SuperTable的多Table同步處理,加上列號
            $("#GridView1 tr").each(function(i) {
                $(this).attr("rowidx", i)
                .find("td").each(function(j) {
                    $(this).attr("pos", i + "_" + j);
                });
            });
            //設定SuperTable
            $("#GridView1").toSuperTable({ width: "640px", height: "480px", fixedCols: 3 })
            .find("tr:even").addClass("altRow");
            //加上全選/全不選功能(.sFHeader為配合SuperTable才加)
            $(".sFHeader #cbxSelAll")
            .click(function() {
                if (this.checked)
                    $(".clsHideCbx").attr("checked", "checked");
                else
                    $(".clsHideCbx").removeAttr("checked");
            });
            //加上隱藏/顯示功能
            $("#btnHide,#btnShowAll").click(toggleRow);
            //隱藏/顯示共用一個事件,由evt.target判別按的是哪一顆
            function toggleRow(evt) {
                var show = (evt.target.id == "btnShowAll");
                var rowSet = (show) ?
                    $(".sFData tr:not(:visible)") : $(".sFData .clsHideCbx:checked").closest("tr");
                rowSet.toggle(show)
                //一般情況到hide()即可,以下這段是為了SuperTable而加的
                //把捲動區裡那一份<table>對應的資料列也同步藏起來
                .each(function() {
                    $(".sData tbody tr[rowidx=" + $(this).attr("rowidx") + "]").toggle(show);
                });
                $(".sData tbody").find(".altRow").removeClass("altRow")
                .end().find("tr:visible:even").addClass("altRow");
                //修正IE隱藏結尾會不齊的問題
                if ($.browser.msie) {
                    var fixedColZone = $(".sFDataInner");
                    var cellZone = $(".sData table");
                    var p1 = fixedColZone.offset().top;
                    var p2 = cellZone.offset().top;
                    if (p1 != p2) {
                        fixedColZone.css("top", (p2 - fixedColZone.parent().offset().top) + "px");
                    }
                }
            }
 
            function getPosVal(s) {
                return parseInt(s.replace("px", ""));
            }
 
            $("#btnScroll").click(function() {
                scrollToRow($("#rowIdx").val());
            });
 
            //捲動到指定列數
            function scrollToRow(rowIdx) {
                //IE下有一列的位移,鋸箭法校正
                if ($.browser.msie && !isNaN(rowIdx))
                    rowIdx = parseInt(rowIdx) - 1;
                var x = $(".sData tr[rowidx=" + rowIdx + "]");
                if (x.length > 0) {
                    //alert($(".sData").scrollTop() + "," + x.position().top);
                    $(".sData").scrollTop(
                        $(".sData").scrollTop() +
                        x.position().top);
                }
            }
            //捲動到指定的欄數
            function scrollToCol(colIdx) {
                var x = $(".sData td:eq(" + colIdx + ")");
                $(".sData").scrollLeft($(".sData").scrollLeft() + x.position().left);
            }
 
            //保留所有Cell的集合
            var allCells = $(".sData #GridView1 td");
 
            $("#btnFind").click(function() {
                var keywd = $("#keywd").val();
                //先找到上次的焦點
                var focusIdx = 0;
                var hasPrevFocus = false;
                allCells.filter("td[findfocus]").each(function() {
                    focusIdx = allCells.index(this);
                    //將focus移除
                    $(this).removeAttr("findfocus")
                    //去除Highlight
                    .html($(this).text());
                    hasPrevFocus = true;
                });
                //由焦點開始往後找
                allCells.filter("td:gt(" + focusIdx + ")")
                .each(function() {
                    var td = $(this);
                    if (td.text().indexOf(keywd) > -1) {
                        var p = td.attr("pos").split("_");
                        scrollToRow(p[0]);
                        var cell = allCells.filter("td[pos=" + td.attr("pos") + "]");
                        cell.attr("findfocus", "true")
                        .html(cell.text().replace(keywd, 
                            "<span style='background-color:yellow;'>" + keywd + "</span>"));
                        scrollToCol(p[1]);
                        return false;
                    }
                });
                if (!allCells.is("td[findfocus]")) {
                    if (hasPrevFocus) {
                        if (confirm("已搜尋至結尾,要從頭開始再找一次嗎?"))
                            $("#btnFind").click();
                    } else
                        alert("找不到指定的關鍵字!");
                }
            });
 
        });
    </script>
    <style type="text/css">
    .clsLinkButton  
    { font-size: 9pt; cursor: pointer; text-decoration: underline; color: Blue; }
    #rowIdx { width: 20px; text-align: right; }
    #spnCmdBar { font-size: 9pt; margin-left: 15px; }
    </style>
</head>
<body>
<span id="spnCmdBar">
關鍵字: <input type="text" id="keywd" style="width: 80px;" /> 
<input type="button" id="btnFind" value="尋找" />&nbsp;
移至第<input type="text" id="rowIdx" value="1" />
<input type="button" value="捲動吧! 表格" id="btnScroll" /></span>
<span id="btnHide" class="clsLinkButton" style="margin-left: 150px;">隱藏選取列</span>
<span id="btnShowAll" class="clsLinkButton">顯示全部</span>

這只是個jQuery Coding Demo,不是產品或解決方案的發表會,大家要冷靜,關於功能面的提議不要太熱烈吶~~~ Coding的部分倒歡迎大家討論。

【茶包射手專欄】ODP.NET烏龍事件

還原了一個VM,要新裝測試網站,執行用到ODP.NET的ASPX時遇到此一錯誤:

[NullReferenceException: Object reference not set to an instance of an object.]
   Oracle.DataAccess.Client.OracleException.get_Message() +33
   System.Web.UnhandledErrorFormatter.get_ColoredSquare2Content() +495
   System.Web.UnhandledErrorFormatter.PrepareFormatter() +45
   System.Web.ErrorFormatter.GetHtmlErrorMessage(Boolean dontShowSensitiveInfo) +56
   System.Web.HttpResponse.WriteErrorMessage(Exception e, Boolean dontShowSensitiveErrors) +590
   System.Web.HttpResponse.ReportRuntimeError(Exception e, Boolean canThrow, Boolean localExecute) +225
   System.Web.HttpRuntime.FinishRequest(HttpWorkerRequest wr, HttpContext context, Exception e) +383

Google的結果多指向ODP.NET沒安裝,但檢視Oracle Home bin目錄下已有Oracle.DataAccess.dll,ODP.NET 9207安裝程式也顯示已安裝。不放心,重裝一次ODP.NET 9207,錯誤依舊。

最後同事終於找到問題,原因挺有趣的(雖然追查的過程很痛苦)。

原來是裝機過程依循SOP,執行了一些預先預先寫好的Script,其中有一個reg檔用來設定NLS_LANG等設定,但壞在它也指定了ORACLE_HOME,標準主機Oracle Client都會裝在D:\,而這台測試VM則只有C:\,因此ora92是裝在C:,造成了路徑錯指。

修正後再IISRESET,問題排除。

GridView的標題欄、列凍結效果(跨瀏覽器版)

稍早發表了GridView的標題列凍結效果,足以滿足工作上的需求,不過存在兩個缺點: 只支援FF及IE6/7、只能凍結列不能凍結欄(行)...

不甘心事情只做一半,又挖了一下,驚喜地發現另一個版本: Super Tables,可以支援Firefox 2+, Internet Explorer 5.5+, Safari 3+, Opera 9+ 以及Chrome,而且也支援直欄的凍結效果,在功能上大勝ScrollableTable,二話不說,通通包起來。

SuperTable的原理與ScrollableTable不同,它需要額外的CSS以及在Table外包一層<div>,可視範圍大小由<div> Style決定,設定時參數也較多(如:fixedCols, headerRows...),所以我寫了一個jQuery Plugin(jquery.superTable.js)把它包起來。有了Plugin的加持,只要一個toSuperTable(options)就可立即升級成有凍結效果的GridView了。

/////////////////////////////////////////////////////////////////////////////////////////
// Super Tables Plugin for jQuery - MIT Style License
// Copyright (c) 2009 Jeffrey Lee --- blog.darkthread.net
//
// A wrapper for Matt Murphy's Super Tables http://www.matts411.com/post/super_tables/
//
// Contributors:
//
/////////////////////////////////////////////////////////////////////////////////////////
////// TO CALL: 
// $("...").toSuperTable(options)
//
////// OPTIONS: (order does not matter )
// cssSkin : string ( eg. "sDefault", "sSky", "sOrange", "sDark" )
// headerRows : integer ( default is 1 )
// fixedCols : integer ( default is 0 )
// colWidths : integer array ( use -1 for auto sizing )
// onStart : function ( any this.variableNameHere variables you create here can be used later ( eg. onFinish function ) )
// onFinish : function ( all this.variableNameHere variables created in this script can be used in this function )
// margin, padding, width, height, overflow...: Styles for "fakeContainer"
//
////// Example:
// $("#GridView1").toSuperTable(
//              { width: "640px", height: "480px", fixedCols: 2,
//                onFinish: function() { alert('Done!'); } })
 
(function($) {
    $.fn.extend(
            {
                toSuperTable: function(options) {
                    var setting = $.extend(
                    {
                        width: "640px", height: "320px",
                        margin: "10px", padding: "0px",
                        overflow: "hidden", colWidths: undefined,
                        fixedCols: 0, headerRows: 1,
                        onStart: function() { },
                        onFinish: function() { },
                        cssSkin: "sSky"
                    }, options);
                    return this.each(function() {
                        var q = $(this);
                        var id = q.attr("id");
                        q.removeAttr("style").wrap("<div id='" + id + "_box'></div>");
 
                        var nonCssProps = ["fixedCols", "headerRows", "onStart", "onFinish", "cssSkin", "colWidths"];
                        var container = $("#" + id + "_box");
 
                        for (var p in setting) {
                            if ($.inArray(p, nonCssProps) == -1) {
                                container.css(p, setting[p]);
                                delete setting[p];
                            }
                        }
                        
                        var mySt = new superTable(id, setting);
 
                    });
                }
            });
})(jQuery);

完整的Demo程式如下:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        DataTable t = new DataTable();
        t.Columns.Add("序號", typeof(int));
        t.Columns.Add("料號", typeof(string));
        t.Columns.Add("單價", typeof(decimal));
        for (int i = 1; i <= 10; i++)
            t.Columns.Add("庫存" + i, typeof(int));
        Random rnd = new Random();
        for (int i = 0; i < 80; i++)
        {
            DataRow row = t.NewRow();
            row["序號"] = i + 1;
            row["料號"] = Guid.NewGuid().ToString().Substring(0, 13).ToUpper();
            row["單價"] = rnd.NextDouble() * 100;
            for (int j = 1; j <= 10; j++)
                row["庫存" + j] = rnd.Next(10000);
            t.Rows.Add(row);            
        }
        GridView1.AutoGenerateColumns = false;
        foreach (DataColumn c in t.Columns)
        {
            BoundField bf = new BoundField();
            bf.DataField = c.ColumnName;
            bf.HeaderText = c.ColumnName;
            if (c.DataType == typeof(decimal))
                bf.DataFormatString = "{0:#,0.00}";
            else if (c.DataType == typeof(int))
                bf.DataFormatString = "{0:#,0}";
            bf.ItemStyle.HorizontalAlign =
                (!string.IsNullOrEmpty(bf.DataFormatString)) ?
                HorizontalAlign.Right : HorizontalAlign.Center;
                
            GridView1.Columns.Add(bf);
        }
        GridView1.DataSource = t;
        GridView1.DataBind();
    }
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
    .altRow { background-color: #ddddff; }
    </style>
    <link href="superTables.css" rel="stylesheet" type="text/css" />
    <script type="text/javascript" src="jquery-1.3.1.js"></script>
    <script type="text/javascript" src="superTables.js"></script>
    <script type="text/javascript" src="jquery.superTable.js"></script>
    <script type="text/javascript">
        $(function() {
            $("#GridView1").toSuperTable({ width: "640px", height: "480px", fixedCols: 2 })
            .find("tr:even").addClass("altRow");
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:GridView ID="GridView1" runat="server" Font-Size="9pt" EnableViewState="false">
    </asp:GridView>
    </form>
</body>
</html>

我放了一個線上Demo在http://www.darkthread.net/MiniAjaxLab/ScrollTable ,或者你也可以下載程式包回去玩。

Keywords: fixed column, fixed header, freeze, scroll, excel, datagrid, gridview, html table, plugin, jQuery

GridView的標題列凍結效果

應該有不少人被這麼要求過吧?

"你們做的網頁表格為什麼不能像Excel一樣凍結標題列,底下捲來捲去也不會跑掉?"

前陣子看到一個不錯的Javascript Library,只用一行Code就可以輕鬆做出HTML表格列凍結的效果,剛好部門裡也常有這種需求,我就弄了一個簡單的範例。

大家可以先看Scrollable HTML Table Plugin範例,它本來是個純Javascript Library,Plugin只是做了簡單的包裝,把new ScrollableTable()一列指令再包起來,意義不大,所以我的示範裡只使用webtoolkit.scrollabletable.js,省去額外載入webtoolkit.jscrollable.js的功夫。

弄了一個虛構的DataTable,Bind到GridView上,稍作處理再var t = new ScrollableTable($("#GridView1")[0], 400, 640);,就變成以下的樣子:

完整程式碼如下:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    protected void Page_Load(object sender, EventArgs e)
    {
        DataTable t = new DataTable();
        t.Columns.Add("序號", typeof(int));
        t.Columns.Add("料號", typeof(string));
        t.Columns.Add("單價", typeof(decimal));
        t.Columns.Add("數量", typeof(int));
        t.Columns.Add("金額", typeof(decimal), "單價*數量");
        Random rnd = new Random();
        for (int i = 0; i < 200; i++)
        {
            t.Rows.Add(
                i + 1, 
                Guid.NewGuid().ToString().Substring(0, 13).ToUpper(),
                rnd.NextDouble() * 100,
                rnd.Next() * 2000);
        }
        GridView1.AutoGenerateColumns = false;
        foreach (DataColumn c in t.Columns)
        {
            BoundField bf = new BoundField();
            bf.DataField = c.ColumnName;
            bf.HeaderText = c.ColumnName;
            if (c.DataType == typeof(decimal))
                bf.DataFormatString = "{0:#,0.00}";
            else if (c.DataType == typeof(int))
                bf.DataFormatString = "{0:#,0}";
            bf.ItemStyle.HorizontalAlign =
                (!string.IsNullOrEmpty(bf.DataFormatString)) ?
                HorizontalAlign.Right : HorizontalAlign.Center;
                
            GridView1.Columns.Add(bf);
        }
        GridView1.DataSource = t;
        GridView1.DataBind();
    }
</script>
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <style type="text/css">
    #GridView1 tr { border: solid 1px #f0f0f0; }
    #GridView1 td, th { padding: 3px; border: solid 1px white; }
    td, th { padding: 3px; border: solid 1px white; }
    #GridView1 thead tr { background-color: #dddddd; }
    .altRow { background-color: #ddddff; }
    </style>
    <script type="text/javascript" src="jquery-1.3.1.js"></script>
    <script type="text/javascript" src="webtoolkit.scrollabletable.js"></script>
    <script type="text/javascript">
        $(function() {
            $("#GridView1 tr:first").wrap("<thead></thead>");
            $("#GridView1 thead").insertBefore("#GridView1 tbody");
            $("#GridView1 tbody tr:odd").addClass("altRow");
            $("#GridView1").attr("cellspacing", "1");
            var t = new ScrollableTable($("#GridView1")[0], 400, 640);
        });
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:GridView ID="GridView1" runat="server" Font-Size="9pt">
    </asp:GridView>
    </form>
</body>
</html>

由於Scrollable HTML Table需要由<thead>, <tbody>區分凍結區及捲動區,而GridView所產生的HTML Table標題列及內容列都放在<tbody>中且無<thead>,因此要做些事後加工,將標題列提取出來,另外放入新增的<thead>中,程式示範了如何jQuery wrap, insertBefore來解決這個問題,都是酷酷地一步到位,蠻值得參考的。

注意: 此解決方案無法跨瀏覽器,目前測試只有IE6, IE7, FF3可用, 不支援IE8, Safari, Chrome, Opera。

【2009-02-18 更新】找到另一個跨瀏覽器且支援標題欄、列凍結的版本,請見: GridView的標題欄、列凍結效果(跨瀏覽器版)

【茶包射手專欄】Vista Mandatory Integrity Level

 

不經一事,不長一智。

從考完NT 4.0 MCSE後,我就鮮少在Windows上下苦功,都是抱持著用到哪學到哪的精神。

今天在Windows 2008(有開UAC)上調Trixie設定時,發現設定結果無法儲存,沒彈出錯誤,但下次開IE就又回到修改前的樣子。

我知道IE7啟用了所謂的Protected Mode,在存取本機資源時設下諸多限制,以防止透過瀏覽器發動的攻擊,這問題八成與權限有關。二話不說,召來茶包一哥進行偵察,果不其然,由Log來看就是權限設定問題。Trixie的設定要寫入Trixie.config.tmp時出現了Access Denied(下圖中紅色部分)。

一般來說,利用Run As Administrator方式跑IE就可以解決問題,但不知什麼原因,我Run As Admin後無法在IE開啟Trixie設定畫面,感覺上設定畫面出現的瞬間就被關掉。於是我試了另一個方式,暫時關掉Protected Mode後,就可以順利儲存設定。(下圖後段的Log即為順利存檔時的對照)

 

在我的腦海中,存取成功與否由權限設定控制,所以我修改了C:\Program Files (x86)\Bhelpuri\目錄的NTFS權限設定,改為EveryOne均可以讀寫。開啟Protected Mode再試一次,ProcMon檢視結果還是顯示Access Denied!!

這就奇了,NTFS權限都全開了,寫檔動作為何還被擋下來? 臨時Google惡補一下,才知道自己Lag大了... 在Vista中(Windows 2008亦然)又多了新的安全防議法寶--Mandatory Integrity Level,不但檔案上可以設NTFS權限控制,又多了第二層控管,資料物件跟Process各有Integrity Level設定,系統絕對禁止卑微低下的Process去接觸高尚尊貴等級的資源,就算該Process是用管理者身份執行的也不例外。

這好比你受邀參加朋友在某間固特異米其林四星餐廳(Bhelpuri\Trixie資料夾)的Party,明明賓客名單上就有你的名字(NTFS安全設定裡已指定權限),卻因為你穿了T恤+牛仔褲+藍白拖鞋(開啟Protected Mode的IE,Integrity Level被標為Low)而被擋在門外。

在此補充另一位MVP的解說,應該就更明白了。

因此,透過Vista中新增的MIC(Mandatory Integrity Level,強制完整性控制功能),為微軟作業系統增加一道新的安全防線。
他進一步解釋,在Vista作業系統裡,包含程序(Process)和其他資源的安全物件(Securable Object)都有畫分完整性級別(Integrity Level,IL)的等級,分成不信任(Untrusted)、低(Low)、中(Medium)、高(High)及系統(System)等5個級別,級別低的程式不能修改級別高的檔案或程式碼。「而一般的使用者登入多為中權限。」邱銘彰說。
摘自: iThome online : : Vista 夠安全嗎?

而Integrity Level的設定無法由Windows檔案總管等UI查得,SysInternals工具組裡有個AccessChk可以顯示,

C:\Program Files (x86)\Bhelpuri>accesschk -d trixie

Accesschk v4.23 - Reports effective permissions for securable objects
Copyright (C) 2006-2008 Mark Russinovich
Sysinternals - www.sysinternals.com

C:\Program Files (x86)\Bhelpuri\Trixie
  Medium Mandatory Level (Default) [No-Write-Up]
  RW Everyone
  RW BUILTIN\Users
  RW NT SERVICE\TrustedInstaller
  RW NT AUTHORITY\SYSTEM
  RW BUILTIN\Administrators

是的,目錄的IL是Default(Medium),Protected Mode下的IE的IL是Low,因而存取遭拒。問題的解法不外乎: 讓IE以非保護模式執行;或開放Trixie目錄允許IL=Low的程式存取,而我打算嘗試後者。

微軟似乎沒有釋出修改IL的工具(更正: 有內建工具icacls可修改IL,見文後說明),除了Call SDK硬幹,網路上大家多用Mark Minasi(Windows技術書作家)自己寫的chml工具。

C:\Program Files (x86)\Bhelpuri>chml trixie

chml v1.011 -- Change Windows Integrity Level
by Mark Minasi (c) 2006-2008 www.minasi.com
"chml -?" for syntax, examples and notes.

trixie's Windows integrity level=unlabeled (medium)

C:\Program Files (x86)\Bhelpuri>chml trixie -i:l

chml v1.011 -- Change Windows Integrity Level
by Mark Minasi (c) 2006-2008 www.minasi.com
"chml -?" for syntax, examples and notes.

Integrity level of trixie successfully set to low.

C:\Program Files (x86)\Bhelpuri>chml trixie

chml v1.011 -- Change Windows Integrity Level
by Mark Minasi (c) 2006-2008 www.minasi.com
"chml -?" for syntax, examples and notes.

trixie's Windows integrity level=low

修改Trixie目錄IL設為為Low,再試一次,Trixie設定在Protected Mode下也可以順利儲存囉!

[2008-02-17 Update] 其實Vista/Windows已有內建的工具可以修改Integrity Level: icacls http://msdn.microsoft.com/en-us/library/bb625965.aspx,謝謝Phoenix補充。

【延伸閱讀】

jQuery 1.3 小抄!

開始噗浪之後,結識了一些網友,偶爾會有一句沒一句的回應起來,也算是小小的社群互動。

今天算是參加噗浪後首次有具體收獲,kenlai大大回了一則消息,說網路上已經有jQuery 1.3小抄,而我的資料還停留在: 黑白版(http://www.gscottolson.com/weblog/2008/01/11/jquery-cheat-sheet/) 字體較小但資訊較詳細,彩色版(http://colorcharge.com/jquery/) 字較大並有彩色,較賞心悅目。

看了一下,Kris Thompson推出的jQuery 1.3小抄跟黑白版一樣詳細,又兼具彩色,還多了jQuery 1.3增加的新API,順理成章取代我先前用到己經斑駁破損的黑白版,成為我的鎮桌之寶。

雖然已經訂閱了每天看都看不完的Blog RSS,1/28釋出的小抄我卻遲至今天才知道,在這個資訊爆炸的時代,還是要靠大家互通有無,勤於通風報信,集眾人之力才能免於不知不覺Lag很大的窘境。不過,這麼搞下來,大家的資訊焦慮症病情恐怕又要加重囉~~~

Mini jQuery Lab Online

Mini jQuery Lab is a handy HTML page to test simple Javascript and jQuery.  It is useful when you want to verify uncertain Javascript syntax or test new jQuery APIs you just learned. 

Write CSS, document.body's HTML and script, then you can see the result by one click, it's faster and easier to get answer then using Visual Studio 2008 or other powerful but compicated IDE.

This test page is originally a demonstration tool in my jQuery tutorials on MSDN Taiwan. After posting the introduce of Mini jQuery Lab, I was surprised that serveral friends inquired about its download and license issue, as well as I had plan to enhance the features for the future tutorials, so I decide to turn it to be an online service to provide the latest version of Mini jQuery Lab to Internet, you can find it on http://www.darkthread.net/MiniJQueryLab.

The usage of Mini jQuery Lab is quite simple, just input CSS styles, body's HTML tags, and type Javascript code in three separated textareas, click "Execute" button, then you get the result (or the answer).  The jquery.1.3.1.js is included and the code you type will be included in a $(function() { ... }); block, so you can use $() directly.

The "View HTML Source" button on the top will the show the final full-page HTML source, you can use SyntaxHighlighter's powerful clipboard feature to save it as a standalone web page for latter test.

Elijah Manor has an excellent introduction about Mini jQuery Lab, too.

If you find any bug, have any suggestion or idea, please leave your comment here.

Revision Notes

Ver 1.2 on 2009-2-14

** Make view HTML source function workable on Firefox
** Add "About" link with ThickBox plugin for online version

60萬人次紀念

三更半夜了,還在為jQuery教學博命... 啵! 啵! 啵! 右上腹發出三聲悶響,咦? 這就是所謂的爆肝聲嗎?

睡前連一下網站,才發現Blog已偷偷突破60萬人次,依慣例還是要貼文留念一番。

【成長歷程】

祝大家情人節快樂~~~

【茶包射手專欄】又是Parameters.Add闖的禍

同事用ODP.NET跑一段SQL,得到驚人的結果:

select ....  from
    ( select  .... from TABLE_1 where someDate = :pDate and .... ) p 
join
    ( select .... from TABLE_2 where someDate = :pDate  and .... group by ... 
      union
      select ... from TABLE_3 where someDate = :pDate and ...  group by ...) c 
on p.col1 = c.col1 and p.col2 = c.col2 and p.col3 = c.col3
where p.col4 <> c.col4

中間只用了一個Parameter :pDate,看似並不複雜的查詢,居然耗時要一分鐘以上才會得到結果。暴怒之餘,從ODP.NET 9207改用System.Data.OracleClient,猜怎麼著? 不用一秒就搞定!

接獲ODP.NET又跑出來撤野的線報,我像鯊魚嗅到血腥味一樣又興奮了起來... (謎之聲: 你跟ODP.NET有不共戴天之仇哦?)

仔細看了程式,我發現原程式用了cmd.Parameters.Add("pDate", date)這種自動判別資料型別的寫法,之前我吃過一次自動型別判斷的虧,於是試著改成cmd.Parameters.Add("pDate", OracleDbType.Date).Value = date,沒想到馬上跟閃雷一樣,瞬間得到結果。

跟其他同事討論,有人貢獻傳入string再做TO_DATE的心得,我索性整理一下,來做做個各式方法的效能評比。

private void TestODPClient(string cnStr, string cmdText, DateTime date)
{
    ODP.OracleCommand cmd = new ODP.OracleCommand(cmdText);
    cmd.Parameters.Add("pDate", date);
    RunTest(@"Add DateTime wo OracleDbType", cmd, cnStr);
    
    cmd = new ODP.OracleCommand(cmdText);
    cmd.Parameters.Add("pDate", ODP.OracleDbType.Date).Value = date;
    RunTest(@"Add DateTime w/ OracleDbType", cmd, cnStr);
    
    cmd = new ODP.OracleCommand(cmdText);
    cmd.CommandText = cmd.CommandText.Replace(":pDate",
        "TO_DATE('" + date.ToString("yyyyMMdd") + "', 'YYYYMMDD')");
    RunTest("Ad-Hoc SQL Style", cmd, cnStr);
 
    cmd = new ODP.OracleCommand(cmdText);
    cmd.CommandText = cmd.CommandText.Replace(":pDate", 
        "TO_DATE(:pDateStr, 'YYYYMMDD')");
    cmd.Parameters.Add("pDateStr", date.ToString("yyyyMMdd"));
    RunTest(@"Add String wo OracleDbType", cmd, cnStr);
 
    cmd = new ODP.OracleCommand(cmdText);
    cmd.CommandText = cmd.CommandText.Replace(":pDate", 
        "TO_DATE(:pDateStr, 'YYYYMMDD')");
    cmd.Parameters.Add("pDateStr", ODP.OracleDbType.Varchar2).Value = 
        date.ToString("yyyyMMdd");
    RunTest(@"Add String w/ OracleDbType", cmd, cnStr);
 
}
 
private void RunTest(string testName, ODP.OracleCommand cmd, string cnStr)
{
    using (ODP.OracleConnection cn = new 
        Oracle.DataAccess.Client.OracleConnection(cnStr))
    {
        cmd.Connection = cn;
        DataTable dt = new DataTable();
        cn.Open();
        Stopwatch sw = new Stopwatch();
        sw.Start();
        ODP.OracleDataReader dr = cmd.ExecuteReader();
        dt.Load(dr);
        sw.Stop();
        cn.Close();
        Response.Write(
            string.Format("<li>Test {0} Rows={1} Duration={2:#,0}ms",
                testName, dt.Rows.Count, sw.ElapsedMilliseconds));
    }
 
}

第1次測試

  • Test Add DateTime wo OracleDbType Rows=33 Duration=81,124ms
  • Test Add DateTime w/ OracleDbType Rows=33 Duration=3,090ms
  • Test Ad-Hoc SQL Style Rows=33 Duration=42ms
  • Test Add String wo OracleDbType Rows=33 Duration=36ms
  • Test Add String w/ OracleDbType Rows=33 Duration=36ms

第3次測試

  • Test Add DateTime wo OracleDbType Rows=33 Duration=65,022ms
  • Test Add DateTime w/ OracleDbType Rows=33 Duration=41ms
  • Test Ad-Hoc SQL Style Rows=33 Duration=41ms
  • Test Add String wo OracleDbType Rows=33 Duration=33ms
  • Test Add String w/ OracleDbType Rows=33 Duration=34ms

    第3次測試

  • Test Add DateTime wo OracleDbType Rows=33 Duration=70,750ms
  • Test Add DateTime w/ OracleDbType Rows=33 Duration=43ms
  • Test Ad-Hoc SQL Style Rows=33 Duration=36ms
  • Test Add String wo OracleDbType Rows=33 Duration=36ms
  • Test Add String w/ OracleDbType Rows=33 Duration=33ms

    第4次測試

  • Test Add DateTime wo OracleDbType Rows=33 Duration=52,096ms
  • Test Add DateTime w/ OracleDbType Rows=33 Duration=38ms
  • Test Ad-Hoc SQL Style Rows=33 Duration=31ms
  • Test Add String wo OracleDbType Rows=33 Duration=34ms
  • Test Add String w/ OracleDbType Rows=33 Duration=36ms
  • 測試結果指出,只有Add("pDate", date)的時間異常,是其他方法的數千倍,一樣沒指定資料型別的Add("pDateStr", date.ToString("yyyyMMdd"))速度卻正常。

    由此推論,會出問題的只有Add("paramName", dateTypeValue)。不過,如果是我,我會乖乖加上OracleDbType參數。不不不,如果不是被刀架著脖子、被槍指著頭,我應該會用System.Data.OracleClient。

    更多文章 下一頁 »

    搜尋

    Go

    <February 2009>
    SunMonTueWedThuFriSat
    25262728293031
    1234567
    891011121314
    15161718192021
    22232425262728
    1234567
     
    RSS
    【工商服務】
    最新回應

    Tags 分類檢視
    關於作者

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

    文章典藏
    其他功能

    這個部落格


    BlogLook Score and Rank

    Syndication