cppquery:用C++模仿jquery的探索

CPPQuery是一个模仿jQuery的C++库,旨在简化GUI代码的管理和界面风格控制。它提供了数据绑定、事件处理和CSS样式化的功能,帮助开发者实现更高效、整洁的代码组织。目前包括Window对象、闭包模板等特性,未来还有更多功能待推出。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取cppquery: https://github.com/coderebot/cppquery


CPPQuery是什么

CPPQuery是仿照jquery,顾名思义,就是c++ query。它是针对windows API的GUI,提供一套类似jquery的接口。目的是:简化GUI的编程,最终目的是构建一个更加简洁和智能的MVC架构。主要目标有:
  • 将GUI中分散的代码集中处理,特别是各种事件处理代码;
  • 提供一个数据绑定框架,实现数据和控件之间的智能联动;
  • 提供一个数据源框架,实现容器类数据的自动填充、数据变化的联动;
  • 提供一套类似CSS的界面风格控制框架,分离界面的显示与控制。
cppquery使用c++的模板为主要手段,通过模板模拟闭包和匿名函数,期望达到类似jquery的使用方法。

cppquery的大部分代码将以模板的形式出现,因此,cppquery是一个非常小巧和轻量的GUI封装库。 cppquery初期将考虑cover MFC的大部分特性,如View-Document架构等;后期将提供一个可以制作绚丽界面的图形库。

为什么做CPPQuery?

现在的Windows GUI其实已经有很多成熟的库了,如MFC,商业的有UIPower等。但是,这些库都有类似的架构,一般都是将一个窗口封装到一个类里面,如果你要编写界面,必须编写一个对应的类。很多的控件,都对应这一个类。 
虽然使用了C++的继承特性,但是每个类的接口都有差别,要完成一个任务,通常需要记住很多类和他们的接口。
近年来,随着HTML5的发展和jquery的兴起,一种新的界面开发框架出现了,这种框架不是面向对象,而是面向切面的。比如,jquery可以通过CSS选择子,在不需要了解HTML DOM结构的情况下,就可以得到想要的节点,并添加事件处理句柄、改变页面的显示状态。这种方法分离的更彻底,代码的耦合更低,而且使用更加简单。更重要的是,它很多地方采用异步模式,这大大提高了界面的反应速度和用户体验。
有鉴于此,我做cppquery,期望将这种思想引入到Native的界面开发中。

之所以选择Windows平台,主要是windows平台的用户受众更多,开发者也更多;我自己也比较熟悉windows开发;windows平台更加成熟,我不需要考虑过多底层实现的问题,专心于上层的架构。
在windows上取得成功后,可以推广到其他平台,如android(事实上android平台已经有一个 aquery了)。

开始

目前CPPQuery只实现了最简单的功能。
我使用VS生成一个Win32工程,只有一个简单的窗口,有一个菜单,只有“Exit"和"About"两个菜单项。这是VS自动生成的代码,我修改这个代码,功能保持不变,但是实现方法,使用cppquery。

先给个代码一览:

// cppquery.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "cppquerytest.h"
#include "api/cppquery.h"
using namespace cppquery;

#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name
HWND g_hMainWnd;

// Forward declarations of functions included in this code module:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: Place code here.
	MSG msg;
	HACCEL hAccelTable;

	// Initialize global strings
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_CPPQUERY, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Perform application initialization:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_CPPQUERY));


	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}



//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage are only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= DefWindowProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_CPPQUERY));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_CPPQUERY);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Store instance handle in our global variable

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   static WindowFrontPeer::MessageHandle about_handles[] = {
        WindowFrontPeer::Command(IDOK, MK_FUNC(FUNC(EndDialog), _1, 0)),
        WindowFrontPeer::Command(IDCANCEL, MK_FUNC(FUNC(EndDialog), _1, 0)),
        NULL,
   };
   
    Window window(hWnd);
    window.onCommand(IDM_EXIT, MK_FUNC(FUNC(DestroyWindow), hWnd))
          .onDestroy(MK_FUNC(FUNC(PostQuitMessage), 0))
          .onCommand(IDM_ABOUT, MK_FUNC(FUNC(DoDialogModel),hInst, hWnd, IDD_ABOUTBOX, about_handles));
   return TRUE;
}

