2010年12月30日 星期四

UNION VS. UNION ALL 的效能

以前並沒有特別在意 UNION 與 UNION ALL 對於 T-SQL 效能的影響,直到最近 UNION 上萬筆資料後,才深深發現這小小的差異,竟還有很深的學問。

如果將 TableA UNION TableB,得到的結果,會類似將 TableA 與 TableB 的資料匯總之後再進行 Distinct 。所以 UNION 後的結果是不會重複的。

至於另一個 UNION ALL 呢? TableA UNION ALL TableB ,所得到的會是 TableA 與 TableB 的資料匯總,即便有重複資料,也都會被保留。

解釋過 UNION 與 UNION ALL 後,可以知道 UNION 比人家多了一個 Distinct 的動作,而這一額外動作就會對整個查詢效能造成影響,尤其是當你的資料量很大時,差異就會更明顯。

舉例來說,我建了2個暫存資料表,分別各放幾筆測試資料。

create table #tmp1 (t_Name varchar(30))
create table #tmp2 (t_Name varchar(30))
 
insert into #tmp1(t_Name) values ('paladin')
insert into #tmp1(t_Name) values ('hugo')
insert into #tmp1(t_Name) values ('ken')
insert into #tmp1(t_Name) values ('lillian')
insert into #tmp1(t_Name) values ('panda')
 
insert into #tmp2(t_Name) values ('polly')
insert into #tmp2(t_Name) values ('keen')
insert into #tmp2(t_Name) values ('hana')
insert into #tmp2(t_Name) values ('gemma')

select t_Name from #tmp1
union
select t_Name from #tmp2
 
 
select t_Name from #tmp1
union all
select t_Name from #tmp2

透過「顯示估計執行計畫」,可以得到下面的結果:


透過執行計畫可以瞭解, UNION 多了一個相異排序(Distinct Sort) 動作,這額外的動作,致使他的執行效能比 UNION ALL 慢了些。但是自己實際遇到的資料量,遠比這幾筆測試資料多太多了。於是修改了測試資料的筆數,並將測試數據列述如後。



declare @i_count int
set @i_count=0
 
while (@i_count<100000)  -- 分別用 1000,10000,100000
begin
 insert into #tmp1(t_Name) values ('paladin@@')
 insert into #tmp2(t_Name) values ('panda@@')
 set @i_count=@i_count+1
end


當測試資料達10萬筆之後,兩種語法的效能相差了 3 倍之多。

所以,如果您所要 UNION 的兩個資料表並不會有重複資料或是本身您並不在意有重複資料,改用 UNION ALL 會大大提昇整體 SQL 的查詢效率。

參考:

01.SQL SERVER – Union vs. Union All – Which is better for performance?

02.The effects UNION in a SQL query



你的網頁已經下載完畢了?

在撰寫 JavaScript 時,我們可能會透過 getElemnetById 或 getElemnetByName 來取得頁面上的物件,或者你想去呼叫某個已經事先定義好的函數。可是,如果網頁上的 DOM 尚未完全載入完畢時,你所執行的 JavaScript 語法,就很有可能會出現錯誤。我參考「Are you ready for this」這篇文章,作者提了一個問題,「你怎麼確定你的網頁已經下載完畢了?」

對於這個問題,首先要清楚網頁對於「下載完畢」的定義有分兩種:第一種:window.onready,是真的所有東西都下載完畢,包括:頁面所參考引用的所有 CSS、Script、Image、Flash。第二種:DOM-ready,則是網頁的DOM階層樹建置完畢,包括頁面上所引用的 CSS、Script。可想而知,第一種 window.onready 是所謂網頁真正完全下載完畢,但是目前的網頁越來越花俏,可能你的頁面有一個非常巨大的圖片連結,而要達到 window.onready 的地步,勢必要花不少時間。比較合適的作法,是第二種 DOM-ready,只需要確保我們執行的 JavaScript 所需的 DOM 階層架構都已經完整,就可以先執行了,不需要去等待圖片或是 flash 的下載。


在我們常見的一個 JavaScript 語法, window.onload=function(){ //Do something. };
他的效果比像上面所提到的第一種 window.onready,所以在我們使用它用的很開心的時候,應該好好想想,這種寫法會不會讓你的程式效能或是給使用者的感觀受影響。「jQuery in Action」一書的作者,在他書裡的第一章節,也提到為了避免讓使用者長時間的等待頁面圖片的下載,建議使用:

$(document).ready(function(){ // Do something. });

因為 jQuery.ready( )的效果,是我們介紹的第二種 DOM-ready 模式 ,可以讓使用者減少等待的時間。不過附帶一提的是, 作者認為使用 window.onload 的另一個缺點,就是他只能使用一次,如果被除重複呼叫或是你程式裡還有其他 third party 函式庫也使用了 window.onload ,就會造成被蓋來蓋去的問題。其實,這部份可以參考「JavaScript 小陷阱 (5) – window.onload()」這篇文章,透過一個小技巧,也是可以讓你重複呼叫多次的 window.onload 。

以下程式碼節錄自JavaScript 小陷阱 (5) – window.onload()

var oldOnload = window.onload || function () {};
window.onload = function ()
{
oldOnload();
// Do Something...
}

透過事先將舊的 onload 事件存放在一個變數,並於自行定義的 onload 事件中,找個適當位置觸發舊的事件。

在上一篇紀錄:「Internet Explorer 無法開啟網際網路網站」,除了使用者自己的瀏覽器問題之外,程式撰寫本身也是有可能會造成這問題。程式的部份,就是 DOM 有沒有完全下載完畢所導致。透過比較清楚的瞭解後,至少下次發生問題時,可以降低因為程式撰寫不良而讓系統出錯的機率。

參考:


01.Are you ready for this
http://www.hunlock.com/blogs/Are_you_ready_for_this
02.JavaScript 小陷阱 (5) – window.onload()
http://www.jaceju.net/blog/archives/160
03.Internet Explorer 無法開啟網際網路網站
http://paladinprogram.blogspot.com/2010/12/internet-explorer.html
04.The window.onload Problem
http://peter.michaux.ca/articles/the-window-onload-problem-still

2010年12月21日 星期二

Internet Explorer 無法開啟網際網路網站

最近有頁程式,壓下一個按鈕後,會執行一段 JavaScript 去作 window.open( ) 的動作。但不知為何,就有一位 user 反應他會出現「Internet Explorer 無法開啟網際網路網站」的錯誤訊息。


在 Google 找了好幾篇文章,建議把 IE 的 plug-in 套件移除或關閉,甚至蠻多人說是安裝了 skype 的 plug-in,但自己試了,還是沒用。最後在一篇文章「IE無法開啟網際網路網站,操作已中止」,有提到:如果出現這個畫面,代表在IE在DOM尚未載入完全的時候,就嘗試存取DOM而產生的訊息。所以,我猜想可能是我執行 window.open( ) 時,頁面上的 DOM 還沒完全載入所造成。既然如此,為了確保頁面上的 DOM 能完全載入,我想起透過 jQuery 的

$(document).ready(function(){  //Do Some ... });

透過這種寫法,可以確保所有 DOM 都載入後,才開始執行裡面的程式。

所以我將原先 window.open( ) 的動作,移到  $(document).ready(function(){  //Do Some ... }); 裡試試,果然就沒在出現原先的錯誤訊息了。

private void btnPrintSignNew_Click(object sender, System.EventArgs e)
{  
  string scriptString=string.Empty;
  scriptString = string.Format(@"
  <script language=JavaScript>
  <!-- begin 
  $(function(){{
  window.open('xxx');
  }});
  //end -->
  </script> 
"));

  this.RegisterStartupScript("MsgDownLoad",scriptString);
}


參考:
01:IE無法開啟網際網路網站,操作已中止

2010年12月16日 星期四

透過 Proxy 產生 Reporting Service 2008R2 報表

透過 proxy 呼叫 Reporting Service 所提供的 Web Service,可以提供我們更有彈性的報表管理。在 Reporting Service 2000 時,如果要產生 pdf 報表,使用 http://Repsdev/ReportServer/ReportService.asmx 的 Render( ) 方法來產生報表(Repsdev 是伺服器名稱)。隨著物換星移,當初的 Reporting Service 2000 現今也已換成 Reporting Service 2008R2 了,伴隨著微軟打著無痛升級、效率更好、功能更強的口號,系統也一一升級了,直到... user 打電話通知,報表不能 Run 之後,才發現事情並不是我想的這麼簡單。


版本升級對目前的設計所造成的衝擊,有以下幾點:

1.原先使用的 WebService http://Repsdev/ReportServer/ReportService.asmx ,現在已經不能用了,取而代之的,是可以選擇使用 http://Repsdev/ReportServer/ReportService2005.asmx 或 http://Repsdev/ReportServer/ReportService2010.asmx。確切來說,要知道你的主機上可以使用哪些 WebService,主要是看主機上安裝了哪些版本的 SQL Server。將各個版本的 Reporting Service 列出來比較,就能明瞭了。

SQL Server 2000 Reporting Server >>ReportService.asmx
SQL Server 2005 Reporting Server >>ReportService2005.asmx
SQL Server 2008 Reporting Server >>ReportService2006.asmx
SQL Server 2008R2 Reporting Server >>ReportService2010.asmx

目前我可以使用 ReportService2005.asmx 與 ReportService2010.asmx,是因為主機上裝了 SQL 2005 與 SQL 2008R2,由此解釋,應該可以通吧!


2. Render( ) 方法的入口點,已由 ReportService.asmx 改為 ReportExecutionService2005.asmx,這改變真的也讓人理不出個所以然來。只能靠網友間口耳相傳才會知道,自己則是在 Rendering a report using web services with 2008 R2 (ReportService2010) 這篇文章找到原因的。

3. Render( ) 引數的改變。 Render( ) 所接受的引數,在 ReportExecutionService2005.asmx 是跟 ReportService.asmx 不一樣的。新的 Render( ) 內容,可以參考 ReportExecutionService.Render Method

折騰一陣子後,終於把透過 Proxy 來產生 Reporting Service 報表的問題解決了。最後將完整的測試程式整理於後,以便有興趣的朋友或未來的自己參考參考。

範例是用 VS2003 所撰寫,所以有點懷舊的味道...只差程式沒辦法強調是黑白的而已。


using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Configuration;

namespace WebApplication1
{
 /// <summary>
 /// Summary description for WebForm1.
 /// </summary>
 public class WebForm1 : System.Web.UI.Page
 {
  protected System.Web.UI.WebControls.Button btnPDF;
 
  private void Page_Load(object sender, System.EventArgs e)
  {
   // Put user code to initialize the page here
  }

 

  private void btnPDF_Click(object sender, System.EventArgs e)
  {
   MakePDF();
  }

  private void MakePDF()
  {
   //PDF 暫存檔路徑(必須已經存在的檔案路徑)
   string filepath = "D:\\upload\\engage_upd\\PDF\\";
   string filename = filepath+"Quote.PDF";


   //取得 WebService 服務(SQL 2008R2 的 webservice 要使用 ReportExecution2005.asmx )   
   string strRSTmp="http://Repsdev/ReportServer/ReportExecution2005.asmx";   

   
   repsdev.ReportExecutionService rs=new WebApplication1.repsdev.ReportExecutionService();

   //設定 windows 帳號認證(正式使用時,採用此行)   
   //rs.Credentials=System.Net.CredentialCache.DefaultCredentials;

   //測試資料【連接Reporting Server】(測試時,採用此行,固定測試帳號)
   rs.Credentials=new System.Net.NetworkCredential("52xxxx","passwd","network");


   //重新指派 WebService 服務位址
   rs.Url=strRSTmp;
   
   Byte [] result=null;

   //參數收集(呼叫報表時必須傳入的參數,目前範例要傳入 seqsn,ver,isdraft 三個參數)
   repsdev.ParameterValue [] parameter=new WebApplication1.repsdev.ParameterValue[3] ;
   parameter[0]=new WebApplication1.repsdev.ParameterValue();
   parameter[0].Name="seqsn";
   parameter[0].Value="545";
   parameter[1]=new WebApplication1.repsdev.ParameterValue();
   parameter[1].Name="ver";
   parameter[1].Value="";
   parameter[2] = new WebApplication1.repsdev.ParameterValue();
   parameter[2].Name="isdraft";
   parameter[2].Value="1";


   repsdev.ExecutionInfo execInfo=new WebApplication1.repsdev.ExecutionInfo();
   repsdev.ExecutionHeader execHeader=new WebApplication1.repsdev.ExecutionHeader();

   //報表位置
   string reportPath = "/EPath/rptTest";
   string historyID = null;

   rs.ExecutionHeaderValue = execHeader;
   execInfo = rs.LoadReport(reportPath, historyID);
   rs.SetExecutionParameters(parameter, "en-us");


   string mimeType;   
   repsdev.Warning [] warnings=null;
   string [] streamIDs=null;
   string devInfo=@"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>";
   string extension;
   string encoding;
   try
   {
    result=rs.Render("PDF",devInfo,out extension,out mimeType,out encoding,out warnings,out streamIDs);
   }
   catch(Exception e)
   {
    if(e.Message.IndexOf("401")>-1)
    {
     //HTTP 401:沒通過授權
     String scriptString = @"
<script language=JavaScript>
<!-- begin 
alert('您必須登入網域,才能有權限讀取本報表!') ;
//end -->
</script> 
";
     this.RegisterStartupScript("AccessDeny",scriptString);

     return;
    }
    else
     return;
   }

   FileStream fs=new FileStream(filename,FileMode.Create);
   fs.Write(result,0,result.Length);
   fs.Close();
   rs.Dispose();       
  }


 }
}



參考:

01.ReportExecutionService.Render Method

02.What is reportexecution2005.asmx i used this as webreference

03.Rendering a report using web services with 2008 R2 (ReportService2010)

04.SSRS 2008, C# and ReportingService.Render()

05.SSRS 亂七八糟的SOAP API 配對

2010年12月8日 星期三

一看就懂的圖解心經


站在書店的門口,在一個顯眼處的地方,發現一本圖文並茂,可愛到不行的書。翻翻封面,於背後有著一段文字:「心是人的主宰,心是認識一切的開始,宇宙的萬象變化,都是內心意識的變化所致。修行的重點是心,心無所執著心就解脫了,心一解脫一切就解脫了。就像風鈴一樣,不管是涼風、熱風、冷風吹來,風鈴都一樣搖曳作響毫不執著,凡人同樣也能得到大自在而就心無罣礙了。」

作個小實驗,請記得你現在的心情是什麼樣子,接著看一段下面影片:




現在再把你的心拿出來檢視一下,是否跟一開始的心境有所不同?

影片的內容,可以看作是一陣暖風,你的心,則是風鈴。你看影片時,隨著劇情的演進而感動,讓你心中的風鈴噹噹作響。但當影片結束時,你的心,是否也可以跟風鈴一樣靜止下來呢?

此刻的你我,也許正吹著冷風,也許吹著暖風,不管是東西南北風,風一停,都試著讓你的心也停。

如果你想更有智慧一點,可以看看這本書,他會告訴你一切的事物,都是緣生則聚、緣滅則散,目前自己會有所執著,多半是你的心有所貪求、心有所怨恨、心有所愚昧所致。正如華嚴經所云:「心如工畫師,能畫諸世間。」喜怒哀樂既然由心而起,自然也就由心而去囉!

2010年12月7日 星期二

ASP.Net 取得使用者真實 IP

ASP.Net 如何抓取 User 的 IP ? 於 Request.UserHostAddress 屬性,可以看到微軟的解釋為:取得遠端用戶端的 IP 主機位址。這句話沒錯,只是現在許多使用者的電腦與網站主機之間,常常會透過 Proxy 伺服器來存取主機上的網頁,也許是你自己設定的,也許是公司網管設定的,或是 ISP 業者,都有可能讓 Request.UserHostAddress 抓不到使用者的真實 IP。


更進一步來說,可能會聽到資安專家,直接要你死了這條心,在網路世界裡,是無法找到真實 IP的,因為網路封包的標頭都是可以假冒的,也就是說,你抓到的 IP ,有可能是假的。有許多論壇的網站,都很習慣將發表文章的作者 IP 連同文章內容一起公開,也就是說,該網站的運作方式,會去抓 User 的 IP ,然後存進資料庫。這動作,曾經讓不安好心的人起了個念頭。假設這個站台不注重資安,是用了

string sql = "INSERT INTO (IP) VALUE ('" + IP + "')";

這寫法, 會讓有心人士將 HTTP 標頭裡將原先紀錄 IP 位址的地方改成惡意的SQL 指令碼,那當你的系統在執行儲存 IP 動作時,也同時執行了惡意程式碼。而這一點,是所有準備要留下 IP 紀錄的工程師,必須要考慮的一個安全議題。當然,養成好習慣,驗證你所要傳給 SQL 的參數內容,是自保的不二法門。

以下提供網友提供的驗證是否為合法 IP 的 Regular :

^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$

用 C# 寫成 CheckIP( )

///
/// 檢查 IP 是否合法
/// 
/// strPattern:需檢測的 IP
/// true:合法 false:不合法
private bool CheckIP(string strPattern)
{
// regular: ^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$
Regex regex = new Regex("^\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}$");
Match m = regex.Match(strPattern);

return m.Success;
}

撇開特別的情況,抓真實 IP 的功能,還是得寫,總不能讓你無法跟老闆交待吧!但記得要先跟他打預防針,讓他理解到,無法保證百分百取得真實 IP,因為 IP 有可能被假冒,但正常情況下,取得大多數的使用者 IP 則是可行的。

假設使用者沒有透過代理伺服器,則使用 Request.ServerVariables["REMOTE_ADDR"] 就可以抓到 IP;但如果有透過代理伺服器,則要改成使用 Request.ServerVariables["HTTP_X_FORWARDED_FOR"]。而在kingwkb的专栏 的一篇文章「[hidotnet]真正的取真实IP地址及利弊」提到,如果使用者的環境是使用多重代理伺服器時,則使用Request.ServerVariables["HTTP_X_FORWARDED_FOR"] 會得到「真實IP,第一層代理IP,第二層代理IP,...」(ex:「140.134.4.4,140.134.4.250,140.128.2.10」)的結果。而 kingwkb 文章也進一步去將所謂內部 IP 的資訊事先在程式中進行了篩選,只留下相對可信的資訊。


最後整理了測試程式,結果如下:

撰寫了 GetIP( ) 函式來取得真實 IP,並搭配 CheckIP( ) 來驗證所抓的 IP 是否合法。

protected void Page_Load(object sender, EventArgs e)
{      
    //自己定義的 Label,用來顯示 IP 訊息
    lbIP.Text = GetIP();
       
}

private string GetIP()
{
    string ip;
    string trueIP=string.Empty;

    //先取得是否有經過代理伺服器
    ip=Request.ServerVariables["HTTP_X_FORWARDED_FOR"];

    if (!string.IsNullOrEmpty(ip))
    {
        //將取得的 IP 字串存入陣列
        string[] ipRange = ip.Split(',');

        //比對陣列中的每個 IP
        for (int i = 0; i < ipRange.Length; i++)
        {
            //剔除內部 IP 及不合法的 IP 後,取出第一個合法 IP
            if (ipRange[i].Trim().Substring(0, 3) != "10." &&
                ipRange[i].Trim().Substring(0, 7) != "192.168" &&
                ipRange[i].Trim().Substring(0, 7) != "172.16." && 
                CheckIP(ipRange[i].Trim()))
            {
                trueIP = ipRange[i].Trim();
                break;
            }
        }
            
    }
    else
    {
        //沒經過代理伺服器,直接使用 ServerVariables["REMOTE_ADDR"]
        //並經過 CheckIP( ) 的驗證
        trueIP = CheckIP(Request.ServerVariables["REMOTE_ADDR"])?
            Request.ServerVariables["REMOTE_ADDR"]:"";
    }

    return trueIP;
}

/// <summary>
/// 檢查 IP 是否合法
/// </summary>
/// <param name="strPattern">需檢測的 IP</param>
/// <returns>true:合法 false:不合法</returns>
    
private bool CheckIP(string strPattern)
{
    // 繼承自:System.Text.RegularExpressions
    // regular: ^\d{1,3}[\.]\d{1,3}[\.]\d{1,3}[\.]\d{1,3}$
    Regex regex = new Regex("^\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}[\\.]\\d{1,3}$");
    Match m = regex.Match(strPattern);

    return m.Success;
}


參考文章:
01:[hidotnet]真正的取真实IP地址及利弊
02:使用HTTP_X_FORWARDED_FOR获取客户端IP的严重后果
03:PHP利用HTTP_X_FORWARDED_FOR抓取訪客ip
04:How to get client “IP Address” using Asp.net /C#

2010年12月1日 星期三

VS2010 使用 Source Control 的小細節

當使用 VS2010 搭配 Source Control 時,自己覺得有些地方用起來很不習慣。大致上將兩個地方說明如下:

第一:當使用「Split」設計模式時,很奇怪的都會自動將檔案簽出,而我只是想看看而已,並沒有打算修改,這樣檔案也莫名的被我簽出了。為了解決這問題,可以到 Tools / Options 的設定裡,在左邊分頁的 Source Control / Environment ,可以在右邊找到 Checked-in 區塊,然後把 Saving 與 Editing 的「Prompt for check out」挑選起來,日後有要簽出時,就會跳出視窗詢問你了。


第二:當我將檔案 check out 出來時,VS2010並不會自動幫我抓取最新的程式,所以有可能我現在即將要改得程式,並不是最新的。這時候,可以讓 VS2010每次 check out 程式時,就自動抓最新版程式回來。設定方式,可以在 Tools / Options ,於左邊分頁選擇 Source Control / Visual Studio Team Fundation Server ,並在右邊將 「Get latest version of item on check out」打勾,這樣就可以確保每次編輯都是最新的程式了。


非常感謝熱心的 Hugo 救我於水火,功德物量啊!!

2010年11月25日 星期四

透過 jQuery 抓取鍵盤壓下的按鍵

透過 jQuery 抓取 user 所敲的鍵盤按鍵,可以使用 .keydown( ) 方法。詳細介紹可以參考官網 API 的介紹。自己在實作過程中,發現了 .keydown( ) 所回傳的結果,並不一定會與原先我所認知的結果符合。舉例來所,壓下鍵盤上的「G」這個按鍵,理論上他應該回傳「71」的值,但我卻得到「229」。耐著性子把26個英文字母、數字、符號敲完,也都是「229」,還真是怪哩!

於是查了一下,出現229,代表的意思是:ecircumflex

感覺有查等於沒查,還真的看不懂 ecircumflex 代表啥!

於是自己寫了一段測試的 Code 如下:

<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-1.4.4.js"></script> 


<script>
$(function(){
 $("#tbxCatch").keydown(function(evt){alert(evt.keyCode);});
});
</script>

</head>
<body>
<input type="text" id="tbxDefault" >
<br>
<input type="text" id="tbxCatch" >
</body>
</html>

測試過程中,曾經有一陣子我按 「G」按鍵,竟然可以得到 「71」的值,很天真的以為是不是自己眼花了!不過,反覆測試了好幾天,終於找到原因了。兇手是:「輸入法」!!在上面的測試範例中,如果你有將輸入法切換到中文模式,則不管你敲鍵盤上的哪個英文字母,都會是「229」,但如果將輸入法切換成英文模式,就可以得到預期的結果。

日後,如果有需要攔截使用者鍵盤所敲的按鍵時,別忘了 「229」喔!!

參考:
http://api.jquery.com/keydown/

補充:

鍵盤編碼方式


keycode   0 =
keycode   1 =
keycode   2 =
keycode   3 =
keycode   4 =
keycode   5 =
keycode   6 =
keycode   7 =
keycode   8 = BackSpace BackSpace
keycode   9 = Tab Tab
keycode  10 =
keycode  11 =
keycode  12 = Clear
keycode  13 = Enter
keycode  14 =
keycode  15 =
keycode  16 = Shift_L
keycode  17 = Control_L
keycode  18 = Alt_L
keycode  19 = Pause
keycode  20 = Caps_Lock
keycode  21 =
keycode  22 =
keycode  23 =
keycode  24 =
keycode  25 =
keycode  26 =
keycode  27 = Escape Escape
keycode  28 =
keycode  29 =
keycode  30 =
keycode  31 =
keycode  32 = space space
keycode  33 = Prior
keycode  34 = Next
keycode  35 = End
keycode  36 = Home
keycode  37 = Left
keycode  38 = Up
keycode  39 = Right
keycode  40 = Down
keycode  41 = Select
keycode  42 = Print
keycode  43 = Execute
keycode  44 =
keycode  45 = Insert
keycode  46 = Delete
keycode  47 = Help
keycode  48 = 0 equal braceright
keycode  49 = 1 exclam onesuperior
keycode  50 = 2 quotedbl twosuperior
keycode  51 = 3 section threesuperior
keycode  52 = 4 dollar
keycode  53 = 5 percent
keycode  54 = 6 ampersand
keycode  55 = 7 slash braceleft
keycode  56 = 8 parenleft bracketleft
keycode  57 = 9 parenright bracketright
keycode  58 =
keycode  59 =
keycode  60 =
keycode  61 =
keycode  62 =
keycode  63 =
keycode  64 =
keycode  65 = a A
keycode  66 = b B
keycode  67 = c C
keycode  68 = d D
keycode  69 = e E EuroSign
keycode  70 = f F
keycode  71 = g G
keycode  72 = h H
keycode  73 = i I
keycode  74 = j J
keycode  75 = k K
keycode  76 = l L
keycode  77 = m M mu
keycode  78 = n N
keycode  79 = o O
keycode  80 = p P
keycode  81 = q Q at
keycode  82 = r R
keycode  83 = s S
keycode  84 = t T
keycode  85 = u U
keycode  86 = v V
keycode  87 = w W
keycode  88 = x X
keycode  89 = y Y
keycode  90 = z Z
keycode  91 =
keycode  92 =
keycode  93 =
keycode  94 =
keycode  95 =
keycode  96 = KP_0 KP_0
keycode  97 = KP_1 KP_1
keycode  98 = KP_2 KP_2
keycode  99 = KP_3 KP_3
keycode 100 = KP_4 KP_4
keycode 101 = KP_5 KP_5
keycode 102 = KP_6 KP_6
keycode 103 = KP_7 KP_7
keycode 104 = KP_8 KP_8
keycode 105 = KP_9 KP_9
keycode 106 = KP_Multiply KP_Multiply
keycode 107 = KP_Add KP_Add
keycode 108 = KP_Separator KP_Separator
keycode 109 = KP_Subtract KP_Subtract
keycode 110 = KP_Decimal KP_Decimal
keycode 111 = KP_Divide KP_Divide
keycode 112 = F1
keycode 113 = F2
keycode 114 = F3
keycode 115 = F4
keycode 116 = F5
keycode 117 = F6
keycode 118 = F7
keycode 119 = F8
keycode 120 = F9
keycode 121 = F10
keycode 122 = F11
keycode 123 = F12
keycode 124 = F13
keycode 125 = F14
keycode 126 = F15
keycode 127 = F16
keycode 128 = F17
keycode 129 = F18
keycode 130 = F19
keycode 131 = F20
keycode 132 = F21
keycode 133 = F22
keycode 134 = F23
keycode 135 = F24
keycode 136 = Num_Lock
keycode 137 = Scroll_Lock
keycode 138 =
keycode 139 =
keycode 140 =
keycode 141 =
keycode 142 =
keycode 143 =
keycode 144 =
keycode 145 =
keycode 146 =
keycode 147 =
keycode 148 =
keycode 149 =
keycode 150 =
keycode 151 =
keycode 152 =
keycode 153 =
keycode 154 =
keycode 155 =
keycode 156 =
keycode 157 =
keycode 158 =
keycode 159 =
keycode 160 =
keycode 161 =
keycode 162 =
keycode 163 =
keycode 164 =
keycode 165 =
keycode 166 =
keycode 167 =
keycode 168 =
keycode 169 =
keycode 170 =
keycode 171 =
keycode 172 =
keycode 173 =
keycode 174 =
keycode 175 =
keycode 176 =
keycode 177 =
keycode 178 =
keycode 179 =
keycode 180 =
keycode 181 =
keycode 182 =
keycode 183 =
keycode 184 =
keycode 185 =
keycode 186 =
keycode 187 = acute grave
keycode 188 = comma semicolon
keycode 189 = minus underscore
keycode 190 = period colon
keycode 191 =
keycode 192 = numbersign apostrophe
keycode 193 =
keycode 194 =
keycode 195 =
keycode 196 =
keycode 197 =
keycode 198 =
keycode 199 =
keycode 200 =
keycode 201 =
keycode 202 =
keycode 203 =
keycode 204 =
keycode 205 =
keycode 206 =
keycode 207 =
keycode 208 =
keycode 209 =
keycode 210 = plusminus hyphen macron
keycode 211 =
keycode 212 = copyright registered
keycode 213 = guillemotleft guillemotright
keycode 214 = masculine ordfeminine
keycode 215 = ae AE
keycode 216 = cent yen
keycode 217 = questiondown exclamdown
keycode 218 = onequarter onehalf threequarters
keycode 219 =
keycode 220 = less greater bar
keycode 221 = plus asterisk asciitilde
keycode 222 =
keycode 223 =
keycode 224 =
keycode 225 =
keycode 226 =
keycode 227 = multiply division
keycode 228 = acircumflex Acircumflex
keycode 229 = ecircumflex Ecircumflex
keycode 230 = icircumflex Icircumflex
keycode 231 = ocircumflex Ocircumflex
keycode 232 = ucircumflex Ucircumflex
keycode 233 = ntilde Ntilde
keycode 234 = yacute Yacute
keycode 235 = oslash Ooblique
keycode 236 = aring Aring
keycode 237 = ccedilla Ccedilla
keycode 238 = thorn THORN
keycode 239 = eth ETH
keycode 240 = diaeresis cedilla currency
keycode 241 = agrave Agrave atilde Atilde
keycode 242 = egrave Egrave
keycode 243 = igrave Igrave
keycode 244 = ograve Ograve otilde Otilde
keycode 245 = ugrave Ugrave
keycode 246 = adiaeresis Adiaeresis
keycode 247 = ediaeresis Ediaeresis
keycode 248 = idiaeresis Idiaeresis
keycode 249 = odiaeresis Odiaeresis
keycode 250 = udiaeresis Udiaeresis
keycode 251 = ssharp question backslash
keycode 252 = asciicircum degree
keycode 253 = 3 sterling
keycode 254 = Mode_switch
keycode 188 = comma semicolon
keycode 189 = minus underscore
keycode 190 = period colon
keycode 191 =
keycode 192 = numbersign apostrophe
keycode 193 =
keycode 194 =
keycode 195 =
keycode 196 =
keycode 197 =
keycode 198 =
keycode 199 =
keycode 200 =
keycode 201 =
keycode 202 =
keycode 203 =
keycode 204 =
keycode 205 =
keycode 206 =
keycode 207 =
keycode 208 =
keycode 209 =
keycode 210 = plusminus hyphen macron
keycode 211 =
keycode 212 = copyright registered
keycode 213 = guillemotleft guillemotright
keycode 214 = masculine ordfeminine
keycode 215 = ae AE
keycode 216 = cent yen
keycode 217 = questiondown exclamdown
keycode 218 = onequarter onehalf threequarters
keycode 219 =
keycode 220 = less greater bar
keycode 221 = plus asterisk asciitilde
keycode 222 =
keycode 223 =
keycode 224 =
keycode 225 =
keycode 226 =
keycode 227 = multiply division
keycode 228 = acircumflex Acircumflex
keycode 229 = ecircumflex Ecircumflex
keycode 230 = icircumflex Icircumflex
keycode 231 = ocircumflex Ocircumflex
keycode 232 = ucircumflex Ucircumflex
keycode 233 = ntilde Ntilde
keycode 234 = yacute Yacute
keycode 235 = oslash Ooblique
keycode 236 = aring Aring
keycode 237 = ccedilla Ccedilla
keycode 238 = thorn THORN
keycode 239 = eth ETH
keycode 240 = diaeresis cedilla currency
keycode 241 = agrave Agrave atilde Atilde
keycode 242 = egrave Egrave
keycode 243 = igrave Igrave
keycode 244 = ograve Ograve otilde Otilde
keycode 245 = ugrave Ugrave
keycode 246 = adiaeresis Adiaeresis
keycode 247 = ediaeresis Ediaeresis
keycode 248 = idiaeresis Idiaeresis
keycode 249 = odiaeresis Odiaeresis
keycode 250 = udiaeresis Udiaeresis
keycode 251 = ssharp question backslash
keycode 252 = asciicircum degree
keycode 253 = 3 sterling
keycode 254 = Mode_switch

2010年11月23日 星期二

Reporting Service 2008 R2 全形英文及符號出現亂碼

近日開始用 Reporting Service 2008 開發新的報表,我使用的是英文版。在佈署報表之後,透過 URL 去存取報表服務,我選擇的是 PDF 格式輸出。檢視 PDF 內容時,發現所有全形的英文或是標點符號,全都變成亂碼了。


有點不解的,是在預覽報表時,結果其實還蠻正常的。點選匯出 PDF 時,這份 PDF 內容也是正常的。



詳細去檢視報表內容的屬性時,發現在 FontFamily 的預設值是「Arial」,試著換別的字型,於是我選了「(新)細明體」,再重新佈署一次報表。發現換了細明體後, PDF 的內容就可以正常顯示全形的英文及標點符號了。



原來在 Reporting Service 裡,你所要呈現的文字,跟你所挑選的字形會有如此大的關係。但依常理來說,開發工具是不該讓設計者有如此困惑的情況,或許這次微軟也有什麼難言之隱也說不定(因為我在預覽報表時,執行匯出 PDF 是正常;但透過 URL 存取報表卻出現亂碼)。除了使用(新)細明體能解決上述我所遇到的問題外,測試其它種中文字型,「標楷體」也可以正常顯示,但,「微軟正黑體」則不行。

結論是,在 Reporting Service 2008 R2,透過 URL 存取 PDF 格式的報表時,若要正常顯示全形的英文及標點符號,必須使用「(新)細明體」或「標楷體」。

在 Reporting Service 2008 R2 後,開始支援匯出 word 格式的報表,最後也針對匯出 word , excel 格式報表去檢視是否有上述的問題,結果是 word 與 excel 都可以正常顯示。所以只有在報表格式是 PDF 時,需要特別注意這亂碼問題。


參考文章:
01:http://missice.pixnet.net/blog/post/24265150
02:http://social.msdn.microsoft.com/forums/zh-TW/240/thread/324bdcc8-7a60-4d1d-bd94-3e97fd701c99

2010年11月18日 星期四

禍之所由,常在於智用,不在於性動。

「夫不慮而欲,性之動也;識而後感,智之用也。性動者,遇物而當,足則無餘;智用者,從感而求,勌而不已。故世之所患,禍之所由,常在於智用,不在於性動。」


這句話是引自嵇康的<答難養生論>,其意略作如下:
那些不需思索而產生的欲望,是屬於人類本性的需求。譬如你肚子餓了,自然就會有想要吃東西的欲望。但累積了許多不同經驗之後,你開始就會有好惡差別的感受,這是因為你的「心」已經開始在分別,知道哪些是好吃的、哪些又是不好吃的。

對於原始本性的需求,只要獲得了基本的物質供給,就不會再有多餘的乞求。但是對於那些已起美醜、好惡的心來說,則會為了滿足各種不同感受上的需求來費盡心思,也給自己起了許多煩惱、多了很多事情去憂心。譬如說,如果提供午餐的菜單只有一種,那你就沒啥好挑剔,就它了。但如果有很多種菜單可選擇,那反而你會因為不知道要挑哪一種菜單而煩惱。

所以,世人所面臨到的煩惱、所遭遇到的禍害,常常是起因於內心的貪求與好惡,而並不是自己最原始的需求。

而在嵇康同文出處,也提到養生五難,都可視為「智用」的壞處:

原文:

養生有五難:名利不滅,此一難也。喜怒不除,此二難也。聲色不去,此三難也。滋味不絕,此四難也。神慮轉發,此五難也。五者必存,雖心希難老,口誦至言,咀嚼英華,呼吸太陽,不能不回其操,不夭其年也;五者無於胸中,則信順日濟,玄德日全,不祈喜而有福,不求壽而自延。此養生大理之所效也。


注:

勌,勉也。勸或作勌。讀作:ㄐㄩㄢˋ

參考:

01:老、莊養生哲學的流變與影響
02:兩漢魏晉哲學史
03:嵇康與葛洪的養生論

2010年11月8日 星期一

jQuery 判斷 DataList 是否被挑選



Asp.net 的 DataList,轉換成 HTML 後,就會變成:


<select size="4" name="ListBox1" id="ListBox1">
<option value="Item01">Item01</option>
<option value="Item02">Item02</option>
<option value="Item03">Item03</option>
</select>

當我們想透過 jQuery 來判斷使用者是否挑選了其中一個項目,其判斷方法可以如下:

if ($("select[id$=ListBox1] option:selected").size() == 0)
{
    // 沒有被挑選
        $("#spanShow").html("No Selected");
}
else
{
  //有被挑選
       $("#spanShow").html($("select[id$=ListBox1] option:selected").val());
}


參考網站:Check if option is selected with jQuery, if not select a default

2010年10月2日 星期六

T-SQL 顯示民國年

在 Hunterpo's IT Vision 看到一篇「自訂 SQL Server 日期轉民國年格式函數」文章,Hunterpo 用心製作了一個很完善的 Function,並考慮了各種日期輸出格式。但保哥回了一段要注意,使用Function 可能會影響效能的話,著實讓我想著是否有更簡單的方式可以完成民國年的需求。

後來發現,其實也不需要用到自訂函數這麼華麗,用短短幾行也可以完成。


declare @now datetime
set @now=getdate()

select convert(varchar(3),datepart(year,@now)-1911) 
+right('0'+ convert(varchar(2),datepart(month,@now)),2)
+right('0'+ convert(varchar(2),datepart(day,@now)),2) as ChineseYear

至於使用 Function 是否會影響效能?答案是肯定會的。簡單的比喻,如果你自己撰寫了一個自訂的 Function,假設執行一次要花 0.01 秒,當你有 50000筆資料時,

select  dbo.myFunction(x) from myTable

就會多花 0.01X50000 的時間。然而,這只是簡單推算,實際上你可能會有加上 where 條件,將資料筆數限制在比較少的情況,正因為所篩選的結果資料量不大,同時也就減少了使用 Function 所帶來額外花費時間的衝擊。

然而, 使用 Function 最大的危險,並不在於 select 敘述裡,而是在 where 條件敘述裡。剛剛有提到,如果只是放在 select 敘述裡,花費時間只會因為搜尋後結果資料集的大小而有影響;但如果把他放在 where 條件敘述裡,則很有可能是每一筆資料都要去執行一次你的 Function,甚至破壞您原先建立 Index 的效益。這裡用「有可能」這字眼,代表著,只要你使用正確,還是可以開開心心使用 Function的,這裡所指的 Function,是同時包括使用者自訂 Function 與系統內建的 Function。

建議可以參考「Improving SQL Server Performance」這篇文章,裡面的一小節「Avoid Explicit or Implicit Functions in WHERE Clauses」有個很棒的範例可以說明。如果你在 where 條件敘述句裡,使用了 Function ,且 Function 裡面是用了某個資料欄位當參數,那這就犯了兵家大忌,要打屁股了。

小弟不才,剽竊了原文的程式碼,也不想告知(人家是 msdn 嘛...)。

SELECT OrderID FROM NorthWind.dbo.Orders WHERE DATEADD(day, 15, 
OrderDate) = '07/23/1996'

SELECT OrderID FROM NorthWind.dbo.Orders WHERE OrderDate = DATEADD(day, 
-15, '07/23/1996')

以上兩行指令,結果是一樣的,但第一行指令,他會使用 Index Scan,每筆資料一行一行去比對;而第二行指令,則是使用 Index Seek。這兩種方法,搜尋成本就差了4倍以上。所以,即便你只是使用系統內建的 Function,但還是要注意擺放的時機與位置,避免日後無眠無夜的調校程式。


參考來源:
1.http://www.dotblogs.com.tw/hunterpo/archive/2010/08/15/17208.aspx
2.http://msdn.microsoft.com/en-us/library/ff647793.aspx

2010年9月19日 星期日

小冰箱之除霜記

凡那比颱風今天正在肆虐台灣,無聊之餘,想到冰箱裡冷凍櫃的白鬍子太多了,順手把它刮一刮。過了幾小時後,發現除霜盤上的積水滿到快溢出來了,此時如果不將水排出,過沒多久我冰箱就會淹水,但如果稍稍移動除霜盤,那它上面的積水又會不小心濺的到處都是,有夠為難的。

這時想到以前小學教的毛細效應,再配合自己刻意安排的位能差來持續不斷地把高處的水傳遞到低處,並於低處放個小鋼杯去接水,而高地處的連接橋樑就透過一條萬能的抹布就搞定了。如此過沒多久,就可以看到小鋼杯裡盛滿了除霜後的水,真是開心啊!以後除霜就不用弄的到處濕答答了。


補充:20110613

今晚回到家,發現前天除霜後,一直忘了自己有在除霜,原本已將裝滿水的盛水盤,目前都已經結冰了。剛開始,我拿著裝滿熱水的鋼杯盤,疊放在冰塊上,的確有加快溶冰的速度,但...真的等到快睡著了。最後,在我喪失理智的情況下,把冰箱電源關掉,另外拿起吹風機,把溫度調到最高,接著...對著我那結冰的盛水盤狂吹,不到5分鐘,整個冰就化了快1/4,趁著我那排冷管與冰分離的時候,將整個盛水盤抽出,直接拿去沖熱水,不到30分鐘就把這冰都除光了。

2010年9月17日 星期五

在 Console Mode下, 透過 FreeSMTP 定期發通知信

要定期的去發通知信,可以利用 SQL Server 的 SQL Server Agent 或 Windows 排程來觸發。而發信的工具可以使用 SQL Mail 或 FreeSMTP。

最近剛好有機會測試了 FreeSMTP 搭配 Windows 排程的方案。原先主要是想透過撰寫 thread 來完成定期發信功能,但考量在不增加系統 loading 與避免該 thread 莫名被終止,所以才考慮用 windows 排程。然而,原先的 ASP.Net 專案如何用 windows 排程來觸發呢?是個難題。所以,我將發信的功能改寫成 Console Mode,也就是可以用 DOS 指令來執行。但,FreeSMTP 可以用 DOS 指令來發信嗎?猶豫了許久,就寫了一段程式來測試。

首先,建立了一個 console mode 的專案,並在該專案加入了 FreeSMTP.Net.dll 的參考。

接著 Imports Quiksoft.FreeSMTP.SMTP
接著撰寫 DoSend( ) 事件以供 Main( )來叫用。

以下是程式碼節錄:


Imports Quiksoft.FreeSMTP.SMTP

Module Module1

    Sub Main()
        DoSend()
    End Sub

    'Sub that does the sending.
    Sub DoSend()

        '宣告 SMTP
        Dim objSMTP As Quiksoft.FreeSMTP.SMTP

        '設定 SMTP Server        
        objSMTP = New Quiksoft.FreeSMTP.SMTP("3probe.com.tw")

        '設定 信件內容
        Dim objMsg As Quiksoft.FreeSMTP.EmailMessage = New Quiksoft.FreeSMTP.EmailMessage("paladin@3probe.com.tw", _
                                    "paladinlee@3probe.com.tw", _
                                    "paladin", _
                                    "Dear paladin,
這封是我特別給您的 :測試信。", _
                                    Quiksoft.FreeSMTP.BodyPartFormat.HTML)
        '設定信件編碼
        objMsg.CharsetEncoding = System.Text.Encoding.GetEncoding("BIG5")

        '發信
        objSMTP.Send(objMsg)


    End Sub


End Module


直接測試這段程式,信件可以正常寄出,接著自己也加入一項 windows 排程,把內容設定為執行剛剛撰寫好的 console mode 專案的 .exe 執行檔。現下自我感覺良好中,可以開開心心去泡茶了...

2010年9月16日 星期四

利用 ASP.Net 壓縮檔案

若要透過 ASP.Net 將 Web 站台的某些檔案進行壓縮,之後再讓使者下載,這需求透過 DotNetZip 來協助,問題就幾乎解決一大半,剩下的,就是如何讓產生的壓縮檔自動下載到使用者的電腦。

可以到 DotNetZip 下載最新 release 的版本。以目前所抓到的 v1.9版來說,解開之後,只要將 DotNetZip-v1.9\Release 裡的 Ionic.Zip.dll 加入到 Visual Studio 的參考就可完成 DotNetZip 的引用。重點是,這是免費的,作者甚至認為,有了他,就不用去買價值 29 美金的 winzip 了,作者也在網站上介紹他自己撰寫的程式,用以取代 winzip。

要將資料夾壓縮的語法,非常簡潔:

using Ionic.Zip;


using (ZipFile zip = new ZipFile())
{
    //將資料夾加入 zip
    zip.AddDirectory(MapPath(".\\SourceFiles"));

    //產生 zip 檔
    zip.Save(MapPath(".\\") + "MyZipFile.zip");
    
}

產生好壓縮檔後,如何讓這檔案下載到使用者電腦呢?當然,可以在網頁上放個超連結,給使用者點選,但如過進一步想更自動一點,則就是直接將附件檔自動下載。

這時,我在 .aspx 頁面放了一個 iframe ,並去設定這 iframe 的 src 屬性為下載連結。而我下載的連結則是透過 .ashx 來處理(可參考測試程式)。

<iframe id="ifies" runat="server" style="visibility:hidden"></iframe>
</form>

這裡我偷偷埋了一個 iframe,並將他的 visibility 設為隱藏,這樣使用者在網頁上就看不到他的存在了。最後,在產生壓縮檔程式裡頭加上 :

ifies.Attributes["src"] = string.Format("filedownload.ashx?filename={0}&path={1}", "MyZipFile.zip",MapPath(".\\"));

using Ionic.Zip;


using (ZipFile zip = new ZipFile())
{
    //將資料夾加入 zip
    zip.AddDirectory(MapPath(".\\SourceFiles"));

    //產生 zip 檔
    zip.Save(MapPath(".\\") + "MyZipFile.zip");

    //讓網頁執行下載動作
    ifies.Attributes["src"] = string.Format("filedownload.ashx?filename={0}&path={1}", "MyZipFile.zip",MapPath(".\\"));
    
}

最後可以觀察執行結果:

FireFox:直接跳出另存新檔


IE6:第一次會詢問安全性問題


Chrome:直接就幫你下載了


DotNetZip 除了能壓縮,也能解壓縮、支援 Dos 指令來操作,是程式開發人員不可或缺的好工具。

參考:http://dotnetzip.codeplex.com/
測試程式:下載

補充:20100929
最近使用 dotnetzip 時發生一個問題,就是中文檔名的文件,壓縮後都會變成亂碼。還好在一篇文章中有提到解法:

http://cheeso.members.winisp.net/DotNetZipHelp/html/aecbb638-1ecf-807e-b933-5bb1a2a95e81.htm

所以,要壓縮中文檔名時,記得程式要加上:

(承接上面的範例)
zip.ProvisionalAlternateEncoding = System.Text.Encoding.GetEncoding("big5")

2010年9月11日 星期六

你是否習慣問自己問題?



媽媽,為什麼樹上的小鳥會飛?為什麼雞會生蛋?...

還記得自己小時候,也問過一連串類似的問題嗎。如今的你我,又有多少機會再提出這麼多問題了呢?若是說,隨著年紀的增長,經驗多了,知識廣了,所以問題少了,你相信嗎?我比較相信年紀大了,油條了,所以變懶了。

人的大腦,有兩個很重要的能力,一個是思考能力,一個是記憶能力。當你還是小娃兒時,透過哭聲開始了與父母親的基本溝通。而你慢慢開始知道,不舒服、飢餓時,只要哭,就會有人幫你解決。

漸漸地,你開始對這世界產生興趣,日常生活周遭的人事物,對你來說,都是如此的新鮮,此時此刻,你的心中隨時都存有著十萬個「為什麼?』。

你如何去解決心裡頭埋藏的十萬個「為什麼?」,這與你的父母、家庭環境有著密不可分的關係。有些父母亦師亦友,對你的問題無所不談;有些家庭藏書豐厚,或懂得利用圖書資源;此外,尚有權威一言堂的家庭,也有鬼神論者,而這些生長環境都是決定著一個小孩「解決問題」的能力。

快樂的 6 年過去,小孩子開始入學了,學校裡開始傳授更多的學理知識給你,但此刻的你,畢竟童心未泯,還是比較喜歡玩樂。如果遇到高明的老師,他會讓你知道學習與玩樂是可以兼得的。如果遇到普通的老師,則是學習與玩樂只能取其一。更慘的,是遇到玩也不能玩,讀也讀不會的情況,怎麼辦?只好自己四處尋找刺激、快感了。

在求學的年紀,不管有收穫、沒收穫,但上千個日子經過之後,總是會讓既定的思考行為進而成為你潛意識裡的一部分。其實,對人類進化而言,它是有幫助的,將你平日的反應記憶下來,日後如果遇到突發狀況時,不就可以立即反應了嗎?

舉例來說,如果有人說話說到你的痛處時,你的自我防衛心理開始起來,而你這時刻的直覺反應,就是最真實的你。這真實的自己,則是經歷了無數次的思考之後所慢慢塑成的,是日後你遇事決斷時的第一個念頭。

初念淺,轉念深。每個念頭,都是過去經驗所留下。人,總不能老是用老方法來處理事、來生活吧!所以要時時鼓勵自己,再多想一些,再深入一點,去探索每一個思想的盲點。你小時候那種「為什麼,為什麼,為什麼...」的精神還在嗎?把他找回來吧!

2010年9月9日 星期四

透過 jQuery 實現攔截鍵盤的 Ctrl+S 事件

想要在網頁上,允許使用者透過鍵盤的 Ctrl+S 來觸發事件,jQuery 可以幫你。

Ganeshji 在其部落格文章「Ctrl + Key Combination – Simple Jquery Plugin」介紹當如果只需要簡單的 Ctrl+? 鍵,又不想使用類似 js-hotkeys 功能這麼完整的外掛套件時,單單只靠幾行 jQuery 就可以完成了。

Ganeshji 首先使用 jQuery 的擴展定義了自己的 ctrl 函式:

$.ctrl = function(key, callback, args) {
    var isCtrl = false;
    $(document).keydown(function(e) {
        if(!args) args=[]; // IE barks when args is null

        if(e.ctrlKey) isCtrl = true;
        if(e.keyCode == key.charCodeAt(0) && isCtrl) {
            callback.apply(this, args);
            return false;
        }
    }).keyup(function(e) {
        if(e.ctrlKey) isCtrl = false;
    });
};

日後要引用 ctrl 函式,方法如下:

$.ctrl('S', function() {
    alert("Saved");
});

這範例,只要使用 Ctrl + S ,就會觸發 alert("Saved"); 實際上測試了不同瀏覽器,IE, Chrome, 都正常,但 FireFox 有點惱人的問題,就是當執行 Ctrl+S 時,FireFox 會跳出兩次另存新檔的對話視窗,這其實並不是我想要的結果。

為了避免 FireFox 跳出另存新檔的對話視窗,可以去攔截 keypress 的動作,並將原始的 Ctrl+S 事件濾掉。所以加了以下一段程式來完成。

$(window).keypress(function(event) {
      if ((event.which == 115 && event.ctrlKey)){
          event.preventDefault();
      }
    }); 

如此修改後,整個程式就更趨完整了。

附上範例程式:[下載]

參考:
01:Ctrl + Key Combination – Simple Jquery Plugin
02:js-hotkeys

後記:

在 Ganeshji 文章中有提到 js-hotkeys ,實際去玩了一下,發現他在 FireFox 也同樣存在著使用 Ctrl+S 會跳出兩次另存新檔視窗的問題,但用本編文章介紹的方式去攔截 keypress 就可解決。的確, js-hotkeys 真的是解決 web 上使用 keyboard 快捷鍵的好幫手,而 Ganeshji 則是提供了羽量級的解決方案,各位可斟酌使用。

附上 js-hotkeys 的測試程式:[下載]

2010年8月27日 星期五

如何在SQL Server 資料庫中 找出符合你的關鍵字的Store Procedure

要讓你在資料庫中,找出符合某關鍵字的 Store Procedure,要怎麼去完成呢?

以前龐統曾獻三計給劉備奪川蜀,現在也有三種方法來取 Store Procedure。

下計
一個一個點開 Stroe Procedure,並用搜尋的方式,將每個符合你關鍵字的 Store Procedure都記錄下來。

中計
使用 SQL Server 提供的「產生指令碼」功能,並將所有 Store Procedure都點選,匯出成一個檔案。開啟這檔案,用搜尋的方式找到符合你關鍵字的 Stre Procedure。

上計
善用SQL Sever 提供的 view :INFORMATION_SCHEMA.ROUTINES 來完成。

劉備認為上計過急,下計又太緩,故依中計而行。而我早已被 user 逼急了,所以應該會用上計吧!

在資料庫裡,每一支 Store Procedre 或 Function ,都會有對應的一筆 INFORMATION_SCHEMA.ROUTINES紀錄。而這 view 裡的欄位 ROUTINE_DEFINITION 就保存著目前使用者所定義的程式碼。如果今天你想要取得符合某個關鍵字的所有 Store Procedure,那只要在 INFORMATION_SCHEMA.ROUTINES 查詢 ROUTINE_DEFINITION 欄位就可以達成。如果你想更進一步去限制只要查詢Store Procedure類型,那可以在 ROUTINE_TYPE 設定條件為 「PROCEDURE」。(目前有兩種類型:PROCEDURE / FUNCTION)

以下是個範例,找出所有關鍵字為 delete prog_base 的 Store Procedure:
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_DEFINITION LIKE '%prog_base%'
AND ROUTINE_TYPE = 'PROCEDURE';

INFORMATION_SCHEMA.ROUTINES是系統預設提供的 view,換言之,他也是從某些現有的資料表或 view 萃取資料而來。所以,我們當然也可以不透過這 view,直接去從資料表抓。以下,是更原始些的語法,而效果也是一樣。

SELECT distinct sys.sysobjects.name, sys.sysobjects.type
FROM sys.sysobjects INNER JOIN syscomments 
ON sys.sysobjects.id = sys.syscomments.id
WHERE sys.syscomments.text LIKE '%prog_base%' 
AND sys.sysobjects.type = 'P'
ORDER BY sys.sysobjects.NAME

如果要考慮彈性的話,可以考慮使用 syscomments 的使用方法,因為他可以支援的類型,不侷限於 Stroe Procedure , Function,他還支援 Table , View 。所以使用 syscomments 是一個比較完整的解決方案。

而近來自己實際比較了使用 INFORMATION_SCHEMA.ROUTINES 與 syscomments 查詢結果,發現兩邊查出來的資料竟然會不一致,細究原因,原來是 INFORMATION_SCHEMA.ROUTINES 裡的 ROUTINE_DEFINITION 欄位,只支援到長度 4000,換言之,如果有一個 Store Procedure 寫了超過 4000 字以上的程式碼,剛好你的查詢關鍵字又落到 4000 字以後,這樣使用 INFORMATION_SCHEMA.ROUTINES 就查不出資料了。發現了這原因後,就更建議使用 syscomments 來完成你的需求了。詳細原因可參考 Madhivanan 部落格的介紹。他建議若要完整取得 ROUTINE_DEFINITION 欄位 裡的資料,要使用以下方式:

--Method 1
select object_definition(object_id('your_procedure'))

--Method 2
EXEC sp_helptext 'your_procedure'

2010年8月24日 星期二

在 JavaScript 裡,知道 === 與 == 的區別嗎?

印象中,以前有遇過這問題,但當下認為他們是一樣的,所以也就沒有很認真去區分他們間的差別。但今天在看 jQuery 的原始碼時,發現這些老外都喜歡用 ===  而不用 ==,難到,真的只是因為老外比較長這原因而已嗎?

而我在 devguru 找到老外比較長的原因了。

一般在寫 JavaScript 時,我們「等號運算子(==)」的左右兩邊,你可能偶而會發現,如果有一個變數,他的值是整數,但如果在等號運算子的另一邊用字串來比較,有可能會通過。舉個例子來說:

var v=5;
if ( v=="5")
  alert('the same');
else
  alert('not the same');

這程式執行的結果,會跑出 「the same」出來。原因是你使用 == 運算子時,他會試圖自動去幫你作轉型的動作,以符合懶人的期望。但這一懶,卻也跑出了些潛在的問題。

第一,雖然程式幫你作了自動轉型來比較,但你可能已經誤用或誤會了這個變數他原本所宣告的變數型態,現在他包容了你,但不保證當你的程式變肥之後,日後在型態處理上吃了一記悶棍。

第二,是效能問題。如果要讓程式去幫你作自動轉型的動作,則他必須要多花一些額外的時間將所有型態一一去跟你目前的變數比較,這動作會比你一開始就直接講明兩邊都是要同樣型態會來得花時間。

所以,如果要讓自己程式寫得嚴謹,使用嚴格的等號運算子(===)是比較建議的。而他的雙胞胎兄弟,則是 !==,就請大家自個兒舉一反三了。

個人在 stackoverflow 論壇看到一篇很詳盡的介紹,很推薦去看他那篇說明,或找關鍵字 strict equal operator 搜尋更多其他文章。

參考:
01:http://www.devguru.com/Technologies/ecmascript/quickref/comparison_operators.html
02:http://stackoverflow.com/questions/359494/javascript-vs-does-it-matter-which-equal-operator-i-use
03:http://www.webreference.com/js/column26/stricteq.html

2010年8月22日 星期日

ImageButton 似乎跟 Repeater、DataList、GridView 不合

最近為了讓網頁變得花俏,所以每個按鈕都有專屬的一張圖使用,不過是按鈕放張圖而已嘛~
但跑出的結果,卻讓人噴飯 >_<
「無效的回傳或回呼引數」,這就是美麗的代價嗎?

我本來是要做上圖的功能,透過 Repeater ,並在每一個資料列中放「刪除」按鈕,執行刪除後就會重新更新一次資料。

所以在 Page_Load( ) 事件,就沒有將繫結 Repeater 的動作包在 if(!IsPostBack) 的判斷式中。我曾經試著用錯誤畫面裡的建議,將 <%@ Page 裡的  EnableEventValidation 屬性設為 false 。雖然這動作可以避免產生「無效的回傳或回呼引數」錯誤訊息,但是原先針對「刪除」按鈕的事件動作卻變成不會被觸發,真是折磨人啊!

我決定,要去抓個墊背來試試...
我就棄 ImageButton 改用 Button 試試。發現他一樣有這種現象。
接著測試  LinkButton ,成功了!這三個Button 兄弟,總算有一個有出息,沒有全軍覆沒。

可是,使用 LinkButton,有沒有辦法放圖呢?於是,把腦筋動到他的 Text 屬性,把 <img src=btn_delete2.jpg border=0 /> 寫到 Text 屬性裡,發現竟然可以哩~

今天發生的這種現象,並非只存在於 Repeater ,DataList 、 GridView 也是都這樣。為何以前自己沒注意到有這現象發生,這就在於繫結資料的動作,以前我都習慣將他放到 if(!IsPostBack) 判斷式裡,還有另一種可能,就是 ... 程式寫太少啦,你這懶鬼 !!!

測試檔案下載

網路上相同受害者

2010年7月19日 星期一

使用 jqPlot 畫圓餅圖

在網頁上想要製作圓餅圖,免費且好看的,推薦一家:jqPlot。

實際使用後,發現 jqPlot 操作上並沒有很複雜,程式碼也相當簡潔。

看看這次實作的步驟:

首先是一堆引用的語法。

//針對 IE 瀏覽器而另外處理 (excanvas)
<!--[if IE]><script language="javascript" type="text/javascript" src="../js/excanvas.min.js"></script><![endif]--> 

<link rel="stylesheet" type="text/css" href="../css/jquery.jqplot.css" /> 

<!-- BEGIN: load jquery --> 
<script language="javascript" type="text/javascript" src="../js/jquery-1.3.2.min.js"></script> 
<!-- END: load jquery --> 

<!-- BEGIN: load jqplot --> 
<script language="javascript" type="text/javascript" src="../js/jquery.jqplot.min.js"></script> 
<script language="javascript" type="text/javascript" src="../js/jqplot.pieRenderer.min.js"></script> 
<script language="javascript" type="text/javascript" src="../js/jqplot.trendline.min.js"></script> 

<!-- END: load jqplot --> 

接著,在頁面上放置要呈現圖形的範圍:
<div id="pie" style="margin-top:20px; margin-left:20px; width:500px; height:500px;"></div>

最後,設定圖形資料的內容:
<script>$(document).ready(function(){   
  $.jqplot.config.enablePlugins = true; 
   plot1 = $.jqplot(
     'pie',
     [[['paladin(30)',30],['polly(28)',28],['hugo(35)',35],['keen(25)',25]]],
     { 
       seriesDefaults:{renderer:$.jqplot.PieRenderer, trendline:{show:true},rendererOptions:{sliceMargin:8},rendererOptions:{sliceMargin:8} },legend:{show:true} 
     }); 
 }); 
</script>

而完成後的作品如下:
觀看完成的作品後,如果你進一步希望將右上角的詳細資訊能有個連結的功能,則可以進一步去改寫圖形資料內容。譬如說,「paladin 」的資料,是由:['paladin(30)',30] 而來,而可以改寫成['<a href="#" onclick=DoLink("paladin")>paladin(30)</a>',30],如此,詳細資料裡面的 paladin 就可以支援點選功能了。此外只需要加上自行定義 的 DoLink。
<script>function DoLink(strName){alert(strName);}</script>  


2010年7月13日 星期二

線上編輯照片( pixlr )

如果 windows 的小畫家不能滿足你,但又不想裝其他影像編輯軟體,可以試試這線上編輯軟體 http://pixlr.com/editor/

點進去後,可以在 Language 切換語系,包含繁體中文。您可以上傳想要編輯的相片,並透過 pixlr 所提供的影像編輯功能盡情的揮灑一番,至於最後要儲存結果,也是沒問題,他可以讓你把自己的作品下載回去。

自己不用額外安裝軟體就可以直接編輯相片,真的蠻方便的。



參考 :

01. 用雲端影像軟體幹掉Photoshop、ACDSee
02. http://pixlr.com/editor/

2010年7月8日 星期四

IE 不支援 array.indexOf

今天在一篇介紹 JavaScript: array.indexOf 的文章,將他的程式碼下載執行一下,發現奇怪的問題,在 Chreom & FireFox 都可以正常執行,唯獨 IE 不可以。後來拜讀了 Colin Pear 的文章後,順利找到解法。

日後如果要使用 array.indexOf ,要在呼叫前,先引用一段來處理支援各家瀏覽器的方法:

if(!Array.indexOf){
  Array.prototype.indexOf = function(obj){
   for(var i=0; i<this.length; i++){
 if(this[i]==obj){
  return i;
 }
   }
   return -1;
  }
}

完整範例:

<html>
    <head><h1>JavaScript Array indexof </h1>
        <title>JavaScript array indexof </title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <script language="javascript" type="text/javascript">
            var array=new Array();
            array[0]="Rose";
            array[1]="India";
            array[2]=".net";
            array[3]="Rohini";
            document.write("<b>"+"Element in the array are "+"</b>"+ "<br />");
            document.write("================="+ "<br />");
            for(var i=0;i<array.length;i++){
                document.write(array[i]+"<br>")
            }
            document.write("================="+ "<br />");


   if(!Array.indexOf){
     Array.prototype.indexOf = function(obj){
      for(var i=0; i<this.length; i++){
    if(this[i]==obj){
     return i;
    }
      }
      return -1;
     }
   }


            var index = array.indexOf("India");
            document.write("<b>"+"Index of India on the array is: "+"</b>"+index);
        </script> 
    </body>
</html>
參考:
http://www.roseindia.net/java/javascript-array/javascript-array-index-of.shtml
http://www.pearweb.com/javascript/array-index-of.html

Google Docs Demo 真正即時的多人線上編輯功能

不知道大家對於多人編輯的環境需求機會多不多?如果你需要與許多人同時編輯一份文件時,在乎的應該是所謂的「即時性」,希望能夠越快看到別人所回應的訊息越方便。而 Google 也提供了一個簡單的共同文件「即時」編輯功能。

可以連結到 http://docs.google.com/demo


然後 Google Docs 會自動在網址後面加上他定義的編號,用來識別你目前正在編輯的這份文件。如果你希望你的朋友也可以一同編輯這份文件,那只需要將「Invite others to try this with you just by sharing this link」下方的 URL 分享給你的朋友,那大家就可以共同編輯了。

有一個需要注意的,就是這份共同編輯文件,存活時間只有 24 小時,時間到了之後,就會自動消失,所以你有保存的需求,則自己要另外去儲存這份文件就是了。

2010年7月1日 星期四

Session 遺失的狀況之一:ShowModalDialog之後,不能用window.open來開窗

在美麗的 selene 同事部落格,看到一篇有關於 session 遺失的探討,其中引用了 「javascript 的showModalDialog與window.open造成session遺失的問題」文章,進行了 showModalDialog 與 window.open 混搭使用的測試,自己覺得蠻有趣的。並非這兩者不能並存使用,但要記得,ShowModalDialog之後如果再用window.open來開窗,你的 session 值就會不見,這真不知是什麼怪邏輯,但自己還真的傻傻全部演練一遍,但都有準哩。日後如有使用 session 在存東西,這點可能就要好好注意一下了。

參考:
01. Session Issues from selene
02. javascript 的showModalDialog與window.open造成session遺失的問題

2010年6月29日 星期二

固定Table 的標頭 (Header)

當自己的 Table 內容太長時,會希望當 scrollbar 下拉後,還是能夠看到標頭。其實稍微搜尋一下,也可以看到蠻多範例在介紹。然而,如果你要考慮同時能用在 IE, FireFox, Chrome 等瀏覽器,其實還需花點時間。

我一開始是從 [jQuery]外掛特輯10:jscrollable-固定標頭與頁尾 這篇入門,而這的確真的很簡單,可惜,只能用在 IE。如果你只需要在 IE 上使用,可以考慮用這個。

而在黑暗執行續的一篇 GridView的標題欄、列凍結效果(跨瀏覽器版),的確作者很用心的介紹了他改良 Super Tables 的一些心路歷程。這真的可以跨瀏覽器,很不錯,也比較強大。

但自己最後挑選的,則是這個:Fixed Header Tables。主要是跟我目前專案需求的外觀比較相似。




測試程式下載

參考資料:
01. Fixed Header Tables
      http://fixedheadertable.com/
02.[jQuery]外掛特輯10:jscrollable-固定標頭與頁尾
      http://blog.finalevil.com/2009/10/jquery10jscrollable.html
03.GridView的標題欄、列凍結效果(跨瀏覽器版)
      http://blog.darkthread.net/blogs/darkthreadtw/archive/2009/02/18/supertable-plugin-for-jquery.aspx
04.jQuery Tools
      http://flowplayer.org/tools/index.html
      這一篇是在收集資料時,無意中看到的 jQuery 工具,免費,而且有些功能蠻炫的。譬如先
      前自己有介紹過一篇「使用 jQuery 實作 Tips 功能」,現在看到
      http://flowplayer.org/tools/tooltip/index.html ,就發現自己遜掉了。所以是個很不錯的參考。

20110927補充:
另外推薦 ken 所撰寫的「IdeaSparks ASP.NET CoolControls簡介」,對於習慣使用 GridView 的開發人員來說,是提供固定標頭最簡便的方式。

讓 Div 有 scrollbar

有時後,頁面顯示的字數太多,容易造成頁面編排變的很醜,但有不想犧牲掉需要顯示的資訊時,就會希望能有拉霸(scrollbar)的效果。

要達成這效果,只要使用一個 DIV ,並設定 overflow:auto 屬性即可。

範例如下:


<div style="width:300px;height:250px;overflow:auto;">
您要輸入的內容
</div>

參考資料:
01. DYNAMIC Forums
02. w3cschools - CSS overflow Property

2010年6月22日 星期二

給我一個圓角的框框

對於四四方方的按鈕或表格,如果可以讓它的邊邊角角圓滑一點,似乎會比較好看些。在 css3 ,其實早已提供相關的設定,但...這些的好處,偏偏 IE 都不支援;而工程師的難處,偏偏老闆都不知道。

最近看到 IE-CSS3 讓 IE 支援 CSS3  這篇文章的介紹,提到了 IE-CSS3 可以解決這問題。下載後,自己改寫了一下程式,讓原本四四方方的按鈕有了弧度。


先到 http://fetchak.com/ie-css3/ 去下載 ie-css3.htc
接著定義了樣式內容:

.button {   
 background-color:#3961CA;
 color:White;
 cursor:pointer;
 height: 22px; 
 border: 1px solid #c6ac6c;
 position: relative;
 padding: 1px 5px 1px 5px; 
 -moz-border-radius: 6px;
 -webkit-border-radius: 6px;
 border-radius: 6px;

 behavior: url(ie-css3.htc);
}


然後直接讓該按鈕  的 class 套用 button 。這樣就可以呈現出圓角按鈕了。

實作後發現,因為我的按鈕是使用伺服器控制項拉的,只要一按,頁面就會 postback 且重新載入一次頁面,這時,就會很明顯的看到我的圓弧按鈕變成正正方方,最後又在變成圓弧。對於這樣的變化,總是覺得有點怪,心裡頭似乎不大滿意這樣的效果。

於是另外又比較了一個可以產生圓弧的 JavaScript : DD_roundies 。他只會處理針對 IE 瀏覽器的圓弧效果,但這也還好,因為其他瀏覽器幾乎都支援 css3,可以很容易的達成我所需的效果。

實作 DD_roundies,首先要到 http://www.dillerdesign.com/experiment/DD_roundies/  下載 0.0.2a-min.js,因為我並不打算去改寫作者寫好的 code,所以直接下載壓縮後的檔案即可。

而使用方式,則是將符合您所要套用的物件放進 DD_roundies.addRule( ) 裡,而非 IE 的部份,則自行定義在 Style 裡面。

以下是完整的測試程式碼:

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>未命名頁面</title>
  <script type="text/javascript" src="DD_roundies_0.0.2a-min.js"></script>
  <script>
  DD_roundies.addRule('.button', '6px');
  </script>
  <style>
  .button {  
  background-color:#3961CA; color:White; cursor:pointer;padding: 1px 5px 1px 5px; 
  /* Do rounding (native in Safari, Firefox and Chrome) */
  -webkit-border-radius: 6px;
  -moz-border-radius: 6px;    
  }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <div>    
  <asp:Button ID="btnSearch" CssClass="button"  Text="存檔" runat="server" />
  </div>
  </form>
</body>
</html>


經過這兩個範例,自己比較欣賞 DD_roundies ,因為即便是網頁發生 postback 重新載入,圓弧的按鈕都不會有任何變化,這正也是我想要的結果。

參考頁面:
01. CSS3 support for Internet Explorer 6, 7, and 8
02. DD_roundies
03. CSS 3 的新玩意
04. IE-CSS3 讓 IE 支援 CSS3

觸發 post back 固定畫面位置(二)

在 .net 2.0 開始,提供了  MaintainScrollPositionOnPostback 可供我們控制網頁 post back 後還可以回到原先瀏覽的位置。

設定方式很簡單,只要在 aspx 頁面寫下:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ScrollTest3.aspx.cs" Inherits="ScrollTest3"  MaintainScrollPositionOnPostback="true" %>

經實測,IE ok, Firefox ok,  但 Google Chrome 與 Safari 卻失敗...

為了達到放諸四海皆準的目的,不得動些手腳來完成。

首先,加入一個「瀏覽資訊檔」。並將他放到 App_Browsers 資料夾下。


點進去剛剛新建的檔案,可以看得出來他是用 xml 格式定義了各種不同瀏覽器的設定。而我們需要將目前未定義的瀏覽器,手動新增進去。所以,複製以下內容,貼到 <browsers></browsers>裡。


<browser id="Safari3" parentID="Safari1Plus">
    <identification>
      <userAgent match="Safari/\d+\.\d+" />
    </identification>
    <capture>
      <userAgent match="Version/(?'version'\d+\.\d+)" />
    </capture>
    <capabilities>
      <capability name="browser" value="Safari3" />
      <capability name="version" value="${version}" />
    </capabilities>
    <controlAdapters>
      <adapter controlType="System.Web.UI.WebControls.Menu"
               adapterType="" />
    </controlAdapters>
  </browser>
  <browser id="GoogleChrome" parentID="Safari3">
    <identification>
      <userAgent match="Chrome/(?'version'\d+\.\d+)" />
    </identification>
    <capabilities>
      <capability name="browser" value="Googlebot" />
      <capability name="supportsMaintainScrollPositionOnPostback" value="true" />
    </capabilities>
  </browser> 

修改後在 chrome 重新執行,就可以正常返回原先的位置了。

如果你懶的每一頁都去設定,可以直接在 web.config 的 <system.web></system.web> 加上 <pages maintainScrollPositionOnPostBack="true"></pages>

參考資料:
01. 最简单的方法,让 ASP.NET Menu 控件在 Google Chrome 浏览器上正常显示
02. 讓Chrome瀏覽器支援MaintainScrollPositionOnPostback屬性
03. postback 後,網頁如何 keep 在相同位置
04. 觸發 post back 固定畫面位置
05. 網頁PostBack後回到原來停留的位置

2010年6月21日 星期一

ubuntu上 擷取 mp3 檔案

在 Ubuntu,如果想要針對某個 mp3 檔案,進行片段擷取,可以使用 mp3splt 這軟體,蠻簡單、速度也蠻快的。安裝方式,只需要打開 Synaptic 套件管理程式,在搜尋列打上 mp3splt ,就可以找到相關套件。你只要點選 mp3splt ,就會將相關的程式也一併安裝好。但如果你希望使用圖形介面,則要記得再勾選 mp2splt-gtk 。


自己比較喜歡用命令模式來操作,因為圖形模式,個人覺得有點太囉唆了。只要在命令列上寫:
                mp3splt     檔名      起始時間     結束時間

這樣就可以很快的完成切割動作,真的很快喔,害我一度以為是否發生錯誤了,怎麼都沒有在動。其中,起始時間 與 結束時間 是用 分.秒   來表示, 舉例來說, 2.20  代表 2 分 20 秒



參考資料:

01: Split mp3 in Ubuntu
02: Mp3Splt:MP3/OGG 分割器
03: Video2mp3: 可轉換 YouTube 和其它類似網站

jQuery 判斷 checkbox 是否被勾選

判斷 checkbox 是否有被勾選有兩個方式。

假設在頁面上宣告了一個 checkbox

<input type="checkbox" id="cb1" />Item1

方法一:


透過 attr( ) 去取得 checked 目前的屬性狀態,如果有被勾選,會回傳 true ; 反之, 回傳 false

 if($("#cb1").attr('checked')==true)
            alert('cb1 checked');

方法二:

直接在 selectors 裡面限制要抓的必須是有被勾選的項目。如果有被勾選,會回傳 on ; 反之,回傳 undefined


 if($("#cb1:checked").val()=="on")
            alert('type2:cb1 checked');

相關文章:

1.checkbox 全選或取消
2.JQuery 取得 checkbox 群組內被選取的項目
3.jQuery 取得 radio button 目前選取的值
4.jQuery 取得 dropdown list 選取的值

2010年6月20日 星期日

ubuntu 收聽廣播

在 windows 上,我喜歡連結到 http://jason.onweb.idv.tw/radio.asp 收聽廣播,但在 linux (ubuntu) ,似乎一時找不到可以播放廣播的方法。還好看了 Linux 上面聽 Hinet 廣播 這篇文章,發現 Hinedo 這軟體,真的很棒。

以下分享如何安裝 Hinedo 的步驟:

1.到 http://of.openfoundry.org/projects/814/download 下載 hinedo_0.4-1ubuntu1_i386.deb
2.將下載後的檔案解開並安裝,直接按右鍵,選擇「以 GDebi套件安裝程式 開啟」。接著就直接一直裝下去即可。
3. 安裝完成後,就會在  應用程式-影音 找到 Hinedo 電台選播器,並點擊它。
4.接著就可以在工具列上,看到 「Hi」的小圖示,點選後,我們第一次使用可以先按「線上更新電台清單」,以取得最新的電台清單。日後,只需在 Hinedo 的分類裡,找到你想要收聽的廣播,點擊後就可以收聽了。

2010年6月17日 星期四

遠端桌面與本機的檔案互傳

使用 windows 的遠端桌面,可以讓你直接去操作遠端的機器。但是,當你有檔案需要上載到遠端機器上時,老是透過 usb 插來插去或 email 傳來傳去,總是覺得不方便。當然,如果有開網路芳鄰來解決,當然會是一個好方法,可我偏偏遇到一個不允許使用網芳的遠端機器。

這件事我一直耿耿於懷的對同事抱怨,結果他說,「這麼簡單事還不會?」

蒼天有眼,小弟真的不會。接著就看他傳來武功祕笈:

1.在遠端桌面的視窗,點選「選項」。


2.選擇「本機資源」頁籤,並點選「詳細資料」。接著會跳出「本機裝置和資源」,然後將所有選項都打勾,最後一直按確定。打完收工。


跪拜,再跪拜,三跪拜 .....

2010年6月9日 星期三

jQuery 取得 radio button 目前選取的值

可以使用 $("input[name=ra]:checked").val()

但如果想進一步去判斷使用者有沒有做出選擇,
則可以加上一句來判斷:

if($("input[name=ra]:checked").val()==undefined)

一個簡單範例如下:

function show()
{

    if($("input[name=ra]:checked").val()==undefined)
        alert('您尚未選取!');
    else
        alert($("input[name=ra]:checked").val());
}


相關連結:jQuery 取得 dropdown list 選取的值

2010年6月4日 星期五

攔截「具有潛在危險 Request.Form 的值」事件

當 user 在 asp.net 開發的網頁上,敲入: <span> 等 Html 標籤符號時,會出現以下錯誤訊息:

具有潛在危險 Request.Form 的值已從用戶端 (ctl00$ContentPlaceHolder1$tbxQuestion=\"<span>ss</span>\") 偵測到

如果想進一步去攔截這訊息,事先把相關錯誤處置好,以免讓 user 看到所謂「當機」或「亂碼」的頁面,則可以在

Global.asax 的 void Application_Error(object sender, EventArgs e) 加上以下程式碼來處理。

void Application_Error(object sender, EventArgs e) 
    { 
        // 發生未處理錯誤時執行的程式碼
        // At this point we have information about the error
        HttpContext ctx = HttpContext.Current;

        Exception exception = ctx.Server.GetLastError();

        string errorInfo =
           "<br>Offending URL: " + ctx.Request.Url.ToString() +
           "<br>Source: " + exception.Source +
           "<br>Message: " + exception.Message +
           "<br>Stack trace: " + exception.StackTrace;


        if (exception.StackTrace.Contains("ValidateString"))
        {   
     //因為我所要攔截的訊息會有「ValidateString」字眼,所以我將這事件另外導到其他頁面
            ctx.Response.Redirect("Bid_ValidError.aspx?PreUrl=" + ctx.Request.Url.ToString());            
        }
        else
        {
            ctx.Response.Write(errorInfo);
        }

        // --------------------------------------------------
        // To let the page finish running we clear the error
        // --------------------------------------------------
        ctx.Server.ClearError();
       

    }

參考資料:
要如何攔截 ASP.Net 防範 XSS 攻擊的錯誤畫面?
ASP.Net Custom Error Pages

2010年5月12日 星期三

SyntaxHighlighter 讓文章的程式碼顯示行號

常常看到有些網友的文章,有關程式碼的段落都可以處理得很漂亮,原來是有個SyntaxHighlighter 好幫手。可以直接到 http://alexgorbatchev.com/wiki/SyntaxHighlighter 網站下載完整程式來測試一下。而以下則是一段簡單 Demo :

function getValue()
{
  var i=0;
  i=i+1;
  alert(i);
  
}  

套用了SyntaxHighlighter  JS 後,只需在想要呈現效果的地方,加上 <pre class="brush: html"> ... </pre>,就可以很輕鬆的完成,只是要等一會兒時間,效果才會出來,可能因為要下載比較多的 js 與 css 檔,所以沒有這麼即時。

接著紀錄自己修改 blogspot 的步驟。
1. 點選右上角的[自訂],並進入[版面配置],選擇修改 HTML。

2.在修改範本裡,找到 <head> 標籤,並在裡面加上以下程式:

<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css'  rel='stylesheet' type='text/css'/>
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPhp.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPython.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushRuby.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushVb.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'></script>
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushPerl.js' type='text/javascript'></script>
<script language='javascript'>
SyntaxHighlighter.config.bloggerMode = true;
SyntaxHighlighter.config.clipboardSwf = 'http://alexgorbatchev.com/pub/sh/current/scripts/clipboard.swf';
SyntaxHighlighter.all();
</script>

3.最後按下「儲存範本」,如此,日後只要使用<pre class="brush: html"> ... </pre>,就可以出現顯示行號的效果了。

以前曾經介紹過 Blog 文章中貼程式碼,現在再推薦另一種顯示方式,可以多多比較。

【補充20101001】
在上述範例中,我過於信任作者每次 release 的版本應該是最新最好的,所以我都是參考他 current 的版本。但沒想到作者釋出 3.0.83 版後,畫面真的有點怪,行號出現的位置似乎都會亂掉,但發現許多網友使用舊的版本 2.x 版都還蠻穩定的,所以後來自己將原先使用 current 改為指定舊的版本 2.1.382 ,反而比較美觀。

可以參考作者歷次 release 的版本:
http://alexgorbatchev.com/SyntaxHighlighter/hosting.html


【補充20120214】
補充一篇德瑞克的說明。請參考:

接 store procedure 的回傳結果

如果已經有人寫好了一支共用 store procedure,他回傳值可能是:

欄位A   欄位B   欄位C
====     ====     ====
A001     A002    A003
B001     B002    B003
C001     C002   C003

那我們可能會建一個暫存資料表
去接收 stroe procedure 的回傳值。

create table #tmp ( fieldA  nvarchar(max), fieldB nvarchar(max), fieldC nvarchar(max) )

insert into #tmp ( fieldA, fieldB, fieldC)
exec mystoreprocedure

然而,如果共用的 store procedure 回傳了30 幾個欄位,如果用上述方法,就得建立一個30幾個欄位的暫存資料表。這作法真的會讓人很無力,且容易發生「 An INSERT EXEC statement cannot be nested 的錯誤訊息[注3]」。所以這情況,可以考慮 OPENROWSET[注1] 來完成。雖然共用的 store procedure 回傳了30幾個欄位,但我真正會使用的,可能只有其中二、三個。所以可以寫成:


SELECT com_empno,com_cname FROM
OPENROWSET('SQLOLEDB',
'.';'sql_account';'sql_password',
'exec swapdb.dbo.pr_Find_ITRI_BossID')

此外,有個重要的地方需注意,就是使用  OPENROWSET 時的查詢字串,一定要「常數」,不可以是「變數」。因此,如果你的共用 store procedure 需要傳參數進去,則不可以將查詢字串寫成  'exec swapdb.dbo.pr_Find_ITRI_BossID  '+@seqsn   (參考[注2])

如果遇到需要傳參數的 store procedure,則可以透過先宣告一個字串變數( declare @str),來組出 OPENROWSET 的所有指令,最後再用 exec 去執行它 ( exec (@str) )來完成,這樣就可以避開 OPENROWSET 的查詢字串,一定要「常數」的限制。



參考:
[注1]OPENROWSET (Transact-SQL)
[注2]OpenDataSource、OPENQUERY、OPENROWSET用法
[注3]An INSERT EXEC statement cannot be ne...
[注4]感謝 Ken 熱心指導

2010年5月1日 星期六

工作病

常見的工作病:

」:遇到棘手事情,都推給別人。
」:沒有執行力,老是愛拖延。
」:害怕不熟悉的事物,缺乏膽識。
」:明明自己沒出半點力,卻都吹噓是自己的功勞。
」:只顧自己爽快,不會替他人想想。


藥方一:在時間體力可負荷之下,樂當「Yes Man」。
藥方二:列出你的待辦清單,從最不想做的事情開始動手。
藥方三:功不唐捐。今天你的努力,會是明天你的資產。
藥方四:你今天撒一個謊,明天會用更多的謊言來掩飾今天的行為。
藥方五:心裡放不下別人,是沒有慈悲;心裡放不下自己,是沒有智慧。

仔細想想,自己好像也得了一些工作病了ㄟ...
這五種病,好像跟燙傷處理的 沖、脫、泡、蓋、送 很相似。

2010年4月28日 星期三

透過 CSS 讓列印時 Table 的標頭內容可以每頁重複

希望透過 CSS 在使用 window.print() 時,列印出來的 Table 內容(假設資料量很大),可以在每一頁都能重複標頭(Header)內容。首先,可以使用 CSS 裡的 page-break-before: always,可以強迫換頁。所以,我在範例中定義了:

<style type="text/css">
.newpage
{
page-break-before: always; 
}
</style>

假設我目前的 Table 內容如下:

<table border="1" id="tbDemo" >
  <thead>
  <tr><th id="pageHeader">編號</th></tr>
  </thead>
  
  <tbody>
  <tr><td>1</td></tr>
  <tr><td>2</td></tr>
  <tr><td>3</td></tr>
  <tr><td>4</td></tr>
  <tr><td>5</td></tr>
  <tr><td>6</td></tr>                             
  </tbody>
</table>    
接下來,使用 jQuery 讓 Table 每兩筆資料(實際上是情況而調整筆數設定)就自動產生一個標頭內容,且將這標頭內容套用剛剛加入的 CSS 設定。

<script>
$(function(){

var $rows=$("#tbDemo > tbody > tr");  
$rows.each(function(i) {
//取2的倍數,排除最後一筆
if((i+1) % 2==0 && i!=($rows.size()-1))
{
   $(this).after("<tr class='newpage'><th>"+$("#pageHeader").text()+"</th></tr>");      
}
})
})
</scrip>

如此,在使用預覽列印時,就可以看到以下每兩筆資料一頁且有標頭的結果:

原本以為,故事這樣就結束了,但...唉,看一下 FireFox 的結果~

在 FireFox (ver 3.6.3) 的標頭檔,卻重複了兩次。主要原因,是因為我的 Table 定義,用了 <thead> 與 <tbody> 的樣式。在 FireFox 裡,如果遇到換頁的時候,他會自動為每一頁的 Table 內容加上 <thead>  ,而我剛剛又手動用 jQuery 新增了一次,所以才會得到這結果。換言之,使用 FireFox 的瀏覽器,只要你的 Table 內容有用<thead> 與 <tbody> 的樣式,那每一頁自動都會幫你加上標頭喔。只是,目前我真的不希望他主動幫我加。

此時,或許加上判斷瀏覽器的程式,來決定是否要使用 jQuery 來手動增加標頭,會是一個辦法。但,我沒有這麼做。我索性就不用<thead> 與 <tbody> 的樣式。我將 Table 的定義改成以下:

<table id="tGen"  border="1" >
<tr class="trHeader"><td>編號</td></tr>
<tr class="trBody"><td>1</td></tr>
<tr class="trBody"><td>2</td></tr>
<tr class="trBody"><td>3</td></tr>
<tr class="trBody"><td>4</td></tr>
<tr class="trBody"><td>5</td></tr>
<tr class="trBody"><td>6</td></tr>
</table>

而 jQuery 也調整如下:
<script>
$(function(){
 var $rows=$("#tGen tr[class=trBody]");  
   $rows.each(function(i) {
   //取2的倍數,排除最後一筆
  if((i+1) % 2==0 && i!=($rows.size()-1))
  {     
      $(this).after("<tr class='newpage' ><td>"+$("#tGen tr[class=trHeader]").text()+"</td></tr>");
  }
   })    
});   
</script>

而最後出來的結果,就皆大歡喜了。

文章參考:
MEMO-網頁列印強迫分頁
CSS page-break-before Property
.after()
web打印中如何强制分页
Printing Tables
Printing Headers

測試程式下載:
https://sites.google.com/site/paladinsharefiles/home/filelist/RPT.zip?attredirects=0&d=1

使用 css 的 @media 控制列印樣式

想讓目前正在瀏覽的網頁直接列印出來,同時希望有些按鈕或控制項在列印時隱藏,以前我都會用 javascript 去隱藏列印時不想出現的物件。但當我看了 CSS Media Types 這篇文章後,真的就有不一樣的想法了。原來可以透過 css 裡的 media="screen" 或 media="print" 來宣告是要在螢幕上面顯示,還是只有在列印時顯示。

範例程式:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Print Friendly</title>
    <style type="text/css">
        
   .button
   {
   text-align:center;
   color:#00B3ED;
   border:1px solid #BBBBBB;
   background-color:#F0F0F0;
   line-height:12px;
   padding:4px 0px 0px;
   width: auto;
   font-size: 12px;
   }
          
    @media print 
    {
        /* 列印將物件隱藏 */
        .button { display: none; } 
    }
    </style>
    
</head>
<body>

<input type="button" id="btnPrint" value="列印" class="button" >
網頁列印
</body>
</html>

我在 style 裡面,定義了 .button  的樣式,但接著在 @media print 指定列印時,.button 的額外設定,也就是要隱藏所有有套用 .button 樣式的物件。

下圖是範例程式的執行結果,可以發現,在預覽列印時,畫面上的「列印」按鈕已經消失了。透過 CSS 來完成這功能,不管是使用瀏覽器選單選列印、預覽列印或Ctrl+P,都可以適用。



最後,附上一張沒有使用@media print 指定列印的結果,以供比較。可以看出在預覽列印時,畫面上的「列印」按鈕跑出來了。


參考文章:
活到老學到老-CSS Media Types