元件製作之五(光碟機元件)

這將是最後一個元件了,目標定為非視覺化,事實上非視覺化元件要比視覺化元件難做,因為是從TComponent繼承而來,就沒有了很多屬性和事件。而這些都要我們從頭來做過。
這個非視覺化元件,我決定為光碟機元件,其中用到的技術較多,我不如列一個表出來,然後再來講解好一點。另外,可能篇幅會多一些,請耐心看。
用到的技術:
1.作為核心功能,當然是光碟機的應用啦。
2.光碟機元件怎麼樣影響到主視窗最小化時隱藏
3.光碟機如何處理訊息
4.元件編輯器的用法
上面每一個技術都非常有趣,讓我們一個個來看吧:
 
一、光碟機,是系統殼編程的一個功能,相信我們也看過很多啦,大概知道它用起來是什麼樣子的。
那麼它是如何實現的呢,
Windows定義了這樣一個結構來存放光碟機的資訊:
typedef struct _NOTIFYICONDATA { // nid 
DWORD cbSize;
    HWND hWnd;
    UINT uID;
    UINT uFlags;
    UINT uCallbackMessage;
    HICON hIcon;
    char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;
cbSize是NOTIFYICONDATA結構的尺寸,我們一般用Sizeof就可以了
hWnd一個視窗控制碼,用於檢索光碟機消息的。然而我們的非可視元件並沒有視窗呀,這就是技術列表第三條要講的,這裏從略
uID 唯 一標識光碟機圖示的,我們可以隨便指定一個數,但如果同時有不同的圖示,則數應該不同
uFlags是NIF_ICON,NIF_MESSAGE,NIF_TIP中的一個或多個,我們全用就可以了。
uCallbackMessage;光碟機消息,是我們自定義的消息,這裏我們定義為:
    const
      WM_TrayMsg=WM_USER+10;
hIcon光碟機圖示控制碼
szTip這個是光碟機提示,當光碟機出現時,滑鼠移到哪里,就會出現該提示。
Delphi將這個結構重定義為TNotifyIconData,我們照這個來用就行了
 
我們應用光碟機要用到API函數Shell_NotifyIcon,其中有兩個參數,第一個為
NIM_ADD,NIM_DELETE  ,NIM_MODIFY中的一個,分別表示添加光碟機(圖示出現)
修改光碟機(比如圖示,提示),刪除(圖示消失)第二個參數是NOTIFYICONDATA的指針
嗯,光碟機應該差不多了。
 
二、這個元件能夠決定主表單最小化時,是否是正常最小化並沒有光碟機圖示。還是最小化到螢幕之外,使我們看不見,且光碟機區出現了圖示。這裏有一個成員為FActive來決定。
那麼我們是怎麼樣影響到主表單呢,也即怎麼截獲表單的最小化消息呢。
總體變數Application有一個方法為procedure HookMainWindow(Hook: TWindowHook);
顧名思義,就是鉤到主視窗的所有消息。裏面的參數是TWindowHook類型,它是一個方法指標,定義如下:
type TWindowHook = function(var Message: TMessage): Boolean of object;
我們要自己定義過程的,然後傳給HookMainWindow:
function AppMsgHook(var Msg:TMessage):Boolean;
Application.HookMainWindow(AppMsgHook);
這樣做之後,主視窗的所有消息都會經過AppMsgHook方法啦,最小化消息也不例外,則我們可以在裏面截獲這個消息,並做一些操作:
 
做什麼操作呢,先判斷元件是否為設計時,如果是,不進行操作,如果不是進行下一步
if not (csDesigning in ComponentState) then
這樣的意圖是很明顯的,因為當設計時的主窗其實是Delphi的IDE,如果讓他處理該消息,其實是處理IDE的最小化消息,這時如果你最小化IDE,就會出現光碟機啦。所以不能。
 
下一步是是否截獲了最小化消息,以及FActive是否為真:
if (Msg.Msg=WM_SYSCOMMAND) and(FActive) then
兩樣都成立,執行裏面的代碼,代碼中有解釋,這裏只說兩個:
SetWindowLong(Application.Handle,GWL_EXSTYLE ,WS_EX_TOOLWINDOW);
設置了這個屬性後,視窗最小化就不會停在任務欄了,而是停在螢幕的某個位置,這個位置在哪里呢,由
placement.flags:=WPF_SETMINPOSITION;
     placement.ptMinPosition.x:=1050;
     placement.ptMinPosition.y:=800;
     SetWindowPlacement(Application.Handle,@placement);
決定,具體的看代碼,自己查幫助吧,這裏不多說
 
而上說的設置SetWindowLong後,問題來了,視窗最小化的風格一變了,當你把Factive設為False,再最小化視窗,此時是沒有光碟機圖示,但視窗還是最小化到螢幕的那個位置去了,我們看不到,又不能使其恢復(沒有光碟機)。怎麼辦呢,
原來還有一個GetWindowLong函數會返回當前風格的值,我們可以在控制項的構造函數中這樣調用
OldStyleEX:=GetWindowLong(Application.Handle,GWL_EXSTYLE);
這時,OldStyleEX:就保存了視窗原來最小化的風格了,視窗最小化,調用SetWindowLong,設置了新的最小風格。而當我們觸發光碟機事件,使表單恢復大小時,我們在處理函數中調用
SetWindowLong(Application.Handle,GWL_EXSTYLE ,OldStyleEX);
這樣,視窗又回到了原來的風格,這時我們設Factive為False,則視窗就能正常最小化了。
 
到控制項被釋放時,我們一定要調用Application.UnhookMainWindow(AppMsgHook);來解除鉤子
 
其實這裏也有一個不完善的地方,應該再設一個成員變數,確定設置光碟機時,視窗是正常最小化,還是最小化到看不見。而我沒有這麼做,直接如果 FActive為True,最小化會出現光碟機圖示,並且視窗最小化到看不見。不過影響不大,有興趣的朋友看了之後可以幫我完善一下,也當做自己的練習 嗎。
 
三、光碟機如果處理消息,上面說到,要設置光碟機結構,一定要有一個視窗控制碼,才能檢索光碟機消息,那麼這個控制碼是什麼呢,非可視元件沒有視窗控制碼呀。
 
如果你有看過TTimer的源碼,一定知道這一句代碼:
FWindowHandle := AllocateHWnd(WndProc);
它創建一個看不見的視窗,返回他的控制碼,並指定WndProc為視窗的消息處理過程
我們何不效仿它呢。
於是也定義一個成員控制碼:
FHandle: HWnd;
把該控制碼賦給NOTIFYICONDATA的hWnd欄位
再定義一個消息處理過程:
procedure WndProc(var Msg: TMessage);
再在元件構造函數中:
FHandle := AllocateHWnd(WndProc);
如此之後,元件就可以截獲光碟機的消息了,並在WndProc過程中作相應處理。這裏有必要對光碟機的自定義消息做一個介紹:
我們自定義了這個消息WM_TrayMsg,它的lParam與光碟機的uID相同,wParam是滑鼠在圖示上發生的事件消息,比如單擊,雙擊等。
我們就要把這些消息轉化為事件,供給用戶處理,所以定義幾個事件調度函數:
//以下為事件的調度函數
    procedure DblClick; dynamic;
    procedure Click; dynamic;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
procedure MouseMove(Shift: TShiftState; X, Y: Integer); dynamic;
意思很明顯,不多說,
當然也有幾個事件方法指針:
FOnIconClick: TNotifyEvent;
FOnIconDblClick: TNotifyEvent;
FOnIconMouseMove: TMouseMoveEvent;
FOnIconMouseDown: TMouseEvent;
FOnIconMouseUp: TMouseEvent;
然後在WndProc中判斷消息,並調用相應的事件調度函數。看代碼吧,有解釋。
 
好了,三個技術解決了,第四個呢,還是等代碼出來以後再加組件編輯器吧。以下是源代碼:
 
unit MyTray;
 
interface
 
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls,
  Forms, Dialogs, ShellApi, ExtCtrls,StdCtrls;
 
const
//自定義光碟機消息
   WM_TrayMsg=WM_USER+10;
 
type
 //恢復視窗的方式,左雙擊,右雙擊,左單擊,右雙擊
  TRMode=(LDbClick,RDbClick,LCLick,RClick);
 
  TMyTray=class(TComponent)
  private
  //私有成員
    FIcon:TIcon;   //圖示
    FDfIcon:THandle; //應用程式的默認圖示
    FSetDfIcon:Boolean; //是否用應用程式的圖示,如果為True,則Ficon為nil
    FIconData: TNotifyIconData;  //光碟機資料結構
    isMin:Boolean;//標識是否視窗最小化了
    FHandle: HWnd;  //不可視建表單控制碼,用於處理光碟機事件
    FActive: Boolean;  //是否啟用光碟機
    FHint: string;  //光碟機提示字串
    FRMode:TRMode; //恢復視窗的方式
    isClickIn:Boolean;//標識滑鼠是否點在圖示上
    OldStyleEX:longInt; //保存老的視窗風格
  //事件成員
    FOnIconClick: TNotifyEvent;
    FOnIconDblClick: TNotifyEvent;
    FOnIconMouseMove: TMouseMoveEvent;
    FOnIconMouseDown: TMouseEvent;
    FOnIconMouseUp: TMouseEvent;
  //設置方法
    procedure SetIcon(value:TIcon);
    procedure SetDfIcon(value:boolean);
    procedure SetActive(value:boolean);
    procedure SetHint(value:string);
    procedure SetRMode(value:TRMode);
  //私有方法
    procedure SetTray(Way:DWORD);  //設置光碟機樣式,修改,刪除,增加
    function GetActiveIcon:THandle; //取得有用的圖示控制碼
  protected
    //應用程式的消息鉤子,獲得主視窗的最小化消息
    function AppMsgHook(var Msg:TMessage):Boolean;
    procedure WndProc(var Msg: TMessage);//不可視視窗的視窗過程
    //以下為事件的調度函數
    procedure DblClick; dynamic;
    procedure Click; dynamic;
    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); dynamic;
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); dynamic;
  public
     constructor Create(AOwner:TComponent);override;
     destructor  Destroy;override;
  published
     property Active:Boolean read FActive write SetActive default False;
     property Icon:TIcon read FIcon write SetICon;
     property SetDfIconed: boolean read FSetDfIcon write SetDfIcon default true;
     property Hint:String read FHint write SetHint;
     property RMode:TRmode read FRmode write SetRMode default LDbClick;
  //事件的方法指針
     property OnIconClick: TNotifyEvent read FOnIconClick write FOnIconClick;
     property OnIconDblClick: TNotifyEvent read FOnIconDblClick write FOnIconDblClick;
     property OnIconMouseMove: TMouseMoveEvent read FOnIconMouseMove write FOnIconMouseMove;
     property OnIconMouseDown: TMouseEvent read FOnIconMouseDown write FOnIconMouseDown;
     property OnIconMouseUp: TMouseEvent read FOnIconMouseUp write FOnIconMouseUp;
  end;
 
procedure Register;
 
implementation
 
procedure Register;
begin
  RegisterComponents('Wind', [TMyTray]);
end;
 
///////////TmyTray////////////////////////////
constructor TMyTray.Create(AOwner:TComponent);
begin
  inherited Create(AOwner);
  //設置程式鉤子,指定AppMsgHook為處理函數,
  //則,應用程式的任何消息都將經過這個函數
   Application.HookMainWindow(AppMsgHook);
   FICon:=TICon.Create;
   //得到默認圖示的控制碼,圖示為應用程式的圖示
   FDfIcon:=Application.Icon.Handle;
   FSetDfIcon:=True;
   FActive:=False;
   FRMode:=LDbClick;
   isMin:=False;
  //創建一個不可視視窗,並指定視窗過程,以處理光碟機事件
    FHandle := AllocateHWnd(WndProc);
  //保存表單的老的風格,在恢復視窗的同時也恢復原來的視窗風格
    OldStyleEX:=GetWindowLong(Application.Handle,GWL_EXSTYLE);
end;
 
destructor TMyTray.Destroy;
begin
  Application.UnhookMainWindow(AppMsgHook);
  //物件釋放之前先消除光碟機
    SetTray(NIM_DELETE);
  //釋放不可能窗口的控制碼
  DeallocateHWnd(FHandle);
  FICon.Free;
  inherited Destroy;
end;
//應用程式鉤子,可以截獲應用程式的所有消息
function TMyTray.AppMsgHook(var Msg:TMessage):Boolean;
var placement:WINDOWPLACEMENT;
begin
 Result:=False;
 //保證程式不會在設計時處理最小化消息
 if not (csDesigning in ComponentState) then
 if (Msg.Msg=WM_SYSCOMMAND) and(FActive) then
 begin
   if msg.WParam=SC_MINIMIZE Then
    begin
    //設置了這個屬性後,視窗最小化就不會停在任務欄了,而是停在螢幕,
    //位置由SetWindowPlacement來決定
     ShowWindow(Application.Handle,SW_HIDE);
     SetWindowLong(Application.Handle,GWL_EXSTYLE      ,WS_EX_TOOLWINDOW);
     GetWindowPlacement(Application.Handle,@placement);
     placement.flags:=WPF_SETMINPOSITION;
     placement.ptMinPosition.x:=1050;
     placement.ptMinPosition.y:=800;
     SetWindowPlacement(Application.Handle,@placement);
     SetTray(NIM_ADD );
   end;
 end;
end;
 
procedure TMyTray.SetIcon(Value:TIcon);
begin
   FIcon.Assign(Value);
   FsetDfIcon:=False; //有了自定義的圖示,則默認圖示自動設為False
   if FIcon.Empty then
    FsetDfIcon:=True;
   if (isMin)and(Factive) then
     SetTray(NIM_MODIFY );
end;
//設置是否為默認圖示,與FIcon為互相的變數,只能有其中一個
procedure TMyTray.SetDfIcon(Value:Boolean);
begin
  if FSetDfIcon<>Value then
  begin
    FSetDfIcon:=Value;
    if not FSetDfIcon then
    begin
      if FIcon.Empty then begin
        FSetDfIcon:=True;
        exit;
      end;
    end
    else begin
        if (IsMin)and(FActive) then
         SetTray(NIM_MODIFY);
    end;
  end;
end;
 
procedure TMyTray.SetActive(Value:Boolean);
begin
  if FActive<>Value then
  begin
    FActive:=Value;
  end;
end;
 
procedure TMyTray.SetHint(Value:String);
begin
   if FHint<>Value then
   begin
     FHInt:=Value;
     if (IsMin)and(FActive) then
        SetTray(NIM_MODIFY);
   end;
end;
 
procedure TMyTray.SetRMode(Value:TRMode);
begin
  if FRmode<>Value then
    FRmode:=Value;
end;
//設置光碟機方式,顯示,修改,刪掉,重要方法
procedure TMyTray.SetTray(Way:DWORD);
begin
   FIconData.cbSize:=Sizeof(FIconData);
   FIconData.Wnd:=FHandle;
   FIConData.uID:=0;
   FIConData.uFlags:=NIF_ICON or NIF_MESSAGE or NIF_TIP;
   FIConData.uCallbackMessage:=WM_TrayMsg;
   FIConData.hIcon:=GetActiveIcon;
   StrLCopy(FIConData.szTip,Pchar(FHint),63);
   Shell_NotifyIcon(Way,@FIconData);
end;
//取得可用的圖示
function TMyTray.GetActiveIcon:THandle;
begin
   if not FSetDfIcon then
     result:=FIcon.Handle
   else
     result:=FDfIcon;
