使用SOUI4中的STreeView控件

STreeView控件是一个基于虚表技术实现的高性能树形控件。

和STreeCtrl这种传统的树形控件将数据和控件固定在一起不同,STreeView数据和控件分离,使用一个adapter进行连接。

用过soui的朋友应该对soui提供的SListView很熟悉,SListView里可以轻松实现超大数据量的管理,显示,编辑等。和传统控件不同,在这种mvc框架下,控件占用的内存和数据量没有关系。

SListView使用起来相对简单,STreeView会相对复杂一点,这里专门写一篇小文章来介绍。

和使用SListView就是搞清楚使用IAdapter一样,使用STreeView就是要搞清楚如何实现ITvAdapter这个接口。

在C++的使用中,我们通常不会直接实现ITvAdapter, 而是使用soui提供的一个helper类:STreeAdapterBase

下面先看看这个类的实现。

 1 template <class BaseClass>
 2 class TvAdatperImpl : public BaseClass {
 3   public:
 4     void notifyBranchChanged(HSTREEITEM hBranch)
 5     {
 6         m_obzMgr.notifyChanged(hBranch);
 7     }
 8 
 9     /**
10      * Notifies the attached observers that the underlying data is no longer valid
11      * or available. Once invoked this adapter is no longer valid and should
12      * not report further data set changes.
13      */
14     void notifyBranchInvalidated(HSTREEITEM hBranch, bool bInvalidParents = true, bool bInvalidChildren = true)
15     {
16         m_obzMgr.notifyInvalidated(hBranch, bInvalidParents, bInvalidChildren);
17     }
18 
19     void notifyBranchExpandChanged(HSTREEITEM hBranch, BOOL bExpandedOld, BOOL bExpandedNew)
20     {
21         m_obzMgr.notifyExpandChanged(hBranch, bExpandedOld, bExpandedNew);
22     }
23     
24     //notify the item will be removed
25     void notifyItemBeforeRemove(HSTREEITEM hItem){
26         m_obzMgr.notifyItemBeforeRemove(hItem);
27     }
28 
29     STDMETHOD_(void, registerDataSetObserver)(ITvDataSetObserver *observer) OVERRIDE
30     {
31         m_obzMgr.registerObserver(observer);
32     }
33 
34     STDMETHOD_(void, unregisterDataSetObserver)(ITvDataSetObserver *observer) OVERRIDE
35     {
36         m_obzMgr.unregisterObserver(observer);
37     }
38 
39   protected:
40     STvObserverMgr m_obzMgr;
41 };