引入头文件:
#include "api/cppquery.h"
using namespace cppquery;

关键部分代码

   static WindowFrontPeer::MessageHandle about_handles[] = {
        WindowFrontPeer::Command(IDOK, MK_FUNC(FUNC(EndDialog), _1, 0)),
        WindowFrontPeer::Command(IDCANCEL, MK_FUNC(FUNC(EndDialog), _1, 0)),
        NULL,
   };
   
    Window window(hWnd);
    window.onCommand(IDM_EXIT, MK_FUNC(FUNC(DestroyWindow), hWnd))
          .onDestroy(MK_FUNC(FUNC(PostQuitMessage), 0))
          .onCommand(IDM_ABOUT, MK_FUNC(FUNC(DoDialogModel),hInst, hWnd, IDD_ABOUTBOX, about_handles));

仅此而已,原来自动生成的WndProc和About函数都已经不需要了。

下面重点解说

Window对象

Window是代表一个窗口的封装类。和普通的封装不一样,它实际上只是一个方便访问的接口。它有很多以"on"开头的函数,每个函数代表一个消息回调。如onCommand函数会添加一个WM_COMMAND消息的处理句柄;onDestroy会添加一个WM_DESTROY的处理函数。

Window的每个函数都会返回Window对象自身,这样,它就可以连续调用了。这样的设计的目的主要是为了书写方便和效率。因为Window对象将会通过query方法大量的被查找和生成,通过这个方法,可以尽可能的避免重新生成Window对象。

闭包模板

大量使用到的MK_FUNC宏和FUNC宏实际上是对模板函数的重定义。如语句
MK_FUNC(FUNC(DestroyWindow), hWnd)
实际上形成一个对 DestroyWindow(hWnd) 具有相同效果的调用,只是,这个调用要在窗口收到WM_COMMAND消息,且LOWORD(wParam) == IDM_EXIT的时候才会触发。
这是一个类似boost的封装。

这两个宏的定义是
#define MK_FUNC make_function
#define FUNC func_ptr

func_ptr是一个模板函数,在这个例子中,它对应的是
template<class R, class T1> struct function_ptr_cbapi1 {
    typedef R result_type;
    typedef R (CBAPI *Func)(T1); 
    Func f_;
    function_ptr_cbapi1(Func const& f) : f_(f) {}
    function_ptr_cbapi1(const function_ptr_cbapi1& f) : f_(f.f_) {}
    template<class A1>
    result_type operator()(A1 a1) const 
    { return f_(arg_cast<T1>(a1)) ; }
    result_type operator()(T1 t1) const 
    { return f_(t1); }
};
template<class R, class T1>
function_ptr_cbapi1<R, T1> func_ptr(R (CBAPI *f)(T1) ) 
{ return function_ptr_cbapi1<R, T1>(f); }

func_ptr返回了一个function_ptr_cbapi1对象。(实际上我定义了function_ptr_cbapi0~function_ptr_cbapi9,后面的数字表示参数个数,CBAPI定义为__stdcall,这是windows callback的回调方式)。因为DestroyWindow有一个参数,所以它对应的是function_ptr_cbapi1。



function_ptr_cbapi只是一个封装模板类,它的目的就是为了能够让DestroyWindow以我期望的方式调用。

make_function也是一个模板函数,它的声明是
template<class F, class A1>
function_t<F, list1<A1> > make_function(F const& f, A1 a1)
{ return function_t<F, list1<A1> >(f, list1<A1>(a1)); }
make_function也有10个重载,根据参数不同。
这里,它对应的是有一个参数的重载。

它生成一个function_t对象,改对象还包含了一个list1对象。

function_t对象:
template<class F, class L>
struct function_t : public func_base_t<function_t<F, L> >{
	typedef typename F::result_type result_type;
	F f_;
	L l_;

	function_t(F const& f, L const &l) : f_(f), l_(l) { }
	function_t(const function_t & f)  : f_(f.f_), l_(f.l_){ }

	
	result_type operator()() const {
		list0 l;
		return l_(f_, l);
	}

	template<class R>
	R operator()(type<R>) const {
		list0 l;
		return call(type<R>(), type<result_type>(), l_, f_, l);
	}
	template<class R, R const RDef>
	R operator()(type_default<R, RDef>) const {
		list0 l;
		return call(type_default<R, RDef>(), type<result_type>(), l_, f_,l);
	}

	/* dynamic_list call */ 
	result_type operator()(dynamic_list const& l) const { 
		return l_(f_, l.driver()); 
	} 
	template<class R> 
	R operator()(type<R> const& r, dynamic_list const& l) const {
		return call(r, type<result_type>(), l_, f_, l.driver());
	}
	template<class R, R const RDef>
	R operator()(type_default<R, RDef> const& rd, dynamic_list const& l) const {
		return call(rd, type<result_type>(), l_, f_, l.driver());
	}
	
	template<class TList>
	bool checkArg(const list_t<TList>& l) const {
		return l_.isElementHolderInList(l);
	}


	template<class A1> result_type operator()(list1<A1> const& l) const
	{ return l_(f_, l); }
	template<class R, class A1> R operator()(type<R> const& r, list1<A1> const& l) const 
	{ return call(r, type<result_type>(), l_, f_, l); }
	template<class R, R const RDef, class A1> R operator()(type_default<R, RDef> const& rd, list1<A1> const& l) const 
	{ return call(rd, type<result_type>(), l_, f_, l); }
	template<class A1> result_type operator()(A1 a1) const 
	{ return (*this)(list1<A1>(a1)); }
	template<class R, class A1> R operator()(type<R> const& r, A1 a1) const 
	{ return (*this)(r, list1<A1>(a1)); }
	template<class R, R const RDef, class A1> R operator()(type_default<R, RDef> const& rd, A1 a1) const 
	{ return (*this)(rd, list1<A1>(a1)); }
....... //更多的重载,适应不同的参数类型和个数
};
function_t核心是两个成员,Func和list。func是用来保存被调用的函数的,list则是用来保存参数列表的。

当我们调用function_t的operator() 重载操作符时,function_t将调用 l_(f_, l)。 l_就是list模板,表示已经保存的参数模板,l表示调用者给出的参数。

l_(f_, l)这样的调用方式是为了保证参数被正确调用。例如在例子DestroyWindow中,function_t中的Func就是DestroyWindow的指针,而l_则是list1<HWND> ,保存了hWnd的指针。 l_总是保存着和f_数目和类型一致的参数,这样,通过l_(f_,l)进行调用,就可以保证函数被正确调用了。

placeholder

大家关注一下这部分代码
   static WindowFrontPeer::MessageHandle about_handles[] = {
        WindowFrontPeer::Command(IDOK, MK_FUNC(FUNC(EndDialog), _1, 0)),
        WindowFrontPeer::Command(IDCANCEL, MK_FUNC(FUNC(EndDialog), _1, 0)),
        NULL,
   };
请注意_1这个对象。
首先说明下,WindowFrontPeer::MessageHandle表示一个消息的处理句柄,可以通过WindowFrontPeer的静态函数Command,Destroy等创建。它们与onCommand和onDestroy功能是类似的,所不同的是,on系列函数会创建MessageHandle并关联到窗口,而它们只创建不关联。只有当窗口窗口成功后才会关联。

_1是一个placeholder对象,也就是占位符,它表示对这个位置将使用参数表中的第一个参数代替。 
上面的例子:
WindowFrontPeer::Command(IDOK, MK_FUNC(FUNC(EndDialog), _1, 0)),
当WM_COMMAND消息到来时,会以这种形式调用:  command_handle(HWND hwnd, int id); 所以上面的代码,实际上会这样
function_t<Endialog, list2<HWND,INT> > endialog_func;
endialog_func.f_ = EndDialog;
endialog_func.l_.t1 = _1;
endialog_func.l_.t2 = 0;
//调用
endialog_func(hDlg, IDOK); 
//等同于 EndDialog(hDlg, 0)的调用
_1就是将hDlg填充到EndDialog的第一个参数的位置。

_1的定义是
static const ArgIndexHolder<1> _1;

ArgIndexHolder是几个模板类,起到占位的作用。它将从参数表中提取第一个参数填充到_1原来所在的位置。

有兴趣的童鞋可以阅读api/cqholder.h中的代码。

更多,请期待.....



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值