end;
//光碟機消息的截獲,以調用相應的事件調度方法
procedure TMyTray.WndProc(var Msg: TMessage);
var p:TPoint;
begin
  if (Msg.Msg=WM_TrayMsg)and(FActive) then
  begin
    case Msg.LParam of
      WM_LBUTTONDBLCLK://左雙擊
      begin
        GetCursorPos(p);
        DblClick;
        MouseDown(mbLeft, KeysToShiftState(TWMMouse(Msg).Keys)+[ssDouble], P.X, P.Y);
        if FRmode=LDbclick then
        begin
          ShowWindow(Application.Handle,SW_SHOW);
          //這裏很重要的一個就是恢復視窗風格,不然下次把Active設為True
          //最小化後,視窗依然會往左下角飛去,而光碟機圖示卻看不見了.
          SetWindowLong(Application.Handle,GWL_EXSTYLE    ,OldStyleEX);
          SendMessage(Application.Handle,WM_SYSCOMMAND,SC_RESTORE,0);
          SetTray(NIM_DELETE);
        end;
      end;
      WM_RBUTTONDBLCLK://右雙擊
      begin
        GetCursorPos(P);
        DblClick;
        MouseDown(mbRight, KeysToShiftState(TWMMouse(Msg).Keys)+[ssDouble], P.X, P.Y);
        if FRmode=RDbclick then
        begin
          ShowWindow(Application.Handle,SW_SHOW);
          SetWindowLong(Application.Handle,GWL_EXSTYLE    ,OldStyleEX);
          SendMessage(Application.Handle,WM_SYSCOMMAND,SC_RESTORE,0);
          SetTray(NIM_DELETE );
        end;
      end;
      WM_MOUSEMOVE: //滑鼠移動
      begin
        GetCursorPos(P);
        MouseMove(KeysToShiftState(TWMMouse(Msg).Keys), P.X, P.Y);
      end;
      WM_LBUTTONDOWN: //左單擊下
      begin
        GetCursorPos(P);
        IsClickIn:=True;
        MouseDown(mbLeft, KeysToShiftState(TWMMouse(Msg).Keys) + [ssLeft], P.X, P.Y);
      end;
      WM_LBUTTONUP:  //左單擊彈起
      begin
        GetCursorPos(P);
        if IsClickIn then
        begin
          IsClickIn:=False;
          Click;
          if FRmode=LClick then
          begin
            ShowWindow(Application.Handle,SW_SHOW);
            SetWindowLong(Application.Handle,GWL_EXSTYLE ,OldStyleEX);
            SendMessage(Application.Handle,WM_SYSCOMMAND,SC_RESTORE,0);
            SetTray(NIM_DELETE );
          end;
        end;
          MouseUp(mbLeft, KeysToShiftState(TWMMouse(Msg).Keys)+ [ssLeft], P.X, P.Y);
      end;
      WM_RBUTTONDOWN: //右單擊下
      begin
        GetCursorPos(P);
        IsClickIn:=True;
        MouseDown(mbRight, KeysToShiftState(TWMMouse(Msg).Keys) + [ssRight], P.X, P.Y);
      end;
      WM_RBUTTONUP: //右單擊彈起
      begin
        GetCursorPos(P);
        if IsClickIn then
        begin
          IsClickIn:=False;
          Click;
          if FRmode=RClick then
          begin
            ShowWindow(Application.Handle,SW_SHOW);
            SetWindowLong(Application.Handle,GWL_EXSTYLE ,OldStyleEX);
            SendMessage(Application.Handle,WM_SYSCOMMAND,SC_RESTORE,0);
            SetTray(NIM_DELETE );
          end;
        end;
        MouseUp(mbRight, KeysToShiftState(TWMMouse(Msg).Keys)+ [ssRight], P.X, P.Y);
       end;
      end;
  end
  else
     Msg.Result := DefWindowProc(FHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
//以下為幾個事件的調度函數,比較簡單.
procedure TMyTray.DblClick;
begin
  if Assigned(FOnIconDblClick) then
    FOnIconDblClick(Self);
end;
 
procedure TMyTray.Click;
begin
  if Assigned(FOnIconClick) then
    FOnIconClick(Self);
end;
 
procedure TMyTray.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnIconMouseDown) then
    FOnIconMouseDown(Self, Button, Shift, X, Y);
end;
 
procedure TMyTray.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnIconMouseUp) then
    FOnIconMouseUp(Self, Button, Shift, X, Y);
end;
 
procedure TMyTray.MouseMove(Shift: TShiftState; X, Y: Integer);
begin
  if Assigned(FOnIconMouseMove) then
    FOnIconMouseMove(Self, Shift, X, Y);
end;
 
end.
 
組製作完畢,相信經過上面的講解,以及代碼的注釋,應該不難理解。接下來是什麼呢,給我的光碟機控制項來點效果,即在設計器中,當雙擊該組件,或右擊快顯功能表第一項時,會彈出一個About對話方塊,來說明我的光碟機元件。
這個就要用到元件編輯器啦 。幾本經典書中都有說及,比如Deplphi開發人員指南,我也是從那裏學來的,不過卻遇到了一些問題,折磨了幾天才解決。
這裏不想詳細介紹,去看一下那些書,大概也就知道了,只略說一下。
其原理就是實現一個繼承自TComponentEditor的子類TTrayIconEditor,並在其中覆蓋以下三個方法:
function GetVerbCount: Integer; override;
function GetVerb(Index: Integer): string; override;
procedure ExecuteVerb(Index: Integer); override;
可以精略理解為:
GetVerbCount指定控制項快顯功能表的項數
GetVerb指定快顯功能表中的相關項的名字
ExecuteVerb執行點擊快顯功能表項後的動作
 
接著在Register方法中調用RegisterComponentEditor(TMyTray,TTrayIconEditor);
第一個參數為元件類名,第二個為元件編輯器的類名。
而上面的方法必須引用DesignIntf,DesignEditors。
 
當我在我的元件單元這樣做之後出現問題了,編譯安裝沒有問題。我建立測試程式,並拉一個光碟機元件,雙擊它,可以出現About對話方塊,右擊功能表第一項也沒有問題。可是當我運行測試程式時,卻出現了這樣的編譯錯誤:
[Fatal Error] Unit1.pas(7): File not found: 'DesignEditors.dcu'
 
這讓我痛苦了好幾天,書上是這麼說的,應該沒有什麼錯誤呀。後來經過摸索,才找到了解決之道。
解決的辦法就是將元件編輯器類放在另一個單元中,並在這個單元引用我的光碟機元件單元。
並安裝之。這才可以正常運行,這個編輯器單元如下:
 
unit AboutTray;
 
interface
 
uses
  SysUtils,Classes,DesignIntf,DesignEditors,Forms,
  MyTray;
 
type
 TTrayIconEditor = class (TComponentEditor)
    function GetVerbCount: Integer; override;
    function GetVerb(Index: Integer): string; override;
    procedure ExecuteVerb(Index: Integer); override;
  end;
 
procedure Register;
 
implementation
 
///////TTrayIconEditor////////////////////////
procedure TTrayIconEditor.ExecuteVerb(index:integer);
begin
 case index of
 0: application.MessageBox('你好,這是風做的光碟機組件!!','關於');
 end;
end;
 
function TTrayIconEditor.GetVerb(index:integer):String;
begin
  case index of
    0:Result:='About MyTray';
  end;
end;
 
function TTrayIconEditor.GetVerbCount:integer;
begin
  Result:=1;
end;
 
 
procedure Register;
begin
  RegisterComponentEditor(TMyTray,TTrayIconEditor);
end;
 
end.
 
至此,光碟機組件完畢,拉下它放在表單設計器中,雙擊,彈出對話方塊
裏面內容為:“你好,這是風做的光碟機組件!!”。哈哈,你成功啦
 
做為元件製作的最後一個內容,我想用一個包來把我的所有元件單元包含起來,並放在我自己新建的一個面板中。
這樣做之前,要把以前安裝下去的組件刪除。知道怎麼樣刪除,如果不知道,請看我在第一篇中說的。
然後在打開所有的元件單元,把RegisterComponents(‘Samples', [TCoolMemo]);裏面的
Samples改為Wind。然後保存
接著,在IDE中點File-》New-》Other…
彈出來的New Items對話方塊,選中New頁面,並選中其中的Package,
這裏彈出一個新建的包編輯器。
先在IDE中點File-》Save。將包編輯器保存。保存在元件的單元所在的檔夾中
我的所有元件單元都放在Delphi7\MyCom檔夾中。因此這個包當然也保存在這裏。
 
然後,點包編輯器上邊的Add,將所有的元件單元加進去,當然也保括上面說的元件編輯器單元啦。
加進去後,點包編輯器上邊的Compile,編譯完畢,再點Insall。
成功,看看面板。所有以前做過的組件全在Wind面板中了。
而這時候,我的任務也完畢了。
 
 
結語
 
這次的組件之旅終於走完了,也許有人會笑我淺薄,認為這麼簡單的東西,有必要拿出來麼。也許是比較簡單吧,但一定有人會需要的,相信我的文章會給他們幫助 的。因為這些是我曾經學到的知道,遇到的問題並解決它。所以我個人覺得是很珍貴的。並且經過寫這幾篇,我把這些知識記得更牢了。這種利己利人的事,何樂而 不為呀。
在此,謝謝大家的閱讀,也許下次還有機會再見面,不過現在要說再見了。祝你們愉快。

posted on 2005-09-07 10:22  Jason Cheng  阅读(489)  评论(0编辑  收藏  举报