这是基类,提供了常规的ITvAdapter和STreeView交互的方法。

  1 template <typename T>
  2 class STreeAdapterBase : public TObjRefImpl<TvAdatperImpl<ITvAdapter>> {
  3   public:
  4     STreeAdapterBase()
  5     {
  6         memset(m_rootUserData, 0, sizeof(m_rootUserData));
  7     }
  8     ~STreeAdapterBase()
  9     {
 10     }
 11 
 12     struct ItemInfo
 13     {
 14         ULONG_PTR userData[DATA_INDEX_NUMBER];
 15         T data;
 16     };
 17 
 18     //获取hItem中的指定索引的数据
 19     STDMETHOD_(ULONG_PTR, GetItemDataByIndex)(HSTREEITEM hItem, DATA_INDEX idx) const OVERRIDE
 20     {
 21         if (hItem == ITEM_ROOT)
 22             return m_rootUserData[idx];
 23         ItemInfo &ii = m_tree.GetItemRef((HSTREEITEM)hItem);
 24         return ii.userData[idx];
 25     }
 26 
 27     //保存hItem指定索引的数据
 28     STDMETHOD_(void, SetItemDataByIndex)(HSTREEITEM hItem, DATA_INDEX idx, ULONG_PTR data) OVERRIDE
 29     {
 30         if (hItem == ITEM_ROOT)
 31             m_rootUserData[idx] = data;
 32         else
 33         {
 34             ItemInfo &ii = m_tree.GetItemRef((HSTREEITEM)hItem);
 35             ii.userData[idx] = data;
 36         }
 37     }
 38 
 39     STDMETHOD_(HSTREEITEM, GetParentItem)(HSTREEITEM hItem) const OVERRIDE
 40     {
 41         if (hItem == ITEM_ROOT)
 42             return ITEM_NULL;
 43         HSTREEITEM hParent = m_tree.GetParentItem((HSTREEITEM)hItem);
 44         if (hParent == NULL)
 45             hParent = ITEM_ROOT;
 46         return (HSTREEITEM)hParent;
 47     }
 48 
 49     STDMETHOD_(BOOL, HasChildren)(HSTREEITEM hItem) const OVERRIDE
 50     {
 51         return GetFirstChildItem(hItem) != ITEM_NULL;
 52     }
 53 
 54     STDMETHOD_(HSTREEITEM, GetFirstChildItem)(HSTREEITEM hItem) const OVERRIDE
 55     {
 56         SASSERT(hItem != ITEM_NULL);
 57         return (HSTREEITEM)m_tree.GetChildItem((HSTREEITEM)hItem, TRUE);
 58     }
 59 
 60     STDMETHOD_(HSTREEITEM, GetLastChildItem)(HSTREEITEM hItem) const OVERRIDE
 61     {
 62         SASSERT(hItem != ITEM_NULL);
 63         return (HSTREEITEM)m_tree.GetChildItem((HSTREEITEM)hItem, FALSE);
 64     }
 65 
 66     STDMETHOD_(HSTREEITEM, GetPrevSiblingItem)(HSTREEITEM hItem) const OVERRIDE
 67     {
 68         SASSERT(hItem != ITEM_NULL && hItem != ITEM_ROOT);
 69         return (HSTREEITEM)m_tree.GetPrevSiblingItem((HSTREEITEM)hItem);
 70     }
 71 
 72     STDMETHOD_(HSTREEITEM, GetNextSiblingItem)(HSTREEITEM hItem) const OVERRIDE
 73     {
 74         SASSERT(hItem != ITEM_NULL && hItem != ITEM_ROOT);
 75         return (HSTREEITEM)m_tree.GetNextSiblingItem((HSTREEITEM)hItem);
 76     }
 77 
 78     STDMETHOD_(BOOL,IsDecendentItem)(CTHIS_ HSTREEITEM hItem,HSTREEITEM hChild) const OVERRIDE{
 79         HSTREEITEM hParent = GetParentItem(hChild);
 80         while(hParent){
 81             if(hParent == hItem)
 82                 return TRUE;
 83             hParent = GetParentItem(hParent);
 84         }
 85         return FALSE;
 86     }
 87 
 88     STDMETHOD_(int, getViewType)(HSTREEITEM hItem) const OVERRIDE
 89     {
 90         return 0;
 91     }
 92 
 93     STDMETHOD_(int, getViewTypeCount)() const OVERRIDE
 94     {
 95         return 1;
 96     }
 97 
 98     STDMETHOD_(void, getView)(THIS_ HSTREEITEM hItem, SItemPanel *pItem, SXmlNode xmlTemplate)
 99     {
100     }
101 
102     STDMETHOD_(void, getView)
103     (THIS_ HSTREEITEM hItem, IWindow *pItem, IXmlNode *pXmlTemplate) OVERRIDE
104     {
105         SItemPanel *pItemPanel = sobj_cast<SItemPanel>(pItem);
106         SXmlNode xmlTemplate(pXmlTemplate);
107         return getView(hItem, pItemPanel, xmlTemplate);
108     }
109 
110     STDMETHOD_(void, getViewDesiredSize)(SIZE *ret,HSTREEITEM hItem, SItemPanel *pItem, int wid, int hei)
111     {
112         pItem->GetDesiredSize(ret,wid, hei);
113     }
114 
115     STDMETHOD_(void, getViewDesiredSize)
116     (SIZE *ret,HSTREEITEM hItem, IWindow *pItem, int wid, int hei) OVERRIDE
117     {
118         SItemPanel *pItemPanel = sobj_cast<SItemPanel>(pItem);
119         getViewDesiredSize(ret,hItem, pItemPanel, wid, hei);
120     }
121 
122     STDMETHOD_(void, InitByTemplate)(SXmlNode xmlTemplate)
123     {
124         (xmlTemplate);
125     }
126 
127     STDMETHOD_(void, InitByTemplate)(IXmlNode *pXmlTemplate) OVERRIDE
128     {
129         SXmlNode xmlTemplate(pXmlTemplate);
130         return InitByTemplate(xmlTemplate);
131     }
132 
133     STDMETHOD_(BOOL, isViewWidthMatchParent)() const OVERRIDE
134     {
135         return FALSE;
136     }
137 
138     STDMETHOD_(void, ExpandItem)(HSTREEITEM hItem, UINT uCode) OVERRIDE
139     {
140         BOOL bExpandedOld = IsItemExpanded(hItem);
141         BOOL bExpandedNew = bExpandedOld;
142         switch (uCode)
143         {
144         case TVC_COLLAPSE:
145             bExpandedNew = FALSE;
146             break;
147         case TVC_EXPAND:
148             bExpandedNew = TRUE;
149             break;
150         case TVC_TOGGLE:
151             bExpandedNew = !bExpandedOld;
152             break;
153         }
154         if (bExpandedOld == bExpandedNew)
155             return;
156 
157         SetItemExpanded(hItem, bExpandedNew);
158         notifyBranchExpandChanged(hItem, bExpandedOld, bExpandedNew);
159     }
160 
161     STDMETHOD_(BOOL, IsItemExpanded)(HSTREEITEM hItem) const OVERRIDE
162     {
163         if (hItem == ITEM_ROOT)
164             return TRUE; //虚拟根节点自动展开
165         return (BOOL)GetItemDataByIndex(hItem, DATA_INDEX_ITEM_EXPANDED);
166     }
167 
168     STDMETHOD_(void, SetItemExpanded)(HSTREEITEM hItem, BOOL bExpanded) OVERRIDE
169     {
170         SetItemDataByIndex(hItem, DATA_INDEX_ITEM_EXPANDED, bExpanded);
171     }
172 
173     STDMETHOD_(BOOL, IsItemVisible)(HSTREEITEM hItem) const OVERRIDE
174     {
175         HSTREEITEM hParent = GetParentItem(hItem);
176         while (hParent != ITEM_NULL)
177         {
178             if (!IsItemExpanded(hParent))
179                 return FALSE;
180             hParent = GetParentItem(hParent);
181         }
182         return TRUE;
183     }
184 
185     STDMETHOD_(HSTREEITEM, GetFirstVisibleItem)() const OVERRIDE
186     {
187         return GetFirstChildItem(ITEM_ROOT);
188     }
189 
190     STDMETHOD_(HSTREEITEM, GetLastVisibleItem)() const OVERRIDE
191     {
192         HSTREEITEM hItem = GetLastChildItem(ITEM_ROOT);
193         if (hItem == ITEM_NULL)
194             return hItem;
195         for (; IsItemExpanded(hItem);)
196         {
197             HSTREEITEM hChild = GetLastChildItem(hItem);
198             if (hChild == ITEM_NULL)
199                 break;
200             hItem = hChild;
201         }
202         return hItem;
203     }
204 
205     STDMETHOD_(HSTREEITEM, GetPrevVisibleItem)(HSTREEITEM hItem) const OVERRIDE
206     {
207         SASSERT(IsItemVisible(hItem));
208         HSTREEITEM hRet = GetPrevSiblingItem(hItem);
209         if (hRet == ITEM_NULL)
210         {
211             hRet = GetParentItem(hItem);
212             if (hRet == ITEM_ROOT)
213                 hRet = ITEM_NULL;
214         }
215         return hRet;
216     }
217 
218     STDMETHOD_(HSTREEITEM, GetNextVisibleItem)(HSTREEITEM hItem) const OVERRIDE
219     {
220         SASSERT(IsItemVisible(hItem));
221         if (IsItemExpanded(hItem))
222         {
223             HSTREEITEM hChild = GetFirstChildItem(hItem);
224             if (hChild != ITEM_NULL)
225                 return hChild;
226         }
227 
228         HSTREEITEM hParent = hItem;
229         while (hParent != ITEM_NULL && hParent != ITEM_ROOT)
230         {
231             HSTREEITEM hRet = GetNextSiblingItem(hParent);
232             if (hRet)
233                 return hRet;
234             hParent = GetParentItem(hParent);
235         }
236         return ITEM_NULL;
237     }
238 
239     STDMETHOD_(HRESULT, QueryInterface)(THIS_ REFGUID id, IObjRef **ppObj) OVERRIDE
240     {
241         return E_NOINTERFACE;
242     }
243 
244   public:
245     HSTREEITEM InsertItem(const T &data, HSTREEITEM hParent = STVI_ROOT, HSTREEITEM hInsertAfter = STVI_LAST)
246     {
247         ItemInfo ii = { 0 };
248         ii.data = data;
249         return m_tree.InsertItem(ii, hParent, hInsertAfter);
250     }
251 
252     void DeleteItem(HSTREEITEM hItem,BOOL bNotifyChange = TRUE)
253     {
254         HSTREEITEM hParent = GetParentItem(hItem);
255         if(!hParent) hParent = STVI_ROOT;
256         if(bNotifyChange){
257             notifyItemBeforeRemove(hItem);
258         }
259         m_tree.DeleteItem(hItem);
260         if(bNotifyChange) {
261             notifyBranchChanged(hParent);
262         }
263     }
264 
265     BOOL DeleteItemEx(HSTREEITEM hItem)
266     {
267         return m_tree.DeleteItemEx(hItem);
268     }
269 
270     const T &GetItemData(HSTREEITEM hItem) const
271     {
272         SASSERT(hItem != STVI_ROOT);
273         ItemInfo &ii = m_tree.GetItemRef((HSTREEITEM)hItem);
274         return ii.data;
275     }
276 
277     void SetItemData(HSTREEITEM hItem, const T &data)
278     {
279         SASSERT(hItem != STVI_ROOT);
280         ItemInfo& ii = m_tree.GetItemRef((HSTREEITEM)hItem);
281         ii.data = data;
282     }
283   protected:
284     CSTree<ItemInfo> m_tree;
285     ULONG_PTR m_rootUserData[DATA_INDEX_NUMBER];
286 };

