公告

國明的網路筆記

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

其他相關資訊




2013年4月8日 星期一

Windows Server 2012 的常見管理工作及瀏覽方式



資料來源 : http://technet.microsoft.com/zh-tw/library/hh831491.aspx


[開始] 畫面就是 Windows 應用程式的首頁。若要開啟 [開始] 畫面,請使用下列其中一種方法:
  • 按 Windows 標誌鍵。在虛擬機器中,您可以按 Ctrl+Esc
  • 將滑鼠游標暫留在畫面右上角,然後按一下 [開始]。
  • 在桌面上,將滑鼠游標暫留在畫面左下角,然後在 [開始] 畫面縮圖出現時按一下。
  1. 將滑鼠游標暫留在畫面右上角,然後按一下 [設定]。
  2. 按一下 [電源],然後按一下 [關機]。
  1. 將滑鼠游標暫留在畫面右上角,然後按一下 [設定]。
  2. 按一下 [電源],然後按一下 [重新啟動]。
  • 按一下您在 [開始] 畫面右上角的使用者名稱,然後按一下 [鎖定]。或者,在 [開始] 畫面上,按 Windows 標誌鍵+L。
  • 按一下您在 [開始] 畫面右上角的使用者名稱,然後按一下 [登出]。
如果有 Windows 應用程式 (像是 Internet Explorer) 已開啟但未使用,則它們會自動最小化且成為非使用中,藉以釋放資源給其他應用程式。此功能類似行動電話、Tablet PC 或其他行動電腦。雖然不一定要關閉應用程式,但是如果您想這樣做,則可依照本節步驟進行。
  • 讓應用程式成為使用中,將滑鼠游標暫留在畫面頂端邊緣直到變成手形,按一下應用程式並拖曳到畫面底部,然後放開。
  • 按 Windows 標誌鍵+i,開啟目前螢幕 (例如 [開始]、桌面或 Windows 應用程式) 的 [設定] 列。
  • 或者,將滑鼠游標暫留在畫面右上角,然後按一下 [設定]。
您可以從 [開始] 畫面或從桌面存取 [控制台]。
  • 在 [開始] 畫面上,按一下 [控制台]。
  1. 在桌面上,將滑鼠游標暫留在畫面右上角,然後按一下 [設定]。
  2. 按一下 [控制台]。
  1. 開啟 [控制台]。
  2. 在 [控制台] 的 [搜尋] 方塊中,輸入桌面
  3. 在 [控制台] 的 [搜尋] 結果中,按一下 [顯示] 中的 [顯示或隱藏桌面上的一般圖示]。
  4. 在 [桌面圖示設定] 中,選取 [控制台],然後按一下 [確定]。
[系統管理工具] 資料夾含有常見的 Microsoft Management Console (MMC) 嵌入式管理單元的連結,包括 [電腦管理]、[事件檢視器],以及已安裝的角色或功能的管理工具。
Tip提示
[開始] 畫面設定 (在 [開始] 畫面上按 Windows 標誌鍵+i 即可存取) 包括一個選項,決定是否要讓 [系統管理工具] 出現在搜尋結果中或 [開始] 畫面。預設會啟用此設定。
  • 按一下伺服器管理員功能表列中的 [工具],以存取 [系統管理工具] 資料夾的內容。
  1. 在 [開始] 畫面上輸入系統管理工具,然後按一下 [搜尋] 列中的 [設定]。
  2. 按一下 [設定] 結果中的 [系統管理工具]。
Tip提示
如果停用 [顯示系統管理工具] 設定,[系統管理工具] 資料夾及其內容就不會出現在 [設定] 結果中。當您將滑鼠游標移至 [開始] 畫面的右上或右下邊緣時,就會出現 [顯示系統管理工具] 設定,這時請按一下 [設定]。
  • 開啟 [控制台],按一下 [系統及安全性],然後按一下 [系統管理工具]。
  1. 在 [Windows 檔案總管] 中,瀏覽至您要建立捷徑的程式位置。
  2. 在程式資料夾的可執行檔上按一下滑鼠右鍵,然後按一下 [建立捷徑]。Windows 不允許在某些資料夾建立捷徑,包括 [Program Files] 資料夾。
  3. 當系統提示您選取捷徑的位置時,請瀏覽至 [桌面] 資料夾。
  1. 在 [開始] 畫面上,搜尋或瀏覽至您要釘選到桌面工作列的應用程式。
  2. 在應用程式磚上按一下滑鼠右鍵,然後在應用程式列中,按一下 [釘選到工作列]。
  1. 開啟 [Windows 檔案總管]。
  2. 瀏覽至您要釘選到桌面工作列之程式所在的資料夾。
  3. 在程式資料夾的可執行檔上按一下滑鼠右鍵,然後按一下 [釘選到工作列]。
  1. 開啟 [Windows 檔案總管]。
  2. 瀏覽至您要釘選到 [開始] 畫面的程式或資料夾。
  3. 在可執行檔或資料夾上按一下滑鼠右鍵,再按一下 [釘選到 [開始] 畫面]。
  • 在桌面上按 Windows 標誌鍵+R,以開啟 [執行] 對話方塊。
    或者,在 [開始] 畫面上,輸入執行,然後按 Enter。
  1. 在 [開始] 畫面上,瀏覽至您要以系統管理員身分執行的應用程式。
  2. 在應用程式磚上按一下滑鼠右鍵,然後在應用程式列中,按一下 [以系統管理員身分執行]。
  1. 在 [Windows 檔案總管] 的可執行檔上按一下滑鼠右鍵,或是在桌面的程式捷徑上按一下滑鼠右鍵。
  2. 按一下 [以系統管理員身分執行]。
  • 執行下列其中一項。
    • 藉由啟用下列群組原則設定即可將 [以其他使用者身分執行] 命令新增至應用程式列:User Configuration/Administrative Templates/Start Menu and Taskbar/Show "Run as different user" command on Start。若要啟動本機群組原則編輯器,在 [開始] 畫面上輸入 gpedit.msc,當它顯示時按一下 [gpedit] 磚。
    • 從命令提示字元使用 runas 命令。如需如何在命令提示字元中使用 runas 命令的詳細資訊,請輸入 runas /?,然後按 Enter
伺服器管理員 預設會在 Administrators 群組成員登入執行 Windows Server 2012 的電腦時啟動。如果 伺服器管理員 尚未開啟,而您是伺服器的標準 (非系統管理員) 使用者,或是系統管理員變更了 伺服器管理員 預設設定,讓 伺服器管理員 不會在登入時自動開啟,則可使用本節的程序開啟。
  • 在 [開始] 畫面上,按一下 [伺服器管理員]。
Tip提示
如果停用 [顯示系統管理工具] 設定,[伺服器管理員] 磚就不會出現在 [開始] 畫面上。
  • 在工作列上,按一下 [伺服器管理員]。
  • 在 [開始] 畫面上,按一下 [Windows PowerShell]。
  • 在工作列上,按一下 [Windows PowerShell]。
  • 若要以系統管理員身分從 [開始] 畫面執行 Windows PowerShell,請在 [Windows PowerShell] 磚上按一下滑鼠右鍵,然後在應用程式列中,按一下 [以系統管理員身分執行]。
  • 若要以系統管理員身分從桌面執行 Windows PowerShell,請在工作列中的 [Windows PowerShell] 捷徑上按一下滑鼠右鍵,然後按一下 [以系統管理員身分執行]。
  1. 在 [開始] 畫面上,輸入 mstsc
  2. 按一下 [搜尋應用程式] 結果中的 [mstsc]。
  1. 在桌面上按 Windows 標誌鍵+R,以開啟 [執行] 對話方塊。
  2. 在 [執行] 對話方塊中,輸入 mstsc,然後按 Enter
  1. 在 [開始] 畫面上,輸入 cmd
  2. 按一下 [應用程式] 結果中的 cmd
  1. 在桌面上按 Windows 標誌鍵+R,以開啟 [執行] 對話方塊。
  2. 在 [執行] 對話方塊中,輸入 cmd,然後按 Enter
  1. 在 [開始] 畫面上,輸入 mmc
  2. 按一下 [應用程式] 結果中的 mmc
  1. 在桌面上按 Windows 標誌鍵+R,以開啟 [執行] 對話方塊。
  2. 在 [執行] 對話方塊中,輸入 mmc,然後按 Enter
  1. 在 [開始] 畫面上,輸入嵌入式管理單元的可執行檔名稱。
    範例:輸入 gpedit.msc
  2. 當 [應用程式] 結果顯示嵌入式管理單元時,按一下磚。
  1. 在桌面上按 Windows 標誌鍵+R,以開啟 [執行] 對話方塊。
  2. 輸入嵌入式管理單元的可執行檔名稱,然後按 Enter
    範例:輸入 gpedit.msc
