公告

國明的網路筆記

2014年7月10日 星期四

解決 GridView 內容的資料匯出成 Excel 檔時產生的問題:

我要將 GridView  HTML內容的資料匯出成 Excel 檔,這部份的功能可以透過Control.RenderControl的方式
Control.RenderControl :
輸出伺服器控制項內容,並在啟用追蹤功能的情況下,儲存控制項的追蹤資訊。
http://msdn.microsoft.com/zh-tw/library/system.web.ui.control.rendercontrol(VS.80).aspx
GridView 內的資料匯出成 Excel 檔的程式碼
    protected void Button2_Click(object sender, EventArgs e)
    {
        Response.ClearContent();
        
string excelFileName = "測試Excel檔案.xls";
        Response.AddHeader(
"content-disposition""attachment;filename=" + Server.UrlEncode(excelFileName));
        Response.ContentType = 
"application/excel";
        System.IO.StringWriter stringWrite = 
new System.IO.StringWriter();
        System.Web.UI.HtmlTextWriter htmlWrite = 
new HtmlTextWriter(stringWrite);
        GridView1.RenderControl(htmlWrite);
        Response.Write(stringWrite.ToString());
        Response.End();
    }

在這邊會遇到兩個問題
問題1. 產生型別 'GridView' 的控制項 'GridView1' 必須置於有 runat=server 的表單標記之中的問題
這個問題我在 topcat 大大的Bog得到解決方式,加上此段程式碼即可
    public override void VerifyRenderingInServerForm(Control control) 
    { 
        // '處理'GridView' 的控制項 'GridView' 必須置於有 runat=server 的表單標記之中   
    }  
關於此錯誤以及遇到網頁中有分頁時的處理方式,可以參考 topcat 大大的Blog

問題2.  GridView 中有中文字時,使用上述的程式碼所匯出的Excel會有中文亂碼
解決此問題的方式參考 MSDN 論壇上 LOLOTA 大大的方法,加上這段程式碼解決轉碼問題即可
Response.Write("<meta http-equiv=Content-Type content=text/html;charset=utf-8>");

完整的程式碼如下 :
using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls;
 
public partial class _Default : System.Web.UI.Page
{
    
protected void Button2_Click(object sender, EventArgs e)
    {
        Response.ClearContent();
        Response.Write(
"<meta http-equiv=Content-Type content=text/html;charset=utf-8>");
        
string excelFileName = "測試Excel檔案.xls";
        Response.AddHeader(
"content-disposition""attachment;filename=" + Server.UrlEncode(excelFileName));
        Response.ContentType = 
"application/excel";
        System.IO.StringWriter stringWrite = 
new System.IO.StringWriter();
        System.Web.UI.HtmlTextWriter htmlWrite = 
new HtmlTextWriter(stringWrite);
        GridView1.RenderControl(htmlWrite);
        Response.Write(stringWrite.ToString());
        Response.End();
    }

    
public override void VerifyRenderingInServerForm(Control control) 
    {
        
// '處理'GridView' 的控制項 'GridView' 必須置於有 runat=server 的表單標記之中   
    } 
}

問題3檔名中有中文字時,使用上述的程式碼所匯出的Excel會有中文亂碼

解決方法就是利用HttpUtility.UrlEncode來進行轉碼動作。
Response.AddHeader("Content-Disposition", String.Format("attachment; filename={0}", System.Web.HttpUtility.UrlEncode(FileName & ".xls", System.Text.Encoding.UTF8)))

問題4RegisterForEventValidation 只能在 Render(); 期間呼叫。

這項網路上也有許多文章提出一個解法,大部分的人提出要在的標籤中,修改AutoEventWireup="true"並增加EnableEventValidation = "false";另一個人提出只需要增加EnableEventValidation = "false",並不需要將AutoEventWireup改為true。測試之後只需要增加EnableEventValidation = "false"屬性即可,但也有可能AutoEventWireup會解決另一個問題,還是先記錄下來吧!

問題5將GridView匯出excel,如果GridView有分頁的話則會發生錯誤。

  // 先解除分頁
  GridView1.AllowPaging = False;
  GridView1.DataBind();

  GridView1.RenderControl(hw);
  Response.Write(sw.ToString());
  Response.End();

  // 再設為分頁
  GridView1.AllowPaging = True;
  GridView1.DataBind();






2014年6月8日 星期日

.net發現某元件不存在於目前內容中

有些人的習慣,修改版本測試時,通常會將舊檔案加上一個數字如 xxx_1.aspx,沒將其他相關連結改過或移到別處,造成抓到舊版本而產生錯誤,像"名稱 'BtnA' 不存在於目前內容中",但設計網頁裡明明就有BtnA,所以只要將更名後的檔案移到別處後就OK了!

2013年12月22日 星期日

直接以RegEdit來設定SQL用戶端以別名網路連線


若要設定SQL用戶端網路連線,但沒有 SQL Server 所提供的公用程式時,
可以直接變更預設的網路程式庫,以RegEdit來直接設定,請移至下面登錄機碼:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer\Client\ConnectTo

然後新增一個類型字串的值,名稱為連線別名 。您可以使用下面列出的值來變更網路程式庫:
TCP/IP: DBMSSOCN
具名管道: DBNMPNTW
多重通訊協定 (RPC): DBMSRPCN
NWLink IPX/SPX: DBMSSPXN
AppleTalk: DBMSADSN
Banyan VINES: DBMSVINN

例如要連接一台名稱為 abc 的SQL主機,主機是透過TCP/IP協定,IP=192.168.1.1 設定值如下:
DBMSSOCN,192.168.1.1



2013年6月28日 星期五

截斷 MS SQL Server Log 檔案

幾年前,學校的選課系統的資料庫選用 MS SQL Server,因資料庫的Log檔案會無盡的擴大,常常比資料庫還要大上好幾倍,最後造成硬碟空間不足,雖然近年來,硬碟容量越來越大,已經不在發現類式問題,但Log檔案還是會造成資料讀取的速度問題,之前就索性將Log檔案給予截斷,採用 T_SQL 指令

dump transaction database_name with truncate_only
再利用工具壓縮資料庫(按右鍵->所有工作->壓縮資料庫)即可 (SQL 的壓縮有可能就是 Pack)



Backup Log database_name with truncate_only
DBCC ShrinkDataBase(database_name, 30, truncateonly ) --實際釋放空間, 留30%空間

// --------------------------------------
從網路上查到:
下列語法是針對MS-SQL 2000及MS-SQL 2005的截斷Scritp:
--1.截斷交易記錄
BACKUP LOG [資料庫名稱] WITH TRUNCATE_ONLY

--2.顯示資料庫檔案,找出交易記錄檔的邏輯檔名
EXEC sp_helpdb '資料庫名稱'

--3.壓縮交易記錄檔
USE 資料庫名稱
DBCC SHRINKFILE([資料庫名稱_log],2)
------------------------------------------------
--進行Log瘦身
--SQL 2000及SQL2005適用
BACKUP LOG 資料庫名稱 TO Disk='C:\LogTemp\DevLog.bak' WITH  init  --先備份Log檔
DBCC SHRINKDATABASE(資料庫名稱,30,NOTRUNCATE)    -- 先挪空間,NoTruncate表示不截斷,30表示留30%大小
DBCC SHRINKDATABASE(資料庫名稱,30,TRUNCATEONLY ) --實際釋放空間

--SQL 2008 Log瘦身適用
-- SQL 2008要截斷Log必須先改成簡單模式.
ALTER DATABASE 資料庫名稱  
    SET RECOVERY SIMPLE;
GO
-- Shrink the truncated log file to 1 MB.
DBCC SHRINKFILE (資料庫Log名稱, 100);
GO
-- Reset the database recovery model.
ALTER DATABASE資料庫名稱  
    SET RECOVERY FULL;
GO
// --------------------------------------------

-- 總整理 --

SQL SERVER 2008

USE [資料庫名稱]
GO
ALTER DATABASE [資料庫名稱] SET RECOVERY SIMPLE WITH NO_WAIT
DBCC SHRINKFILE(記錄檔邏輯名稱, 1)
ALTER DATABASE [資料庫名稱] SET RECOVERY FULL WITH NO_WAIT
GO

邏輯名稱可在資料庫上按右鍵=>[屬性]=>[檔案]中查看,注意邏輯名稱不一定會和檔名相同,例如:

USE [TestDB]
GO
ALTER DATABASE [TestDB] SET RECOVERY SIMPLE WITH NO_WAIT
DBCC SHRINKFILE(TestDB_log, 1)
ALTER DATABASE [TestDB] SET RECOVERY FULL WITH NO_WAIT
GO

----------------------------------------------------------------------

之前的版本(SQL SERVER 2000, 2005)的話可以使用以下指令:

USE [資料庫名稱]
GO
DBCC SHRINKFILE(記錄檔邏輯名稱, 1)
BACKUP LOG [資料庫名稱] WITH TRUNCATE_ONLY
DBCC SHRINKFILE(記錄檔邏輯名稱, 1)
GO

例如:

USE [TestDB]
GO
DBCC SHRINKFILE(TestDB_log, 1)
BACKUP LOG [TestDB] WITH TRUNCATE_ONLY
DBCC SHRINKFILE(TestDB_log, 1)
GO
----------------------------------------------------------------------




2013年4月11日 星期四

一些 BCB6 應用原始程式


BCB6 源碼任務心得分享
取材自【源代碼工作室】:http://home.kimo.com.tw/bruce0829/

 
【取得 TMemo 物件的游標位置】
■ 取得編輯游標(Edit Cursor)的目前座標

求 y 及 x 座標

int y = Memo1->CaretPos.y;
int x = Memo1->CaretPos.x;

int y = SendMessage(Memo1->Handle,EM_LINEFROMCHAR,Memo1->SelStart,0);
int x = Memo1->SelStart - SendMessage(Memo1->Handle,EM_LINEINDEX,y,0);

int y = Memo1->Perform(EM_LINEFROMCHAR, -1, 0);
int x = Memo1->SelStart - Memo1->Perform(EM_LINEINDEX, -1, 0);

根據測試 (BCB 5.0) 使用 Memo1->CaretPos.y , Memo1->CaretPos.x
會有 bug , 在某些位置會讀出負值出來
故下面的例子不使用

//-------------------------------------------------------------------------
//顯示目前編輯座標的函式,本函式可放於 Memo 的 OnClick() 及 OnKeyUp() 事件中
//隨時將目前編輯座標位置顯示在 Label1 中
//-------------------------------------------------------------------------
void __fastcall TForm1::DispPosition()
{
int begin_position=1; //座標開始位置(筆者喜歡把第一列看做是 Line 1 而不是 Line 0)
int y=SendMessage(Memo1->Handle,EM_LINEFROMCHAR,Memo1->SelStart,0);
int x=Memo1->SelStart - SendMessage(Memo1->Handle,EM_LINEINDEX,y,0);

Label1->Caption="Line:"+IntToStr(y+begin_position)+" Col:"+IntToStr(x);
}


【 將 MEMO 某列反白】

直接在程式碼中做說明 , 使用方法為
若要將 MEMO1 第 5 列整列反白則呼叫
LineIndexTo(5);

void __fastcall TForm1::LineIndexTo(int row)
{
long TopRow,StartPostion,EndPostion;

//將 Memo 的 View 移到目的行
TopRow=SendMessage(Memo1->Handle, EM_GETFIRSTVISIBLELINE, 0, 0); //抓取目前 Memo1 中可視的第一列(Row)列號
SendMessage(Memo1->Handle, EM_LINESCROLL, 0, row - TopRow); //向下捲 row - TopRow 列 (若為負值則向上)

//取得 row 頭尾字元位置
StartPostion = SendMessage(Memo1->Handle, EM_LINEINDEX, row, 0); //取得 Memo 中第 row 列開頭的字元位置
EndPostion = SendMessage(Memo1->Handle, EM_LINEINDEX, (row + 1), 0) - 1 ;

//將所指定的範圍位置反白
SendMessage(Memo1->Handle, EM_SETSEL, StartPostion, EndPostion);
//上列程式碼同下
//Memo1->SelStart=StartPostion;
//Memo1->SelLength=EndPostion-StartPostion-1;

Memo1->SetFocus();
}


【 如果您不想寫一個資料庫程式還要手動去設 BDE 別名時】
void __fastcall TForm1::FormCreate(TObject *Sender)
{
shortString MyAlias="MyAlias";
if (!Session->IsAlias(MyAlias))
{
Session->AddStandardAlias(MyAlias, ExtractFilePath(Application->ExeName)+"DB\\LocalDB", "PARADOX");
//Session->SaveConfigFile(); //不存也可,但程式結束,此臨時別名也會消失
ShowMessage("自動加入資料庫別名:"+MyAlias);
}

 
【資料庫的搜尋方法】
常用的搜尋方法有兩種,一種是 FindKey方法,另一種是 Locate方法;FindKey 的方法在早期就已使用,
現在只是為了相容性而存在,而且要使用FindKey方法來搜尋某鍵值,必須存在該鍵值的索引;
而 Locate方法不一定需要索引(有則會自動引用),建議讀者盡量使用Locate方法,兩者方法都會在
搜尋到鍵值時會將資料庫記錄指標停在找到的記錄上,並傳回一個 true 值

■ FindKey

使用 FindKey只搜尋"一個"鍵位值時 (要先設好 Table1 之索引鍵)
Table1->FindKey(new TVarRec(搜尋值), 0);

使用 FindKey搜尋"複合鍵位"(兩個以上)值時 (要先設好 Table1 之索引鍵)
Table1->FindKey(OPENARRAY(TvarRec, (搜尋值1, 搜尋值2, 搜尋值3)))

■ Locate

使用 Locate 只搜尋"一個鍵位"值時 (Table1 不需有索引)
Table1->Locate(“欲搜尋之欄位名稱”, 搜尋值, TLocateOptions() << loPartialKey);

使用 Locate 搜尋"複合鍵位"(兩個以上)值時 (Table1 不需有索引)
Variant locvalues[]={搜尋值1, 搜尋值2, 搜尋值3};
Table1->Locate("欄位名稱1; 欄位名稱2; 欄位名稱3", VarArrayOf(locvalues,(sizeof(locvalues)/16-1)),TLocateOptions() << loPartialKey);

註 : 有些市售騙錢 BCB 書籍講了一堆...卻只單純介紹"單鍵"搜尋 ....

 
【防止程式重複執行】
■ 要防止自己的程式重複執行,使用的方法有好幾種,有些方法甚至要去修改*.bpr檔(C++ Builder 專案檔),
但筆者不建議讀者任意修改 C++ Builder 自動維護的專案檔;使用本節介紹的方法只要將下列程式碼原封不動的
抄到您的 FormCreate() 事件中即可,不但不會讓相同的程式同時存在第二個實體,而且在執行時發現第一個實體
已存在而且是最小化時,還會將其彈出展開於桌面上....


//-------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
Application->Title=Form1->Caption;
  //TrayIcon1->Hint=Form1->Caption;
HANDLE PrevInstHandle;
HANDLE Mutex = OpenMutex(SYNCHRONIZE,false,Application->Title.c_str());

if (Mutex != NULL)
{
String AppTitle=Application->Title;
SetWindowText(Application->Handle,NULL);
PrevInstHandle = FindWindow("TApplication", AppTitle.c_str());
if (PrevInstHandle != 0)
{
if (IsIconic(PrevInstHandle))
ShowWindow(PrevInstHandle, SW_RESTORE);
else
BringWindowToTop(PrevInstHandle);

SetForegroundWindow(PrevInstHandle);
}
Application->ShowMainForm = false;
Application->Terminate();
}
else CreateMutex(null, false, Application->Title.c_str());

// 接下來可以插入您其它要寫在 FormCreate 中的程式

}

■ 方法二

 // Please ADD Your Form1 header file
//Form1.hpp
HANDLE mx;

//Please ADD Form1::Create 1st line
Form1::Create(..)
{
mx = CreateMutex( NULL, true, "AbacusPosAP" );
if( GetLastError() ){
Application->Terminate(); //Finsh Application
}
}


//Please ADD Form1::Close Final Line
Form1::Close(..)
{
.................
......
ReleaseMutex( mx );
}

 
【如何讓程式執行時,馬上隱藏於桌面右下角】
1.在設計程式時,加入一個 TrayIcon 元件(在 C++Builder 之 Sample 元件頁中第二個)於主 Form 中,
並將該元件 Visible 屬性設為 true

2.在主 Form 的 OnActive 事件中寫入兩行程式碼:
Application->Minimize();
ShowWindow(Application->Handle, SW_HIDE);

3.以後這個程式於執行時,會馬上隱藏於桌面右下角成為 Tray Icon

重點在第二點 ....

 
源碼任務心得分享 : 以程式控制 Windows 2000 關機
以程式控制 Windows 2000 關機

//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,
&hToken);

LookupPrivilegeValue(null, SE_SHUTDOWN_NAME,
&tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken,
false,
&tkp,
0,
(PTOKEN_PRIVILEGES)null,
0);

// 下面八個動作中任選一種執行 ....

// 一般關閉,關閉前會詢問其它執行中的程式是否存檔
//ExitWindowsEx(EWX_LOGOFF,0); // 登出, 重新登入使用者
//ExitWindowsEx(EWX_REBOOT,0); // 重新開機
//ExitWindowsEx(EWX_SHUTDOWN,0); // 結束作業系統,出現"您可以放心關機畫面"
//ExitWindowsEx(EWX_POWEROFF,0); // 結束作業系統,並關閉電源(僅支援ATX 規格)

// 強制關閉,其它執行中的應用程式資料並不會被儲存
//ExitWindowsEx(EWX_LOGOFF | EWX_FORCE,0); // 登出, 重新登入使用者
//ExitWindowsEx(EWX_REBOOT | EWX_FORCE,0); // 重新開機
//ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE,0); // 結束作業系統
//ExitWindowsEx(EWX_POWEROFF | EWX_FORCE,0); // 結束作業系統,並關閉電源 (僅支援ATX 規格)
}

 
【 結束或最小化指定的程式】
■ 結束指定的程式

要結束指定的程式,得先要知道要結束的對象程式標題名稱是什麼,然後利用FindWindow() 來找出其視窗 HANDLE,
再呼叫 PostMessage() 送出 WM_CLOSE 訊息以結束該視窗,PostMessage()的用法與 SendMessage()完全相同,
所不同的是 SendMessage() 會等待接收訊息的視窗處理完訊息後才返回,而PostMessage()則是送出訊息命令後馬上返回

//-------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE DestHandle;
DestHandle = FindWindow(null,"test.txt - 記事本");
if (DestHandle != 0) PostMessage(DestHandle, WM_CLOSE, 0, 0);
}
//-------------------------------------------------------------------

■ 最小化指定的程式

指定某一程式最小化有三種做法:(DestHandle 承上節求出)
1. CloseWindow(DestHandle); (勿以為這是「結束」指定程式的用法)
2. ShowWindow(DestHandle, SW_MINIMIZE);
3. SendMessage(DestHandle, WM_SIZE, SIZE_MINIMIZED, 0);

■ FindWindow() 的問題

以上,當我們在尋找目標視窗時,FindWindow()內需一字不漏的填入完整的視窗標題名稱,否則會找不到目標視窗;
所以我們進一步利用 GetWindow()函式,搜尋全部可見視窗,再逐一比對其視窗標題名稱,這樣就可做到所謂「概略搜尋」,
以下的程式範例會將桌面上所有視窗標題名稱含有”記事本”字樣的視窗通通關閉

//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hCurrentWindow = GetWindow(Handle,GW_HWNDFIRST);
char buff[255];
String Text;

while (hCurrentWindow!=0)
{
if ( ( GetWindowText(hCurrentWindow,buff,255)>0) &&
IsWindowVisible(hCurrentWindow) )
{
//ShowMessage(buff);
Text=buff;
if (Text.Pos("記事本")!=0)
{
PostMessage(hCurrentWindow, WM_CLOSE, 0, 0);
}
}
hCurrentWindow = GetWindow(hCurrentWindow,GW_HWNDNEXT);
}
}

 
『執行特定程式或文件 ShellExecute』
使用 ShellExecute

一般我們看到市售程式中,按個按鈕可連到開發公司的首頁,或按個按鈕可自動開啟 OutLook Express 準備寄信給作者,這些功能都可用 ShellExecute 實做之

■ 語法

ShellExecute(
HWND hwnd, // 父視窗 Handle
LPCTSTR lpOperation, // 開啟或操作方式
LPCTSTR lpFile, // 檔案名稱
LPCTSTR lpParameters, // 參數內容
LPCTSTR lpDirectory, // 命令所在目錄
int nShowCmd // 執行時視窗型態
);

不重要的參數可用 null 取代,其它詳細內容請參考線上說明

■ 應用

用「系統預設」的瀏覽器開啟某一個網址:
ShellExecute(null,null,"http://www.hinet.net/",null,null,SW_SHOW);

用「系統預設」郵件軟體開啟一個新郵件:
ShellExecute(null,null,"mailto:test@ms5.hinet.net",null,null,SW_SHOW);

ShellExecute(null,null,"mailto:test@ms5.hinet.net?Subject=哈囉&body=您好",null,null,SW_SHOW);

用「系統預設」的程式開啟某個檔案 (不用管該檔案是 Word 檔還是 Excel 檔,系統會先自動呼叫開啟該檔案型態的主程式):
ShellExecute(null,null,"c:\\test.doc",null,null,SW_SHOW);

ShellExecute(null,null,"c:\\test.xls",null,null,SW_SHOW);

指定用某程式開啟或列印某個檔案:
ShellExecute(null,"open","notepad.exe","c:\\test.txt", null,SW_SHOW);

ShellExecute(null,"print","notepad.exe","c:\\test.txt", null,SW_SHOW);

啟動某個應用程式:
ShellExecute(null,null,"c:\\PosAgent.exe",null,null,SW_SHOW);
或啟動某個內部命令
ShellExecute(null,null,"cmd.exe","/k dir c:\\",null, SW_SHOW);

 
『如何模擬 FileListBox 功能』
如果程式需要抓取一堆文字檔來處理,可使用現成的 FileListBox 元件並將元件之Directory屬性指向目標目錄位址,我們要找的文字檔就會出現在 FileListBox 中了,利用修改其 ItemIndex 屬性值,就可逐筆取出 FileListBox1->FileName 傳回之檔案名稱;FileListBox 元件還有個好處,那就是它還可利用 Mask 屬性過濾副檔名,我們可以要求只列出某一目錄中副檔名為 *.TXT (可同時選擇多種不同副檔名)的檔案;但如果程式是一個無 Form 的不可視程式,或程式的 Form 中沒有地方放這個 FileListBox 元件,
而我們卻需要其「列出檔案清單」的功能,在背景做一些檔案處理的動作(如一直偵測某一目錄發現收到文字檔後 , 做轉資料庫的動作)
但畫面上卻不需要上插入 FileListBox 這個元件,那該如何做呢?

1.以程式碼建立一個 TstringList 物件(非可視物件)
2.利用 FindFirst() 函式收集檔案名稱,放入這個 TstringList 物件中
3.TstringList 物件的使用方法類似 ListBox 或 FileListBox 元件
4.用完要自行釋放這個 TstringList 物件

void __fastcall TForm1::Button1Click(TObject *Sender)
{
String Dir;
String FileName;
TSearchRec SearchRec;
int iAttributes = 0;
iAttributes |= faAnyFile * true;

//偵測 c:\ 底下有無 *.tot 這個類型的檔案
//有的話則收集到 TempList 這個區域物件中 , 用完即釋放掉

Dir=("C:\\");

TStringList *TempList = new TStringList; // declare the list
try {
//use the string list
TempList->Clear();

//搜集 *.tot
if (FindFirst(Dir+"*.tot", iAttributes, SearchRec) == 0)
{
do {
if ((SearchRec.Attr & iAttributes) == SearchRec.Attr)
{
TempList->Add(SearchRec.Name);
}
} while (FindNext(SearchRec) == 0);

FindClose(SearchRec);
}


for ( int i = 0; i <= (TempList->Count-1); i++)
{
FileName=(Dir+TempList->Strings[i]);

//本迴圈可抓出所有符合條件之檔名至 FileName 變數中

//..........................
//可將後續運用程式碼寫在這裡
//..........................
}
}
__finally
{
delete TempList; // destroy the list object
ProgressBar2->Position=100;
Label2->Visible=true;
}

}

 
『利用迴圈變更50個TLabel property』
for (int i = 1; i <= 50; i++)
{
((TLabel *)FindComponent("Label"+IntToStr(i)))->Caption="";
}


 
源碼任務心得分享 : PACK 一個 Paradox Table
//---------------------------------------------------------------------------
// <<使用方法>> if (ParadoxPack("AbcPSV","TOT")) ShowMessage("資料庫 TOT ...PACK 完成");
// <<注意事項>>
// 1. 在呼叫 ParadoxPack 前 , 連到 TOT 的 TTable 元件 Active 必須是 false
// 2. 第一個傳入參數一定要用資料庫別名 , 不能用路徑
// (如 ParadoxPack("C:\\AbcPSV\\DB","TOT")) 是錯誤的 )
//---------------------------------------------------------------------------
bool _ParadoxPack(char *Alias, char *TblName)
{ hDBIDb hDb ;
DBIResult dbResult ;
CRTblDesc TblDesc ;
bool r=false;

//Initialize the BDE
DbiInit(null);

//Open a Database
DbiOpenDatabase(Alias,null,dbiREADONLY,dbiOPENSHARED,null,0,null,null,hDb);

memset((void *) &TblDesc, 0, sizeof(CRTblDesc));
lstrcpy(TblDesc.szTblName, TblName);
lstrcpy(TblDesc.szTblType, szPARADOX);
TblDesc.bPack = true;
dbResult = DbiDoRestructure(hDb, 1, &TblDesc, null, null,null, false);
if(dbResult == DBIERR_NONE) r=true;

//Close Database
DbiCloseDatabase(hDb);

return r;

}

 
『以程式碼建立 Paradox Table』
下面的程式碼會在您事先設好的 "My_Alias" BDE 別名路徑下
自動建立一個名叫 Plu.db 的資料表實體
(若名稱中加入日期流水號 , 每執行一次就會產生一個 Table)
欄位有字串及整數型態 , 並以 PLU_NO 欄位當 Primery Key

void __fastcall TForm1::Button1Click(TObject *Sender)
{

TTable *NewTable = new TTable(Form1);
NewTable->Active = false;
NewTable->TableType = ttParadox; //自行更改
NewTable->DatabaseName = "My_Alias"; //自行更改
NewTable->TableName = "Plu.db"; //自行更改

NewTable->FieldDefs->Clear();
NewTable->FieldDefs->Add("PLU_NO",ftString,16,true);
NewTable->FieldDefs->Add("TEN_CODE",ftString,4,false);
NewTable->FieldDefs->Add("DEPT_NO",ftString,8,true);
NewTable->FieldDefs->Add("PACK_AMT",ftInteger,0,true);
NewTable->FieldDefs->Add("PACK_QTY",ftInteger,0,true);
NewTable->FieldDefs->Add("UNIT_PRICE",ftInteger,0,true);
NewTable->FieldDefs->Add("SALE1",ftInteger,0,true);
NewTable->FieldDefs->Add("SALE2",ftInteger,0,true);
NewTable->FieldDefs->Add("SALE3",ftInteger,0,true);

//設定以 PLU_NO 欄位當 Primery Key
NewTable->IndexDefs->Clear();
NewTable->IndexDefs->Add("","PLU_NO", TIndexOptions() << ixPrimary << ixUnique);

NewTable->CreateTable();
NewTable->Free();
}

 
『列出目前桌面上所有視窗代碼』
主 Form 上放一個 Timer1 , ListBox1 , CheckBox1(其 Caption 設為"只列出可視視窗") , 然後在 OnTimer 中加入下列程式碼 , 就可在 ListBox1 列出目前桌面上所有視窗的 Caption , 類別名稱 , Hanlde 代碼
除錯監控時很好用

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
int olditemindex=ListBox1->ItemIndex;
ListBox1->Items->Clear();
HANDLE hCurrentWindow = GetWindow(Handle, GW_HWNDFIRST);
char szText[254];
char className[254];
//GetClassName(hWnd,className,95);
//GetWindowText(hWnd,title,110);

String t;
String c;
while (hCurrentWindow != 0)
{
if ((GetWindowText(hCurrentWindow, szText, 255) > 0) &&
(IsWindowVisible(hCurrentWindow) || !CheckBox1->Checked))
{
GetClassName(hCurrentWindow,className,255);
t=szText;
c=className;
ListBox1->Items->Add(t+" --> "+c+" ; Handle = "+IntToStr((int)hCurrentWindow));
}

hCurrentWindow = GetWindow(hCurrentWindow, GW_HWNDNEXT);
}

if (olditemindex<=(ListBox1->Items->Count))
ListBox1->ItemIndex=olditemindex;
}

 
【 如何使用TChart?】
首先先在 Form 中放一個 Tchar 物件
什麼屬性都不要設
再放一個 button , 然後在 Button 的 OnClick 事件中加入下段程式碼
你就知道大概如何手動控制 Tchar 了 , 其他請舉一反三

void __fastcall TForm1::Button1Click(TObject *Sender)
{
Chart1->RemoveAllSeries(); // 清除Chart1上所有舊 Series

Chart1->View3D=false; // 不要 3D 立體
Chart1->Legend->Visible=false; // 不秀圖例說明

// 設定此 char Title 名稱
//(是 StringList 不是 String 所以不能用 Chart1->Title->Text="xxx" ...)
Chart1->Title->Text->Clear();
Chart1->Title->Text->Add("test");


// 動態宣告一個 THorizBarSeries 型態的 Series
// 其它的 Series 型態有哪些 請看 TChartSeries 之 Help
Series1=new THorizBarSeries(Chart1) ;
Series1->ParentChart=Chart1;

Series1->Marks->Visible=true; // 設定要提示說明
Series1->Marks->Style=smsValue; // 提示說明內容為 Label
//(註) Series1->Marks->Style 內容請參考 TSeriesMarksStyle 之 Help

Series1->SeriesColor=clBlue; // 設線條1為藍色,不設則自動給色

// 輸入假資料
int V; // Value
String L; //Label
for (int i = 1; i <= 8; i++)
{
V=i; // Series 值
L="V"+IntToStr(i); // 軸名稱
Series1->Add( V , L , clTeeColor );
}
}

 
『拆解字串段的自寫函式 _StringSegment()』
String A="ABCD,EFG,H,IJK,LM";
String B=_StringSegment(A , "," , 3); // 以逗號來做分隔 , 求第 3 段字串

所以 B 就等於 "H"
也可不用逗號做分隔 , 用您指定的其他符號做分隔


String __fastcall TForm1::_StringSegment(AnsiString Str , AnsiString Comma , int Seg)
{
if ((Str=="") || (Seg<1)) return "";

String C=Comma; if (C=="") C=",";

String s=Str;
String sTmp;
String r;
int iPosComma;

TStringList *TempList = new TStringList; // declare the list

TempList->Clear();
while (s.Pos(C)>0)
{
iPosComma = s.Pos(C); // locate commas
sTmp = s.SubString(1,iPosComma - 1); // copy item to tmp string
TempList->Add(sTmp); // add to list
s = s.SubString(iPosComma + 1,s.Length()); // delete item from string
}
// trap for trailing filename
if (s.Length()!=0) TempList->Add(s);

if (Seg > TempList->Count)
r="";
else
r= TempList->Strings[Seg-1];

delete TempList; // destroy the list object

return r;
}

 
『序列埠讀寫』
在 Windows 作業系統下(尤其是 NT & Win 2000),作業系統已禁止我們直接對硬體I/O Port做讀寫的動作,所以要控制 RS-232 Port,我們可利用檔案裝置的觀念來看待 RS-232 Port;這觀念源自 Unix 作業系統,所有的 I/O 週邊都可把它看成是一個檔案代碼,如 Com1 的檔案代號就是 “Com1”, Com2 的檔案代號就是 “Com2”,所以利用處理文字檔的觀念,我們就可以讀寫 RS-232 Port

序列埠的資料寫入

以程式碼的撰寫容易程度來看,RS-232 Port的「寫入」比「讀出」簡單,基本上有下列三種寫入方式,讀者可以看得出來,只要會處理文字檔的讀寫就看得懂本程式在做什麼,程式範例是假設有要秀出一字串到一週邊(如收銀機的「客戶抬頭顯示器」),除送出欲秀出的字串內容資料外,前導命令碼要加在資料之前,以便通知該週邊秀出方式的命令,本範例之週邊(假設接在 Com2)前導控制碼及資料格式假設為「Esc+’q’+’A’」+「欲秀出的字串」+「Return Code」,那我們來看看如何送出資料:

■ 使用傳統 C 的 文字檔處理語法

//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
String m="fprintf 測試"; //欲秀出的字串

FILE *outfile;
if ((outfile=fopen("COM2","w"))== NULL) return;

fprintf(outfile,"\x1bqA%s\r",m);

fclose(outfile);
}
//-------------------------------------------------------

■ 使用標準 C++ 的 文字檔串流(stream)處理語法

//-------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
String m="ofstream 測試"; //欲秀出的字串

String s="\x1bqA"+m+"\r";
char *s1=s.c_str();
ofstream outfile("COM2"); if (!outfile) return ;
outfile << s1;
outfile.close();
}
//-------------------------------------------------------

■ 使用 Win32 API 之 CreateFile 函式(較複雜,但可控制BaudRate、ByteSize、Parity、StopBits等參數)很多市售的 RS-232 元件內部也都是用這種方式處理 RS-232資料輸出

//-------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
String m="CreateFile 測試"; //欲秀出的字串