前面STDMETHOD_开头的方法是ITvAdatper的实现,后面部分方法是树结构的数据管理。

可以看到这个ITvAdapter使用了一个CSTree类来管理数据(该类是作者自己实现的一个多叉树数据结构)。

看起来接口的方法不少,实际上使用并没有那么复杂。

下面我们以xliveplayer这个播放器的房间显示TreeView来介绍使用方法。

先看一下这个STreeView的adapter实现,这个adapter有两层,外面是js, 里面是c++, js实现的具体逻辑,这里只要看C++层就够了。

  1 typedef int TvKey;
  2 class STvAdapter : public STreeAdapterBase<TvKey>, public JsThisOwner
  3 {
  4 public:
  5     typedef STreeAdapterBase<TvKey> _baseCls;
  6 public:
  7     STvAdapter()  {}
  8 
  9     void notifyBranchChanged(HSTREEITEM hBranch)
 10     {
 11         _baseCls::notifyBranchChanged(hBranch);
 12     }
 13     void notifyBranchInvalidated(HSTREEITEM hBranch, bool bInvalidParents = true, bool bInvalidChildren = true)
 14     {
 15         _baseCls::notifyBranchInvalidated(hBranch, bInvalidParents, bInvalidChildren);
 16     }
 17 
 18     void notifyBranchExpandChanged(HSTREEITEM hBranch, BOOL bExpandedOld, BOOL bExpandedNew)
 19     {
 20         _baseCls::notifyBranchExpandChanged(hBranch, bExpandedOld, bExpandedNew);
 21     }
 22 
 23     HSTREEITEM InsertItem(TvKey key, HSTREEITEM hParent, HSTREEITEM hInsertAfter) {
 24         return _baseCls::InsertItem(key, hParent, hInsertAfter);
 25     }
 26 
 27     HSTREEITEM GetChildItem(HSTREEITEM hParent,BOOL bFirst) const{
 28         return bFirst?_baseCls::GetFirstChildItem(hParent): _baseCls::GetLastChildItem(hParent);
 29     }
 30 
 31     HSTREEITEM WINAPI GetParentItem(HSTREEITEM hItem) const{
 32         return _baseCls::GetParentItem(hItem);
 33     }
 34 
 35     HSTREEITEM GetNextSibling(HSTREEITEM hItem) const {
 36         return _baseCls::GetNextSiblingItem(hItem);
 37     }
 38 
 39     HSTREEITEM GetPrevSibling(HSTREEITEM hItem) const {
 40         return _baseCls::GetPrevSiblingItem(hItem);
 41     }
 42 
 43     TvKey GetItemData(HSTREEITEM hItem) {
 44         return _baseCls::GetItemData(hItem);
 45     }
 46 
 47     void SetItemData(HSTREEITEM hItem, TvKey key) {
 48         _baseCls::SetItemData(hItem, key);
 49     }
 50 
 51     void DeleteItem(HSTREEITEM hItem,BOOL bNotifyChange) {
 52         _baseCls::DeleteItem(hItem, bNotifyChange);
 53     }
 54 
 55     void DeleteAllItems() {
 56         DeleteItem(STVI_ROOT,true);
 57     }
 58 
 59     void SetItemExpended(HSTREEITEM hItem, bool expended) {
 60         _baseCls::SetItemExpanded(hItem, expended);
 61     }
 62 
 63     bool IsItemExpended(HSTREEITEM hItem) {
 64         return _baseCls::IsItemExpanded(hItem)==TRUE;
 65     }
 66 
 67     void WINAPI ExpandItem(HSTREEITEM hItem, uint32_t mode) {
 68         _baseCls::ExpandItem(hItem, mode);
 69     }
 70 
 71 public:
 72     STDMETHOD_(void, getView)(THIS_ HSTREEITEM hItem, IWindow* pItem, IXmlNode* pXmlTemplate) OVERRIDE
 73     {
 74         if(!m_funGetView.IsFunction())
 75             return;
 76         Context* ctx = m_funGetView.context();
 77         Value args[3];
 78         args[0] = NewValue(*ctx, hItem);
 79         args[1] = NewValue(*ctx, pItem);
 80         args[2] = NewValue(*ctx, pXmlTemplate);
 81         ctx->Call(GetJsThis(), m_funGetView, 3, args);
 82     }
 83 
 84     STDMETHOD_(int, getViewType)(HSTREEITEM hItem) const OVERRIDE
 85     {
 86         if(!m_funGetViewType.IsFunction())
 87             return 0;
 88         Context* ctx = m_funGetViewType.context();
 89         Value args[1];
 90         args[0] = NewValue(*ctx, hItem);
 91         Value ret = ctx->Call(GetJsThis(), m_funGetViewType, 1, args);
 92         if (ret.IsNumber()) {
 93             return ret.ToInt32();
 94         }
 95         else
 96         {
 97             return 0;
 98         }
 99     }
100 
101     STDMETHOD_(int, getViewTypeCount)() const OVERRIDE
102     {
103         if(!m_funGetViewTypeCount.IsFunction())
104             return 1;
105         Context* ctx = m_funGetViewTypeCount.context();
106         Value ret = ctx->Call(GetJsThis(), m_funGetViewTypeCount, 0, NULL);
107         return ret.ToInt32();
108     }
109 
110     STDMETHOD_(void, InitByTemplate)(IXmlNode* pXmlTemplate) OVERRIDE
111     {
112         if(!m_funInitByTemplate.IsFunction())
113             return;
114         Context* ctx = m_funInitByTemplate.context();
115         Value args[1];
116         args[0] = NewValue(*ctx, pXmlTemplate);
117         ctx->Call(GetJsThis(), m_funInitByTemplate, 1, args);
118     }
119 
120     STDMETHOD_(BOOL, isViewWidthMatchParent)() const OVERRIDE
121     {
122         if(!m_funIsViewWidthMatchParent.IsFunction())
123             return FALSE;
124         Context* ctx = m_funIsViewWidthMatchParent.context();
125         Value ret = ctx->Call(GetJsThis(), m_funIsViewWidthMatchParent, 0, NULL);
126         return ret.ToBool();
127     }
128 
129     virtual const WeakValue& GetJsThis() const override {
130         if (m_cbHandler.IsObject())
131             return m_cbHandler;
132         else
133             return JsThisOwner::GetJsThis();
134     }
135 public:
136     static void Mark(STvAdapter* pThis, JS_MarkFunc* mark_fun) {
137         pThis->m_cbHandler.Mark(mark_fun);
138         pThis->m_funGetView.Mark(mark_fun);
139         pThis->m_funGetViewType.Mark(mark_fun);
140         pThis->m_funGetViewTypeCount.Mark(mark_fun);
141         pThis->m_funIsViewWidthMatchParent.Mark(mark_fun);
142         pThis->m_funInitByTemplate.Mark(mark_fun);
143     }
144 
145     Value m_cbHandler;
146     Value m_funGetView,
147         m_funInitByTemplate,
148         m_funGetViewTypeCount,
149         m_funGetViewType,
150         m_funIsViewWidthMatchParent;
151 };

除了那些树结构相关的接口外,

我们需要关注的接口有下面几个(新手通常也就这几个接口的用法搞不清楚):

通知一个分支的数据及状态(展开,收缩)发生变化,刷新界面,包括子节点。 

void notifyBranchChanged(HSTREEITEM hBranch)

 

通知一个节点的数据发生改变,刷新界面显示,后面两个参数控制是否刷新父节点或者子节点。

void notifyBranchInvalidated(HSTREEITEM hBranch, bool bInvalidParents = true, bool bInvalidChildren = true);

 

通知一个节点的展开状态发生变化。

void notifyBranchExpandChanged(HSTREEITEM hBranch, BOOL bExpandedOld, BOOL bExpandedNew);

明白了上面这几个方法,接下就是和SListView基本一样了,再实现下面几个和界面显示相关的方法即可:

 

初始化一个列表项,包含连接控件的事件。

STDMETHOD_(void, getView)(THIS_ HSTREEITEM hItem, IWindow* pItem, IXmlNode* pXmlTemplate) OVERRIDE

 

获取列表项的XML模板类型(当列表有多个模板时需要实现)

STDMETHOD_(int, getViewType)(HSTREEITEM hItem) const OVERRIDE

 

获取列表项的XML模板类型数量(当列表有多个模板时需要实现)

STDMETHOD_(int, getViewTypeCount)() const OVERRIDE

 

通过XML初始化adapter时调用,方便获取XML中配置的一些特定参数,一般不需要实现。

STDMETHOD_(void, InitByTemplate)(IXmlNode* pXmlTemplate) OVERRIDE

 

是否树节点占满一行,一般不需要实现。

STDMETHOD_(BOOL, isViewWidthMatchParent)() const OVERRIDE

具体使用方法参数SLivePlayer中的用法: https://github.com/soui4/soui4js

posted @ 2023-02-09 10:53  启程软件  阅读(278)  评论(0编辑  收藏  举报