2012年9月20日 星期四

POI 自訂方法

copyRow() - 複製Row的method
updateFormula() - 計算Excel公式結果的方法

複製Row的method

POI有cloneSheet()這個複製sheet的方便方法
但是卻缺少複製sheet裡的row的方法
當然考量到公式的計算與合併的儲存格的位置
這個方法須自訂是不太令人意外

private void copyRow(HSSFSheet worksheet, int sourceRowNum, int destinationRowNum) {
    // Get the source & new row
    HSSFRow newRow = worksheet.getRow(destinationRowNum);
    HSSFRow sourceRow = worksheet.getRow(sourceRowNum);

    // If the row exist in destination, push down all rows by 1 else create a new row
    if (newRow != null) {
        worksheet.shiftRows(destinationRowNum, worksheet.getLastRowNum(), 1);
    } else {
        newRow = worksheet.createRow(destinationRowNum);
    }

    // Loop through source columns to add to new row
    for (int i = 0; i < sourceRow.getLastCellNum(); i++) {
        // Grab a copy of the old/new cell
        HSSFCell oldCell = sourceRow.getCell(i);
        HSSFCell newCell = newRow.createCell(i);

        // If the old cell is null jump to next cell
        if (oldCell == null) {
            newCell = null;
            continue;
        }

        // Use old cell style
        newCell.setCellStyle(oldCell.getCellStyle());

        // If there is a cell comment, copy
        if (newCell.getCellComment() != null) {
            newCell.setCellComment(oldCell.getCellComment());
        }

        // If there is a cell hyperlink, copy
        if (oldCell.getHyperlink() != null) {
            newCell.setHyperlink(oldCell.getHyperlink());
        }

        // Set the cell data type
        newCell.setCellType(oldCell.getCellType());

        // Set the cell data value
        switch (oldCell.getCellType()) {
            case Cell.CELL_TYPE_BLANK:
                newCell.setCellValue(oldCell.getStringCellValue());
                break;
            case Cell.CELL_TYPE_BOOLEAN:
                newCell.setCellValue(oldCell.getBooleanCellValue());
                break;
            case Cell.CELL_TYPE_ERROR:
                newCell.setCellErrorValue(oldCell.getErrorCellValue());
                break;
            case Cell.CELL_TYPE_FORMULA:
                newCell.setCellFormula(oldCell.getCellFormula());
                break;
            case Cell.CELL_TYPE_NUMERIC:
                newCell.setCellValue(oldCell.getNumericCellValue());
                break;
            case Cell.CELL_TYPE_STRING:
                newCell.setCellValue(oldCell.getRichStringCellValue());
                break;
        }
    }

    // If there are are any merged regions in the source row, copy to new row
    for (int i = 0; i < worksheet.getNumMergedRegions(); i++) {
        CellRangeAddress cellRangeAddress = worksheet.getMergedRegion(i);
        if (cellRangeAddress.getFirstRow() == sourceRow.getRowNum()) {
            CellRangeAddress newCellRangeAddress =
                  new CellRangeAddress(newRow.getRowNum(),
                    (newRow.getRowNum() +
                    (cellRangeAddress.getLastRow() - cellRangeAddress.getFirstRow())
                     ),
                    cellRangeAddress.getFirstColumn(),
                    cellRangeAddress.getLastColumn());
            worksheet.addMergedRegion(newCellRangeAddress);
        }
    }
}


計算Excel公式結果的方法

使用POI填入公式時,Excel表並不會將計算後的結果立刻展示
它的作法是顯示出公式(舊版office)或待開啟編輯權限後才計算(office 2010)
以下方法能計算特定行數的Excel公式


private void updateFormula(HSSFWorkbook wb, HSSFSheet sheet, int row){    
    Row r = sheet.getRow(row);    
    Cell cell = null;    
    HSSFFormulaEvaluator eval = new HSSFFormulaEvaluator(wb);    
    for(int i = r.getFirstCellNum(); i < r.getLastCellNum(); i++){    
        cell = r.getCell(i);    
        if(cell.getCellType()== Cell.CELL_TYPE_FORMULA)    
            eval.evaluateFormulaCell(cell);    
    }    
}


HSSFFormulaEvaluator提供了evaluateFormulaCell(Cell cell)方法
計算公式保存結果,但不改變公式
而evaluateInCell(Cell cell)方法是計算公式,並將原公式替換為計算結果,
也就是說該單元格的類型從Cell.CELL_TYPE_FORMULA改為Cell.CELL_TYPE_NUMBERIC

HSSFFormulaEvaluator提供了靜態方法evaluateAllFormulaCells(HSSFWorkbook wb)
計算一個Excel文件的所有公式,用起來很方便

2012年9月19日 星期三

除錯 - java.lang.IllegalStateException: getOutputStream() called

今天碰上的這個錯誤訊息是在JSP頁面發生

ServletResponse.getOutputStream()
ServetResponse.getWriter()

這兩個方法都會有可能在JSP輸出頁面內容的時候被調用
當其中一個方法被調用過後,再呼叫另一個方法就會報錯誤
解決的方法不外如下:

out.clearBuffer();                           //清除buff
out = pageContext.pushBody();    //更新out的內容

2012年7月5日 星期四

C# 資料庫存取

首先關於資料庫的開啟可考慮兩種方式
1.用using{...}區塊
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    con.Open();
    ....
}

這種方法會在脫離區塊後立刻釋放Connection物件的資源
所以不用使用資料庫的關閉方法(如Close()、Dispose())
另外就是正規的寫法

2.

using System.Data.SqlClient;
....
SqlConnection con = new SqlConnection("xxx");       //xxx = connection string
SqlCommand cmd = new SqlCommand("xxx", con); //xxx = SQL command 
SqlDataReader dr;
...
con.Open();
dr = cmd.ExecuteReader();    //使用DataReader物件讀取資料庫內容
...
con.Close();  


SqlCommand物件的幾個常用方法跟屬性
   ExecuteNonQuery() -
   可以執行INSERT、UPDATE、DELETE等SQL command,成功時回傳受影響的紀錄筆數

   ExecuteScalar() -
   適用於結果集只回傳一個資料列的第一欄,例如SQL command 的COUNT()、MAX()等

   ExecuteReader() -
   執行Command物件中所指定的SQL的SELECT敘述,並建立一個DataReader來瀏覽資料

   CommandText -
   設定或取得要執行的命令


SqlCommand使用範例:

string sqlStr = string.Empty;
sqlStr = "INSERT INTO TableA(姓名, 聯絡方式, 薪水)" + "VALUES(@name, @tel, @pay)"
SqlCommand cmd = new SqlCommand( sqlStr , con);
cmd.CommandType = CommandType.StoreProcedure;     //預先編譯SQL處理函式

cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@tel", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@pay", SqlDbType.NVarChar));
cmd.Parameters["@name"].Value = "vc56";
cmd.Parameters["@tel"].Value = "0000000";
cmd.Parameters["@pay"].Value = "$20000";

cmd.ExecuteNonQuery();


DataReader有幾個方法跟屬性較為重要,以下列出:
   FieldCount屬性 -
   將已查詢欄位的總數傳回
 
   Read() -
   將指標移到下一筆,並判斷是否指到EOF
   如果到資料結尾則回傳false

   Item[i] -
   取得第i欄的資料內容

   Item["欄位名稱"]集合 -
   取得指定欄位名稱所指的資料內容

   GetValue(i) -
   取得第i欄位的資料內容,傳回值為object資料型別

   IsDBNull(i) -
   判斷第i個欄位使否為資料庫Null值

使用範例:
while(dr.Read())
{
  for(int i = 0; i < dr.FieldCount; i++)
  {
      textBox1.Text += dr[i].ToString() + "\t";
  }
}

以執行效率來說,使用索引會比使用欄位名稱還快
此外,取出時可使用方法來省略手動轉型的動作,如下:

while(dr.Read())
{
  textBox1.Text += dr.GetString(0) + "\t";
  textBox1.Text += dr.GetInt32(1).ToString() + "\t";
  ...
}


DataSet則是ADO.NET的特色
他會先將資料庫的內容存至記憶體中,並得以執行離線操作
操作完畢後再將內容回寫至資料庫中
適用於多用戶端資料存取,當然占用的記憶體多是不可避免的事
DataSet中可以包含一個以上的DataTable物件
由DataAdapter使用Command物件執行SQL command
再將取得的資料寫至DataSet,如此就能用DataTable存取資料表,範例:

using System.Data.SqlClient;
using System.Data;

