博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用c++11封装win32界面库
阅读量:5241 次
发布时间:2019-06-14

本文共 6009 字,大约阅读时间需要 20 分钟。

0. 前言

  你是否也是一个c++玩家,经常用c++写一些带界面的小程序?厌倦了每次在vs里用鼠标拖各种控件,然后copy / paste一大堆win32的api?没用过mfc,wtl,qt,只用sdk? 本文介绍一种方法把这些api进行封装,弄一个界面库出来,当然前提是对这些api有基本了解。

  之前看过些界面库源码,尤其是egui,好多东西都是从它那学来的。它们都用到像boost这种第三方库,因为当时c++版本没有自带shared_ptr,lambda,functional这些工具, c++11之后包含了这大部分东西,也就不需要第三方库了,但需要较新的编译器。下面的源码可以用MinGW编译,或者vs2012+ 补丁(vs2012不支持xp)。

下载:

1. 介绍

  就称这界面库叫 _gui 吧,整个 _gui 可以分为以下几部分

  1. thunk  封装wnd_proc这种回调函数

  2. property 类似vb的属性

  win->enabled = false;
  edit->text = "xxx";

  3. event  事件

  btn->event.click += []() { cout << "button clicked" << endl; };
  4. initor 初始属性 
  wnd<edit> edt_psw = new_<edit>().text('admin').size(200,30).password_type(true);
  5. layout 布局
  如下图的垂直分割布局,拖动中间那条分隔条可以改变左右大小

例子:

2. thunk

  win32教程上的 wnd_proc 一般都是全局函数,缺点是全局函数无法和类实例一一对应,所以用thunk 把 wnd_proc 封装到类的成员函数,据说ATL就这么搞。

先看一下全局函数和成员函数的区别,调试时从汇编可以看到

push argscall global_func  //call 全局函数push argslea ecx, p_this //对象指针放到ecxcall member_func  //call 成员函数

区别就是成员函数会在ecx 中放入this指针, 所以如果把 WNDCLASS.wnd_proc 指向一段内存,在这段内存里做两件事

1.  lea ecx, p_this(窗口实例)

2.  call member_func

就ok了, 这段内存就是thunk,用一个结构体来表示:

#pragma pack(push, 1) //取消默认的4字节对齐,pack后char,short固定只占1,2字节struct thunk_code {	unsigned short stub1; // lea ecx, p_this	unsigned long p_this; 	unsigned char stub2; // mov eax,member_func	unsigned long member_func; 	unsigned short stub3; // jmp eax	void init() {		stub1 = 0x0D8D; // lea ecx 的机器码		p_this = 0;		stub2 = 0xB8; // mov eax 的机器码		member_func = 0;		stub3 = 0xE0FF; // jmp eax	}};#pragma pack(pop)
调试可以看到内存中代码:

(因为这段内存需要被执行,而如果直接 thunk_code code;  这个code是不可执行的,所以这里用 HeapCreate / HeapAlloc 带上 HEAP_CREATE_ENABLE_EXECUTE 来分配内存,参考 thunk.h 和 heap.h)

_gui的所有控件都是用的这种方式处理事件,所以thunk的初始化放在了基类 wnd_base 中(参考 wnd_base.h)

 

3 property

  操作属性的通常做法是对外提供两个接口 getter 和 setter,类似这样

struct listview {	void set_title(string s) { SetWindowText(...); }	string get_title() { GetWindowText(...); }};
可以把"属性"的概念封装起来
struct listview {	property::rw
title; listview() { title.绑定(get_title, set_title); } void set_title(string s) { SetWindowText(...); } string get_title() { GetWindowText(...); }};wnd
lv;sting s = lv->title; //会调用 get_title()lv->title = "new_title"; //会调用 set_title("new_title")

  这样对外只要访问属性 title 就好了,按权限分为 property::r  property::w  property::rw,有没有感觉简洁一些。(详见 property.h)

 

4 event

btn->event.click += on_btn_click_1;btn->event.click += []() { cout << "button clicked" << endl; };btn->event.click += bind(x::func, &x_obj);

  有一点 .net 的味道,用起来比较方便。 每个事件都是一个signal (见signal.h):

// event.hnamespace event {	struct base {		signal
move; signal
size; signal
paint; signal
enable; // ... virtual void process_msg(wnd_msg& msg) { switch(msg.type) { case WM_MOVE: move(pos(msg.lp.loword(), msg.lp.hiword())); break; case WM_SIZE: size(size(msg.lp.loword(), msg.lp.hiword())); break; case WM_PAINT: paint(msg); break; case WM_ENABLE: enable(!(msg.wp == 0)); break; // ... } } };}

 每个类都有一个 event 成员,如果要自定义消息,创建时候提供event_t 就ok

template
struct wnd_base : wnd32 { event_t event; virtual void process_msg(wnd_msg& msg) { event.process_msg(msg); // thunk 把消息发送给 wnd_base::process_msg,这里再调用event.process_msg }};