HANDLE hCom = CreateFile("COM2",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL );

if(hCom)
{
DCB dcb;
ZeroMemory(&dcb, sizeof(dcb));
dcb.DCBlength = sizeof(dcb);

dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY; // NOPARITY and friends are
dcb.StopBits = ONESTOPBIT; // #defined in windows.h

// Set the port properties and write the string out the port.
if(SetCommState(hCom,&dcb))
{
DWORD ByteCount;
String s="\x1bqA"+m+"\r";
char *msg=s.c_str();
WriteFile(hCom,msg,
strlen(msg),
&ByteCount,NULL);
}
CloseHandle(hCom);
}
}
//-------------------------------------------------------

註 : 以上只是本人研究心得 , 若要深入研究
BCB 現也已有 RS-232 控制之參考書籍可參考之

 
『 序列埠讀寫』
基本資料讀出方式

//---------------------------------------------------------------------------

#include
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "ComPort"
#pragma resource "*.dfm"
TForm1 *Form1;
HANDLE ComPortHandle[10];
bool ComPortActive[10];

//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormActivate(TObject *Sender)
{
_ComPortOpen(1); //開啟 com port
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
_ComPortClose(1); //關閉 com port
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
ShowMessage(_ComPortRead(1)); //測試讀出值 (如刷條碼機之讀出值)
}

//---------------------------------------------------------------------------
// 以下為副程式區
//---------------------------------------------------------------------------

bool __fastcall TForm1::_ComPortOpen(int ComPortID)
{
bool r=false;
if (ComPortActive[ComPortID]) return r;

String ComPortName="COM"+IntToStr(ComPortID);
ComPortHandle[ComPortID] = CreateFile( ComPortName.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL );

if(ComPortHandle[ComPortID])
{
DCB dcb;
ZeroMemory(&dcb, sizeof(dcb));
dcb.DCBlength = sizeof(dcb);

dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY; // NOPARITY and friends are
dcb.StopBits = ONESTOPBIT; // #defined in windows.h

// Set the port properties and write the string out the port.
if(SetCommState(ComPortHandle[ComPortID],&dcb))
{
r=true;
ComPortActive[ComPortID]=true;
}
}

return r;
}
//---------------------------------------------------------------------------
bool __fastcall TForm1::_ComPortClose(int ComPortID)
{
bool r=false;
if (CloseHandle(ComPortHandle[ComPortID]))
{
r=true;
ComPortActive[ComPortID]=false;
}

return r;
}
//---------------------------------------------------------------------------
String __fastcall TForm1::_ComPortRead(int ComPortID)
{
Char Buff[128];
int DataSize;
DWORD ByteCount;

//底下這一段只是為了找出 DataSize 值
DWORD ErrorFlags;
_COMSTAT ComPortStat;
ClearCommError(ComPortHandle[ComPortID],&ErrorFlags,&ComPortStat);

/* // lngSize 是有傳入要求指定的指定長度時
If ComPortStat.cbInQue > DataSize Then
DataSize = ComPortStat.cbInQue
Else
DataSize = lngSize
End If

*/

DataSize=ComPortStat.cbInQue;
if (DataSize<=0) DataSize=0;
//ShowMessage(IntToStr(DataSize));

ReadFile(ComPortHandle[ComPortID],Buff, DataSize,&ByteCount,NULL);
return String(Buff);
}
//---------------------------------------------------------------------------

void __fastcall TForm1::_ComPortClear(int ComPortID)
{
PurgeComm(ComPortHandle[ComPortID],PURGE_RXCLEAR);
}

//---------------------------------------------------------------------------

註 : 以上只是本人研究心得 , 若要深入研究
BCB 現也已有 RS-232 控制之參考書籍可參考之

 
『 鍵盤掃描碼處理』
在鍵盤按鍵被押下時,第一個會產生的是所謂的掃描碼(Scan Code),也就是硬體直接傳回的按鍵代碼,然後作業系統會把它轉換為虛擬碼(Virtual Key ),也就是用一些有意義的符號或代碼來代替之,以便應用程式能夠處理;例如一般鍵盤上都有兩個「Enter」鍵(第二個在鍵盤最右下角),它們在硬體上的掃描碼(Scan Code)一定不同,但虛擬碼(Virtual Key)都是 VK_RETURN (CharCode=13),一般在高階的程式應用中,大部分都只會針對虛擬碼(Virtual Key)做處理,這樣做的好處是可以將硬體隔離於應用程式之外。

在一個 Windows 程式裡,若有某鍵被按下,會有兩個訊息送到程式本身,一個稱之為 WM_KEYDOWN,幾乎鍵盤任何按鍵都會送出;而另一個訊息稱之為 WM_CHAR,只有在數值或字母按鍵被按下時才會送出;換句話說,若是按下了 「A」鍵,會同時得到 WM_KEYDOWN 及 WM_CHAR 兩個訊息,按下了 「F1」卻只會得到 WM_KEYDOWN 訊息。

OnKeyDown事件處理程序對應於 WM_KeyDown 訊息, OnKeyPress事件處理程序則對應於 WM_CHAR訊息 (OnKeyPress事件乾脆改名叫 OnChar 事件比較容易讓人理解),所以 OnKeyDown事件處理程序傳入的 key 變數是 WORD 型態,而 OnKeyPress 事件處理程序傳入的 key 變數是 char 型態。

由上面的介紹可以知道,要偵測所有的按鍵(包含文數字及所有功能鍵)要用OnKeyDown事件處理程序來做,偵測範圍會比較廣,但有個鍵例外,那就是「Tab」鍵;按下 Tab 鍵會轉移元件焦點(Focus),且不會觸發 OnKeyDown及OnKeyPress 事件;另外,OnKeyDown事件也只能偵測到鍵盤虛擬碼(Virtual Key)層級,它雖幾乎可偵測所有的按鍵,但它無法分辨按下的是左邊的 Shift或是右邊的Shift?或按下的是兩個 Enter 鍵中的哪一個?所以,要能分辨所有按鍵個體,必須能夠偵測到鍵盤的硬體掃描碼(Scan Code)層級。

■ 偵測鍵盤上所有按鍵個體

在 Form 中有個 OnShortCut 事件處理程序,抓取其傳入的 Msg 變數就可以完全偵測所有按鍵個體,更令人興奮的是它連 Tab 鍵都能抓得到,原因是從 Msg 變數中可以直接抓出鍵盤的硬體掃描碼(Scan Code);如欲抓取掃描碼可讀出 Msg.KeyData值,抓取虛擬碼(Virtual Key) 則可讀出 Msg.CharCode 值。以下整理出跟按鍵偵測有關的事件處理程序及其偵測範圍:

OnKeyPress 只能抓到數值或字母按鍵及 Esc鍵、空白鍵,但不含功能鍵(F1-F12)
OnKeyDown 能抓到所有的鍵(除 Tab 鍵)但不能分辨「對稱鍵」的不同
OnShortCut 能抓到所有的鍵(含 Tab 鍵)且能分辨「對稱鍵」的不同

註:「對稱鍵」指的是左右兩邊皆有的鍵,如「Enter」、「Shift」、「Ctrl」、「Alt」等等

 
『如何將 Ping 功能寫成一個函式』
使用方法 : bool Ping(String ip_address);

用法就這麼簡單 , 相關的函式用法
翻 MSDN 要傳入一大堆參數指標 ,
頭都昏了 , 因為我又不是要用來寫專業
的 Ping 程式 , 我大部份的用途都是在
Ping 區網內另一台主機在不在
我只要知 回傳值是 True 或 False
即可 , 根本不須知 ping 之 reply time 是多少 ....

不過這個函式有個缺點 , 若該 ip 不能反查出主機名稱
(就是網路芳鄰看到的名稱啦)
則就算 ip 存在 , 傳回值仍是 False
不過針對大部份的 Windows 系統電腦應都沒問題

不能反查出主機名稱的 ip 有
1. 區網內的路由器 - Router (ip 大部份為 x.x.x.254)
2. Linux 主機

###################################################
# 原始參考資料 http://rad.bytesandmore.de/index.htm?http://rad.bytesandmore.de/cpp/snipp/sc08012.php
# bruce0211@yahoo.com.tw 改寫 (2002/06/05)
#
# bool Ping(AnsiString ip_address) 函式
#
# 使用方法 :
#
# if (Ping("tfm002"))
# ShowMessage("true");
# else
# ShowMessage("false");
#
#####################################################

#####################################################
# ping.cpp 由此開始
#####################################################

#include
#pragma hdrstop
#include "Ping.h"

//---------------------------------------------------------------------------
// ip_address 可填入 ip 位置或主機名稱
// 但本函式有個 bug , 若該 ip 不能解析出主機名稱者
// 如 Router 或某些 Linux 主機 , 則回傳值仍是失敗 >_<
//---------------------------------------------------------------------------
bool Ping(AnsiString ip_address)
{
// ICMP.DLL laden:
HANDLE hIcmp = LoadLibrary("ICMP.DLL");
if(hIcmp == NULL)
{
return false;
}

// Zeiger auf die Funktionen besorgen:
PF_CMPCREATEFILE pfIcmpCreateFile = (PF_CMPCREATEFILE)
GetProcAddress(hIcmp, "IcmpCreateFile");
PF_ICMPCLOSEHANDLE pfIcmpCloseHandle = (PF_ICMPCLOSEHANDLE)
GetProcAddress(hIcmp,"IcmpCloseHandle");
PF_ICMPSENDECHO pfIcmpSendEcho = (PF_ICMPSENDECHO)
GetProcAddress(hIcmp,"IcmpSendEcho");

// Funktionszeiger prufen:
if (pfIcmpCreateFile == NULL || pfIcmpCloseHandle == NULL ||
pfIcmpSendEcho == NULL)
{
FreeLibrary(hIcmp);
return false;
}

// WinSock initialisieren
WSADATA wsaData;
int ilRetVal = WSAStartup(0x0101, &wsaData );
if(ilRetVal)
{
WSACleanup();
FreeLibrary(hIcmp);
return false;
}
// Check WinSock version
if(0x0101 != wsaData.wVersion)
{
WSACleanup();
FreeLibrary(hIcmp);
return false;
}

// Prufen, ob es sich bei der Zieladresse um IP-Adresse handelt und
// ggf. den die Adresse zum Namen ermitteln:
struct in_addr iaDest; // Struktur fur die Internet-Adresse
iaDest.s_addr = inet_addr(ip_address.c_str());
LPHOSTENT pHost; // Zeiger auf die Host Entry Struktur

if (iaDest.s_addr == INADDR_NONE)
pHost = gethostbyname(ip_address.c_str());
else
pHost = gethostbyaddr((BYTE *)&iaDest, sizeof(struct in_addr), AF_INET);

// 若該 ip 不能解析出主機名稱者
// 如 Router 或某些 Linux 主機 , 則回傳值仍是失敗 , 問題應出在這裡
// 但又不能拿掉 ....
if(pHost == NULL)
{
WSACleanup();
FreeLibrary(hIcmp);
return false;
}

// IP-Adresse kopieren
DWORD* pAddress = (DWORD*)(*pHost->h_addr_list);

// ICMP Echo Request Handle besorgen:
HANDLE hIcmpFile = pfIcmpCreateFile();

ICMPECHO icmpEcho; // ICMP-Echo Antwortbuffer
IPINFO ipInfo; // IP-Optionenstruktur

int ilTimeSum = 0; // Summe der Round Trip Time-Daten
int ilCount = 0; // Anzahl der Round Trip Time-Daten

for (int ilPingNo = 0; ilPingNo < 3; ilPingNo++)
{
// Default-Werte festlegen:
::ZeroMemory(&ipInfo, sizeof(ipInfo));
ipInfo.bTimeToLive = 255;
// ICMP Echo anfordern:
pfIcmpSendEcho(hIcmpFile, // Handle von IcmpCreateFile()
*pAddress, // Ziel-IP Addresse
NULL, // Zeiger auf den Buffer mit den
// zu sendenden Daten
0, // Buffergrosse in Bytes
&ipInfo, // Request-Optionen
&icmpEcho, // Antwort-Buffer
sizeof(struct tagICMPECHO), // Buffergrosse
5000); // Max. Wartezeit in Millisekunden

// Ergebnisse anzeigen:
iaDest.s_addr = icmpEcho.dwSource;

// falls Fehler aufgetreten:
if(icmpEcho.dwStatus)
{

break;
}
ilTimeSum += icmpEcho.dwRTTime;
ilCount++;
if(ilPingNo < 2) Sleep(200);
}

// Echo-Request File Handle schliessen:
pfIcmpCloseHandle(hIcmpFile);
// ICMP.DLL freigeben:
FreeLibrary(hIcmp);
// Winsock schliessen:
WSACleanup();

// Den Mittelwert aller Round Trip Times zuruckgeben:
//return ilRetVal = ilCount ? ilTimeSum/ilCount : -1;

if (ilRetVal = ilCount) return true;
else
return false;

}

###################################################
# ping.h 由此開始
###################################################

//----------------------------------------------------------------------------
#ifndef PingHPP
#define PingHPP
//---------------------------------------------------------------------------
#include

// Definition der IP-Optionenstruktur
typedef struct tagIPINFO
{
BYTE bTimeToLive; // Time To Live
BYTE bTypeOfService; // Type Of Service
BYTE bIpFlags; // IP-Flags
BYTE OptSize; // Grosse der Options Data Buffers
BYTE FAR *Options; // Zeiger auf Options Data Buffer
} IPINFO, *PIPINFO;

// Definition der ICMP-Echo Antwortstruktur
typedef struct tagICMPECHO
{
DWORD dwSource; // Zieladresse
DWORD dwStatus; // IP-Status
DWORD dwRTTime; // Round Trip Time in Millisekunden
WORD wDataSize; // Grosse des Antwort-Buffers
WORD wReserved;
void FAR *pData; // Zeiger auf die Antwort-Daten
IPINFO ipInfo; // Antwort-Optionen
} ICMPECHO, *PICMPECHO;

// Zeiger auf die Funktionen aus der ICMP.DLL deklarieren:
typedef HANDLE (WINAPI *PF_CMPCREATEFILE)(VOID);
typedef BOOL (WINAPI *PF_ICMPCLOSEHANDLE)(HANDLE);
typedef DWORD (WINAPI *PF_ICMPSENDECHO)(HANDLE,DWORD,LPVOID,WORD,
PIPINFO,LPVOID,DWORD,DWORD);

//---------------------------------------------------------------------------
bool Ping(AnsiString ip_address);
//---------------------------------------------------------------------------
#endif


另外 , 標準 WinInet 32 API 也有個 InternetCheckConnection()
可查某一主機是否連線 , 測試碼如下
但我試過 ,
目標 url 之主機必須啟動 http 或 ftp service 才會傳回 true
所以功能用途比前面找到的方法更狹隘 ....

char url[]="http://172.30.96.200/";
bool status = InternetCheckConnection(url,FLAG_ICC_FORCE_CONNECTION, 0 );

if (status)
ShowMessage("true");
else
ShowMessage("false");

 
『Set (集合型態) 的使用方法』
Set (集合型態) 是 Object Pascal 特有型態 , 在 BCB 中是用一個名為 Set 的類別來模擬
Set 型態 , 由於在 VCL 元件中有許多屬性都會利用到 Set 型態 , 所以在此介紹其使用方法

■ 加入集合元素

語法 : 集合型態原型 << 元素一 [<< 元素二] [<< 元素二] [...]

舉例 :

1. 在 MessageDlg 談出示對話盒中 , 若需要有兩個按鈕 "YES" & "NO"
則使用 TMsgDlgButtons() << mbYes << mbNo
完整範例如 if (MessageDlg("Delete Record?", mtConfirmation, TMsgDlgButtons() << mbYes << mbNo, 0) == mrYes)
DataModule3->Table1->Delete();

2. 我們要將某一元件之字型設為粗體 , 使用物件檢視器(Object Inspector)來設定很簡單 , 只要在
Font 屬性中展開 Style 次屬性 , 再將 fsBold 設為 true 即可

但在程式碼中如何做到以上動作呢 ? (以設定 Edit1 字型為粗體為例)
Edit1->Font->Style = TFontStyles() << fsBold;

若要同時設定其字型為粗體加底線 :
Edit1->Font->Style = TFontStyles() << fsBold << fsUnderline;

■ 判斷是否含有集合元素

□ 判斷 Edit1 目前是否為粗體字型

if (Edit1->Font->Style.Contains(fsBold))
ShowMessage("true");
else
ShowMessage("false");

□ 判斷 KeyDown 事件中是否帶有 Shift

void __fastcall TFm_POS1::FormKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{

if (Shift.Contains(ssShift))
{
......
......
}
}

□ 在做 StringGrid 變色特效時 , 判斷當在固定列或選擇列(藍色光棒列)則不于變色

void __fastcall TForm1::StringGrid1DrawCell(TObject *Sender, int ACol,
int ARow, TRect &Rect, TGridDrawState State)
{
// 如果是固定列或選擇列(藍色光棒列)則不于變色
if (State.Contains(gdFixed) || State.Contains(gdSelected)) return;

if (ARow==2) StringGrid1->Canvas->Font->Color=clRed; //字型變色
if (ACol==3) StringGrid1->Canvas->Brush->Color=clLime ; // 垂直 cell 背景變色
if (ARow==3) StringGrid1->Canvas->Brush->Color=clYellow; // 水平 cell 背景變色

// output the text
StringGrid1->Canvas->TextRect (Rect, Rect.Left, Rect.Top,StringGrid1->Cells[ACol][ARow]);
}

□ 註 : 根據測試 Contains() 中好像無法同時塞入兩個集合元素值 ...

■ 移除集合元素

同 "加入集合元素" 語法 , 但只要將 "<<" 改為 ">>" 即可

if (Edit1->Font->Style.Contains(fsBold))
Edit1->Font->Style = TFontStyles() >> fsBold;

 
『事件共用』
以設計一個小算盤為例
首先插入一個 Panel1 將其 Caption 清空
再插入十個 Button , 其 Caption 分別為 "0" - "9"
(當作計算機按鍵用)
然後在 Button1 之 OnClick 事件中寫入下列程式碼
void __fastcall TForm1::Button1Click(TObject *Sender)
{
Panel1->Caption=Panel1->Caption+((TButton *)Sender)->Caption;
}

其他九個按鍵之 OnClick 都指到 Button1 的 OnClick (共用)
如此十個按鍵在執行時都可以按出數字來
不用分別為十個 Button 寫十個 OnClick 事件程式碼 ......

 
『取得 IP 位址之函式 , String 型態』
String __stdcall GetHostIpAddress(void)
{
struct hostent *thisHost;
struct in_addr in;
char MyName[80];
char *ptr,*hostname;
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested=MAKEWORD(2,0);

err=WSAStartup(wVersionRequested,&wsaData);
if(err!=0)return "";
if(LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=0)
{
WSACleanup();
return "";
}
if (gethostname(MyName,80)==SOCKET_ERROR) return "";
if (!(thisHost=gethostbyname(MyName))) return "";
hostname=thisHost->h_name;
memset((void*)&in,sizeof(in),0);
in.s_addr=*((unsigned long*)thisHost->h_addr_list[0]);
if (!(ptr=inet_ntoa(in))) return "";
WSACleanup();

return (String)ptr;
}

 
『動態連結檔簡易設計說明』

■ 特別說明

1. 動態載入部分 , DLL函式引用在參考資料中都只提到局部引用( 即在一個 BUTTON CLICK 事件中介紹如何載入及引用 DLL函式然後當場釋放 DLL)我光把他改成全域引用(在 FORM CREATE 載入 , Form Close 才釋放 , 這才符合真實應用環境 ) 就測了好久 , 在 MSDN 中還用到 TYPEDEF 來定義空函式指標 , 我測的結果根本不需要 , 就已可做到全域引用
2. 靜態載入部份 , 光參考 DLL 標頭檔 (*.H) 的巨集修改 (為了同時能適用 IMPORT 跟 EXPORT) , 不知是我資質愚昧還是參考資料表達的不詳細, 後來才搞懂其意義 , 但我也發現 , 不使用 DLL 之 *.H 標頭檔 , 不但沒關係 , 程式反而更簡潔有力 , 所以我的測試範例中根本不使用 DLL 的 *.H 標頭檔
3. 也許有些只是我的個人偏見 , 但在遇到瓶頸前我會再繼續研究修改


■ 使用樣板工具建立 DLL 雛形

□ 在 C++Builder 主選單中選擇「File」->「New…」出現下列樣板工具盒
□ 選擇 「DLL Wizard」出現對話選擇
□ 因為我們開發的 DLL 是要支援 C++Builder 本身程式用的,所以直接按「OK」即可,注意!有支援 VCL 的 DLL 只能給 C++Builder & Delphi 使用,若您的DLL不需用到 VCL 元件,而只有一些函數處理程序的話可將「Use VCL」勾勾拿掉,下一步其將自動產生DLL雛形程式碼;在這個空的DLL雛形程式碼內,筆者將自行加入三個自訂函式,以便測試這個 DLL 的功能,第一個函式傳入一個字元陣列然後用ShowMessage秀出這個字元陣列(函式本身沒有回傳值);第二個函式傳入兩個字元陣列,將這兩個字元陣列合併後回傳,回傳型態也是字元陣列;第三個函式傳入兩個整數,也是將這兩個整數相加後回傳,回傳型態是整數值;直接參考程式碼最明瞭,讀者應有能力看出哪些是系統產生的雛形樣板,哪些是作者加入的部分,這個 DLL完整程式碼如下:(專案名稱叫 MyDLL.bpr,程式碼名稱沿用系統內定之名稱 Unit1.pas)
□ 本 DLL 提供三個自寫副程式 Showit() 秀一段訊息 , CharAdd() 做字串相加 , IntAdd() 做整數相加

MyDLL 專案之完整 Unit1.pas
//-------------------------------------------------------

#include
#include
#include
#pragma hdrstop

extern "C" __declspec(dllexport) void __stdcall Showit(char *message);
extern "C" __declspec(dllexport) void __stdcall CharAdd(char *inchr1,char *inchr2 ,char *r);
extern "C" __declspec(dllexport) int __stdcall IntAdd(int a,int b);


#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//-------------------------------------------------------
void __stdcall Showit(char *message)
{
ShowMessage(message);
}
//-------------------------------------------------------
void __stdcall CharAdd(char *inchr1,char *inchr2 ,char *r)
{
sprintf(r,"%s%s%c",inchr1,inchr2,'\0');
}
//-------------------------------------------------------
int __stdcall IntAdd(int a,int b)
{
return(a+b);
}
//-------------------------------------------------------