Tip提示
或者,您也可以從 [系統管理工具] 資料夾開啟技術特定的嵌入式管理單元。如需如何存取 [系統管理工具] 資料夾的相關資訊,請參閱本主題中的存取 [系統管理工具]
本節提供在 Windows 7 與 Windows Server 2008 R2 均相同的鍵盤快速鍵,以及 Windows 8 與 Windows Server 2012 的新快速鍵表格。
note備註
鍵盤快速鍵需要特定設定及環境,才能在遠端桌面或虛擬機器工作階段中運作。如需詳細資訊,請參閱本主題中的在遠端桌面工作階段中使用鍵盤快速鍵在 Hyper-V 虛擬機器中使用鍵盤快速鍵

在 Windows 7 或 Windows Server 2008 R2 均相同的鍵盤快速鍵

按鍵Windows 7 或 Windows Server 2008 R2 功能
Windows 標誌鍵
顯示或隱藏 [開始] 畫面
Windows 標誌鍵+向左鍵
將使用中的傳統型應用程式視窗固定於畫面左半邊 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+向右鍵
將使用中的傳統型應用程式視窗固定於畫面右半邊 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+向上鍵
將使用中的傳統型應用程式視窗最大化 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+向下鍵
將使用中的傳統型應用程式視窗還原或最小化 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+Shift+向上鍵
將使用中的傳統型應用程式視窗垂直最大化並維持寬度 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+Shift+向下鍵
將使用中的傳統型應用程式視窗垂直還原或最小化 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+Shift+向左鍵
將要監視的使用中傳統型應用程式視窗移到左邊 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+Shift+向右鍵
將要監視的使用中傳統型應用程式視窗移到右邊 (對於 Windows 市集應用程式沒有影響)
Windows 標誌鍵+P
顯示投影選項
Windows 標誌鍵+Home
將所有非使用中視窗最小化,並在第二次按鍵輸入時還原 (對於 Windows 應用程式沒有影響)
Windows 標誌鍵+<數字>
開啟或切換到位於工作列指定位置的程式 (範例:Windows 標誌鍵+1 可開啟第一個程式)
Windows 標誌鍵+Shift+<數字>
開啟位於工作列指定位置程式新的或額外的工作階段
Windows 標誌鍵+Ctrl+Shift+<數字>
以系統管理員身分,開啟位於工作列指定位置程式新的或額外的程式工作階段
Windows 標誌鍵+B
將焦點設在通知區域中
Windows 標誌鍵+Break
顯示 [系統內容] 對話方塊
Windows 標誌鍵+D
顯示桌面,並在第二次按鍵輸入時還原 (對於 Windows 應用程式沒有影響)
Windows 標誌鍵+E
開啟 [Windows 檔案總管] 以顯示 [電腦] 頁面
Windows 標誌鍵+Ctrl+F
搜尋電腦 (如果您在網路上)
Windows 標誌鍵+G
循環顯示已安裝的 Windows 桌面小工具
Windows 標誌鍵+L
鎖定電腦 (如果已連接網路網域) 或切換使用者 (如果未連接網路網域)
Windows 標誌鍵+M
將所有視窗最小化
Windows 標誌鍵+Shift+M
將最小化的視窗還原到桌面 (對於 Windows 應用程式沒有影響)
Windows 標誌鍵+R
開啟 [執行] 對話方塊。
Windows 標誌鍵+T
將焦點設在工作列上並循環顯示程式
Windows 標誌鍵+Alt+Enter
開啟 [Windows Media Center]
Windows 標誌鍵+U
開啟 [輕鬆存取中心]
Windows 標誌鍵+X
開啟 [Windows 行動中心]
Windows 標誌鍵+F1
開啟 [Windows 說明及支援]
Windows 標誌鍵+N
建立新便箋 (OneNote)
Windows 標誌鍵+S
開啟畫面剪輯器 (OneNote)
Windows 標誌鍵+Q
開啟 Lync (Lync)
Windows 標誌鍵+A
接受來電 (Lync)
Windows 標誌鍵+X
拒絕來電 (Lync)
Windows 標誌鍵+減號 (-)
拉遠 (放大鏡)
Windows 標誌鍵+加號 (+)
拉近 (放大鏡)
Windows 標誌鍵+Esc
關閉 [放大鏡]

Windows 8 與 Windows Server 2012 新增的鍵盤快速鍵