 5 initor

常见的做法是,给类提供多个构造函数以支持不同的参数
class window {	window() {}	window(string text) { ... }	window(string text, int w, int h) { ... }	window(string text, int w, int h, int x, int y) { ... }	...};window w("title", 100, 200, 300, 400);// 很容易记错,到底 100,200是长宽,还是xy坐标?

 

所以有了 initor, 用来存放创建信息, create() 的时候会去拿 initor 里的各种信息(text, size...)

wnd
w = new_

为了支持链式赋值和扩展性,initor的设计稍显复杂,见 initor.h

每种控件对应的initor,用traits来定义(还在想办法去掉这层定义@_@):

// wnd_traits 定义template
struct wnd_traits { typedef initor::wnd initor_t;};// 针对按钮的特化struct button;template<>struct wnd_traits

 6 layout

  _gui 分为两种控件,基本控件和容器,容器多出了 layout 和 children 两样东西,所以window, tab, panel 这些从 container 继承,而 button,label 等从 wnd_base 继承。

布局这个概念只有容器才有,当容器获大小改变会收到 WM_SIZE 消息,这时候用 layout 进行布局。 参考 container.h

layout 只有一个接口 apply

namespace layout {	struct base {		virtual void apply(wnd_ptr& parent, vector
& children) = 0; };}
各种layout实现这个apply来布置窗口,比如 fit 是把子窗口填充满整个容器
// fit layoutnamespace layout {	struct fit : base {		virtual void apply(wnd_ptr& p, vector
& ch) { rect r = p->client_rect; for(auto& c : ch) { // 通常只有一个子窗口 c->rect = r; } } };}

 比如本文最开头图中的垂直分割布局 vsplit:

// layout/split.hnamespace layout {	struct vsplit : base {		wnd
sp; // 分隔条 vsplit(int offset) { sp = 创建vsplitter; } virtual void apply(wnd_ptr& p, vector
& ch) { std::call_once(第一次布局时在容器p上画出 sp 分隔条); ch[0]->rect = 分隔条左边区域大小; // splitter sp->rect = ..;// 拉伸分隔条高度 = 容器高度 ch[1]->rect = 分隔条右边区域大小; } };}
总之在 apply 内可以实现所有布局,比如可以做一套传统的java布局,我没有考虑实现那些,觉得不够通用。以经典 border 为例,支持5个东西以 "东南西北中" 放置,但要多于5个它就不支持了,除非用嵌套 panel 的方法, 既浪费内存,代码也不易读。

需要一个更通用的布局。

我google了老半天,发觉两个还不错

1. PageLayout A Layout Manager for Java Swing/AWT  () 
    它的 doc 里说道  PageLayout: The Only Layout Manager You Will Ever Need

2. DesignGridLayout for java ()

    如果装了java,可以直接运行他的demo ()

但还是感觉不够通用,还要记一大堆api。

把 layout 问题抽象,其实可以看做一个线性约束问题。比如一个窗口,宽度是W,它包含左右两部分,左边宽度是右边两倍,可以描述成:

w1 == 2 * 2w; // 左边宽度是右边两倍w1 + w2 == W; // 总宽度是W

 或者固定宽度100:

w1 == 100;
或者播放器保持 16:9 比例,最小宽度200
w / h = 16 / 9;w >= 200;

这样一来,布局问题就变成了数学问题,通过解n元一次方程组就能算出每个控件的位置和大小。以后布局就不用记什么 layout api了,直接给几个公式就ok。

就是这么做的,看了它的demo后又发觉个问题,太不直观了。。

继续寻找,发现最直观的是这个 ,就写了个layout::eva:

可以用各大IDE的列模式编辑eva表格,vim的话还有插件可以格式化竖线: 

最后

 如果觉得太素就加个win7 style:

#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")

目前的进度也就到这,只有一个大致框架,准备改成mvc的,然后用到什么控件就改进什么。

有建议请联系, 企鹅号 94566062

good luck

转载于:https://www.cnblogs.com/aj3423/archive/2013/03/29/3150500.html

你可能感兴趣的文章
gis bing map 资料参考
查看>>
LRU cache实现 -Java 转
查看>>
Perl模块的安装方法
查看>>
web-9. 动态网页与数据库
查看>>
C++ 头文件一览
查看>>
spark发现新词
查看>>
用mapreduce 处理气象数据集
查看>>
posix多线程有感--自旋锁
查看>>
《那些年啊,那些事——一个程序员的奋斗史》——104
查看>>
Centos 7防火墙firewalld开放80端口(转)
查看>>
博客园博客背景图片设置
查看>>
mysql客户首末单时间 group by用法_20160927
查看>>
python中的__new__方法
查看>>
[Java]通过java获取计算机名
查看>>
tensorflow mac安装方法
查看>>
enyo官方开发入门教程翻译一Getting Started之A Tour of Enyo
查看>>
Daily Scrum 10.9
查看>>
Parallel Programming-多消费者,多生产者同时运行并行
查看>>
hdu 2044-2050 递推专题
查看>>
hdu 3573(数学+贪心)
查看>>