□ 程式說明

1.本 DLL原始碼 Unit.pas 不需有 Unit.h 標頭檔
2.本 DLL 編譯好後可同時適用於動態載入及靜態載入兩種呼叫方式 (下節介紹)
3.為了考慮共用性,函式命名方式加上 extern “C” 這個修飾詞來達到使用標準C的命名法,而不使用 C++的命名法,因為使用 C++的命名法會在編譯時在函式名稱後加上參數型態等註記,造成其他程式如 VC++、VB 無法使用的困擾
4.函式彙出或彙入之宣告,以32 位元系統之識別字 __declspec(dllexport) 及 __declspec(dllimport) 來取代舊 16 位元系統的識別字 _export 及 _import,因為舊 16 位元系統識別字在某些 C++編譯器下無法通過編譯
5.參數之指標堆疊以 __stdcall 型態為共通及最常用方式
6.在 DLL 中使用「String」參數型態非常麻煩跟無效率,所以建議都用字元陣列來處理字串問題 (如本例中第二個函式傳入兩個字元陣列,將這兩個字元陣列合併後回傳,其實就是用來代替字串相加的處理程序 )
7.到C++Builder之「Project」主選單將整個專案「Builde All Projets」,系統即自動產生 MyDLL.dll 及 MyDLL.lib ( 註:DLL 專案不能直接用來「RUN」)

■ 使用 DLL 檔 – 動態載入方式

□ 動態載入DLL之特點

1.DLL 內容若有修改或擴充,只要主程式所叫用到的函式本身之傳入傳出參數結構不變,重新編譯DLL即可,主執行檔 (EXE 檔)不用重新編譯
2.呼叫這個 DLL 的主執行檔之呼叫程序撰寫較麻煩,需由程式設計者自行引用或釋放 DLL 所佔用之記憶體
3.應用架構如下
(1) 先自行宣告會用到的空函式指標,以便載入DLL時套用DLL內原本之函式
(2) LoadLibrary(“MyDLL.dll”); //載入 DLL
(3) GetProcAddress(DLL之Handle,”函式名稱”); //找出 DLL 內函式之位址指標
(4) FreeLibrary(DLL之Handle) ; // 釋放 DLL
4.若該 DLL 不是自己寫的,可用 C++Builder 內附之 tdump.exe 工具查看該 DLL 內有哪些函式可被開放叫用
5.開始撰寫測試程式,畫面如下,目的為測試之前設計的三個函式功能

測試程式 PJ_3_2_2.exe 之 Unit 完整程式碼:
//-------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"
//-------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"

//先自行宣告會用到的空函式指標,以便載入DLL時套用DLL內原本之函式
void(__stdcall *Showit)(char *message);
void (__stdcall *CharAdd)(char *inchr1,char *inchr2 ,char *buff);
int(__stdcall *IntAdd)(int a,int b);

HINSTANCE DLLInst;

TForm1 *Form1;
//-------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
//於 Form 建立時順便載入 DLL (動態載入)
DLLInst=LoadLibrary("MYDLL.DLL");
(FARPROC&)Showit=GetProcAddress(DLLInst,"Showit");
(FARPROC&)CharAdd=GetProcAddress(DLLInst,"CharAdd");
(FARPROC&)IntAdd=GetProcAddress(DLLInst,"IntAdd");

// 如此,主程式內就有 Shoeit()、CharAdd()、IntAdd()三個
DLL函式可用了
}
//-------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
//於 Form 關閉時時釋放 DLL
FreeLibrary(DLLInst);
}
//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//測試傳入字串陣列然後秀出
Showit("test");
}
//-------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
//測試傳入兩字串陣列然後回傳合併結果
char buff[256];
CharAdd("ab","cd",buff);
ShowMessage(buff);
}
//-------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
//測試傳入兩整數然後回傳相加結果
ShowMessage(IntAdd(1,2));
}
//-------------------------------------------------------

■ 使用 DLL 檔 – 靜態載入方式

□ 靜態載入DLL之特點

1.DLL 內容在修改或擴充時,就算主程式所叫用到的DLL函式本身之傳入傳出參數結構不變,但DLL內函式位置順序有變,不只是DLL檔,連整個主執行檔 (EXE 檔)都需重新編譯
2.主執行檔 (EXE 檔)編譯時需在專案檔(.bpr檔)中加入LIB 檔 (MyDLL.lib,當在製作MyDLL.dll 時,C++Builder 已同時產生),不像動態載入之主執行檔在編譯時根本不需DLL相關檔案存在 (只要執行時DLL檔存在就好了)
3.呼叫這個 DLL 的主執行檔之呼叫程序撰寫較簡單,系統自動管理或釋放 DLL 所佔用之記憶體
4.若主執行(EXE)檔編譯時要引用 MyDLL.h 標頭檔,此標頭檔還要大費周章經過一番修改(可參考其他 C++Builder 相關書籍),本書不使用引用 MyDLL.h 標頭檔的方法來編譯主程式,所以不用 MyDLL.h 標頭檔,減低應用複雜性
5.開始撰寫測試程式,同前例但使用靜態載入語法,目的同樣為測試之前設計的三個函式功能,您可發覺程式碼較動態載入方式簡單

測試程式 PJ_3_2_3.exe 之 Unit 完整程式碼:
//-------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"

//-------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;

//這個地方宣告方式跟動態載入不同
extern "C" __declspec(dllimport) void __stdcall Showit(char *message);
extern "C" __declspec(dllimport) void __stdcall CharAdd(char *inchr1,char *inchr2 ,char *r);
extern "C" __declspec(dllimport) int __stdcall IntAdd(int a,int b);
//以上三行也可拿掉並放在獨立之標頭檔內如 test.h 然後在本
//程式碼開頭 include "test.h"

//-------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//測試傳入字串陣列然後秀出
Showit("test");
}
//-------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
//測試傳入兩字串陣列然後回傳合併結果
char buff[256];
CharAdd("ab","cd",buff);
ShowMessage(buff);
}
//-------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
//測試傳入兩整數然後回傳相加結果
ShowMessage(IntAdd(1,2));
}

//-------------------------------------------------------

注意使用靜態載入方式,主執行檔 (EXE 檔)編譯時尚需在專案檔(.bpr檔)中加入LIB 檔 (MyDLL.lib)


『多執行緒測試』
一般書上或找到的範例 , 皆將 Thread 寫在另一個 Unit (如 Unit2)中
本範例將整個程式寫在一起 , 不知是否正確 , 但 Run 起來沒問題

■ 測試目的

利用一個無窮迴圈來顯示目前時間 , 但卻不會影響 User 在 Memo 中編輯資料 ;
若此無窮迴圈不是放在 Thread 中 , 則整個 cpu 時間會被其佔用 ,
程式 Hold 住根本無法在 Memo 中編輯資料 ....

1.建一個主 Form
2.放一個 Button1 ,其 Caption 設為 "開始 Show Time Thread"
3.放一個 Button2 ,其 Caption 設為 "結束 Show Time Thread"
4.再放一個 Edit1 & 一個 Memo1

完整程式碼如下 :

//-------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//-------------------------------------------------------
#include
#include
#include
#include
//-------------------------------------------------------
class TMyThread : public TThread
{
public:
__fastcall TMyThread(void);

private:
void __fastcall Execute(void);
void __fastcall ShowTime(void);
};
//-------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TButton *Button1;
TButton *Button2;
TEdit *Edit1;
TMemo *Memo1;
void __fastcall FormCreate(TObject *Sender);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
private: // User declarations
public: // User declarations
__fastcall TForm1(TComponent* Owner);

};
//-------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//-------------------------------------------------------
#endif


//-------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"
//-------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TMyThread *MyThread;
//-------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
//
}
//-------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
if (MyThread) MyThread->Terminate();
}
//=======================================================
// TMyThread 程序實作區段開始
//=======================================================
__fastcall TMyThread::TMyThread(void):TThread(true)
{
Resume();
}
//-------------------------------------------------------
void __fastcall TMyThread::Execute(void)
{
FreeOnTerminate=true;
while (!Terminated)
{
// Methods and properties of objects in VCL can only be
// used in a method called using Synchronize()
Synchronize(ShowTime); // 要改變 VCL 物件內容必須透過 Synchronize 呼叫
// 不能把 Form1->Edit1->Text=TimeToStr(Now()) 寫在這裡
Sleep(1000);
}
}
//-------------------------------------------------------
void __fastcall TMyThread::ShowTime(void)
{
//本執行緒真正的動作寫在此
Form1->Edit1->Text=TimeToStr(Now());
}
//=======================================================
// TMyThread 程序實作區段結束
//=======================================================

void __fastcall TForm1::Button1Click(TObject *Sender)
{
MyThread = new TMyThread;
}
//-------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
MyThread->Terminate();
}
//-------------------------------------------------------

 
『公司統一編號檢查函式』
bool __fastcall TForm1::Chk_unify(String d)
{
bool r=false;
String temp;
int x,y;

if (d.Length()!=8) return r;

String A[10];
String A7="";
String Key="12121241"; //統編檢查對應表
int sum1=0;
int sum2=0;

for (int i=1;i<=8;i++)
{
x=StrToInt(d.SubString(i,1));
y=StrToInt(Key.SubString(i,1));
temp=FormatFloat("00",x*y);
A[i]=IntToStr(StrToInt(temp.SubString(1,1))+StrToInt(temp.SubString(2,1)));
if (i==7 && A[i].Length()==2)
{
A7=A[i].SubString(2,1);
A[i]=A[i].SubString(1,1);
sum2=sum2+StrToInt(A7);
}
else
{
sum2=sum2+StrToInt(A[i]);
}

sum1=sum1+StrToInt(A[i]);
}

if ( ((sum1%10)==0) || ((sum2%10)==0) ) r=true;

return r;

}

 
『 刪除整個目錄的函式』
void __fastcall TForm1::_DelTree(String dir)
{
String d=dir;
String FileName;
TSearchRec SearchRec;

if (!DirectoryExists(dir)) return; // 要 #include "FileCtrl.hpp" 才有這個函式

if (d.SubString(d.Length(),1)!="\\") d=d+"\\";

if (FindFirst(d+"*.*", faAnyFile, SearchRec) == 0)
{
do { if (SearchRec.Attr == faDirectory)
{
if ((SearchRec.Name!=".") && (SearchRec.Name!=".."))
{
_DelTree(d+SearchRec.Name); //遞迴呼叫
RemoveDir(d+SearchRec.Name);
}
}
else
{
FileSetAttr(d+SearchRec.Name,faArchive);
DeleteFile(d+SearchRec.Name);
}
} while (FindNext(SearchRec) == 0);

FindClose(SearchRec);

}

RemoveDir(d);

}

 
『參數檔 (INI 檔) 處理』
INI 檔應用的歷史從 Windows 3.1就已存在,雖然它也是文字檔的一種,但它利用「節區(Section)」及「識別字(KeyWord)」觀念,類似資料庫的索引,所以用來當作少量變數資料的儲存器非常適合,除變數管理容易外尚可做程式流程控制,市售套裝軟體有用 ini 檔來控制其軟體版本例子,其主執行檔可能只有一個,但依 INI 檔設定可開放其全功能版(豪華版)與非全功能版;INI檔案 Size 雖然有 64k的限制,但已足夠存放上百個變數資料;在 32 位元系統上,正統取代 INI 機能的方法為使用 Windows Regist,但為了儲存Windows 作業系統本身的資訊,Regist已夠龐大,如果我們每個應用程式又去 Regist內挖一塊空間當作自己儲藏變數或參數的地方,會讓 Regist變得更龐大更複雜,所以筆者還是建議每個程式都使用自己的INI檔來儲存自己的變數,況且 INI檔不會被淘汰,連 Linux Kylix 下都看得到其蹤影

