2011年12月15日 星期四

Crystal Report 9 以後適用的“拆解長字串成多行”函數

//
// 將長字串拆解成數行,中間用 CR+LF 分開
// Crystal Report 9 以後適用
//
StringVar s := {要拆解的欄位};                // 要拆解的字串
StringVar output := '';                     // 輸出字串
NumberVar CharsInALine := 20;               // 一行幾個字
NumberVar currLength := 0;
while Length(s) > 0 do
(
  // 這只是很粗略的判斷,假定使用者只會輸入半形英數字跟中文字,
  //
不會使用半形的特殊符號,如半形日文假名
  if AscW(s) > 256 then
  (   
    currLength := currLength + 2;           // 中文字,長度 + 2
  )
  else
  (
    currLength := currLength + 1;           // 文數字,長度 + 1
  );
  output := output + s[1];
  if currLength >= CharsInALine then
  (
    output := output + Chr(13) + Chr(10);
    currLength := 0;
  );
  s := Mid(s, 2);
);
output;

2011年10月9日 星期日

用 NPOI 在 Excel 工作表中畫斜線

程式碼如下:
using NPOI;
using NPOI.SS.UserModel;
using NPOI.HSSF.UserModel;

……

// 於工作表上建立 HSSFPatriarch,注意!一張工作表只可以建立一個 HSSFPatriarch
HSSFPatriarch p = sheet1.CreateDrawingPatriarch() as HSSFPatriarch;

// new HSSFClientAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2) // 就是從 (col1, row1) 儲存格的 (dx1, dy1) 點 // 到 (col2, row2) 儲存格的 (dx2, dy2) 點建立一個繪圖物件的涵蓋範圍 HSSFClientAnchor a = new HSSFClientAnchor(0, 0, 0, 0, 0, 0, 1, 1); // 建立一個 HSSFSimpleShape 繪圖物件,範圍是 a,物件會充滿整個範圍 HSSFSimpleShape s = p.CreateSimpleShape(a); s.ShapeType = HSSFSimpleShape.OBJECT_TYPE_LINE; // 設定線條類型為線條 s.LineStyle = HSSFSimpleShape.LINESTYLE_SOLID; // 設定為實線 s.LineWidth = 1; // 限定線條寬度為 1

2011年9月15日 星期四

dbExpress 啟動交易時,出現「與異動管理員建立連線的要求遭到拒絶」訊息的 Trouble Shotting

前天收到同事傳來的一封郵件,裡面提到他有一個挑單畫面,挑單之前會先修改數量然後再挑單 (修改的數量不需要更新回資料庫)。挑單確定之後,根據挑選到的資料去異動另外一個資料表。可是,當異動另外一個資料表時,發生了「與異動管理員建立連線的要求遭到拒絶」的錯誤訊息。

原先我以為是 MSDTC 服務沒有啟動,請他確定是否有啟動,同事說已經啟動了仍然發生錯誤,接下來同事貼了他的程式碼,說是在啟動交易時發生的錯誤訊息。

於是我想到是不是因為挑單畫面的「修改數量」產生了擱置的異動,使得接下來的交易啟動時,因為有擱置的異動而無法啟動?

在說明如何修改之前,先說明一下挑單畫面的結構。
挑單畫面的組成很簡單,一個 PageControl,第一頁是挑單畫面,第二頁是查詢畫面。進入挑單時切到第一頁,可以預先傳入條件查詢出可供挑單的資料,也可以手動切換到第二頁自行查詢出供挑單的資料。查詢是使用 SQLQuery + DataSetProvider + ClientDataSet,以 TDBGrid 顯示資料。

我的想法是:如果能避免產生「擱置的異動」,就不會阻擋到接下來的交易了。

於是做了這樣的修改:

  1. 清除畫面上 ClientDataSet 的 ProviderName 屬性
  2. ClientDataSet.Open 之前,設定 ProviderName 屬性,讓 ClientDataSet 可以接到後面的資料來源,把資料收進來
  3. ClientDataSet.Open 之後,清除 ProviderName 屬性,使 ClientDataSet 做出的所有異動與後面的資料來源隔離開來

經過這樣的修改,這個問題就解決了。在此提供給大家做個參考。

2011年6月18日 星期六

改善 ASP.Net Menu 控制項點擊有效區域太小的問題

使用過 ASP.Net Menu 控制項的朋友都會發現這個問題:點擊有效區域 (也就是會變成手型游標的區域) 僅限於功能表的文字部份,但是 Hover 的效果卻及於功能表項目的全部。導致「雖然看到功能表項目變色了,但是點擊無效,必須游標變成手型才可以」,而讓使用者有不好的操作體驗。

如何解決這個問題?靠一點 css 的輔助就可以了。
將以下的 css 加入您的 css 檔

a.menu
{ display:block; width:100%; height:100%; padding-top:8px; } a.menu:hover { background-color: Gray; }

