VC++6.0中創建動態控件是比較偏離基礎的知識,也有一定的難度。它的完整功能是要動態創建控件後再動態響應控件中的事件,兩者全部做到才算完整。
這裡我將展示一個完整的動態控件示例,它可以動態創建控件,然後再動態響應控件事件,並可以保存控件信息至ini配置文件,然後再根據ini文件讀取出控件信息來動態創建控件。相信它能夠解決你在動態控件中所遇到的許多問題。
當然,動態控件的方法有許多種,我展示的只是給我認為較好的。
這裡以VC++6.0創建對話框工程為例,添加菜單,分別添加子項按鈕,文本框,標簽。大家知道,VC中基本上都要靠手寫,所以,這裡先寫三個控件的創建,其它的控件基本一致。
第一步當然是建立一個全局的類(噢 這個只是我個人喜好) 裡面放上滿滿的全局共用數據,我會把它們都放入一個*Global.h文件中。還有一個控件類,裡面包含了每個控件都需要的屬性,比如控件的名稱、大小、坐標以及附加。
除了那兩個類,最重要的還是控件類,因為使用系統的控件類添加事件的響應會比較麻煩(實現不會太難,主要是不好管理)。具體添加方法我想大家都明白,就是使用系統的添加類向導生成三個類(我們現在只做三個不同類型的控件) 一個繼承自按鈕類(CButton)-按鈕一個繼承自編輯框類(CEdit)-文本框,一個繼承自靜態類(CStatic)-標簽,分別命名為CMyButton,CMyEdit,CMyLabel,不會介意吧?。如果使用手動添加的話,則強大的事件類向導將不能使用。
這下應該有二個系統類,三個控件類了吧?當然控件類也會在工程中加入它們的頭文件與程序文件。下面就是設計控件類了。我有考慮使用多繼承來使這三類自定義控件類都繼承控件類(第一步中加入的控件類暫時稱為控件主類為好) 不過沒使用 因該更方便.現在只是在控件主類中聲明了那三個自定義控件 然後加上一開始的一些共公信息 就是下面這樣的了:////////////////
//控件主類
////////////////
class _myControl
{
public:
//共公信息
CString caption;//標題
CRect rect;//坐標大小
int type;//類型
//動態控件
CMyButton myButton;
CMyEdit myEdit;
CMyLabel myLabel;
_myControl()
{
caption="Control";
type=0;//默認為按鈕
rect=CRect(10, 80, 100, 120);//初始坐標大小
}
};
在這個類中,用了三個自定義的控件類成員變量,分別用來存放動態生成的三種不同類型的控件。如果你還想把它保存起來,並能隨時讀取出來的話,還要加上共公信息中的那些成員變量。另外程序中加入了下面這些常量:
#define IDB_MYCONTROL 0x9000 //自定義按鈕的句柄(ID)
程序裡設計了一個_globalData 類,使用它的 globalData 對象可以訪問裡面的全局數據。
#define NUM_CONTROL 128 //數目
//保存配置文件用
const CString APPINFO="appInfo";
const CString CONTROL="Control";
const CString SETTING="Setting";
#define MYBUTTON 1
#define MYEDIT 2
#define MYLABEL 3 ////////////////
//全局數據
////////////////
class _globalData
{
public:
CString appPath;//程序路徑
CString appAllPath;//保存文件全路徑
bool isDraw;//是否可以拖拽控件
//控件信息
//vector <_myControl> myControl;//考慮使用vector也是可以的
_myControl* myControl[NUM_CONTROL];//這裡使用數量受到了限制
int count;//已經建立的控件總數
_globalData()
{
isDraw=false;
//初始控件
for (int i=1;i<=NUM_CONTROL-1;i++) {
globalData.myControl[i]=new _myControl;
}
count=0;
//取得當前路徑
char temp[255]= _T("");//保存當前路徑的變量
GetModuleFileName(NULL,appPath.GetBufferSetLength(sizeof(temp)),sizeof(temp));//取得程序所在的全目錄名
int nPos=appPath.ReverseFind('\\'); //取得除去文件名字符數後的總長度
appPath=appPath.Left(nPos+1); //截取得到的文件路徑長度 最終得到程序所在路徑
appPath.ReleaseBuffer();
appAllPath=appPath+"myControl.ini";
}
}extern globalData;
這些代碼不是很難,相信都能看懂。事實上以後建立控件的話就是創建了一個_myControl* 對象。使用它來管理所有不同類型的控件。我們已經做好了准備 ,現在即將開始。在工程中加入菜單(這裡,我只是想要有三個按鈕來觸發新建的三個不同類型控件的事件)。
addContorl(this,MYBUTTON); //新建按鈕
addContorl函數很重要:
addContorl(this,MYEDIT); //新建文本框
addContorl(this,MYLABEL); //新建標簽// ***********************************************************************************
//新增控件
//參數:
// [1].新建控件的父窗體
// [2].控件的類型
// [3].表示是新增控件還是讀取控件(編號)
// 值為0則表示新增控件 編號使用最大數量;為其它值時 是讀取控件的編號
// ***********************************************************************************
template<class T>
void addContorl(T& object,int type,int readID=0)
{
int _index=0;//標識建立的控件編號(新增控件時為最大控件號 讀取控件時 為傳遞過來的值)
//如果是新增
if (readID==0)
{
globalData.count++;//增加總數
globalData.myControl[globalData.count]->type=type;//確定類型
//公共數據
CString _str;
_str.Format("%d",globalData.count);
globalData.myControl[globalData.count]->caption+=_str;//名稱標題
//這裡設置新建的控件初始坐標為最後一個控件的坐標偏移
CRect _rect;
if (globalData.count>1)
{
_rect=globalData.myControl[globalData.count-1]->rect;
globalData.myControl[globalData.count]->rect.left=_rect.left+10;
globalData.myControl[globalData.count]->rect.top=_rect.top+10;
globalData.myControl[globalData.count]->rect.right=_rect.right+10;
globalData.myControl[globalData.count]->rect.bottom=_rect.bottom+10;
}
else
_rect=globalData.myControl[globalData.count]->rect;
_index=globalData.count;
}else
_index=readID;
// 創建控件
//一.都是要靠消息來完成 按鈕的字體是隨系統的不能改變
HFONT hFont;
hFont = CreateFont(12, 0, 0, 0, 400, 0, 0, 0, ANSI_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
DEFAULT_PITCH || FF_DONTCARE, "宋體");
//各自數據
switch(type) {
case MYBUTTON:
{
globalData.myControl[_index]->myButton.Create(globalData.myControl[_index]->caption,WS_VISIBLE |
WS_CHILD | BS_PUSHBUTTON,globalData.myControl[_index]->rect,object ,IDB_MYCONTROL+_index);
SendMessage(globalData.myControl[_index]->myButton,WM_SETFONT,(DWORD)hFont,TRUE);
};
break;
case MYEDIT:
{
//兩種方法使文本框具有3D風格
/*
//只有使用CreateEx才能創建具有擴展風格的文本框 否則沒有3D效果
globalData.myControl[_index]->myEdit.CreateEx(WS_EX_CLIENTEDGE, // 指明窗口具有3D外觀,這意味著,邊框具有下沉的邊界。
_T("EDIT"), "",//globalData.myControl[_index]->caption
WS_CHILD | WS_VISIBLE,
globalData.myControl[_index]->rect,object, IDB_MYCONTROL+_index);
*/
globalData.myControl[_index]->myEdit.Create(WS_VISIBLE |
WS_CHILD,globalData.myControl[_index]->rect,object ,IDB_MYCONTROL+_index);
globalData.myControl[_index]->myEdit.ModifyStyleEx(0, WS_EX_CLIENTEDGE, SWP_DRAWFRAME) ;
//globalData.myControl[_index]->myEdit.HideCaret();
SendMessage(globalData.myControl[_index]->myEdit,WM_SETFONT,(DWORD)hFont,TRUE);
//這裡EDIT控件要特殊處理一下 因為使用CreateEx創建了帶3D的擴展風格 所以 實際大小會少去4個點用來顯示3D效果 這裡要加上4個點
//CRect rect;
//globalData.myControl[_index]->myEdit.GetClientRect(&rect);
globalData.myControl[_index]->myEdit.SetWindowPos(NULL,
globalData.myControl[_index]->rect.left-2,
globalData.myControl[_index]->rect.top-2,
globalData.myControl[_index]->rect.Width()+4,
globalData.myControl[_index]->rect.Height()+4,NULL);
}
break;
case MYLABEL:
globalData.myControl[_index]->myLabel.Create(globalData.myControl[_index]->caption,WS_VISIBLE |
WS_CHILD | SS_NOTIFY,globalData.myControl[_index]->rect,object ,IDB_MYCONTROL+_index);
SendMessage(globalData.myControl[_index]->myLabel,WM_SETFONT,(DWORD)hFont,TRUE);
globalData.myControl[_index]->myLabel.Invalidate(TRUE);
break;
default:;
}
}
上面的代碼看著真頭痛....其實仔細閱讀也不是太難理解。它是真正負責在窗體上建立控件的代碼。建立控件已經到此就完成了 ,你在例子代碼中可以看到。函數會根據 addContorl 調用的第二個參數的不同在窗體上創建不同的控件,我們要做的不止這些,因為我說過,只有當動態控件能響應幾乎所有事件的話,整個工程才算完整。所以接下來我們將要把控件對事件的響應完成掉。
動態控件的事件響應,兩種最為常用(也許只是我) 一種是在PreTranslateMessage中判斷消息的ID是否是控件ID,然後再判斷事件消息來操作。一種就是使用自己的控件類,在類中添加好控件對消息的事件處理。有人會使用 ON_COMMAND_RANGE,但不總是太好也不能實現大多數消息功能。
因為在一開始,我們使用了系統向導來生成繼承的控件類,所以,它是能夠得到六個文件(三個.h三個.cpp),也就意味著,它是能夠使用ctrl+w類向導來生成事件。你可以試一下? 呵,是的,就是這麼簡單,直接添加事件的響應就可以了,不如在按鈕類裡面來個單擊事件?void CMyButton::OnClicked()
噢,真的能響應,似乎太簡單了!? 也許,任何方法都是不止你所能看到的數量,而只是你我都未發現而已。
{
AfxMessageBox("你單擊了我!(BN_CLICKED)");
}
到此,動態控件的添加與事件響應已經能夠完成了,我還說過要將它能保存與讀取,所以,下面的代碼將完成它。把下面這些代碼都寫到global文件中://////////////////////////
//取得控件在窗體中的坐標與大小(根據控件 窗體相對屏幕的坐標)
//////////////////////////
template<class T,class B>
static CRect getRect(T& myControl,B& obj)
{
CRect _rect,_rect2,_rect3;
int _right,_bottom;//用於保存控件大小
//獲取控件所在父窗體坐標
obj.GetClientRect(&_rect);
obj.ClientToScreen(&_rect);
//獲取自身坐標
myControl.GetClientRect(&_rect2);
_right=_rect2.right;
_bottom=_rect2.bottom;
myControl.ClientToScreen(&_rect2);
_rect3.left=_rect2.left-_rect.left;//控件left值等於自身的left減去父窗體的left
_rect3.top=_rect2.top-_rect.top;//控件top值等於自身的top減去父窗體的top
_rect3.right=_rect3.left+_right;//這是控件的right值 等於left坐標+大小
_rect3.bottom=_rect3.top+_bottom;//這是控件的bottom值 等於top坐標+大小
return _rect3;
}
//////////////////////////
//重新計算控件坐標
//////////////////////////
template<class T>
static void getGUIData(T& obj)
{
CWnd* _wnd;
//重新計算控件坐標信息
for (int i=1;i<=globalData.count;i++) {
switch(globalData.myControl[i]->type) {
case MYBUTTON:
{
_wnd=CWnd::FromHandle(globalData.myControl[i]->myButton.m_hWnd);
}
break;
case MYEDIT:
{
_wnd=CWnd::FromHandle(globalData.myControl[i]->myEdit.m_hWnd);
}
break;
case MYLABEL:
{
_wnd=CWnd::FromHandle(globalData.myControl[i]->myLabel.m_hWnd);
}break;
default:;
}
globalData.myControl[i]->rect=getRect(*_wnd,*obj);
}
}
// **********************************************
//數據保存
// **********************************************
template<class T>
static void saveFile(T& object)
{
getGUIData(object);//重新計算控件坐標及大小
//共用變量
CString _str;
//清空文件
CFile _file(globalData.appAllPath,CFile::modeCreate);//清空文件先CFile::Remove
_file.Close();
//DeleteFile(globalData.appAllPath);//刪除整個文件
//清除
//WritePrivateProfileString(APPINFO,NULL,NULL,globalData.appAllPath);
//保存數量
_str.Format("%d",globalData.count);
WritePrivateProfileString(APPINFO,"count",_str,globalData.appAllPath);
//保存控件信息
//清除
//WritePrivateProfileString(CONTROL,NULL,NULL,globalData.appAllPath);
CString ITEM,_temp;
for (int i=1;i<=globalData.count;i++)
{
_str.Format("%d",i);
ITEM=CONTROL+_str;//項名
//公共屬性
WritePrivateProfileString(ITEM,"caption",globalData.myControl[i]->caption,globalData.appAllPath);
_temp.Format("%d",globalData.myControl[i]->rect.left);
WritePrivateProfileString(ITEM,"left",_temp,globalData.appAllPath);
_temp.Format("%d",globalData.myControl[i]->rect.top);
WritePrivateProfileString(ITEM,"top",_temp,globalData.appAllPath);
_temp.Format("%d",globalData.myControl[i]->rect.right);
WritePrivateProfileString(ITEM,"right",_temp,globalData.appAllPath);
_temp.Format("%d",globalData.myControl[i]->rect.bottom);
WritePrivateProfileString(ITEM,"bottom",_temp,globalData.appAllPath);
_temp.Format("%d",globalData.myControl[i]->type);
WritePrivateProfileString(ITEM,"type",_temp,globalData.appAllPath);
}
}
// **********************************************
//數據讀取
// **********************************************
template<class T>
void readFile(T& object)
{
//清除資源
for (int j=1;j<=globalData.count;j++) {
delete globalData.myControl[j];
globalData.myControl[j]=new _myControl;
}
CString _str,ITEM;
char _buff[255];
globalData.count=GetPrivateProfileInt(APPINFO,"count",NULL,globalData.appAllPath);
for(int i=1;i<=globalData.count;i++)
{
_str.Format("%d",i);
ITEM=CONTROL+_str;//項名
GetPrivateProfileString(ITEM,"caption",NULL,_buff,256,globalData.appAllPath);
globalData.myControl[i]->caption.Format("%s",_buff);
globalData.myControl[i]->rect.left=GetPrivateProfileInt(ITEM,"left",NULL,globalData.appAllPath);
globalData.myControl[i]->rect.top=GetPrivateProfileInt(ITEM,"top",NULL,globalData.appAllPath);
globalData.myControl[i]->rect.right=GetPrivateProfileInt(ITEM,"right",NULL,globalData.appAllPath);
globalData.myControl[i]->rect.bottom=GetPrivateProfileInt(ITEM,"bottom",NULL,globalData.appAllPath);
globalData.myControl[i]->type=GetPrivateProfileInt(ITEM,"type",NULL,globalData.appAllPath);
//調用創建控件函數
addContorl(object,globalData.myControl[i]->type,i);
}
//獲取屏幕分辯率
int nFullWidth=GetSystemMetrics(SM_CXSCREEN);
int nFullHeight=GetSystemMetrics(SM_CYSCREEN);
}
在任何地方調用:
saveFile(this);//保存所有控件信息
我在PreTranslateMessage還加入了對控件拖拽的處理:
readFile(this);//讀取//使用鼠標可以隨意拖動控件
if (pMsg->message==WM_LBUTTONDOWN)
{
if (globalData.isDraw)//自己增加這個變量
{
FromHandle(pMsg->hwnd)->SendMessage( WM_SYSCOMMAND,SC_MOVE+1,0);
this->Invalidate(TRUE);
return true;
}
}
控件拖拽我研究了蠻久的時間。感覺使用這個消息方法是最為方便的,你可以再將它功能增加,比如說控件拖拽改變大小(SC_SIZE 可以做到),那豈不是做成界面設計器了 !
本文配套源碼