■ 應用實例

假設程式中需要紀錄目前發票號碼(invo_no)、交易序號(tran_no),而每次交易完都要將這兩個變數值加1,若程式從頭到尾都不關機,那沒有問題,程式會記錄目前的發票號碼及交易序號已排到幾號;但若程式中途有關機後,下次開機,程式怎知發票號碼及交易序號已排到幾號?所以除了將變數資料存於資料庫中外,更簡單的方法就是使用INI檔來記錄變數值,在程式重新啟動時再將變數值取回,所以該INI檔內容可能長得如下:

[Varible]
invo_no=00000005
tran_no=3

其中,用中括號刮起來的Variable就是「節區(Section)」名稱(可自訂),而其內的 INVO_NO、TRAN_NO則稱為「識別字(KeyWord)」(可自訂);不可有相同名稱的兩個節區(Section),但不同的節區中可有相同名稱的識別字(KeyWord),取用時按節區(Section)索引分開取用,如下圖:

[Varible]
invo_no=00000005
tran_no=3

[Varible_2]
invo_no=00000007
tran_no=5

■ 程式實例

接下來我們介紹使用 INI 檔來儲存或取回變數的方法,在程式中要使用 INI 檔有幾個步驟:

1.程式開頭必須 #include "inifiles.hpp"
2.程式中首先需建立一個 INI 物件以對應實際的 INI 檔,不用時再釋放這個 INI 物件,通常 INI 檔實 體與主執行檔存在同一路徑,甚至檔名也和主執行檔相同,但副檔名則為 *.ini
3.使用 INI 物件的某些方法(Method)來存取變數,取用的方法依變數型態有下列幾種方法(假設這個 INI 物件名為 MyIni)
MyIni->ReadInteger(節區名,識別字,整數Default值);
MyIni->ReadString(節區名,識別字,字串Default值);
MyIni->ReadBool(節區名,識別字,布林Default值)
4.寫入的方法依變數型態也有下列幾種方法
MyIni->WriteInteger(節區名,識別字,欲寫入之整數變數值);
MyIni->WriteString(節區名,識別字,欲寫入之字串變數值);
MyIni->WriteBool(節區名,識別字,欲寫入之布林變數值)

以下為一完整使用INI程式碼的範例
//-------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"
#include "inifiles.hpp" //要 include 這個東西才有 TIniFile 類別
//-------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TIniFile *MyIni;
//-------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//-------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
//建立 ini 物件及實體存放路徑
MyIni = new TIniFile(ChangeFileExt( Application->ExeName, ".ini" ) );

//讀取變數(若變數值不存在則用自動使用 Default 值)
Left=MyIni->ReadInteger( "Form","Left",100);
Caption=MyIni->ReadString( "Form","Caption","Default Caption");
}
//-------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
//將變數寫入 ini 物件
MyIni->WriteInteger("Form","Left",Left);
MyIni->WriteString("Form","Caption",Caption);

// 釋放 ini 物件
delete MyIni;
}
//-------------------------------------------------------

■ INI 物件的生命週期

在上面的程式碼中,可以看見我們把 INI 物件的建立寫在 FormCreate 事件中,而釋放 INI 物件則寫在 FormClose 事件中;事實上,變數真正寫到實體檔案中,是在INI 物件釋放的時候(也就是類似關閉檔案動作);平常則只是暫存於記憶體中,如此可減少磁碟 I/O 動作,但若程式不正常關閉,則INI 物件來不及將暫存於記憶體中的變數存檔,會導致欲存檔的變數遺失;所以為保險起見,我們可在準備寫入變數的時候才去建立 INI 物件,存入後馬上釋放 INI 物件以確保寫入實體檔案中,但這樣卻會增加磁碟I/O讀寫頻繁,其中的斟酌端看應用方向的特性。

//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TIniFile *MyIni;
MyIni = new TIniFile(ChangeFileExt( Application->ExeName, ".ini" ) );

MyIni->WriteInteger("Form","Left",Left);
MyIni->WriteString("Form","Caption",Caption);

delete MyIni;
}
//-------------------------------------------------------

 
『本文檔 ( TEXT檔) 處理』
本文檔 (TEXT 檔,或稱文字檔)的應用非常廣泛,最常見的應用就是不同平台或系統間的資料交換動作,以本文檔作為溝通橋樑;處理本文檔的讀寫有使用傳統 C 的語法及標準 C++ 的文字檔串流(stream)處理語法,雖然在 32位元平台下建議使用標準 C++ 的文字檔串流處理語法,但使用傳統 C 的語法有個好處,那就是 C 語法中 fprintf 及 fscanf 可以「格式化」輸出或輸入的字串的內容。

■ 本文檔的輸出

□使用傳統 C 的處理語法

1.要先於程式開頭 #include "stdio.h"
2.append (附增)模式之宣告
outfile=fopen("C:\\TEST.TXT","a+");
3.overwrite(覆寫)模式之宣告
outfile=fopen("C:\\TEST.TXT","w");

範例:
//-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
String m="fprintf 測試"; //欲輸出的字串行
FILE *outfile;
if ((outfile=fopen("C:\\TEST.TXT","w"))== NULL) return;

fprintf(outfile,"%s\n",m);

fclose(outfile);
}
//-------------------------------------------------------

□使用標準 C++ 的文字檔串流(stream)處理語法

1.要先於程式開頭 #include "fstream.h"

2.使用 ofstream 物件作為檔案串流輸出物件

3.append (附增)模式之宣告
ofstream outfile("c:\\test.txt" , ios::app);

4.overwrite(覆寫)模式之宣告 (Default)
ofstream outfile("c:\\test.txt" , ios::trunc);

ofstream outfile("c:\\test.txt")

範例:

 //-------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
  String m="ofstream 測試"; //欲輸出的字串行
  ofstream outfile("C:\\TEST.TXT");
  if (!outfile) return ;

  outfile << m.c_str();
outfile.close();
}
//-------------------------------------------------------

■ 本文檔的讀入

□ 使用傳統 C 的處理語法

由於 fscanf() 很難用 , 而且遇到空白就當作結束 , 所以拿來讀文字檔會很吃力
這裡省略不介紹


□ 使用標準 C++ 的文字檔串流(stream)處理語法

1.要先於程式開頭 #include "fstream.h"
2.使用 ifstream 物件作為檔案串流讀入物件
3.事先宣告一足夠容納讀入之每行長度的 buffer 當讀入緩衝區,若讀入之行長度超出緩衝區大小,有可能導致程式死當;故雖然在大部分的情況下,我們都可預測每行的讀入長度,但預防有例外狀況讀入某行長度特長,故筆者習慣將緩衝區大小設為 65535
4.使用 while (!infile.eof()) 迴圈來作逐行讀入
5.若於最後一行總是會讀入空白行時,可改用 while (!infile.getline(buff,sizeof(buff)).eof())迴圈來作逐行讀入

範例:

 //-------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
char buff[65535];
  String temp;
ifstream infile;
  infile.open("c:\\test.txt");
  if (!infile) return ;
while (!infile.eof())
{
infile.getline(buff,sizeof(buff));
temp = buff;
if (Trim(temp)=="") continue;

// 讀入之 buff 變數的應用例
Memo1->Lines->Add(temp);

}

infile.close();
}
//-------------------------------------------------------

 
『Tag & Hint 屬性的妙用』

■ 每個 VCL 物件幾乎都有 Tag 屬性 , Tag 可用來放一個 整數值 , 由於此屬性值不會影響物件外觀,或其他物件行為 , 所以通常都用來當作"藏值"的地方 ; 利用事件共用 , 可將一堆相同的物件集合分別藏不同的值 , 然後共用同一事件後再例用分離法 , 將此值取出 ; 如之前介紹的計算機按鈕 , 設計計算機程式時有十個相同的按鈕 0 - 9, 但不用為十個按鈕寫十個 OnClick , 共用同一事件但要能分辨彼此按鍵代表不同的值 ; 但今天若應用在別的地方 , 在 POS 系統觸控螢幕操作畫面 , 按下不同按鈕得出不同的條碼值 , 然後再去資料庫中搜尋商品 , 由於條碼值太長 (13 位數) , 最好是當字串處理 , 那該將每個按鈕所代表不同的條碼值藏在哪呢 ? 筆者第一個想到的就是 Hint 屬性 , 由於面板商品不需 Hint , Hint 設定後只要 ShowHint 不要設為 true , Hint 就不會跑出來 , Hint 就可拿來當作物件本身偷藏"字串"值的地方 , 以下是範例介紹

■ 動態產生"面板商品"按鈕的範例

□ 須使用按鈕 或是"看起來"像按鈕,且能按下去的元件
□ 要求條件 : Caption 要有兩行 , 按鈕可變顏色 , 要有立體感 , 但不能聚焦(Focused)
□ 測試
使用 Panel , 可變色 , 可設立體感 , 但 Caption 不能折行
使用 TSpeedButton 可設立體感 , Caption 可折行 , 但不能變色
使用 Label , 可變色 , 可折行 , 但不能設立體感
最後方案 使用 Label + Bevel(模擬按鈕立體感,及被按凹下去的感覺)
....當然也可使用 Label + Panel

□ 開始實做
1.首先開一個約 800 x 600 的 Form
2.再放一個 800 x 400 左右的 Panel (名叫 Panel1) 在 Form 中間偏下的部位
3.在中間偏上的部分放兩個 button , 一個用來產生動態物件 , 另一個用來釋放動態物件
4.按下"產生動態物件"按鈕後會在 Panel1 上產生一個 8 x 6 的按鈕陣列 , 重點是每個按鈕按下去所得的值都不相同 , 可用來藏"條碼值" , 然後做後續應用 ....
5.完整程式碼如下

//---------------------------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"
#include "fstream.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
TLabel *lb[49]; // 宣告動態 Label 物件陣列 , Global 變數
TBevel *bv[49]; // 宣告動態 Bevel 物件陣列 , Global 變數
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
// 產生動態物件的按鈕事件
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
//須使用不能聚焦元件....
//使用 Panel , 可變色 , 可設立體感 , 但 Caption 不能折行
//使用 TSpeedButton 可設立體感 , Caption 可折行 , 但不能變色
//使用 Label , 可變色 , 可折行 , 但不能設立體感
//最後方案 使用 Label + Bevel(模擬立體感)