然後設定 Menu 控制項的 StaticMenuItemStyle 以及 DynamicMenuItemStyle 的 CssClass="menu"
a.menu 中的 padding-top:8px,以及 a.menu:hover 需要依照實際狀況做調整

這樣套用之後,功能表的點擊有效區域就可以括及功能表項目的全部範圍了

2011年6月6日 星期一

Delphi ListBox 顯示多欄文字

  1. 設定 ListBox.TabWidth,單位是 Dialog Base Unit (DBU),以定寬字來說,英數字寬是 4 DBU,中文字寬是 8 DBU,所以每一欄 20 個英數字寬度的話,TabWidth 就要設定為 80
    不用管一個 DBU 合多少 Pt,DBU 是相對單位,會跟著字的大小而變化的
  2. Items.Add 時,要分欄的地方,插入 Tab 字元,可以用 #9 或是 ^I 例如:
    Items.Add('0001'#9'這是品名');
    或是
    Items.Add('0001'^I'這是品名');
  3. 沒辦法各欄設定不同寬度,所以 TabWidth 請設定大一點,或是最長的欄放最後
    如果一定要各欄不同寬度顯示,請改用 TListView

2011年6月2日 星期四

Delphi + PostgreSQL 處理 bytea 欄位的注意事項

設定 PostgreSQL ODBC 時,要把圖中的「bytea as LO」選項勾起來,這樣使用 bytea 欄位型態就會被判別成 TBlobField,而不是 TVarBytesField

如此一來,就可以使用 TBlobField.LoadFromFile() 跟 SaveToFile() 來存入與讀取 bytea 欄位的內容了

若讀取時只想使用 TImage 顯示,不想存檔也可以 (以 JPG 圖檔為例子)
uses jpeg;     // 支援 JPEG 圖檔
var blob: TStream;

blob := ADODataSet1.CreateBlobStream(BlobField, bmRead);
try
  blob.Seek(0, soFromBeginning);
  Image1.Picture.Graphic := TJPEGImage.Create;
  Image1.Picture.Graphic.LoadFromStream(blob);
finally
  blob.Free
end; image

2011年5月12日 星期四

XMLHttpRequest 的應用:灌垃圾資料給釣魚網站

今天有一個許久未聯絡的同學忽然間丟 MSN 水球給我,第一時間的直覺就是詐騙,要買遊戲儲值卡的。果不其然,聊不到兩句就開始問我有沒有空,可不可以幫他買卡等等,我用幾個爛藉口推掉之後,他留下一個超連結就離線了。

點擊超連結,果然出現一個很像 Windows Live 登入的網頁,做得十分粗糙,連 Title 都沒有改,還是 “Untitled Document”,唉~~要騙人帳號也搞像一點嘛!於是我就拿出了我之前寫的小程式,一個 html 檔案,填入了一些資料,開始向那個釣魚網站灌垃圾資料。

這隻程式很簡單,就是使用 XMLHttpRequest 對釣魚網站 POST 假資料,假資料當然是使用亂數產生,主要的部份是這樣子的:

// 產生一個 XMLHttpRequest 物件
function initRequest()
{
    var A = null;
    try
    {
        A = new ActiveXObject("Msxml2.XMLHTTP")
    } 
    catch(e) 
    {
        try
        {
            A = new ActiveXObject("Microsoft.XMLHTTP")
        } 
        catch(oc) 
        {
            A = null
        }
    }
    if (!A && typeof XMLHttpRequest != "undefined")
    {
        A = new XMLHttpRequest()
    }
    if (!A && window.createRequest) 
    {
        try 
        {
            A = window.createRequest();
        } 
        catch (e) 
        {
            A = null;
        }
    } 
    return A
}
// 取亂數帳號密碼,塞入釣魚網頁
function DoBurst()
{
    try
    {
        // 宣告變數
        username = "";
        password = "";
        // 取得輸入資料
        actionUrl = document.getElementById("actionUrl").value;
        usernameId = document.getElementById("usernameId").value;
        passwordId = document.getElementById("passwordId").value; 
        
        // 取亂數拼湊假的帳號跟密碼
        c = Math.floor(Math.random() * 8) + 5;
        for (n=0; n < c; n++)
        {
            username += charSet.charAt(Math.floor(Math.random() * charSet.length));
        }
        username += document.getElementById("usernameSuffix").value;
        
        c = Math.floor(Math.random() * 8) + 5;
        for (n=0; n < c; n++)
        {
            password += charSet.charAt(Math.floor(Math.random() * charSet.length));
        } 

        // submit
        url = actionUrl + "?" + usernameId + "=" + username + "&" + passwordId + "=" + password;

        if (typeof XmlHttp == "undefined")
        {
            XmlHttp = initRequest();
        }

        XmlHttp.open("POST", url, true);
        XmlHttp.onreadystatechange = ShowResult;
        XmlHttp.send("");

        // 計數器累加,並更新畫面
        i++;
        document.getElementById("sendTimes").innerText = i;
    }
    catch(e)
    {
        // 可做錯誤處理
    }
}

然後使用 interval 定時重複執行,就這樣簡單!