Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8195813
  • 博文数量: 1227
  • 博客积分: 10026
  • 博客等级: 上将
  • 技术积分: 20273
  • 用 户 组: 普通用户
  • 注册时间: 2008-01-16 12:40
文章分类

全部博文(1227)

文章存档

2010年(1)

2008年(1226)

我的朋友

分类: C/C++

2008-04-23 22:07:35

动态创建控件支持事件响应并可保存与读取

作者:

摘要:动态控件是比较繁杂的一个工作 这里演示了从在一个窗体上分别动态创建按钮、文本框、标签框的例子,当然,你还可以直接再添加其它的控件是很容易的.并且它是可以响应动态控件的事件的,你还可以用类向导生成事件,不必再手动添加消息。我还使这些控件的信息保存为文件并可以随时读取。

关键字:动态创建控件,创建动态控件,动态控件,动态控件事件,动态控件保存与读取

  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

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

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

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

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

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 可以做到),那岂不是做成界面设计器了 !
阅读(1775) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~