int x=0; int y=1;
for (int i=1;i<=48;i++)
{
x++;
if (x>8)
{
x=1;
y++;
}

bv[i]= new TBevel(this);
bv[i]->Parent=Panel1;
bv[i]->Width=98;
bv[i]->Height=55;
bv[i]->Style=bsRaised;
bv[i]->Left=(x-1)*(bv[i]->Width+1)-0;
bv[i]->Top=(y-1)*(bv[i]->Height+1)-0;

lb[i]= new TLabel(this);
lb[i]->Parent=Panel1;
lb[i]->OnMouseDown=&FF_Plu_MouseDown; //指派動態物件所需的事件
lb[i]->OnMouseUp=&FF_Plu_MouseUp; //指派動態物件所需的事件
lb[i]->AutoSize=false;
lb[i]->Alignment=taCenter;
lb[i]->Width=96;
lb[i]->Height=53;
lb[i]->Left=bv[i]->Left+1;
lb[i]->Top=bv[i]->Top+1;

lb[i]->Tag=i;
lb[i]->Hint="條碼值 : 47100000000"+FormatFloat("00",i);

lb[i]->Caption="\n"+IntToStr(i)+"\n"+"test";
switch (y)
{
case 0 : lb[i]->Color=clBlack; break; //黑
case 1 : lb[i]->Color=0x000080FF; break; //棕
case 2 : lb[i]->Color=0x00FF80FF; break; //紅
case 3 : lb[i]->Color=0x004080FF; break; //橙
case 4 : lb[i]->Color=0x0080FFFF; break; //黃
case 5 : lb[i]->Color=0x0080FF80; break; //綠
case 6 : lb[i]->Color=0x00FF8000; break; //藍
case 7 : lb[i]->Color=0x00FF0080; break; //紫
case 8 : lb[i]->Color=clBtnFace; break; //灰
case 9 : lb[i]->Color=clWhite; break; //白
}

}

}
//---------------------------------------------------------------------------
// 自行寫給動態物件用的 OnMousDown() 事件
//---------------------------------------------------------------------------
void __fastcall TForm1::FF_Plu_MouseDown(TObject *Sender,
TMouseButton Button, TShiftState Shift, int X, int Y)
{
//按下 Label 時將 Label 中的 Tag 值取出 ,
//再呼叫對應 index 之 Bevel 物件做出外框 "按下"的感覺
bv[((TLabel *)Sender)->Tag]->Style=bsLowered;

//按下 Label 時改變 Label 位置以便有"按下"的感覺"
((TLabel *)Sender)->Left++;
((TLabel *)Sender)->Top++;
((TLabel *)Sender)->Width--;
((TLabel *)Sender)->Height--;

}
//---------------------------------------------------------------------------
//自行寫給動態物件用的 OnMousUp() 事件
//---------------------------------------------------------------------------
void __fastcall TForm1::FF_Plu_MouseUp(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
//放開 Label 時將 Label 中的 Tag 值取出 ,
//再呼叫對應 index 之 Bevel 物件做出外框 "浮起"的感覺
bv[((TLabel *)Sender)->Tag]->Style=bsRaised;

//放開 Label 時改變 Label Caption 位置以便有"浮起"的感覺"
((TLabel *)Sender)->Left--;
((TLabel *)Sender)->Top--;
((TLabel *)Sender)->Width++;
((TLabel *)Sender)->Height++;

ShowMessage(((TLabel *)Sender)->Hint); // 將被按下的 Label 偷藏的字串變數帶出以便應用

}

//---------------------------------------------------------------------------
//刪除動態物件的按鈕事件
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
for (int i=1;i<=48;i++)
{
delete lb[i];
delete bv[i];
}
}

 
『固定 TMediaPlayer 元件撥放影片的 Size』
■前言
我們通常拿 TMediaPlayer 來撥放媒體影片 , 而 TMediaPlayer 的 Display 屬性可設定影片要播放在哪個地方 , 如可以指定播放在某一 Panel 上 (哪些元件可供做播放平台可自行測試), 或是都不設 , 讓程式執行時系統自動另開一 Form 以供播放之用 , 但影片本身播放時的高度跟寬度有大有小 , 如何固定 Size 呢 ? TImage 有一屬性叫做 Stretch 可將影像調整跟 TImage 本身大小一樣 ; 但出乎意料之外的 , TMediaPlayer 的 Display 屬性居然不能設定播放平台為 TImage 元件 , 由於客戶的需求是不管影片本身解析度如何 , 播放出來都要為 800 x 600 Size , 我研究了好久才試出來 , 只要在播放之前指定播放 Size 為所要播放的元件平台 Size 即可

■實作 : 如要將影像播放在 Panel1 上
1.將 Panel1 Size 拉大為 800 x 600
2.在 Play() 之前設定程式碼 :

  TRect r;
  r.Left=0;
  r.Top=0;
  r.Right=Panel1->Width ;
  r.Bottom=Panel1->Height;
  MediaPlayer1->DisplayRect=r;
MediaPlayer1->Play();

則以後不管任何影片播放時 , 長寬度大小一定為 800 x 600

■補充 : 將影片連續播放
1.將 TMediaPlayer 之 AutoRewind 屬性設為 true
2.在 TMediaPlayer 之 OnNotify 事件中加入程式碼如下 :


void __fastcall TForm1::MediaPlayer1Notify(TObject *Sender)
{
  if (MediaPlayer1->NotifyValue == nvSuccessful)
      MediaPlayer1->Play();
}

 
『如何判斷關閉程式的動作』
■前言
有些常駐程式須判斷被關閉的時機,
讓 user 不可以任意關掉程式
但卻允許 user 關機
所以要判斷使用者是按了程式右上角 [X] 按鈕
或是 使用者按了螢幕左下角 [關機] 指令
下面是簡單判斷的程式碼...

//---------------------------------------------------------------------------
#include
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)
{
WindowProc=MyWndProc; //攔截訊息
}
//---------------------------------------------------------------------------
void __fastcall TForm1::MyWndProc(TMessage &Message)
{
 if (Message.Msg == WM_SYSCOMMAND)
 {
  if (Message.WParam== SC_CLOSE) //若Message為 Close Window
  {
   ShowMessage("使用者按了程式右上角 [X] 按鈕");
  }
 }

 if (Message.Msg == WM_ENDSESSION)
 ShowMessage("使用者按了螢幕左下角 [關機] 指令");

 //將訊息還給 Form 原來處理程序,否則只是收到關閉指令,但不會真正執行關閉動作
 WndProc(Message);
}

■補充
通常我們就在以上兩個時機點帶入一個旗標,
到FormCloseQuery()事件中再決定關機與否 ,
但根據我的測試,WM_ENDSESSION 觸發的時機會比 FormCloseQuery() 事件晚到
(WM_SYSCOMMAND 則不會) ,
也就是說錯過了關機判斷時機後,才會有 WM_ENDSESSION 訊息被收到 ,
這時可改判斷是否收到 WM_QUERYENDSESSION 來代替判斷 WM_ENDSESSION ,
程式流程就會正確判斷無誤

 
『 如何將自己的 AP 自動被 Focused』
■前言
最近在寫 POS 第二片螢幕做媒體播放時,媒體播放由於是呼叫另一個獨立執行檔(因為不想跟 POS 主程式混在一起寫),發覺焦點會跑到媒體播放程式上,本想說用以前的知識,先記住主程式的 Handle , 再用下列方法中的幾個方法
ShowWindow(Handle)
BringWindowToTop(Handle)
SetForegroundWindow(Handle)
SetActiveWindow(Handle)
Application->BringToFront()
就可將焦點自動帶回主程式
沒想到主程式是會被帶到 Top 沒錯
但似乎不能自動被 Focused
(按鍵主控權仍然在媒體播放程式上)
且執行上述 API 後
底下工作列,我主程式的小圖示會一閃一閃
表 API 有作用 , 但 就是一定要人用 MOUSE 去點一下
才會被 Focused (鍵盤輸入才會回到主程式)
但我的 POS 程式是無滑鼠系統,
鍵盤也是 POS 專屬鍵盤(沒有 ALT+Tab 可以切換程式焦點)
POS 程式上遺失焦點是件很嚴重的事
後來問人終於得到答案,在此分享:
在主程式中設個 Timer ,隨時檢查焦點並將焦點設回到主程式中

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, 0, SPIF_SENDCHANGE);
 SetForegroundWindow(Application->MainForm->Handle);
}

2013年4月10日 星期三

Windows Server 2012 如何在 Server Core 與 GUI 介面做切換

資料來源: http://www.dotblogs.com.tw/chou/archive/2012/09/05/74618.aspx

安裝 Windows Server 2012 時選錯項目,選成 Server Core,導致沒有 GUI 介面,是否有辦法不重新安裝直接切換回 GUI 介面?

在 Windows Server 2012 中,可以讓您在 Server Core 及 GUI 介面之間切換,透過 PowerShell 輸入指令或是透過移除角色及功能即可進行變更,不需要重新安裝。


問題的解決方法

如果你一開始是選擇安裝 Windows Server 2012(Server 含 GUI 版本)
GUI 介面切換為 Server Core 介面,使用 PowerShell
1. 在工具列上的 PowerShell 圖示上,按滑鼠右鍵,選擇【以系統管理員身分執行】。
image

2. 此時出現【系統管理員: Windows PowerShell】視窗,輸入【Uninstall-WindowsFeature Server-Gui-Mgmt-Infra -Restart】按 Enter 鍵。
image

3. 進行設定 Windows 功能,並會自動重新開機。
Windows Server 2012-2012-09-05-14-10-43

4. 切換為 Server Core 介面。
Windows Server 2012-2012-09-05-14-18-21

GUI 介面切換為 Server Core 介面,使用移除角色及功能
1. 執行【伺服器管理員】,按【管理】,選擇【移除角色及功能】。
image

2. 此時出現【移除角色及功能精靈】,按【下一步】。
image

3. 按【下一步】。
image

4. 按【下一步】。
image

5. 取消勾選【使用者介面與基礎結構】。
image

6. 此時會說明需要同時移除的相依功能,按【移除功能】。
image

7. 按【下一步】。
image

8. 按【移除】開始進行移除。
image

9. 移除完成後,按【關閉】,接著重新啟動 Windows。
image

10. 切換為 Server Core 介面。
Windows Server 2012-2012-09-05-14-43-43

GUI 介面切換為 Server Core 介面
1. 在【命令提示字元】中,輸入【powershell】按 Enter 鍵。
image

2. 輸入【Install-WindowsFeature Server-Gui-Shell -Restart】按 Enter 鍵。
image

3. 進行設定 Windows 功能,並會自動重新開機。
image

4. 切換為 GUI 介面。
Windows Server 2012-2012-09-05-15-05-17


如果你一開始是選擇安裝 Windows Server 2012(Server Core 安裝)
GUI 介面切換為 Server Core 介面
1. 在【命令提示字元】中,輸入【powershell】按 Enter 鍵。
image

2. 接著輸入【Get-windowsimage –imagepath <path to wim>\install.wim】,其中 <path to wim> 是您的安裝媒體 wim 路徑,例如我是安裝媒體在 D:,則輸入【Get-windowsimage -imagepath d:\sources\install.wim】後按 Enter 鍵,取得 Windows Server 2012 的 Index。
image

3. 接著安裝 Windows 功能,包含 Server-Gui-Mgmt-Infra,Server-Gui-Shell,如果您當初是安裝 Standard Server Core 版本,則最後的參數為 2,如果是 DataCenter Server Core 版本,則最後的參數為 4;我當初安裝的是 DataCenter Server Core 版本,因此我設定為 4,輸入指令【Install-WindowsFeature Server-Gui-Mgmt-Infra,Server-Gui-Shell -Source wim:d:\sources\install.wim:4】按 Enter 鍵進行安裝。
image

4. 切換為 GUI 介面。
Windows Server 2012-2012-09-05-15-05-17

其他相關資訊