前文所描述的是 标签栏的UI实现,但是实际上我们还要给程序加上容器的概念,才能让不仅仅标签切换而且功能也要切换。
切换的原理是简单的,就是给每个功能模块创建一个子Dialog,切换到一个标签,隐藏其他Dilaog显示当前标签对应的Dialog。
这点一般程序员都很容易做到。
本文在这里抛砖引玉用模板Template的概念书写一个TabManager类,用更加好看的代码(动态类创建)来实现这个技术。
一般来说创建一个子Dialog,常规的做法是 CDialog * pDlg = new CMyDialog(); 如果是10个Dialog那么就需要预先创建10个 Dialog,如何在程序初始化的时候只是存储10个子Dialog的类名,到想要创建的时候自动创建呢?VC一般来说是不允许动态创建类的,在Doc/View结构下有一个RUNTIME_CLASS宏可以做到,但是这只是在doc/view里面才能这么用,作者这里书写了一个所有类都能使用的动态类管理容器。笔者只所以在这里用简单的应用来诠释复杂的技术,是因为如果读者有机会接触到插件Plugin编程的时候就会遇到类似的问题。
- class RuntimeClassFactory
- {
- private:
- typedef void* (* RUNTIME_Create_REF)();
- typedef void(* RUNTIME_Delete_REF)(void*) ;
- protected:
- struct func_ref
- {
- RUNTIME_Create_REF procCreate;
- RUNTIME_Delete_REF procDelete;
- };
- typedef hash_map< std::string, RuntimeClassFactory::func_ref> class_map;
- public:
- static class_map * s_map;
- public:
- static void register_class( std::string name, RUNTIME_Create_REF p,RUNTIME_Delete_REF p2 )
- {
- if(!s_map)
- s_map = new class_map;
- func_ref ref;
- ref.procCreate = p;
- ref.procDelete = p2;
- (*s_map)[name] = ref;
- }
- static void Clear()
- {
- if(s_map)
- delete s_map;
- s_map = 0;
- }
- static void* Create( std::string name )
- {
- if(s_map)
- {
- class_map::iterator item = s_map->find(name);
- if(item!=s_map->end())
- return item->second.procCreate();
- else
- return 0;
- }
- else
- return 0;
- }
- static void Delete(std::string name,void *p)
- {
- if(s_map)
- {
- class_map::iterator item = s_map->find(name);
- if(item!=s_map->end())
- {
- item->second.procDelete(p);
- }
- }
- }
- };
- template<typename T>
- struct RegisterClassHelper
- {
- RegisterClassHelper(const std::string class_name)
- {
- RuntimeClassFactory::register_class( class_name, &RegisterClassHelper<T>::create_object,&RegisterClassHelper::delete_object );
- }
- static void* create_object(){ return new T; }
- static void delete_object(void* p){delete (T*)p;}
- };
- #define REGISTER_CLASS(X) /
- {/
- RegisterClassHelper<X> __class_help(_T(#X));/
- }
- #define REGISTER_CLASS_BYKEY(K,X)/
- {/
- RegisterClassHelper<X> __class_help(_T(K));/
- }
- #define CLASSNAME(classname)/
- #classname
假设在程序中有2个子类 Class1, Class2;
怎么去动态创建呢?
//注册类
REGISTER_CLASS(Class1);
REGISTER_CLASS(Class2);
//创建类
RuntimeClassFactory::Create(CLASS_NAME(Class1));
//删除类
RuntimeClassFactory::Delete(CLASS_NAME(Class2));
好的,基本技术介绍完毕了,接下来就是封装的TabManager,用来管理标签的切换
- class TabManager
- {
- public:
- struct tab_elem
- {
- tab_elem()
- {
- dialog = 0;
- }
- CDialog * dialog;
- UINT idd;
- string classname;
- };
- public:
- TabManager(CDHtmlUIDemoDlg* parent)
- {
- m_current = -1;
- this->m_parent = parent;
- }
- ~TabManager()
- {
- hash_map<int,tab_elem>::iterator item;
- for(item=this->m_list.begin();item!=this->m_list.end();)
- {
- if(item->second.dialog)
- {
- item->second.dialog->DestroyWindow();
- RuntimeClassFactory::Delete(item->second.classname,item->second.dialog);
- }
- item = m_list.erase(item);
- }
- }
- protected:
- hash_map<int,tab_elem> m_list;
- CDHtmlUIDemoDlg* m_parent;
- int m_current;
- void GetWorkRect(LPRECT rc)
- {
- long l,t,w,h;
- this->m_parent->GetElem("tbOnline").mi_Elem->get_offsetLeft(&l);
- this->m_parent->GetElem("tbOnline").mi_Elem->get_offsetTop(&t);
- this->m_parent->GetElem("tbOnline").mi_Elem->get_offsetWidth(&w);
- this->m_parent->GetElem("tbOnline").mi_Elem->get_offsetHeight(&h);
- long l_s,t_s,w_s,h_s;
- this->m_parent->GetElem("statusBar").mi_Elem->get_offsetLeft(&l_s);
- this->m_parent->GetElem("statusBar").mi_Elem->get_offsetTop(&t_s);
- this->m_parent->GetElem("statusBar").mi_Elem->get_offsetWidth(&w_s);
- this->m_parent->GetElem("statusBar").mi_Elem->get_offsetHeight(&h_s);
- CRect rcClient;
- this->m_parent->GetClientRect(rcClient);
- rc->left = l+2;
- rc->top = t+h;
- rc->right = l+w-2;
- rc->bottom = t_s;
- }
- void CreatePlugin(tab_elem & elem)
- {
- if(elem.dialog)
- return;
- elem.dialog = (CDialog*)RuntimeClassFactory::Create(elem.classname);
- elem.dialog->Create(elem.idd,this->m_parent);
- CRect rcWork;
- GetWorkRect(rcWork);
- elem.dialog->SetWindowPos(0,rcWork.left,rcWork.top,rcWork.Width(),rcWork.Height(),SWP_HIDEWINDOW);
- }
- public:
- CDialog * GetCurrentTab()
- {
- if(this->m_current<0)
- return 0;
- return this->m_list[this->m_current].dialog;
- }
- void Add(const char * title,const char * classname,int IDD,bool autoCreate)
- {
- tab_elem elem;
- try
- {
- elem.idd = IDD;
- elem.classname = classname;
- int index = m_parent->GetTabElemCount();
- m_parent->AddTabElem(title,title,index);
- if(autoCreate)
- CreatePlugin(elem);
- this->m_list[index] = elem;
- }
- catch(string e)
- {
- TRACE("add plugin [%s] failed:%s/n",classname);
- }
- }
- void Switch(int index)
- {
- if(index==m_current)
- return;
- hash_map<int,tab_elem>::iterator item;
- for(item = this->m_list.begin();item!=this->m_list.end();item++)
- {
- if(item->first!=index)
- {
- if(item->second.dialog)
- {
- item->second.dialog->ShowWindow(SW_HIDE);
- }
- }
- else
- {
- tab_elem & elem = item->second;
- CreatePlugin(elem);
- elem.dialog->ShowWindow(SW_SHOW);
- }
- }
- m_current = index;
- this->m_parent->SwitchTabElem(m_current);
- }
- void resize()
- {
- CRect rcWork;
- this->GetWorkRect(rcWork);
- hash_map<int,tab_elem>::iterator item;
- for(item = this->m_list.begin();item!=this->m_list.end();item++)
- {
- tab_elem & elem = item->second;
- if(elem.dialog==0)
- continue;
- if(item->first==this->m_current)
- elem.dialog->SetWindowPos(0,rcWork.left,rcWork.top,rcWork.Width(),rcWork.Height(),SWP_SHOWWINDOW);
- else
- elem.dialog->SetWindowPos(0,rcWork.left,rcWork.top,rcWork.Width(),rcWork.Height(),SWP_HIDEWINDOW);
- }
- }
- };
代码并不难懂,其中有一个函数GetWorkRect是用来得到工作区域的位置,这里采用了Html的Div定位技术
看看DhtmlUIDemo.html的框架可以发现
Body可视部分由3部分组成
Table( tbOnline)
Div(content)
Div(statusBar)
网页设计让tbOnline居上,statusBar居下,content的位置则在程序中经过计算得出
程序中TabManager的使用
假设CTab1,CTab2为创建的2个子Dialog
- TabManager * m_tab;
- REGISTER_CLASS(CTab1);
- REGISTER_CLASS(CTab2);
- m_tab = new TabManager(this);
- m_tab->Add("标签1",CLASSNAME(CTab1),CTab1::IDD,true);
- m_tab->Add("标签2",CLASSNAME(CTab2),CTab2::IDD,false);
- m_tab->Switch(0);
用户点击了标签
- HRESULT CDHtmlUIDemoDlg::OnClickTab(IHTMLElement *phtmlElement)
- {
- BSTR bstr;
- phtmlElement->get_id(&bstr);
- string sid = (LPCTSTR)CString(bstr);
- int pos = sid.find('_');
- int id = atoi(sid.substr(pos+1,sid.length()-pos-1).c_str());
- this->m_tab->Switch(id);
- return S_OK;
- }
总体介绍完了,Demo下载 http://download.csdn.net/source/598661 ,唉,CSDN改版怎么默认下载设置需要1个资源分了,改都改不了
Demo工程是VS2008下的,但是由于demo比较简单,其他都是外部资源文件,如果是VS2003环境,移植也很简单。
本文旨在帮助那些深受自绘控件烦恼的程序员找一个新的捷径,建议读者下载demo以后不要使用“拿来主义”,直接拿到自己的工程里面用,建议从第一篇文章开始,Step by Step去领悟Dhtml UI设计的思路。
祝大家早日打通这个“任督二脉”。
当然DHtml VC7开始就有了,不是什么新鲜玩意,所以没学过的也别觉得困难,其实也很简单,就是操作HTML元素,UI技巧基本都移植到CSS里面去。
文章只是介绍了Toolbar,TabCtrl的模拟情况,关于ListCtrl等等,作者并没有继续模拟,现在比较成熟的JS+CSS实现DataGrid,ProgressCtrl都有,大家可以去网上找一找,我这里推荐一个国外优秀的开源JS库http://extjs.com/,基本WindowUI都实现了,非常完美,缺点就是这个库主要是针对网站Ajax的,但是只要哪位仁兄有这兴趣,可以考虑把它移植到客户端上做界面。
datagrid: