diff --git a/bin/resources/themes/default/virtualbox/icon.png b/bin/resources/themes/default/virtualbox/icon.png
new file mode 100644
index 00000000..68bb6ab1
Binary files /dev/null and b/bin/resources/themes/default/virtualbox/icon.png differ
diff --git a/bin/resources/themes/default/virtualbox/item.xml b/bin/resources/themes/default/virtualbox/item.xml
new file mode 100644
index 00000000..66293a5b
--- /dev/null
+++ b/bin/resources/themes/default/virtualbox/item.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bin/resources/themes/default/virtualbox/main.xml b/bin/resources/themes/default/virtualbox/main.xml
new file mode 100644
index 00000000..92c4c28b
--- /dev/null
+++ b/bin/resources/themes/default/virtualbox/main.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/samples.sln b/samples/samples.sln
index 8eb580a3..b6b79bde 100644
--- a/samples/samples.sln
+++ b/samples/samples.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
-VisualStudioVersion = 12.0.40629.0
+VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tool_kits", "tool_kits", "{66F85B8F-11B7-4964-B51E-99DF85D8FE00}"
EndProject
@@ -31,6 +31,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libcef_dll_wrapper", "..\th
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "controls", "controls\controls.vcxproj", "{8BD95440-9000-4745-8011-27DD553EF06F}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VirtualBox", "VirtualBox\VirtualBox.vcxproj", "{E35589C6-9509-4116-996F-1D045C2DACAE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -127,6 +129,12 @@ Global
{8BD95440-9000-4745-8011-27DD553EF06F}.Release|Win32.Build.0 = Release|Win32
{8BD95440-9000-4745-8011-27DD553EF06F}.Release|x64.ActiveCfg = Release|x64
{8BD95440-9000-4745-8011-27DD553EF06F}.Release|x64.Build.0 = Release|x64
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Debug|Win32.Build.0 = Debug|Win32
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Debug|x64.ActiveCfg = Debug|Win32
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Release|Win32.ActiveCfg = Release|Win32
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Release|Win32.Build.0 = Release|Win32
+ {E35589C6-9509-4116-996F-1D045C2DACAE}.Release|x64.ActiveCfg = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -134,15 +142,16 @@ Global
GlobalSection(NestedProjects) = preSolution
{E106ACD7-4E53-4AEE-942B-D0DD426DB34E} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
{8D9A6595-717A-41C8-B468-0011A72BE3D1} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
+ {9951345F-620B-4BED-BB51-4F928CAFD454} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
+ {0149BA6E-3C0A-426D-AA0A-0B9EC7742F19} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
{F1A9371F-9A34-45A0-98EB-83FF371F067F} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
{2BFFA1EE-039D-479E-9BCC-2D12F8AEDD16} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
{B8588C07-9CE2-456C-83B1-86E4B65D4108} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
{878F5BF0-652A-4FDB-992B-BB7F26D62F0D} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
{FDB5539F-1060-4975-B603-B66454C8C897} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
- {9951345F-620B-4BED-BB51-4F928CAFD454} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
- {0149BA6E-3C0A-426D-AA0A-0B9EC7742F19} = {66F85B8F-11B7-4964-B51E-99DF85D8FE00}
- {A9D6DC71-C0DC-4549-AEA0-3B15B44E86A9} = {1DA0A8E2-5832-42FC-83F7-2CDCAD379C90}
{8BD95440-9000-4745-8011-27DD553EF06F} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
+ {E35589C6-9509-4116-996F-1D045C2DACAE} = {B2087994-3DF6-4A57-B8C6-6F744520D7FA}
+ {A9D6DC71-C0DC-4549-AEA0-3B15B44E86A9} = {1DA0A8E2-5832-42FC-83F7-2CDCAD379C90}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {68CA0970-4242-4E4F-94D2-C19760FCA05D}
diff --git a/samples/virtualbox/Resource.h b/samples/virtualbox/Resource.h
new file mode 100644
index 00000000..4ecc6730
--- /dev/null
+++ b/samples/virtualbox/Resource.h
@@ -0,0 +1,31 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by VirtualBox.rc
+//
+
+#define IDS_APP_TITLE 103
+
+#define IDR_MAINFRAME 128
+#define IDD_VIRTUALBOX_DIALOG 102
+#define IDD_ABOUTBOX 103
+#define IDM_ABOUT 104
+#define IDM_EXIT 105
+#define IDI_VIRTUALBOX 107
+#define IDI_SMALL 108
+#define IDC_VIRTUALBOX 109
+#define IDC_MYICON 2
+#ifndef IDC_STATIC
+#define IDC_STATIC -1
+#endif
+// ¶һĬֵ
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+
+#define _APS_NO_MFC 130
+#define _APS_NEXT_RESOURCE_VALUE 129
+#define _APS_NEXT_COMMAND_VALUE 32771
+#define _APS_NEXT_CONTROL_VALUE 1000
+#define _APS_NEXT_SYMED_VALUE 110
+#endif
+#endif
diff --git a/samples/virtualbox/VirtualTileBox.cpp b/samples/virtualbox/VirtualTileBox.cpp
new file mode 100644
index 00000000..0cff3da9
--- /dev/null
+++ b/samples/virtualbox/VirtualTileBox.cpp
@@ -0,0 +1,545 @@
+#include "stdafx.h"
+#include "VirtualTileBox.h"
+
+
+VirtualTileInterface::VirtualTileInterface()
+:m_CountChangedNotify()
+, m_DataChangedNotify()
+{
+
+}
+
+
+void VirtualTileInterface::RegNotifys(const DataChangedNotify& dcNotify, const CountChangedNotify& ccNotify)
+{
+ m_DataChangedNotify = dcNotify;
+ m_CountChangedNotify = ccNotify;
+}
+
+
+
+void VirtualTileInterface::EmitDataChanged(int nStartIndex, int nEndIndex)
+{
+ if (m_DataChangedNotify) m_DataChangedNotify(nStartIndex, nEndIndex);
+}
+
+void VirtualTileInterface::EmitCountChanged()
+{
+ if (m_CountChangedNotify) m_CountChangedNotify();
+}
+
+
+VirtualTileLayout::VirtualTileLayout()
+:m_bAutoCalcColumn(true)
+{
+ m_nColumns = -1;
+}
+
+ui::CSize VirtualTileLayout::ArrangeChild(const std::vector& items, ui::UiRect rc)
+{
+ ui::CSize sz(rc.GetWidth(), rc.GetHeight());
+
+ VirtualTileBox *pList = dynamic_cast(m_pOwner);
+ ASSERT(pList);
+
+
+ int nTotalHeight = GetElementsHeight(-1);
+ sz.cy = max(nTotalHeight, sz.cy);
+ LazyArrangeChild();
+ return sz;
+}
+
+ui::CSize VirtualTileLayout::AjustSizeByChild(const std::vector& items, ui::CSize szAvailable)
+{
+ VirtualTileBox *pList = dynamic_cast(m_pOwner);
+ ASSERT(pList);
+
+ ui::CSize size = m_pOwner->Control::EstimateSize(szAvailable);
+ if (size.cx == DUI_LENGTH_AUTO || size.cx == 0)
+ {
+ size.cx = m_szItem.cx * m_nColumns + m_iChildMargin * (m_nColumns - 1);
+ }
+ return size;
+}
+
+
+bool VirtualTileLayout::SetAttribute(const std::wstring& strName, const std::wstring& strValue)
+{
+ if (strName == L"column")
+ {
+ int iValue = _ttoi(strValue.c_str());
+ if (iValue > 0)
+ {
+ SetColumns(iValue);
+ m_bAutoCalcColumn = false;
+ }
+ else {
+ m_bAutoCalcColumn = true;
+ }
+ return true;
+ }
+ else {
+ return __super::SetAttribute(strName, strValue);
+ }
+}
+
+int VirtualTileLayout::GetElementsHeight(int nCount)
+{
+ if (nCount < m_nColumns && nCount != -1) return m_szItem.cy + m_iChildMargin;
+
+ VirtualTileBox *pList = dynamic_cast(m_pOwner);
+ ASSERT(pList);
+
+ if (nCount < 0)
+ nCount = pList->GetElementCount();
+
+ int rows = nCount / m_nColumns;
+ if (nCount % m_nColumns != 0) {
+ rows += 1;
+ }
+ if (nCount > 0) {
+ int childMarginTotal;
+ if (nCount % m_nColumns == 0) {
+ childMarginTotal = (nCount / m_nColumns - 1) * m_iChildMargin;
+ }
+ else {
+ childMarginTotal = (nCount / m_nColumns) * m_iChildMargin;
+ }
+
+ return m_szItem.cy * (rows+ 1) + childMarginTotal;
+ }
+ return 0;
+}
+
+void VirtualTileLayout::LazyArrangeChild()
+{
+ VirtualTileBox *pList = dynamic_cast(m_pOwner);
+
+ ASSERT(pList);
+
+ // SetPosʱѾú
+ ASSERT(m_nColumns);
+
+ // ȡVirtualTileBoxRect
+ ui::UiRect rc = pList->GetPaddingPos();
+
+ // ʼλ
+ int iPosLeft = rc.left;
+
+ // Ķʼλ
+ int iPosTop = rc.top + pList->GetScrollPos().cy;
+
+ ui::CPoint ptTile(iPosLeft, iPosTop);
+
+ // index
+ int nTopBottom = 0;
+ int nTopIndex = pList->GetTopElementIndex(nTopBottom);
+
+ int iCount = 0;
+
+ for (auto pControl : pList->m_items)
+ {
+ // Determine size
+ ui::UiRect rcTile(ptTile.x, ptTile.y, ptTile.x + m_szItem.cx, ptTile.y + m_szItem.cy);
+ pControl->SetPos(rcTile);
+
+ //
+ int nElementIndex = nTopIndex + iCount;
+ if (nElementIndex < pList->GetElementCount())
+ {
+ if (!pControl->IsVisible()) pControl->SetVisible(true);
+ pList->FillElement(pControl, nElementIndex);
+ }
+ else {
+ if (pControl->IsVisible()) pControl->SetVisible(false);
+ }
+
+ if ((++iCount % m_nColumns) == 0) {
+ ptTile.x = iPosLeft;
+ ptTile.y += m_szItem.cy + m_iChildMargin;
+ }
+ else {
+ ptTile.x += rcTile.GetWidth() + m_iChildMargin;
+ }
+ }
+}
+
+int VirtualTileLayout::AjustMaxItem()
+{
+ ui::UiRect rc = m_pOwner->GetPaddingPos();
+
+ if (m_bAutoCalcColumn)
+ {
+ if (m_szItem.cx > 0) m_nColumns = (rc.right - rc.left) / (m_szItem.cx + m_iChildMargin / 2);
+ if (m_nColumns == 0) m_nColumns = 1;
+ }
+
+ int nHeight = m_szItem.cy + m_iChildMargin;
+ int nRow = (rc.bottom - rc.top) / nHeight + 1;
+ return nRow * m_nColumns;
+}
+
+VirtualTileBox::VirtualTileBox(ui::Layout* pLayout /*= new VirtualTileLayout*/)
+: ui::ListBox(pLayout)
+, m_pDataProvider(nullptr)
+, m_nMaxItemCount(0)
+, m_nOldYScrollPos(0)
+, m_bArrangedOnce(false)
+, m_bForceArrange(false)
+{
+
+
+}
+
+void VirtualTileBox::SetDataProvider(VirtualTileInterface *pProvider)
+{
+ ASSERT(pProvider);
+ m_pDataProvider = pProvider;
+
+ // עģݱ䶯֪ͨص
+ pProvider->RegNotifys(
+ nbase::Bind(&VirtualTileBox::OnModelDataChanged, this, std::placeholders::_1, std::placeholders::_2),
+ nbase::Bind(&VirtualTileBox::OnModelCountChanged, this));
+}
+
+
+void VirtualTileBox::Refresh()
+{
+ m_nMaxItemCount = GetTileLayout()->AjustMaxItem();
+
+ int nElementCount = GetElementCount();
+ int nItemCount = GetCount();
+
+ // Ƴ
+ if (nItemCount > nElementCount)
+ {
+
+ int n = nItemCount - nElementCount;
+ for (int i = 0; i < n; i++)
+ this->RemoveAt(0);
+ }
+ // С
+ else if (nItemCount < nElementCount) {
+ int n = 0;
+ if (nElementCount <= m_nMaxItemCount)
+ {
+ n = nElementCount - nItemCount;
+ }
+ else {
+ n = m_nMaxItemCount - nItemCount;
+ }
+
+ for (int i = 0; i < n; i++) {
+ Control *pControl = CreateElement();
+ this->Add(pControl);
+ }
+ }
+
+ if (nElementCount <= 0)
+ return;
+
+ ReArrangeChild(true);
+ Arrange();
+
+}
+
+void VirtualTileBox::RemoveAll()
+{
+ __super::RemoveAll();
+
+ if (m_pVerticalScrollBar)
+ m_pVerticalScrollBar->SetScrollPos(0);
+
+ m_nOldYScrollPos = 0;
+ m_bArrangedOnce = false;
+ m_bForceArrange = false;
+}
+
+void VirtualTileBox::SetForceArrange(bool bForce)
+{
+ m_bForceArrange = bForce;
+}
+
+void VirtualTileBox::GetDisplayCollection(std::vector& collection)
+{
+ collection.clear();
+
+ if (GetCount() == 0)
+ return;
+
+ // ȡBoxRect
+ ui::UiRect rcThis = this->GetPaddingPos();
+
+ int nEleHeight = GetRealElementHeight();
+
+ int min = (GetScrollPos().cy / nEleHeight) * GetColumns();
+ int max = min + (rcThis.GetHeight() / nEleHeight) * GetColumns();
+
+ int nCount = GetElementCount();
+ if (max >= nCount)
+ max = nCount - 1;
+
+ for (auto i = min; i <= max; i++)
+ collection.push_back(i);
+}
+
+void VirtualTileBox::EnsureVisible(int iIndex, bool bToTop /*= false*/)
+{
+ if (iIndex < 0 || iIndex >= GetElementCount())
+ return;
+
+ if (!m_pVerticalScrollBar)
+ return;
+
+ auto nPos = GetScrollPos().cy;
+ int nTopIndex = (nPos / GetRealElementHeight()) * GetColumns();
+ int nNewPos = 0;
+
+ if (bToTop)
+ {
+ nNewPos = CalcElementsHeight(iIndex);
+ if (nNewPos >= m_pVerticalScrollBar->GetScrollRange())
+ return;
+ }
+ else {
+ if (IsElementDisplay(iIndex))
+ return;
+
+ if (iIndex > nTopIndex)
+ {
+ //
+ int height = CalcElementsHeight(iIndex + 1);
+ nNewPos = height - m_rcItem.GetHeight();
+ }
+ else {
+ //
+ nNewPos = CalcElementsHeight(iIndex);
+ }
+ }
+ ui::CSize sz(0, nNewPos);
+ SetScrollPos(sz);
+}
+
+void VirtualTileBox::SetScrollPos(ui::CSize szPos)
+{
+ m_nOldYScrollPos = GetScrollPos().cy;
+ ListBox::SetScrollPos(szPos);
+ ReArrangeChild(false);
+}
+
+void VirtualTileBox::HandleMessage(ui::EventArgs& event)
+{
+ if (!IsMouseEnabled() && event.Type > ui::kEventMouseBegin && event.Type < ui::kEventMouseEnd) {
+ if (m_pParent != nullptr)
+ m_pParent->HandleMessageTemplate(event);
+ else
+ ui::ScrollableBox::HandleMessage(event);
+ return;
+ }
+
+ switch (event.Type) {
+ case ui::kEventKeyDown: {
+ switch (event.chKey) {
+ case VK_UP: {
+ OnKeyDown(VK_UP);
+ return;
+ }
+ case VK_DOWN: {
+ OnKeyDown(VK_DOWN);
+ return;
+ }
+ case VK_HOME:
+ SetScrollPosY(0);
+ return;
+ case VK_END: {
+ int range = GetScrollPos().cy;
+ SetScrollPosY(range);
+ return;
+ }
+ }
+ }
+ case ui::kEventKeyUp: {
+ switch (event.chKey) {
+ case VK_UP: {
+ OnKeyUp(VK_UP);
+ return;
+ }
+ case VK_DOWN: {
+ OnKeyUp(VK_DOWN);
+ return;
+ }
+ }
+ }
+ }
+
+ __super::HandleMessage(event);
+}
+
+void VirtualTileBox::SetPos(ui::UiRect rc)
+{
+ bool bChange = false;
+ if (!m_rcItem.Equal(rc))
+ bChange = true;
+
+ ListBox::SetPos(rc);
+
+ if (bChange) {
+
+ Refresh();
+ }
+}
+
+void VirtualTileBox::ReArrangeChild(bool bForce)
+{
+ ScrollDirection direction = kScrollUp;
+ if (!bForce && !m_bForceArrange) {
+ if (!NeedReArrange(direction))
+ return;
+ }
+
+ LazyArrangeChild();
+}
+
+
+ui::Control* VirtualTileBox::CreateElement()
+{
+ if (m_pDataProvider)
+ return m_pDataProvider->CreateElement();
+
+ return nullptr;
+}
+
+void VirtualTileBox::FillElement(Control *pControl, int iIndex)
+{
+ if (m_pDataProvider)
+ m_pDataProvider->FillElement(pControl, iIndex);
+}
+
+int VirtualTileBox::GetElementCount()
+{
+ if (m_pDataProvider)
+ return m_pDataProvider->GetElementtCount();
+
+ return 0;
+}
+
+int VirtualTileBox::CalcElementsHeight(int nCount)
+{
+ return GetTileLayout()->GetElementsHeight(nCount);
+}
+
+int VirtualTileBox::GetTopElementIndex(int &bottom)
+{
+ int nPos = GetScrollPos().cy;
+
+ int nHeight = GetRealElementHeight();
+ int iIndex = (nPos / nHeight) * GetColumns();
+ bottom = iIndex * nHeight;
+
+ return iIndex;
+}
+
+bool VirtualTileBox::IsElementDisplay(int iIndex)
+{
+ if (iIndex < 0)
+ return false;
+
+ int nPos = GetScrollPos().cy;
+ int nElementPos = CalcElementsHeight(iIndex);
+ if (nElementPos >= nPos) {
+ int nHeight = this->GetHeight();
+ if (nElementPos + GetRealElementHeight() <= nPos + nHeight)
+ return true;
+ }
+
+ return false;
+}
+
+bool VirtualTileBox::NeedReArrange(ScrollDirection &direction)
+{
+ direction = kScrollUp;
+ if (!m_bArrangedOnce) {
+ m_bArrangedOnce = true;
+ return true;
+ }
+
+ int nCount = GetCount();
+ if (nCount <= 0)
+ return false;
+
+ if (GetElementCount() <= nCount)
+ return false;
+
+
+ ui::UiRect rcThis = this->GetPos();
+ if (rcThis.GetWidth() <= 0)
+ return false;
+
+ int nPos = GetScrollPos().cy;
+ ui::UiRect rcItem;
+
+ rcItem = m_items[0]->GetPos();
+
+ if (nPos >= m_nOldYScrollPos) {
+ //
+ rcItem = m_items[nCount - 1]->GetPos();
+ int nSub = (rcItem.bottom - rcThis.top) - (nPos + rcThis.GetHeight());
+ if (nSub < 0) {
+ direction = kScrollDown;
+ return true;
+ }
+ }
+ else {
+ //
+ rcItem = m_items[0]->GetPos();
+ if (nPos < (rcItem.top - rcThis.top)) {
+ direction = kScrollUp;
+ return true;
+ }
+ }
+
+ return false;
+
+}
+
+VirtualTileLayout* VirtualTileBox::GetTileLayout()
+{
+ auto* pLayout = dynamic_cast(m_pLayout.get());
+ return pLayout;
+}
+
+int VirtualTileBox::GetRealElementHeight()
+{
+ return GetTileLayout()->GetElementsHeight(1);
+}
+
+int VirtualTileBox::GetColumns()
+{
+ return GetTileLayout()->GetColumns();
+}
+
+void VirtualTileBox::LazyArrangeChild()
+{
+
+ GetTileLayout()->LazyArrangeChild();
+}
+
+void VirtualTileBox::OnModelDataChanged(int nStartIndex, int nEndIndex)
+{
+ for (auto i = nStartIndex; i <= nEndIndex; i++)
+ {
+ if (IsElementDisplay(i))
+ {
+ int nTopItemHeight = 0;
+ int nItemIndex = i - GetTopElementIndex(nTopItemHeight);
+ ASSERT(nItemIndex > 0);
+ FillElement(m_items[nItemIndex], i);
+ }
+ }
+}
+
+void VirtualTileBox::OnModelCountChanged()
+{
+ Refresh();
+}
+
diff --git a/samples/virtualbox/VirtualTileBox.h b/samples/virtualbox/VirtualTileBox.h
new file mode 100644
index 00000000..f2957993
--- /dev/null
+++ b/samples/virtualbox/VirtualTileBox.h
@@ -0,0 +1,203 @@
+#pragma once
+
+typedef function DataChangedNotify;
+typedef function CountChangedNotify;
+
+class UILIB_API VirtualTileInterface
+{
+public:
+ VirtualTileInterface();
+ /**
+ * @brief һ
+ * @return شָ
+ */
+ virtual ui::Control* CreateElement() = 0;
+
+ /**
+ * @brief ָ
+ * @param[in] control ؼָ
+ * @param[in] index
+ * @return شָ
+ */
+ virtual void FillElement(ui::Control *control, int index) = 0;
+
+ /**
+ * @brief ȡ
+ * @return
+ */
+ virtual int GetElementtCount() = 0;
+
+ void RegNotifys(const DataChangedNotify& dcNotify, const CountChangedNotify& ccNotify);
+
+protected:
+ void EmitDataChanged(int nStartIndex, int nEndIndex);
+ void EmitCountChanged();
+
+private:
+ DataChangedNotify m_DataChangedNotify;
+ CountChangedNotify m_CountChangedNotify;
+};
+
+class UILIB_API VirtualTileLayout : public ui::TileLayout
+{
+public:
+ VirtualTileLayout();
+ virtual ui::CSize ArrangeChild(const std::vector& items, ui::UiRect rc) override;
+ virtual ui::CSize AjustSizeByChild(const std::vector& items, ui::CSize szAvailable) override;
+ virtual bool SetAttribute(const std::wstring& strName, const std::wstring& strValue) override;
+ virtual int GetElementsHeight(int nCount);
+ virtual void LazyArrangeChild();
+ virtual int AjustMaxItem();
+
+private:
+ bool m_bAutoCalcColumn;
+};
+
+class UILIB_API VirtualTileBox : public ui::ListBox
+{
+ friend class VirtualTileLayout;
+public:
+ VirtualTileBox(ui::Layout* pLayout = new VirtualTileLayout);
+
+ /**
+ * @brief ݴ
+ * @param[in] pProvider Ҫд VirtualListInterface ĽӿΪݴ
+ * @return
+ */
+ virtual void SetDataProvider(VirtualTileInterface *pProvider);
+
+ /**
+ * @brief ˢб
+ * @return
+ */
+ virtual void Refresh();
+
+ /**
+ * @brief ɾ
+ * @return
+ */
+ virtual void RemoveAll() override;
+
+ /**
+ * @brief Ƿǿ²
+ * @param[in] bForce Ϊ true ΪǿƣΪǿ
+ * @return
+ */
+ void SetForceArrange(bool bForce);
+
+ /**
+ * @brief ȡǰпɼؼ
+ * @param[out] collection б
+ * @return
+ */
+ void GetDisplayCollection(std::vector& collection);
+
+ /**
+ * @brief ÿؼڿɼΧ
+ * @param[in] iIndex ؼ
+ * @param[in] bToTop ǷϷ
+ * @return
+ */
+ void EnsureVisible(int iIndex, bool bToTop = false);
+
+protected:
+ /// дӿڣṩԻ
+ virtual void SetScrollPos(ui::CSize szPos) override;
+ virtual void HandleMessage(ui::EventArgs& event) override;
+ virtual void SetPos(ui::UiRect rc) override;
+
+ /**
+ * @brief ²
+ * @param[in] bForce Ƿǿ²
+ * @return
+ */
+ void ReArrangeChild(bool bForce);
+
+ /**
+ * @brief ̰֪ͨ
+ * @param[in] ch
+ * @return
+ */
+ virtual void OnKeyDown(TCHAR ch) {}
+
+ /**
+ * @brief ̵֪ͨ
+ * @param[in] ch
+ * @return
+ */
+ virtual void OnKeyUp(TCHAR ch) {}
+
+private:
+ enum ScrollDirection
+ {
+ kScrollUp = -1,
+ kScrollDown = 1
+ };
+
+ /**
+ * @brief һ
+ * @return شָ
+ */
+ Control* CreateElement();
+
+ /**
+ * @brief ָ
+ * @param[in] control ؼָ
+ * @param[in] index
+ * @return شָ
+ */
+ void FillElement(Control *pControl, int iIndex);
+
+ /**
+ * @brief ȡԪ
+ * @return Ԫָ
+ */
+ int GetElementCount();
+
+ /**
+ * @brief õnԪضӦĸ߶Ⱥͣ
+ * @param[in] nCount ҪõԪصĸ߶ȣ-1ʾȫԪ
+ * @return ָԪصĸ߶Ⱥ
+ */
+ int CalcElementsHeight(int nCount);
+
+ /**
+ * @brief õɼΧڵһԪصǰһԪ
+ * @param[out] bottom һԪص bottom ֵ
+ * @return һԪص
+ */
+ int GetTopElementIndex(int &bottom);
+
+ /**
+ * @brief жijԪǷڿɼΧ
+ * @param[in] iIndex Ԫ
+ * @return true ʾɼΪɼ
+ */
+ bool IsElementDisplay(int iIndex);
+
+ /**
+ * @brief жǷҪ²
+ * @param[out] direction Ϲ¹
+ * @return true ΪҪ²֣Ϊ false
+ */
+ bool NeedReArrange(ScrollDirection &direction);
+
+ VirtualTileLayout* GetTileLayout();
+
+ int GetRealElementHeight();
+
+ int GetColumns();
+
+ void LazyArrangeChild();
+
+ void OnModelDataChanged(int nStartIndex, int nEndIndex);
+
+ void OnModelCountChanged();
+
+private:
+ VirtualTileInterface *m_pDataProvider;
+ int m_nMaxItemCount; // бʵؼ
+ int m_nOldYScrollPos;
+ bool m_bArrangedOnce;
+ bool m_bForceArrange; // ǿƲֱ
+};
diff --git a/samples/virtualbox/item.cpp b/samples/virtualbox/item.cpp
new file mode 100644
index 00000000..031be6db
--- /dev/null
+++ b/samples/virtualbox/item.cpp
@@ -0,0 +1,47 @@
+#include "stdafx.h"
+#include "item.h"
+
+
+Item::Item()
+:control_img_(nullptr)
+{
+}
+
+
+Item::~Item()
+{
+}
+
+void Item::InitSubControls(const std::wstring& img, const std::wstring& title)
+{
+ // Item µĿؼ
+ if (control_img_ == nullptr)
+ {
+ control_img_ = dynamic_cast(FindSubControl(L"control_img"));
+ label_title_ = dynamic_cast(FindSubControl(L"label_title"));
+ progress_ = dynamic_cast(FindSubControl(L"progress"));
+ btn_del_ = dynamic_cast(FindSubControl(L"btn_del"));
+ // ģ
+ auto timestamp = shared::tools::GenerateTimeStamp();
+ int64_t timestamp_num = 0;
+ nbase::StringToInt64(timestamp, ×tamp_num);
+ t_time = timestamp_num;
+ progress_->SetValue((double)(timestamp_num % 100));
+ // ͼ
+ control_img_->SetBkImage(img);
+ // ɾ
+ btn_del_->AttachClick(nbase::Bind(&Item::OnRemove, this, std::placeholders::_1));
+ }
+
+
+ label_title_->SetText(nbase::StringPrintf(L"%s %d%%", title.c_str(), t_time % 100));
+
+
+}
+
+
+bool Item::OnRemove(ui::EventArgs* args)
+{
+ // ɾʱֻɾݾͿˣҪɾϵԪ
+ return true;
+}
diff --git a/samples/virtualbox/item.h b/samples/virtualbox/item.h
new file mode 100644
index 00000000..3ef96875
--- /dev/null
+++ b/samples/virtualbox/item.h
@@ -0,0 +1,24 @@
+#pragma once
+
+// ui::ListContainerElement м̳пù
+class Item : public ui::ListContainerElement
+{
+public:
+ Item();
+ ~Item();
+
+ // ṩⲿʼ item
+ void InitSubControls(const std::wstring& img, const std::wstring& title);
+private:
+ bool OnRemove(ui::EventArgs* args);
+
+private:
+ ui::ListBox* list_box_;
+
+ ui::Control* control_img_;
+ ui::Label* label_title_;
+ ui::Progress* progress_;
+ ui::Button* btn_del_;
+ int64_t t_time;
+};
+
diff --git a/samples/virtualbox/main.cpp b/samples/virtualbox/main.cpp
new file mode 100644
index 00000000..1a932e86
--- /dev/null
+++ b/samples/virtualbox/main.cpp
@@ -0,0 +1,69 @@
+// VirtualBox.cpp : Ӧóڵ㡣
+//
+
+#include "stdafx.h"
+#include "main.h"
+#include "main_form.h"
+#include "VirtualTileBox.h"
+
+ui::Control* MyCreateControlCallback(const std::wstring& sName)
+{
+ if (sName == L"VirtualTileBox")
+ {
+ return new VirtualTileBox();
+ }
+ return nullptr;
+}
+
+int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
+ _In_opt_ HINSTANCE hPrevInstance,
+ _In_ LPWSTR lpCmdLine,
+ _In_ int nCmdShow)
+{
+ UNREFERENCED_PARAMETER(hPrevInstance);
+ UNREFERENCED_PARAMETER(lpCmdLine);
+
+ // ߳
+ MainThread thread;
+
+ // ִ߳ѭ
+ thread.RunOnCurrentThreadWithLoop(nbase::MessageLoop::kUIMessageLoop);
+
+ return 0;
+}
+
+void MainThread::Init()
+{
+ nbase::ThreadManager::RegisterThread(kThreadUI);
+
+ // ȡԴ·ʼȫֲ
+ std::wstring theme_dir = QPath::GetAppPath();
+#ifdef _DEBUG
+ // Debug ģʽʹñļΪԴ
+ // ĬƤʹ resources\\themes\\default
+ // Ĭʹ resources\\lang\\zh_CN
+ // ָ Startup
+ ui::GlobalManager::Startup(theme_dir + L"resources\\", MyCreateControlCallback, false);
+#else
+ // Release ģʽʹԴеѹΪԴ
+ // Դ뵽ԴбΪ THEMEԴΪ IDR_THEME
+ // ԴʹõDZص zip ļԴе zip ѹ
+ // ʹ OpenResZip һغصԴѹ
+ ui::GlobalManager::OpenResZip(MAKEINTRESOURCE(IDR_THEME), L"THEME", "");
+ // ui::GlobalManager::OpenResZip(L"resources.zip", "");
+ ui::GlobalManager::Startup(L"resources\\", ui::CreateControlCallback(), false);
+#endif
+
+ // һĬϴӰľд
+ MainForm* window = new MainForm();
+ window->Create(NULL, MainForm::kClassName.c_str(), WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, 0);
+ window->CenterWindow();
+ window->ShowWindow();
+}
+
+void MainThread::Cleanup()
+{
+ ui::GlobalManager::Shutdown();
+ SetThreadWasQuitProperly(true);
+ nbase::ThreadManager::UnregisterThread();
+}
diff --git a/samples/virtualbox/main.h b/samples/virtualbox/main.h
new file mode 100644
index 00000000..f2e925d1
--- /dev/null
+++ b/samples/virtualbox/main.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "resource.h"
+
+class MainThread : public nbase::FrameworkThread
+{
+public:
+ MainThread() : nbase::FrameworkThread("MainThread") {}
+ virtual ~MainThread() {}
+
+private:
+ /**
+ * 麯ʼ߳
+ * @return void ֵ
+ */
+ virtual void Init() override;
+
+ /**
+ * 麯߳˳ʱһЩ
+ * @return void ֵ
+ */
+ virtual void Cleanup() override;
+};
diff --git a/samples/virtualbox/main_form.cpp b/samples/virtualbox/main_form.cpp
new file mode 100644
index 00000000..ded6a87f
--- /dev/null
+++ b/samples/virtualbox/main_form.cpp
@@ -0,0 +1,105 @@
+#include "stdafx.h"
+#include "main_form.h"
+#include "provider.h"
+
+const std::wstring MainForm::kClassName = L"VirtualBoxDemo";
+
+
+MainForm::MainForm()
+= default;
+
+
+MainForm::~MainForm()
+= default;
+
+std::wstring MainForm::GetSkinFolder()
+{
+ return L"virtualbox";
+}
+
+std::wstring MainForm::GetSkinFile()
+{
+ return L"main.xml";
+}
+
+std::wstring MainForm::GetWindowClassName() const
+{
+ return kClassName;
+}
+
+void MainForm::InitWindow()
+{
+
+ m_EditColumn = dynamic_cast(FindControl(L"edit_column"));
+ m_CheckBoxItemCenter = dynamic_cast(FindControl(L"checkbox_item_center"));
+ m_EditTotal = dynamic_cast(FindControl(L"edit_total"));
+ m_pTileList = dynamic_cast(FindControl(L"list"));
+ m_OptionColumnFix = dynamic_cast(FindControl(L"option_column_fix"));
+ m_EditUpdate = dynamic_cast(FindControl(L"edit_update"));
+ m_EditTaskName = dynamic_cast(FindControl(L"edit_task_name"));
+ m_EditDelete = dynamic_cast(FindControl(L"edit_delete"));
+ m_EditChildMargin = dynamic_cast(FindControl(L"edit_child_margin"));
+
+ m_pRoot->AttachBubbledEvent(ui::kEventClick, nbase::Bind(&MainForm::OnClicked, this, std::placeholders::_1));
+
+ // ṩ
+ m_DataProvider = new Provider;
+ m_pTileList->SetDataProvider(m_DataProvider);
+
+
+
+}
+
+LRESULT MainForm::OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
+{
+ PostQuitMessage(0L);
+ return __super::OnClose(uMsg, wParam, lParam, bHandled);
+}
+
+
+bool MainForm::OnClicked(ui::EventArgs* args)
+{
+ auto sName = args->pSender->GetName();
+ if (sName == L"btn_set_total")
+ {
+ if (!m_EditChildMargin->GetText().empty())
+ {
+ m_pTileList->SetAttribute(L"childmargin", m_EditChildMargin->GetText());
+ }
+ if (m_OptionColumnFix->IsSelected())
+ {
+ m_pTileList->SetAttribute(L"column", m_EditColumn->GetText());
+ if (m_CheckBoxItemCenter->IsSelected())
+ {
+ m_pTileList->SetAttribute(L"width", L"auto");
+ m_pTileList->SetAttribute(L"halign", L"center");
+ }
+ else {
+ m_pTileList->SetAttribute(L"width", L"stretch");
+
+ }
+ }
+ else {
+ m_pTileList->SetAttribute(L"width", L"stretch");
+ m_pTileList->SetAttribute(L"column", L"-1");
+
+ }
+
+
+ int nTotal = _ttoi(m_EditTotal->GetText().c_str());
+ if (nTotal > 0) {
+ m_DataProvider->SetTotal(nTotal);
+ }
+ }
+ else if (sName == L"btn_update")
+ {
+ m_DataProvider->ChangeTaskName(_ttoi(m_EditUpdate->GetText().c_str())-1,
+ m_EditTaskName->GetText());
+ }
+ else if (sName == L"btn_delete")
+ {
+ m_DataProvider->RemoveTask(_ttoi(m_EditDelete->GetText().c_str()) - 1);
+ }
+
+ return true;
+}
diff --git a/samples/virtualbox/main_form.h b/samples/virtualbox/main_form.h
new file mode 100644
index 00000000..40eabec0
--- /dev/null
+++ b/samples/virtualbox/main_form.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "VirtualTileBox.h"
+#include "provider.h"
+
+class MainForm : public ui::WindowImplBase
+{
+public:
+ MainForm();
+ ~MainForm();
+
+ /**
+ * һӿDZҪдĽӿڣӿ
+ * GetSkinFolder ӿҪƵĴƤԴ·
+ * GetSkinFile ӿҪƵĴڵ xml ļ
+ * GetWindowClassName ӿôΨһ
+ */
+ virtual std::wstring GetSkinFolder() override;
+ virtual std::wstring GetSkinFile() override;
+ virtual std::wstring GetWindowClassName() const override;
+
+ /**
+ * յ WM_CREATE ϢʱúᱻãͨһЩؼʼIJ
+ */
+ virtual void InitWindow() override;
+
+ /**
+ * յ WM_CLOSE Ϣʱúᱻ
+ */
+ virtual LRESULT OnClose(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
+
+ static const std::wstring kClassName;
+
+private:
+ bool OnClicked(ui::EventArgs* args);
+
+private:
+ VirtualTileBox* m_pTileList;
+ Provider* m_DataProvider;
+
+private:
+ ui::RichEdit* m_EditColumn;
+ ui::CheckBox* m_CheckBoxItemCenter;
+ ui::RichEdit* m_EditTotal;
+ ui::Option* m_OptionColumnFix;
+ ui::RichEdit* m_EditUpdate;
+ ui::RichEdit* m_EditTaskName;
+ ui::RichEdit* m_EditDelete;
+ ui::RichEdit* m_EditChildMargin;
+};
diff --git a/samples/virtualbox/provider.cpp b/samples/virtualbox/provider.cpp
new file mode 100644
index 00000000..fde0cc18
--- /dev/null
+++ b/samples/virtualbox/provider.cpp
@@ -0,0 +1,89 @@
+#include "stdafx.h"
+#include "provider.h"
+#include "item.h"
+
+
+int g_index = 1;
+
+Provider::Provider()
+:m_nTotal(0)
+{
+
+}
+
+
+Provider::~Provider()
+= default;
+
+ui::Control* Provider::CreateElement()
+{
+ Item* item = new Item;
+ ui::GlobalManager::FillBoxWithCache(item, L"virtualbox/item.xml");
+ return item;
+}
+
+void Provider::FillElement(ui::Control *control, int index)
+{
+ Item* pItem = dynamic_cast- (control);
+
+
+ std::wstring img = L"icon.png";
+ std::wstring title = nbase::StringPrintf(L"%s [%02d]", m_vTasks[index].sName.c_str(), m_vTasks[index].nId);
+ pItem->InitSubControls(img, title);
+
+}
+
+int Provider::GetElementtCount()
+{
+ //
+ nbase::NAutoLock auto_lock(&lock_);
+ return m_vTasks.size();
+}
+
+void Provider::SetTotal(int nTotal)
+{
+ if (nTotal == m_nTotal) return;
+ if (nTotal <= 0) return;
+
+ //
+ lock_.Lock();
+ m_vTasks.clear();
+ for (auto i=1; i <= nTotal; i++)
+ {
+ DownloadTask task;
+ task.nId = i;
+ task.sName = L"";
+ m_vTasks.emplace_back(task);
+ }
+ lock_.Unlock();
+
+ // ֪ͨTileBox䶯
+ EmitCountChanged();
+}
+
+void Provider::RemoveTask(int nIndex)
+{
+ //
+ nbase::NAutoLock auto_lock(&lock_);
+
+ lock_.Lock();
+
+ auto iter = m_vTasks.begin() + nIndex;
+ m_vTasks.erase(iter);
+
+ lock_.Unlock();
+
+ // ֪ͨTileBox䶯
+ EmitCountChanged();
+}
+
+void Provider::ChangeTaskName(int nIndex, const wstring& sName)
+{
+ if (nIndex > 0 && nIndex < m_vTasks.size())
+ {
+ m_vTasks[nIndex].sName = sName;
+ }
+
+ // ݱ䶯֪ͨ
+ EmitDataChanged(nIndex, nIndex);
+}
diff --git a/samples/virtualbox/provider.h b/samples/virtualbox/provider.h
new file mode 100644
index 00000000..5fdb8b60
--- /dev/null
+++ b/samples/virtualbox/provider.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "VirtualTileBox.h"
+#include