按鍵Windows 8 或 Windows Server 2012 功能
Windows 標誌鍵+空格鍵
切換輸入語言及鍵盤配置
Windows 標誌鍵+O
鎖定裝置方向
Windows 標誌鍵+Y
暫時顯示桌面
Windows 標誌鍵+V
循環顯示通知
Windows 標誌鍵+Shift+V
反向循環顯示通知
Windows 標誌鍵+Enter
開啟 [朗讀程式]
Windows 標誌鍵+PgUp
將 Windows 應用程式往螢幕的左邊移動
Windows 標誌鍵+PgDown
將 Windows 應用程式往螢幕的右邊移動
Windows 標誌鍵+Shift+句號 (.)
將邊緣向左移 (貼齊應用程式)
Windows 標誌鍵+句號 (.)
將邊緣向右移 (貼齊應用程式)
Windows 標誌鍵+C
開啟快速鍵列
Windows 標誌鍵+I
開啟 [設定] 窗格
Windows 標誌鍵+K
開啟 [裝置] 窗格
Windows 標誌鍵+H
開啟 [分享] 窗格
Windows 標誌鍵+Q
開啟 [搜尋] 窗格
Windows 標誌鍵+W
開啟 [設定搜尋] 應用程式
Windows 標誌鍵+F
開啟 [檔案搜尋] 應用程式
Windows 標誌鍵+Tab
循環顯示 Windows 應用程式
Windows 標誌鍵+Shift+Tab
反向循環顯示 Windows 應用程式
Windows 標誌鍵+Ctrl+Tab
循環顯示並貼齊 Windows 應用程式
Windows 標誌鍵+Z
開啟應用程式列
在您連線到遠端桌面 (也稱為 RDP) 工作階段之前,不論工作階段是包含在視窗或佔據整個螢幕,您都可以設定工作階段來接受 Windows 按鍵組合。
  1. 如果 [遠端桌面連線] 對話方塊尚未開啟,請在 [開始] 畫面上輸入 mstsc,然後按 Enter 來開啟該對話方塊。
  2. 在 [遠端桌面連線] 對話方塊上,按一下 [顯示選項] 來顯示連線設定索引標籤。
  3. 在 [本機資源] 索引標籤的 [鍵盤] 區域中,從 [套用 Windows 按鍵組合] 下拉式清單中選取下列其中一項。
    • 若要將鍵盤快速鍵套用到全螢幕的遠端桌面工作階段,請選取 [只有在使用全螢幕時]。
    • 若要將鍵盤快速鍵套用到包含於視窗內的遠端桌面工作階段,請選取 [在遠端電腦上]。
  4. 當您完成遠端桌面工作階段的其他設定後,請按一下 [連線] 連接工作階段並開始工作,或按一下 [一般] 索引標籤上的 [儲存],將連線設定儲存另存為 RDP 檔,以供未來連線使用。
在您啟動虛擬機器連線之前,您可以藉由在 [Hyper-V 管理員] 主控台上編輯實體電腦的 Hyper-V 設定,將 Windows 按鍵組合套用到實體主機電腦上的虛擬機器連線。
note備註
如果 Hyper-V 主機電腦正在執行 Windows Server 2012,預設就會選取這個程序中的設定。如果主機電腦正在執行 Windows 8、Windows Server 2008 R2 或 Windows Server 2008,您必須變更設定才能將 Windows 按鍵組合套用到虛擬機器連線。
  1. 若 [Hyper-V 管理員] 嵌入式管理單元尚未開啟,請將其開啟。
    • 如果您正在執行 Windows 8 的 [遠端伺服器管理工具],或您正在執行 Windows Server 2012,請開啟伺服器管理員,然後從伺服器管理員的 [工具] 功能表中開啟 [Hyper-V 管理員]。
    • 在 [開始] 畫面上,按一下 [Hyper-V 管理員]。
    • 如果 [Hyper-V 管理員] 磚不在 [開始] 畫面,請輸入 Hyper-V Manager 的全部或部分名稱,直到 [Hyper-V 管理員] 磚出現在 [開始] 畫面。
  2. 在樹狀目錄窗格中的實體主機電腦上按一下滑鼠右鍵,然後按一下 [Hyper-V 設定]。
  3. 在瀏覽窗格的 [使用者] 區域中,按一下 [鍵盤] 顯示鍵盤快速鍵設定。
  4. 選取 [用於虛擬機器] 以允許新的虛擬機器連線接受來自實體電腦的 Windows 按鍵組合。按一下 [確定] 以儲存變更,然後關閉 [Hyper-V 設定] 對話方塊。
    note備註
    這個設定不會套用到已開啟的虛擬機器連線。