.....
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    DataSet ds = new DataSet();
    SqlDataAdapter dsContent = new SqlDataAdapter("xxx", con);  //xxx = sql command
    dsContent.Fill(ds, "Content");    //使用Fill()時,DataAdapter會自動連線到資料庫
    /*
    Adapter也可以改成SqlDataAdapter dsContent = new SqlDataAdapter();
                                     dsContent.SelectCommand = cmd;
    這樣就可以使用SqlCommand物件來保持彈性


    另外有幾種特性語法示範
   int n = ds.Tables.Count;                          //取得DataSet中DataTable的總數
   String tName = ds.Table[i].TableName;  //取得第i個DataTable的表格名稱
   */
   DataTable dt = ds.Tables["Content"];     //對應SqlDataAdapter  Fill ()時填入的資料表名
   for(int i = 0; i < dt.Rows.Count; i++)
   {
        for(int j = 0; j < dt.Columns.Count; j++)
        {
              textBox1.Text += dt.Rows[i][j].ToString() + "\t";
        }
        textBox1.Text += Enviroment.NewLine;
   }
}
....


DataSet另外尚有使資料表之間產生關連的操作

ds.Realations.Add(  "關聯名稱",
      ds.Tables["dt1"].Columns["dt1要關聯的欄位名稱"],
      ds.Tables["dt2"].Columns["dt2要關聯的欄位名稱"],
)

日後可與DataView物件結合使用
dataGridView1.DataSource = ds;
dataGridView1.DataMember = "dt1";

dataGridView2.DataSource = ds;
dataGridView2.DataMember = "dt1.關聯名稱";

2012年7月3日 星期二

[轉貼] C# 淺析SqlConnection的dispose和close方法差異

原文

引用微軟ADO.Team的經理的話說,sqlconnection的close和dispose實際是做的同一件事,
唯一的區別是Dispose方法清空了connectionString,即設置為了null.

SqlConnection con = new SqlConnection("Data Source=localhost;Initial Catalog=northwind;User ID=sa;Password=steveg");
        con.Open();
        con.Close();
        con.Open();
        con.Dispose();
        con.Open();

    上例運行發現,close掉的connection可以重新open
    dispose的不行,因為connectionstring清空了,
    會拋出InvalidOperationException提示The ConnectionString property has not been initialized,
    但請注意此時sqlconnection對象還在。

    如果dispose後給connectionString重新賦值,則不會報錯。 
    由此得出的結論是不管是dispose還是close都不會銷毀對象,即不會釋放內存,
    它們會把sqlconnection對象丟到連接池中,那此對象什麼時候銷毀呢?
    我覺得應該是connection timeout設置的時間內,
    如果程序中沒有向連接池發出請求說要connection對象,sqlconnection對象便會銷毀,
    這也是連接池存在的意義。

    剛開始以為dispose會釋放資源清空內存,
    如果這樣的話,連接池不是每次都是要創建新對象,那何來重用connection呢?
    在網上看到很多人說close比dispose好,
    我想真正的原因是dispose後的sqlconnection對象要重新初始化連接字符串而已,
    並不是像某些人說的dispose會釋放對象。

    所以在try..catch和using的選擇上大膽的使用using吧,
    真正的效率差異我想可能只有百萬分之一秒吧
    (連接池重用該連接對象初始化連接字符串的時間),
    而且enterprise library中封裝的data access層全是用的using,
    從代碼的美觀和效率上綜合考慮,using好 

    補充:
    using不會捕捉其代碼快中的異常,
    只會最後執行dispose方法,相當於finally{dispose},
    本文主要是想說明dispose和close的差異,因為using是絕對dispose的,
    可是如果人為的寫try..finally有的人會選擇close有的人會選擇dispose,
    實際上在這2者的選擇上是有差異的。

    2012年7月1日 星期日

    C# 文字控制項-自動完成輸入功能

    C#的自動完成功能提供了當使用者在文字方塊中輸入的資料符合條件時
    便會自動填入或彈出符合的字串以提升使用者輸入資料的效率

    TextBox或ComboBox 中的三個屬性須注意:
    AutoCompleteSource、AutoCompleteMode、AutoCompleteCustomSource



    AutoCompleteSource的常用屬性值包括

    1.HistoryList - 以URL中的歷史清單作為自動完成輸入功能的來源
       ex:textBox1.AutoCompleteSource = AutoCompleteSource.HistoryList;
       程式碼部分以下類推

    2.RecentlyUsedList - 以URL中的歷史清單和最近瀏覽過的URL為來源

    3.AllUrl - 以最近瀏覽過的URL作為自動完成輸入功能的來源

    4.FileSystem - 以檔案系統為來源

    5.FileSystemDirectories - 以磁碟和目錄為來源,不包括檔案

    6.AllSystemSources - 以AllUrl和FileSystem為來源

    7.None - 取消自動完成輸入功能來源

    8.CustomSource - 自訂來源,須搭配AutoCompleteCustomSource屬性設定



    AutoCompleteMode常用屬性

    1.Append - 將最可能相符之候選字串其餘部分附加到現有字串之後,並反白顯示
       ex:textBox1.AutoCompleteMode = AutoCompleteMode.Append;

    2.Suggest - 挑選出最符合的字串於下拉選單的選項中

    3.SuggestAppend - 混用上述兩種功能

    4.None - 停用自動完成輸入功能


    AutoCompleteCustomSource屬性

    除了直接在Widget屬性介面上設計外
    也可以用程式碼部分撰寫,如:

    string[] content = new string[]{"a","b","c"};
    AutoCompleteStringCollection newAdd = new AutoCompleteStringCollection();
    newAdd.AddRange(content);

    textBox1.AutoCompleteMode = AutoCompleteMode.Suggest;
    textBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
    textBox1.AutoCompleteCustomSource = newAdd;

    2012年6月27日 星期三

    [轉貼] Business or Geek?

    原文


    這個話題來自於華蟒用戶組郵件列表2010年1月的一個貼子,因為我本身不是該組的訂閱者,最近無意中搜索到,覺得有點意思就分享一些想法。
    通常想創業的同學應該也遇到過這樣的一些情況,你有一個想法需要想集思廣益地跟大家分享討論,以便*期待*得到更多的人認同,從而再去推進行動。這裡的討論也由一位叫sliuqin的同學展開,他希望跟老婆想要做一個賣茶的電子商務網站:
    *關於我們*: 目前,我們分別在兩家大型的電子商務公司做*前端開發*,工作2年了。朝九晚六的工作雖然挺安逸,但不是自己想要的,對!我們想創業了。


    *創業項目*: 獨立電子商務網站,主營:茶葉和茶葉相關商品。
    *項目優勢*: 老爸是開茶葉店的。
    *項目開發:* 週期:10個月 人員:
    - 我,負責網站後台開發(Ubuntu server + python + django) - 我老婆,負責前端開發,用戶體驗和視覺設計。
    項目資金:10w,包含我們一年的生活費,和後期的服務器等,吃住在家裡(做寄生蟲)
    現在我們開始了一部分工作,但是是公司工作之餘,我們兩個人的人生經歷還少,我本身也沒有做過專業的python開發,所以想聽聽大家的意見:這個計劃*可行嗎* ? 但這裡討論的不是這個項目是否可行,我想說的如同以下兩位
    • @john :  「你是想賣茶葉啊,還是想賣網站?」
    • @誠子 : 「business or geek?」
    這真的值得作為開發人員要創業時仔細想一想,我也總會走進這樣的一個誤區,到底是在為了一項生意還是因技術追求而做呢?而且這種情況往往更多的發生在技術人員向產品轉型的人群,他們會以技術的角度去衡量一個項目,到底是用.net還是Java,是用HTML5還是HTML4,是使用Django還是Webpy等等……而忽略了去判斷商業的本身,商業計劃可行嗎?盈利模式可靠嗎?目標受眾呢?如何推進呢?…….

    Business是事業,geek是愛好。當然,我從不認為純技術流不能創造一家賺錢的互聯網公司,這樣的公司在互聯網行業中俯首皆是。而且還有部分互聯網公司出現的時候就不知道該怎麼盈利,也就總會有聽到說:「等我們的用戶數足夠大了,我們就會想到盈利方式。」但如果我們認為這些創業者們沒有想過怎麼去「Business」的話,那我想一定是我搞錯了一些東西,他們有想,他們有想得到的某個固定人群,通過技術和人群建立競爭壘壁,爭奪人群本身就是一項「商業」。所以他們選擇了快速開發讓市場去驗證終端需求,而絕對不會在技術的選擇上浪費太多的時間。
    繼續拿<REWORK>說事,」start with business, not a startup」說的就是這個意思,從一開始就想辦法賺錢,行動起來!別用10個月時間+不熟悉的python來做一個電子商店了,架個開源網店就開賣吧,兄弟。
    • 問某遊戲公司CEO:「你們都用什麼技術?」答:「沒關係,用最熟悉的PHP。」
    • 問手機遊戲開發者:「如何切入移動開發,iOS or Android?」答:「Android,因為我熟悉Java 。」
    但話說回來,如果目的就是為了學習一門技能或公司沒有生存壓力下,我個人喜愛並推崇項目嘗試使用最新的技術,而不是固步自封。

    [轉貼] 為何我們要從Node.JS遷移到Ruby on Rails

    原文

    聲明:這篇文章絕不是一篇討論Node.JS和Ruby on Rails孰優孰略的檄文。它描述的只是我們做決策過程中的一些思考、決策背後的原因。兩種框架都非常優秀,都出色的完成了它們的設計初衷,這也是為什麼我們部分的模塊仍然運行在Node.JS上的原因。

    我是Node.JS的大粉絲,認為這是一項讓人非常興奮的技術,相信它會變的越來越流行。我對這項技術非常的欣賞——儘管我們最近把Targeter App從Node.JS遷移到了Ruby on Rails。
    我們當時使用Node.JS開發它的原因很簡單。我有一個程序包,能很快的將我們的應用弄上線(我們花了54小時做這個事情),相比起Ruby,我更常使用的是JavaScript。因為我們的技術架構牽涉到MongoDB,我的這些特長只有在Node.JS環境裡才會有意義。然而,隨著應用規模的增長,我認識到,選擇Node.JS來實現這個應用是個錯誤的選擇。下面讓我來概述一下其中的原因。

    Node.JS很適合做那些有大量短生命期請求的應用。對於傳統的CRUD應用,它也很好,但不是非常的理想。在PHP,Ruby,Python語言裡都有很成熟、優化的很好的框架來處理這種應用。Node.JS裡的所有東西都異步執行的理念對於CRUD應用來說沒有任何效果。其它語言裡的流行的框架能提供非常好的緩存技術,你所有的需求都能滿足,包括異步執行。
    Node.JS是一種非常年輕的技術框架,它的周邊程序庫都不是很成熟。我說這些並沒有任何對那些代碼捐贈者冒犯的意思,他們很優秀,開發出來很多優秀的程序庫。然而,大部分程序庫需要改進,而Node.JS的這種快速成長的環境意味著每一版升級中都帶有大量的變化;當你使用一種前沿技術時,你十分有必要盡快的緊跟最新的版本。這給創業型的企業帶來了很多的麻煩。

    另外一個原因是關於測試。Node.JS裡的測試框架還不錯,但跟Django或RoR平台上的相比還是差一些。對於一個每天都有大量的代碼提交、並且在一兩天內就要發佈的應用來說,程序不能出問題是至關重要的,否則你為此辛苦的努力變得得不償失。沒有人願意花一天的時間改一些弱智的bug。 最後一點,我們需要的是一種能緩存一切的東西,並且要盡快的實現。儘管我們的應用在增長,每秒鐘有上萬次的hits,但絕不會出現很大量的訪問請求;這不是一個聊天程序!主程序最多時也就達到1000RPS,這樣的負載對於Ruby on Rails和Nginx來說算不了什麼。

    如果你現在還在讀這篇文章,那你已經看到了我所有要說的了,你也許非常堅持的想知道我們的應用什麼地方還在使用Node.JS。是這樣的,我們的應用由兩部分組成。一是界面,用戶看到的這部分,二是負責報表管理的部分,以及做日誌的功能。後者是Node.JS的一個最佳使用場景,存在有大量的短週期的請求。這部分的動作需要盡快的執行完成,甚至要在我們的數據推送還沒有完成之前。這很重要,當請求執行還未結束,瀏覽器繼續等待響應結束,這會影響用戶使用體驗。Node.JS的異步特性救了我們。數據要麼被存入數據庫,要麼被處理掉,當請求一旦執行完成,瀏覽器就可以開始做其它重要的事情了。