SkylineSoft

莽莽苍节兮 群山巍峨 日月光照兮 纷纭错落 丝竹共振兮 执节者歌 行云流水兮 用心无多 求大道以弹兵兮凌万物而超脱 觅知音因难得兮唯天地与作合
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Qt信息隐藏(Q_D/Q_Q)介绍

Posted on 2011-05-14 17:19  Jiangwzh  阅读(10688)  评论(4编辑  收藏  举报

目录:

        1:基本介绍与二进制兼容

        2:二进制兼容的设计原则        

        3:常见c++/qt信息隐藏

        4Q_Q,Q_D介绍

        5:定制可编辑treewidget与如何访问基类的Private

        6:总结


1:基本介绍与二进制兼容

        作者虽然一直在linux做开发工作,        对于window平台下,软件的开发模式与稳定性及质量一直不太满意,但是对于window系统在系统兼容方面还是十分佩服的,最近拿着win95下的一款软件在win7上安装了一下,依然运行的很好,这就涉及到了程序兼容的感念,一般可以分为二进制兼容(Binarycompatible),与源代码兼容(sourcecompatible)window的开发接口winapi/mfc在这两方面做的非常好,而linux平台下的大部分软件在这方面表现很差劲,当然主要原因在于linux下的很多基本库属于不同的组织,个人或者基金会,这方面的问题已经是linux系统在桌面的普及的一大障碍,面对将近几百个发行版,而且每一个发行版一年内至少更新一次,对于普通用户来说实在是个艰难的选择。可喜的是linux的社区已经开始注意这方面的问题。

        那么什么是二进制兼容呢,二进制兼容是针对动态链接库而言,如果动态库的更新,可以兼容以来此库以前版本的程序的话,我们就说这个库是二进制兼容的(微软提供的大部分library,都在这个级别上),如果程序必须在动态库上重新编译链接的话,那么就是源代码兼容(linux平台大部分库都需要重新编译),如果源代码重新编译不了的话,...,那...太失败了,glibc/gtk++/qt/python在主要版本发生变化时都是这种情况,这里可能引起歧义,程序设计语言可以分为编译型和脚本型,下文限定到编译型语言,你可以直接理解为c/c++.

: m* Q: X& H1 {  @

2:二进制兼容的设计原则        

        此段是从kde社区的一篇文档,针对每一条,笔者并没有去测试(内容主要讲一些基本原则,那些操作可以保持二进制兼容,那些操作是禁止,库设计人员需要遵循的准则等等),如果有兴趣的话,可以从编译链接的角度来解释为什么会这样,欢迎讨论啊...

        http://techbase.kde.org/index.php?title=Policies/Binary_Compatibility_Issues_With_C%2B%2B

        基本原则:

        程序设计中,开发正角度看到的库包括头文件(.h)和lib(window下文.lib,linux下为.so)我们知道编译器链接之后呢,访问类中的元素是通过偏移量来访问,所以抽象出我们的一条原则:

        a: 不可以改变类的大小和类成员的顺序

        如果类中有虚函数的话,虚函数通过虚拟地址表来访问虚函数,这就抽象出第二条原则:

        b:如果类中没有虚函数,那么不能添加虚函数,其实也是影响类的大小的变化。

1 N) S5 ^. B9 @$ v

3:常见c++/qt信息隐藏

        做为软件开发者来说,二进制兼容会经常碰到,在c/c++中如果头文件的变化,按照make的时间戳机制,凡是原来这个头文件的源码文件都会重新编译,而依赖此库的别的库都需要重新编译。而库的实现方法改进,修正bug等等,当然会引起库的不断更新,那么如何继续保持二进制兼容呢.

        我们先来看一个基本的C++类的定义,我们以一个可编辑的treewidget为基础,一步一步介绍基本的设计原则与隐藏信息隐藏方法,最终这个treewidget要支持undo/redo/add/del/copy/paste/drag等等所有想到的功能:

        #第一个版本v1如下(下载:v1.tar.gz)假设我们需要一个支持可添加子节点,兄弟节点的treewidget

        代码如下:

        treeop.h

#ifndef _treeop_h_

6 j% W( c9 u. ?* K: d! g# Z

#define _treeop_h_

0 S( Y- B- C( g9 {* R" ~

#include <QTreeWidget>


#include <QTreeWidgetItem>

9 u; y( G! ?9 j: k( W* G) t0 j; q* p# `

#include <QAction>

2 g2 p. g2 I  A$ g

#include <QAction>


class TreeOpPrivate;


class TreeOp : publicQTreeWidget


{


    Q_OBJECT

' S  s9 J" r# L" O8 B6 s

public:


    TreeOp(QWidget *parent =NULL);


    virtual ~TreeOp();

5 _3 W) s7 _8 L" ~

    virtual QTreeWidgetItem*newTreeItem();


public slots:

: t# V( K4 e# D* N0 B/ H) @) y

    void addChild();

4 E8 c2 o  x& J9 w, `0 D% |& m1 |! I

    void addNextSlibing();


    void addPrevSlibing();


private:


    QAction *m_addChild;

6 T2 \) N% {$ l- B% {

    QAction*m_addPrevSlibing;


    QAction*m_addNextSlibing;


};


' A: ^" G  r3 ~) R

#endif

% _: y6 c0 Y$ @" W! m
源文件请下载:

+ O1 ]; m. u4 c  p/ r+ ~$ A
测试代码为main.cpp,请从附件中下载编译方法qmake&&make,下面的例子不再赘述

1 F6 d/ u; F' I7 R. V: S  Y( h
添加子节点被绑定为Alt+a,添加上一个兄弟节点为Alt+p,下一个兄弟节点为Alt+n。工作的非常好,打包发布,喝咖啡...


第二天有了反馈,用户希望再添加一个删除的功能,好,在TreeOp类中添加一个新的接口,简单,包含treeop.h的文件重新编译一次即可...等等,用户还想再添加支持节点可以moveup/movedown/moveright/moveleft的功能,OMG...,等等TreeOp类中QAction的几个成员名字起的不好,改改名字,每次都要重新编译,如果您依赖TreeOp,您也得重新编译...崩溃5分钟...,有没有好的办法呢。我们可以设计一个私有类,来保存所有的变量和操作


第二个版本:(下载v2.tar.gz )

        treeop.h代码:

#ifndef_treeop_h_

) H$ N5 Q- n% j

#define_treeop_h_


#include<QTreeWidget>


#include<QTreeWidgetItem>


#include<QAction>

0 h0 ?5 J+ W) u: L  K- j( A/ `

#include<QAction>

" y0 }  F! y. Y- m! c

classTreeOpPrivate;


classTreeOp : public QTreeWidget

$ ^# ^  z# ^! ~, c

{

1 T  {& C: r1 C2 y$ f/ K- W

    Q_OBJECT

8 ?' Q! O" B  @) ~. H) G$ V$ `

public:

, o2 m0 N9 U# v: x( c; P: @

    TreeOp(QWidget*parent = NULL);

& W5 W: u5 \# Q/ W/ {% d

    virtual~TreeOp();


    virtualQTreeWidgetItem *newTreeItem();

. A. v7 L! B2 b, w0 h+ r% c9 j3 s

private:


    TreeOpPrivate*m_p;

8 r; h/ c& F8 M; |6 V) f; }( e

};



0 y: L3 l5 f* j+ Y9 T. f

#endif

6 k  P  m3 x: N" j
从代码中可以看到,treeop.h文件已经完全简化了,我们新添加了一个类,TreeOpPrivate;

把所有可变化的量全部写道treeop.cpp文件中去,TreeOpPrivate继承了QObject类,所以可以添加slots,因为在cpp中,所以呢在treeop.cpp的文件末尾我们#include“treeop.moc”,来让Qttreeop.cpp也进行预处理。

' I1 {, M$ K# {# }' n+ i
好了现在不管用户想添加什么功能,我们都可以在private类中实现,无论如何修改,需要编译的文件只有自己,别的即使依赖treeop.h的文件也无须重新编译。世界安静了吗?...等等,再看看程序,在源程序中可以看到很多m_owner->m_p->为什么呢,m_owner->TreeOpPrivate类访问TreeOp的接口,m_p->TreeOp类访问TreeOpPrivate的接口,好麻烦,有别的方法吗,让我们看起来美观又直接,好下面我们进化到第三个版本,看看Qt中的Q_D/Q_Q

4Q_Q,Q_D介绍


第三个版本:(下载v3.tar.gz)

        treeop.h的代码:

#ifndef_treeop_h_


#define_treeop_h_

* {1 b9 M) Y2 R7 E

#include<QTreeWidget>


#include<QTreeWidgetItem>


#include<QAction>

7 a8 o# _: z7 s- X. X: a0 j

#include<QAction>


classTreeOpPrivate;


classTreeOp : public QTreeWidget


{


    Q_OBJECT

! E% N* W" H+ m* `

public:


    TreeOp(QWidget*parent = NULL);

3 W  W% X3 C* o2 q: D& M4 F7 x

    virtual~TreeOp();

( T4 ^0 M+ `5 S4 W* ]2 C3 r

    virtualQTreeWidgetItem *newTreeItem();

: |) k# q! v5 q' V/ L* \

protected:

. Z) _  s! m) h( k7 I$ `

    TreeOpPrivate*const d_ptr;


private:


   Q_DECLARE_PRIVATE(TreeOp);


};

1 K- E5 X! F; e, E" e7 d& M0 n2 p+ E6 e

( I' ^" u4 r9 J, v

#endif        

/ O% X- {" b/ z4 P6 l3 J4 Y
我们可以发现和第二个版本相比,在类TreeOp中多了这几行

protected:

# u$ G& C, b5 _" ?( m! G5 A

    TreeOpPrivate*const d_ptr;


private:

+ A3 V" t0 X/ G0 d" Y$ V

   Q_DECLARE_PRIVATE(TreeOp);


3 j6 i1 ?$ R. \/ r( g
在类TreeOpPrivate中,多了以下几行:

    TreeOp* const q_ptr;


   Q_DECLARE_PUBLIC(TreeOp);


' E( X3 \/ c4 x1 T4 K9 p, k
想要了解一下,把这个宏拆开好了:

#defineQ_DECLARE_PRIVATE(Class) \

        inlineClass##Private* d_func() { return reinterpret_cast<Class##Private*>(d_ptr); } \- s2 F  ?/ Y+ M  i* M; F' a
        inline const Class##Private* d_func() const {return reinterpret_cast<const Class##Private *>(d_ptr); }\
        friend class Class##Private;        

这其中”##”表示的连接的意思,例如a##b,经过编译器预处理后,就成了ab.


从宏的定义可以看出,声明了两个内联的函数d_funcconstd_func(),用于返回对应的d_ptr.并且把Class##Private声明为Class的友元.

* y1 w7 b" G  Z5 w# E$ g
我们再来看看Q_D宏的展开:


#defineQ_D(Class) Class##Private * const d = d_func()

        假如我们传入TreeOp会翻译成 TreeOpPrivate *const d =d_func(); 从而就可以得到了d_ptr的指针。在来看看TreeOp的初始化:

TreeOp::TreeOp(QWidget*parent) : QTreeWidget(parent),d_ptr(new TreeOpPrivate(this))


{


    setHeaderHidden(true);

( H. ]. G7 Z' s# {0 C" _6 H$ |

}

        我们在构造函数中把d_ptr进行初始化,有的同学可能会问,这里写成这样子如何?

TreeOp::TreeOp(QWidget*parent) : QTreeWidget(parent)

{

* z7 |2 s7 V" \: {' r

            d_ptr = newTreeOpPrivate(this);

            setHeaderHidden(true);

9 q% E. a" c5 P

}        

* Q, z% j& h  O" M' Y4 Z
实际编译一下就知道了,应为我们定义的d_ptrconst的,所以这样是无法编译通过的.

/ j1 K  f3 b- C* n, W
再来看看Q_DECLARE_PUBLIC的展开

#defineQ_DECLARE_PUBLIC(Class)  \
inlineClass* q_func() { return static_cast<Class *>(q_ptr); } \
inlineconst Class* q_func() const { return static_cast<const Class*>(q_ptr); } \

friendclass Class;7 m0 Q' Y& n/ I; X8 g
#defineQ_Q(Class) Class * const q = q_func()

9 @4 o$ a) C6 B5 Y- R3 r


可以看出和Q_DECLARE_PRIVATE差不多,可以得到q_ptr的指针。

/ K8 M, M$ ]  F/ a
同时在看看Q_Q

#defineQ_Q(Class) Class * const q = q_func()

* C) R8 C" `, T. |6 V

2 O9 T% {8 ]8 _" S: c
好了差不多,经过这次处理,我们的代码看起来整洁多了,结束了吗?可能你已经想结束了,可是本教程还没呢,如果你还有兴趣,我们还要将到如何定制这个可编辑的treewidget,如何把它用到书签,目录中,如何实现engine/gui的分离,好吗,你们不是来听我罗嗦的,不过确实下面才是重点啊。

9 T2 A  a  t; E% ]4 {
第四个版本:(下载v4.tar.gz)

+ E* O/ L3 B$ N! Q4 K
在完成了上面的功能之后,我们来考虑一下,冲上一杯咖啡,点燃一根香烟,哦算了,照顾下女同志

        a:        从功能角度讲,距离我们预期的目标还很远,我们还缺少copy/paste/cut,drag/drop,undo

/redo/edit等功能

        b:        从用户角度看,基本这个widget什么都没有,实现的功能也都是快捷键,我鼠标点击那里阿,右键菜单呢,怎么保存我修改好的呢,这是谁写的阿,怎么差劲...

        c:        从使用这个widget的开发者来说,这是啥玩意阿,我要自定义QTreeWidgetItem,我中间有目录,有文件,我还要添加别的功能,我的这个item不能支持删除,也不能再添加子节点了,omg,这是谁写的垃圾阿...



哥是从来不会写出垃圾的,不过这个这个...越看越像小学生的哦...好,我们继续...

2 I% ?# M2 Z( j- u# Z7 N
我们先从使用这个widget的开发人员来看,我希望有一个完全可编辑的的treewidget,我还要可以自定义,我要可以美化这个widget,我想把它用到浏览器的书签功能中,我要做个书籍的分类,我要安排一个日程表...,我要它来记录一个公司组织架构图...

" u6 q! [% H) O. p8 U1 U
神啊,软件工程,设计模式,用户行为...,下班了,哦,不行,不实现这些功能都不准下班。


好,我们提供一个基本的treeop,这个用来处理所有的逻辑操作,但是不包含任何用户行为

我们在构造一个TreeOpWidget类,让它负责跟用户交互,如果RD不满意这个类,让他们自己写好了。


再来看看新版的treeop.h的代码:(因为里面涉及copy/paste/undo/redo/drag/drop)笔者就不详细介绍了,有不满意的地方想联系作者,看文章开头:

#ifndef_treeop_h_

$ r9 ^9 M; M: K( y1 S4 f* o

#define_treeop_h_

$ Y( N% V2 [3 Q

#include<QTreeWidget>

8 v  m  Z4 @1 g* ^

#include<QTreeWidgetItem>


#include<QAction>

7 C  x( L3 E9 X' l9 O9 H2 S8 e

#include<QAction>


classTreeOpPrivate;


classTreeOp : public QTreeWidget


{


    Q_OBJECT


public:


    TreeOp(QWidget*parent = NULL);


    virtual~TreeOp();


    voidclearUndoStack();

//清空undo

    voidundo();

//undo操作

    voidredo();

//redo操作

    voidsetEditable(bool);


    boolisEditable();


    voidsetCopyable(bool);

3 ?+ u  H' ?4 }, O# a8 l

    boolisCopyable();


    voidsetDragable(bool);


    boolisDragable();

! f3 B& \; w1 P4 N7 i

    voidsetDelable(bool);


    boolisDelable();


    voidsetUndoable(bool);


    boolisUndoable();


    boolisModified();

//设置是否可编辑,可拷贝,可拖拉,可编辑,可undo,可删除等

    voidcopy();

/ E, ~7 Y# U( X

    voidpaste();

# r! Y7 a% d# g7 X# y: t

    voidcut();

( i* @7 F5 T; Q5 J

    voidmoveUp();


    voidmoveDown();

- [3 `) Y; H% H; x2 S1 }% K

    voidmoveLeft();

7 X# w  k' @3 N3 e5 K6 g! B

    voidmoveRight();

6 `/ l$ o  k1 l0 m5 M0 X3 ]

    voiddel();

//实际操作的接口

    voidsort(bool sub);

//无视

    virtualbool canCopy(QTreeWidgetItem *item);


    virtualbool canPaste(QTreeWidgetItem *item);

. Q+ l$ |# u: T9 Y& `# w: i8 o

    virtualbool canCut(QTreeWidgetItem *item);


    virtualbool canMoveUp(QTreeWidgetItem *item);


    virtualbool canMoveDown(QTreeWidgetItem *item);

" V. L. m# V( S4 w2 u. q

    virtualbool canMoveLeft(QTreeWidgetItem *item);


    virtualbool canMoveRight(QTreeWidgetItem *item);

2 s: a% m8 K2 m3 R

    virtualbool canAddChild(QTreeWidgetItem *item);


    virtualbool canAddNextSlibing(QTreeWidgetItem *item);


    virtualbool canAddPrevSlibing(QTreeWidgetItem *item);

2 i* A+ o8 C! ^

    virtualbool canDel(QTreeWidgetItem *item);

//询问子QTreeWidgetItem,可进行这些操作吗?

    virtualQTreeWidgetItem *newTreeItem();

//创建一个新item

    virtualQTreeWidgetItem *treeItemFrom(const QString &str);


    virtualQString treeItemTo(QTreeWidgetItem *item);

//用于支持copy/paste操作,子QTreeWidgetItem需要自己实现这些功能,产生一个str,并且从strnew一个item出来

    virtualQString mimeType();

//copy/pastemimeType

    virtualvoid updateItem(QTreeWidgetItem *item,int col);

//Item被更新

    voidclearMenu();


    voidaddAction(QAction*);


    voidaddSeaparator();

//做右键菜单

    voidaddChild();


    voidaddNextSlibing();

( Y; K4 z7 u8 b

    voidaddPrevSlibing();

//添加节点

    voidrefreshSignals();

//重新刷一边使能信号

protected:


    voiddragEnterEvent(QDragEnterEvent *event);

: ?$ _5 N$ ]2 H* Q% d7 e

    voiddropEvent(QDropEvent *event);


    voidcontextMenuEvent(QContextMenuEvent *event);


signals:


    voidsigUndoEnabled(bool);

/ J1 M6 `. F' G/ N" a: X" |

    voidsigRedoEnabled(bool);


    voidsigCopyEnabled(bool);


    voidsigPasteEnabled(bool);


    voidsigCutEnabled(bool);


    voidsigDelEnabled(bool);


    voidsigMoveUpEnabled(bool);


    voidsigMoveDownEnabled(bool);

5 n  R- }0 y9 q& w! J$ P

    voidsigMoveLeftEnabled(bool);

# `1 z- ~0 p/ N" {* o8 N. _3 R

    voidsigMoveRightEnabled(bool);

+ O$ |, \4 b# g0 L3 c* r( u' t

    voidsigAddChildEnabled(bool);

; v/ u7 M- e# f7 A5 |) g: ]: a& t6 b$ d

    voidsigAddNextSlibingEnabled(bool);

8 {. y. [  y/ V" s; P$ _

    voidsigAddPrevSlibingEnabled(bool);

//操作的使能信号

protected:

# \' C# n, J+ Q

    TreeOpPrivate* const d_ptr;

8 t5 F; Z4 ^* y( G

private:

  u* I+ [5 a& I

   Q_DECLARE_PRIVATE(TreeOp);


};

; G  N& Z2 @) w' {7 Q
1 s2 M, v1 [% f5 O

#endif        

! r7 H& x3 h/ D7 E% v! ]


代码的旁边有简单的注视,我们可以把接口分为几类,

2 A# s- J% B$ s5 \) F. w
一类是使能信号,通过signal送出,表明这时候可以进行这项操作吗?例如voidsigAddChildEnabled(bool);

表示是否可进行添加子节点的操作。

; ?: W# P4 b& x
一类是虚拟类,如virtual boolcanCopy(QTreeWidgetItem *item);

用来判断这个item是否可进行copy

# S$ U7 D! g/ E0 ]% o. |3 `
一类是实际操作类,voidcopy(),就是进行copy这个动作。

5 @' f% s1 W$ p, I8 c( q3 @2 Z


我们有新添加了一个文件叫做treeopwidget.h从名字可以看出这个文件会输出一个可编辑的widget。来看看treeopwidget.h的代码

#ifndef_treeopwidget_h_


#define_treeopwidget_h_


#include<QWidget>

+ Z2 W4 ]( A# Y+ o4 K

#include<QTreeWidget>

1 g, K4 ~# k6 c( ?4 h0 h

classTreeOpWidgetPrivate;

5 _- `$ S9 w3 P: p

classTreeOp;

# O4 E/ Z3 J' u. r2 Z8 q. B1 g% r' q

classTreeOpWidget : public QWidget


{

3 O3 E8 Y9 y$ t+ [5 e7 ?

    Q_OBJECT

2 R% Z2 g- p6 u3 z+ `0 L( ?

public:

# W+ z6 `1 M/ Y6 {+ J+ i

    TreeOpWidget(TreeOp*tree = NULL,QWidget *parent = NULL);

( R+ W, r0 p. T( |+ _( v* u

    virtual~TreeOpWidget();

4 {4 T4 H% X- F

    voidshowToolbar(bool);


    TreeOp*treeWidget();


protected:

6 e& L8 i! x' e; i2 t

    TreeOpWidgetPrivate* const d_ptr;


private:


   Q_DECLARE_PRIVATE(TreeOpWidget);


};

8 J8 R3 Z& H6 }* T


#endif

8 C4 P/ N6 n% y2 K- W
; ~% \1 F$ w9 N' X  b; ^5 M

# P/ W: }5 j/ D
同样我们定义TreeOpWidgetPrivate,TreeOpWidget

+ @) r; k3 e6 |) a2 @
看构造函数:

        TreeOpWidget(TreeOp*tree = NULL,QWidget *parent = NULL);


5 P& P4 {) q, |- B: f
如果用户没有继承TreeOp的话,tree为空,那么cpp中就自己new一个出来。在treeopwidget的实现文件中,我们定义了所有的操作,和action,请参看代码:

( U6 x/ Y& _; u9 @$ D' C
2 c) L, L& X/ s3 s2 {2 E8 s4 K4 o

' u7 g& R) {# c
到这里第一步功能总算完成了,一个支持所有操作的TreeOp类,一个可以把这个导出为一个WidgetTreeOpWidget类,而且我们对TreeOpWidget的定制不会影响二进制兼容,yeah


嗯,总算有点样子了。可是这个widget好丑,那个up/down/left/rightaction我不想要怎么办,而且放在上面也难看了,一个RD想做个bookmark管理器,要求好多,不过没办法,谁让我们追求完美呢,我们进化到下一个版本


5:定制可编辑treewidget与如何访问基类的Private  (下载v5.tar.gz)

        bookmark的开发者说,那个TreeOpWidget的类挺好的,想完全抛弃它,能不能在上面改改来完成我的功能呢,比如,我想添加一个open/save的菜单到上面,用来打开和保存编辑内容,你能帮我想想办法吗,
哥哥总是心太软,心里面想你为啥不自己写一个TreeOpWidget呢,可是嘴里说没问题,我来看看,小妹妹阿...


她的需求一句话,虽然她在不同项目组中,但是她希望可以访问到TreeOpWidgetPrivate类,哦,等等,我的TreeOpWidgetPrivate类是在cpp中定义的阿,这这...,把它暴露出来,影响二进制兼容啊,不暴露,怎么使用呢?怎么办,怎么办...

/ I" a% D3 @2 O" O* K& G2 t' _% y
看看QT的源代码吧,为什么存在怎么多_p.h文件呢?...

2 @) ~! G  Q- y; h* K  y( L
既然bookmark想用到TreeOpWidgetPrivate类,那么我们就把它输出出来,但是不能暴露给别的不使用这个类的模块,好,开始...


我们有一个bookmarks.xml文件,内容如下:

<bookmarksversion="0.1">

0 A/ D% U. G% o) h# {4 E- e

    <categoryname="linux">

/ Z. G5 z8 T0 o8 m, D$ j8 W6 c

        <dataname="linxu program"/>

' T& j  Z# J; t4 e" R1 M

        <dataname="linux program"/>


        <dataname="linux program"/>

$ S# _: N  S/ l

    </category>

4 k$ _, ~4 E0 t# x8 d- k5 b

</bookmarks>

        category表示这是一个目录,可编辑,data表示这是一项数据,不可编辑。


我们现在把TreeOpWidgetPrivate的定义放到一个新建文件中,treeopwidget_p.h然后在treeopwidget.cppinclude这个文件,treeopwidget.h定义如下:

#ifndef_treeopwidget_h_

; y3 e: d+ ~$ b" G

#define_treeopwidget_h_


#include<QWidget>

" t  d" x. V$ e9 J& H, N* Q

#include<QTreeWidget>

& J) |! z7 X* }- S8 N* @

classTreeOpWidgetPrivate;


classTreeOp;

* S  y  P( {( _" Z5 J- Q5 D2 V- k

classTreeOpWidget : public QWidget

6 ?' L( _; W" {9 A

{


    Q_OBJECT

6 i) U- h. R  `( l  N, g

public:


    TreeOpWidget(TreeOp*tree = NULL,QWidget *parent = NULL);


    virtual~TreeOpWidget();

+ c. Q4 `; D. N4 K+ M! }% L

    voidshowToolbar(bool);


    TreeOp*treeWidget();

, E3 G0 w& b4 t' M$ u

protected:

' L) b4 E3 ?) b

    TreeOpWidgetPrivate* const d_ptr;


   TreeOpWidget(TreeOpWidgetPrivate&dd,QWidget *parent = NULL);

2 L* i, y- p) n, P8 }7 S0 ~

private:

1 A4 u# g& W, b3 S8 I! F

   Q_DECLARE_PRIVATE(TreeOpWidget);

$ y$ [, q& b5 W6 X

};


. y# z6 W4 g7 u, U4 Y+ m) }
& ], W8 z6 z/ [3 m4 k8 O

#endif



可以看到我们能新添加了一个接口TreeOpWidget(TreeOpWidgetPrivate&dd,QWidget *parent = NULL);

一个Protected构造接口,方便来bookmark类构造父类.

9 Y' \$ E9 A8 T% F
bookmark定义,在book/目录下bmwidget.h

#ifndef_bmwidget_h_


#define_bmwidget_h_


#include<QWidget>

4 K$ M% `6 f( L% D0 ^

#include"treeopwidget.h"

, c( }0 n& W! T! J" W7 h! Q

classBookmarksTreeItem;


classBookmarkWidgetPrivate;


classTreeOpWidget;


classBookmarkWidget : public TreeOpWidget


{


    Q_OBJECT


public:


    BookmarkWidget(QWidget*parent = NULL);


    virtual~BookmarkWidget();

- I$ n% B1 I  S2 |. ^9 _

protected:

5 ~. r2 |" x6 j0 T: ?6 x: S% q

    BookmarkWidgetPrivate* const d_ptr;


private:

, b" F  i) m% k: X

   Q_DECLARE_PRIVATE(BookmarkWidget);


};

* l% S$ D( P$ |7 V: M$ @4 u- i

#endif

        BookmarkWidget继承了TreeOpWidget,BookmarkWidgetPrivatebmwidget.cpp中,它继承了TreeopWidgetPrivate类,看看BookmakrWidget的构造:

BookmarkWidget::BookmarkWidget(QWidget*parent) : d_ptr(newBookmarkWidgetPrivate(this)),TreeOpWidget(*d_ptr,parent)


{

- K3 o( y! |8 P' \2 S. w

}

通过TreeOpWidget(TreeOpWidgetPrivate&dd,QWidget *parent = NULL);

来初始化了父类。

运行效果如图:

, ?; f* ]. U4 w6 O# a: U# m
% H+ g/ d3 T8 ?2 w: f/ r  R5 a
: g' E+ U7 k6 u5 C1 ]/ a
  v# ^8 V) m* n' U5 |2 L

0 O. O4 t; a: Z9 t: J
在最前添加了load,save接口。


6:总结

        在此教程中我们从二进制兼容开始,讲到了信息隐藏的技术,实际构建了一个可编辑的treewidget,介绍了Q_D,Q_Q等等,其实在window系统我们经常会用到这种技术,最最典型的的就是processMsg(inttype,long *lParam,long*wParm),通过lParam,wParam这两个指针可以传递任何数据类型,在这个接口面前,本文介绍的都是浮云,不管如何,希望你能有点体会,如果想跟作者交流.