程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 動態創建控件支持事件響應並可保存與讀取

動態創建控件支持事件響應並可保存與讀取

編輯:關於VC++

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)
#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 
程序裡設計了一個_globalData 類,使用它的 globalData 對象可以訪問裡面的全局數據。////////////////
//全局數據
////////////////
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(this,MYEDIT);  //新建文本框
  addContorl(this,MYLABEL);  //新建標簽
addContorl函數很重要:// ***********************************************************************************
//新增控件
//參數:
// [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);//保存所有控件信息
   readFile(this);//讀取
我在PreTranslateMessage還加入了對控件拖拽的處理://使用鼠標可以隨意拖動控件
  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 可以做到),那豈不是做成界面設計器了 !

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved