使用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