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

使用SOUI4中的STreeView控件的更多相关文章

  1. 在DevExpress程序中使用SplashScreenManager控件实现启动闪屏和等待信息窗口

    在我很早的WInform随笔<WinForm界面开发之"SplashScreen控件">有介绍如何使用闪屏的处理操作,不过那种是普通WInform和DevExpress ...

  2. 在WPF中使用WinForm控件方法

    1.      首先添加对如下两个dll文件的引用:WindowsFormsIntegration.dll,System.Windows.Forms.dll. 2.      在要使用WinForm控 ...

  3. wpf telerik中的book控件

    下载 telerik中的书本控件,仅供学习使用.

  4. [原创]在Framelayout中放置button控件出现的覆盖问题

    android Framelayout(帧布局)是很常用的布局,主要用来处理需要多个view叠加显示的情况. 然而在使用中,我发现Framelayout中的Button控件,会挡住所有其他控件,而不论 ...

  5. (转)客户端触发Asp.net中服务端控件事件

    第一章. Asp.net中服务端控件事件是如何触发的 Asp.net 中在客户端触发服务端事件分为两种情况: 一. WebControls中的Button 和HtmlControls中的Type为su ...

  6. 在web中使用windows控件,实现摄像头功能

    最近做的一个Web版的视频会议项目,需要在网页中播放来自远程摄像头采集的实时视频,我们已经有了播放远程实时视频的使用C#编写的windows控件,如何将其嵌入到网页中去了?这需要使用一种古老的技术,A ...

  7. WPF中的image控件的Source赋值

    WPF中的Image控件Source的设置 1.XAML中 简单的方式(Source="haha.png"); image控件的Source设置为相对路径后(Source=&quo ...

  8. 在Web中使用Windows控件

    版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[+] 将Net控件转化为ActiveX控件 1GUID 2实现IObjectSafety接口 3程序集设定 制作安装程序 Web集 ...

  9. asp.net中的ListBox控件添加双击事件

    问题:在Aspx页里的ListBox A中添加双击事件,将选中项添加到另一个ListBox B中,双击ListBox B中的选中项,删除当前选中项 页面: <asp:ListBox ID=&qu ...

  10. C#中的BackgroundWorker控件+Delegate.Invoke (委托同步调用)

    C#中的BackgroundWorker控件+Delegate.Invoke (委托同步调用) 简单代码,记录一下.一个BackgroundWorker控件  backgroundWorkerRefr ...

随机推荐

  1. WiFi流量劫持—— 浏览任意页面即可中毒!

    大家都知道公共场所的Wifi安全性很差,但并不清楚究竟有多差.大多以为只要不上QQ.不登陆网站账号就没事了,看看新闻小说什么的应该毫无关系. 的确如此,看看新闻网页没有涉及任何敏感的账号信息.即便是数 ...

  2. docker之网络与数据管理

    docker默认使用bridge(单主机互联)和overlay(可跨主机互联)两种网络驱动来进行容器的网络管理.如需要,还可以自定义网络驱动插件进行docker容器的网络管理. 1.docker默认网 ...

  3. 2023 CCPC 深圳

    2023 CCPC 深圳 D. Bot Brothers 有一棵 \(n\) 个点的树,\(m\) 个叶子,编号为 \(1∼m\).两人在树上博弈,均从根出发,轮流行动,每次走向一个当前所在节点的子节 ...

  4. Filter内存马

    概述 最近感冒了,不想BB太多,直接开始调试吧,先写个Filter来调试 Filter代码 新建一个FilterShell类,代码如下: 一个类如果想要成为Filter则需要继承Filter,然后重写 ...

  5. Winform多线程持续读取PLC数据

    1.Winform窗体界面 2.后台代码 点击查看代码 using Modbus.Device; using System; using System.Collections.Generic; usi ...

  6. 前端每日一知之web攻击方式

    脑图在线链接 本文内容依据[js每日一题]公众号精彩文章总结而来

  7. openEuler欧拉安装Jenkins并修改构建workspace路径

    ​一.系统优化 关闭防火墙 systemctl stop firewalld systemctl disable firewalld 关闭selinux sed -ri 's/SELINUX=enfo ...

  8. Elm 和 Jetpack Compose 殊途同归及 MVVM 缺点分析

    Html.lazy · An Introduction to Elm 可能搞 vdom 的都会到 lazy renderer 这一步,react 可能也可以这么搞或者已经这么搞了我不知道,Elm 提到 ...

  9. 迁移现有用户数据到ABP vNext

    前言 使用 ABP vNext(下文简称 ABP)时,通常都是从 cli 开始新建模板,从一个空项目开始.对已经存续的项目来说,现有的数据,特别是用户等核心数据需要进行迁移. 老的项目,随着规模越来越 ...

  10. Redis应用—8.相关的缓存框架

    大纲 1.Ehcache缓存框架 (1)Ehcache的核心对象 (2)单独使用Ehcache (3)Spring整合Ehcache (4)Spring Boot整合Ehcache (5)实际工作中如 ...