Skip to content

多列列表视图控件 (SMCListView)

Warning

The current page still doesn't have a translation for this language.

You can read it through google translate.

SOUI中的多列列表视图控件,类似于 Windows 的列表控件,支持多列数据显示,具有表头、排序、列宽调整等功能。基于虚拟化技术实现,适用于大量数据的表格形式展示。

基本信息

  • 类名SMCListView
  • 控件标签mclistview
  • 基类SListView
  • 功能:提供多列数据表格形式展示

属性说明

基本属性

属性名 类型 默认值 说明
headerHeight int - 表头高度
gridLines bool 1 是否显示网格线

使用示例

基础多列列表视图

<!-- 基础多列列表视图 -->
<mclistview pos="10,10,-10,-10" name="mclist_basic"
            headerHeight="30">
    <header align="center" itemSwapEnable="1" 
            fixWidth="0" sortHeader="1">
        <items>
            <item width="100">名称</item>
            <item width="80">大小</item>
            <item width="150">修改日期</item>
            <item width="100">类型</item>
        </items>
    </header>
</mclistview>

自定义样式多列列表视图

<!-- 自定义样式多列列表视图 -->
<mclistview pos="10,10,-10,-10" name="mclist_custom"
            headerHeight="40" colorBkgnd="#FFFFFF">
    <header align="left" sortSkin="skin_sort"
            colorText="#333333" colorBkgnd="#F5F5F5">
        <items>
            <item width="200">文件名</item>
            <item width="100">大小</item>
            <item width="150">类型</item>
            <item width="200">路径</item>
        </items>
    </header>
</mclistview>

带复选框的多列列表视图

<!-- 带复选框的多列列表视图 -->
      <mclistview name="mclv_test" colorBkgnd="@color/white" pos="10,10,-10,-10" headerHeight="30" colorGrid="@color/red">
        <headerStyle wndclass="header" align="center" name="mclv_test_header" sortSkin="skin_lcex_header_arrow" itemSkin="skin_lcex_header" itemSwapEnable="1" fixWidth="0" font="underline:0,adding:-3" sortHeader="1" colorBkgnd="#ffffff">
          <items>
            <item weight="480" align="left">@string/mccol_1</item>
            <item weight="95" >@string/mccol_2</item>
            <item weight="100" >@string/mccol_3</item>
            <item weight="100" align="right">@string/mccol_4</item>
            <item weight="100">@string/mccol_5</item>
            <item weight="100">@string/mccol_6</item>
          </items>
          <check name="chk_mclv_sel" text="全选" />
        </headerStyle>
        <template itemHeight="80" colorHover="#cccccc" colorSelected="#0000ff">
          <window name="col1" clipClient="1">
            <img name="img_icon" skin="skin_icon6" pos="10,8,@64,@64" />
            <text name="txt_name" pos="[5,16" font="bold:1,adding:-1">火狐浏览器</text>
            <text name="txt_desc" pos="{0,36,-10,-10" font="bold:1,adding:-4" dotted="1">速度最快的浏览器</text>
            <text name="txt_index" pos="|0,|0" offset="-0.5,-0.5" font="adding:10" colorText="#ff000088">10</text>
          </window>
          <window name="col2" clipClient="1">
            <ratingbar name="rating_score" starSkin="skin_star1" starNum="5" value="3.5" pos="10,16" />
            <text name="txt_score" pos="10,36" font="adding:-5">8.5分</text>
            <link pos="[5,36" cursor="hand" colorText="#1e78d5" href="www.163.com" font="adding:-5">投票</link>
          </window>
          <window name="col3" clipClient="1">
            <text name="txt_size" pos="0,26,-0,-26" font="adding:-4" align="center">85.92M</text>
          </window>
          <window name="col4" clipClient="1">
            <text name="txt_installtime" pos="0,26,-0,-26" font="adding:-4" align="center">2015-01-09</text>
          </window>
          <window name="col5" clipClient="1">
            <text name="txt_usetime" pos="0,26,-0,-26" font="adding:-4" align="center">今天</text>
            <animateimg pos="|0,|0" offset="-0.5,-0.5" skin="skin_busy" name="ani_test" tip="animateimg is used here" msgTransparent="0" />
          </window>
          <window name="col6" clipClient="1">
            <imgbtn animate="1" pos="|-35,|-14" font="adding:-3" align="center" skin="skin_install" name="btn_uninstall">卸载</imgbtn>
          </window>
        </template>
      </mclistview>

事件处理

多列列表视图控件支持以下事件:

事件名 EventID 说明
EVT_LV_SELCHANGING EventLVSelChanging::EventID 选择改变前事件
EVT_LV_SELCHANGED EventLVSelChanged::EventID 选择改变事件
EVT_LV_ITEMCLICK EventLVItemClick::EventID 项目双击事件
// 事件处理示例
EVENT_MAP_BEGIN()
    EVENT_NAME_HANDLER(L"mclist_basic", EventLVSelChanged::EventID, OnMCLVSelChanged)
EVENT_MAP_END()

void OnMCLVSelChanged(IEvtArgs *pEvt)
{
    EventLVSelChanged *pRealEvt = sobj_cast<EventLVSelChanged>(pEvt);
    int nOldSel = pRealEvt->nOldSel;
    int nNewSel = pRealEvt->nNewSel;
    // 处理选择改变事件
}

代码操作

// 查找多列列表视图控件
SMCListView *pMCListView = FindChildByName2<SMCListView>(L"mclist_basic");

// 设置适配器
pMCListView->SetAdapter(pAdapter);

// 获取当前选中项
int nCurSel = pMCListView->GetSel();

// 设置选中项
pMCListView->SetSel(0);

// 获取项目数量
int nCount = pMCListView->GetCount();

// 获取列数
int nColCount = pMCListView->GetHeaderCtrl()->GetItemCount();

// 获取选中项数量(多选模式下)
int nSelCount = pMCListView->GetSelCount();

// 获取所有选中项索引(多选模式下)
SArray<int> selItems;
pMCListView->GetSelItems(selItems);

实现自定义适配器

#include <helper/SAdapterBase.h>

// 文件项数据结构
struct FileItemData
{
    SStringT strName;      // 文件名
    SStringT strPath;      // 完整路径
    SStringT strType;      // 文件类型
    ULONGLONG nSize;       // 文件大小
    FILETIME tModified;    // 修改时间
    BOOL bIsFolder;        // 是否为文件夹
};

// 高级多列列表适配器
class CFileListAdapter : public SMcAdapterBase
{
protected:
    SArray<FileItemData> m_items;
    int m_nSortCol;
    BOOL m_bAscending;

public:
    CFileListAdapter() : 
        m_nSortCol(-1), m_bAscending(TRUE)
    {
    }

    // 获取项目数量,注意 WINAPI 调用约定
    virtual int WINAPI getCount()
    {
        return m_items.GetCount();
    }

    SStringW WINAPI GetColumnName(int iCol) const {
        return SStringW().Format(L"col%d", iCol + 1);
    }

    // 执行排序,注意 WINAPI 调用约定
    virtual void WINAPI Sort(int columnId, BOOL ascending)
    {
        m_nSortCol = columnId;
        m_bAscending = ascending;

        // 使用快速排序
        qsort_s(m_items.GetData(), m_items.GetCount(),
                sizeof(FileItemData), 
                CompareFunc, this);

        notifyDataSetChanged();
    }

    // 排序比较函数
    static int __cdecl CompareFunc(void *context,
                                  const void *elem1,
                                  const void *elem2)
    {
        CFileListAdapter *pThis = (CFileListAdapter*)context;
        const FileItemData *p1 = (const FileItemData*)elem1;
        const FileItemData *p2 = (const FileItemData*)elem2;

        int nResult = 0;

        switch(pThis->m_nSortCol)
        {
        case 0: // 按名称排序
            nResult = p1->strName.CompareNoCase(p2->strName);
            break;
        case 1: // 按大小排序
            if(p1->bIsFolder && !p2->bIsFolder)
                nResult = -1;
            else if(!p1->bIsFolder && p2->bIsFolder)
                nResult = 1;
            else
                nResult = (p1->nSize > p2->nSize) ? 1 : 
                         (p1->nSize < p2->nSize) ? -1 : 0;
            break;
        case 2: // 按修改时间排序
            nResult = CompareFileTime(&p1->tModified, &p2->tModified);
            break;
        case 3: // 按类型排序
            nResult = p1->strType.CompareNoCase(p2->strType);
            break;
        }

        return pThis->m_bAscending ? nResult : -nResult;
    }

    // 获取视图,注意 WINAPI 调用约定
    virtual void WINAPI getView(int position, SItemPanel* pItem, SXmlNode xmlTemplate)
    {
        if(pItem->GetChildrenCount() == 0)
        {
            pItem->InitFromXml(&xmlTemplate);
        }

        if(position >= 0 && position < m_items.GetCount())
        {
            const FileItemData &item = m_items[position];

            // 设置图标
            SWindow* pIcon = pItem->FindChildByName(L"img_icon");
            if(pIcon)
            {
                pIcon->SetAttribute(L"skin", 
                    item.bIsFolder ? L"skin_folder" : L"skin_file");
            }

            // 设置名称
            SWindow* pName = pItem->FindChildByName(L"txt_name");
            if(pName)
            {
                pName->SetWindowText(item.strName);
            }

            // 设置大小
            SWindow* pSize = pItem->FindChildByName(L"txt_size");
            if(pSize)
            {
                pSize->SetWindowText(FormatFileSize(item.nSize));
            }

            // 设置修改时间
            SWindow* pDate = pItem->FindChildByName(L"txt_date");
            if(pDate)
            {
                SYSTEMTIME st;
                FileTimeToSystemTime(&item.tModified, &st);
                SStringT strDate;
                strDate.Format(L"%04d-%02d-%02d %02d:%02d",
                    st.wYear, st.wMonth, st.wDay,
                    st.wHour, st.wMinute);
                pDate->SetWindowText(strDate);
            }
        }
    }

    // 添加文件项
    void AddFile(const FileItemData &item)
    {
        m_items.Add(item);

        // 如果已经排序,则重新排序
        if(m_nSortCol >= 0)
        {
            Sort(m_nSortCol, m_bAscending);
        }
        else
        {
            notifyDataSetChanged();
        }
    }

    // 移除文件项
    void RemoveFile(int position)
    {
        if(position >= 0 && position < m_items.GetCount())
        {
            m_items.RemoveAt(position);
            notifyDataSetChanged();
        }
    }

    // 获取文件项数据
    const FileItemData* GetItemData(int position)
    {
        if(position >= 0 && position < m_items.GetCount())
            return &m_items[position];
        return NULL;
    }

    // 格式化文件大小
    SStringT FormatFileSize(ULONGLONG nSize)
    {
        if(nSize < 1024)
            return SStringT().Format(L"%d B", (int)nSize);
        else if(nSize < 1024 * 1024)
            return SStringT().Format(L"%.1f KB", (float)nSize / 1024);
        else if(nSize < 1024 * 1024 * 1024)
            return SStringT().Format(L"%.1f MB", (float)nSize / (1024 * 1024));
        else
            return SStringT().Format(L"%.1f GB", (float)nSize / (1024 * 1024 * 1024));
    }
};

// 使用示例
void InitFileList()
{
    SMCListView *pMCListView = FindChildByName2<SMCListView>(L"mclist_basic");
    if(pMCListView)
    {
        CFileListAdapter *pAdapter = new CFileListAdapter();

        // 添加测试数据
        for(int i = 0; i < 50; i++)
        {
            FileItemData item;
            item.strName.Format(L"文件%d.txt", i+1);
            item.strPath.Format(L"C:\\测试文件夹\\文件%d.txt", i+1);
            item.strType = L"文本文件";
            item.nSize = (i + 1) * 1024; // 模拟不同大小
            item.bIsFolder = (i % 5 == 0); // 每5个文件有一个是文件夹

            // 设置修改时间
            GetSystemTimeAsFileTime(&item.tModified);

            pAdapter->AddFile(item);
        }

        // 设置适配器
        pMCListView->SetAdapter(pAdapter);
        pAdapter->Release();
    }
}

最佳实践

  1. 表头设计:合理设置 headerHeight 和列宽度,确保表头内容清晰可见
  2. 数据展示:使用合适的模板设计项目视图,确保数据展示清晰易读
  3. 交互功能:根据需要启用复选框和多选功能,提供灵活的数据操作方式

常见问题

Q: 数据修改后如何刷新视图?

A: 如果要刷新全部视图,通过调用 adapter->notifyDataSetChanged() 方法刷新;如果只需要刷新指定的项目,可以通过调用 adapter->notifyItemChanged(position) 方法。

Q: 表头显示不完整怎么办?

A: 检查 headerHeight 是否设置合适,确保表头内容完整显示。

Q: 处理排序功能异常怎么办?

A: 确保实现了 compareItem 方法并正确处理了排序逻辑。

Q: 数据显示异常怎么办?

A: 检查适配器实现是否正确,确保 adatper->GetColumnName 方法返回正确的数据, adatper->GetColumnName是获取列名,mclistview使用这个返回的列名,从XML模板中获取name为该列名的子窗口,并匹配header的索引号,在当前行中布局这一列的显示。

代码示例解析

让我们看一个更复杂的多列列表视图示例:

// 文件项数据结构
struct FileItemData
{
    SStringT strName;      // 文件名
    SStringT strPath;      // 完整路径
    SStringT strType;      // 文件类型
    ULONGLONG nSize;       // 文件大小
    FILETIME tCreated;     // 创建时间
    FILETIME tModified;    // 修改时间
    FILETIME tAccessed;    // 访问时间
    BOOL bEnabled;         // 是否可用
};

// 高级多列列表适配器
class CAdvancedMcListAdapter : public SMcAdapterBase
{
protected:
    SArray<FileItemData> m_items;
    int m_nSortCol;
    BOOL m_bAscending;

public:
    CAdvancedMcListAdapter() : 
        m_nSortCol(-1), m_bAscending(TRUE)
    {
    }

    // 获取项目数量,注意 WINAPI 调用约定
    virtual int WINAPI getCount()
    {
        return m_items.GetCount();
    }

    // 获取列文本,注意 WINAPI 调用约定
    virtual SStringT WINAPI getColumnText(int position, int column)
    {
        if(position < 0 || position >= m_items.GetCount())
            return SStringT();

        const FileItemData &item = m_items[position];
        SStringT strText;

        switch(column)
        {
        case 0: // 文件名
            strText = item.strName;
            break;
        case 1: // 大小
            strText = FormatFileSize(item.nSize);
            break;
        case 2: // 类型
            strText = item.strType;
            break;
        case 3: // 修改日期
            SYSTEMTIME st;
            FileTimeToSystemTime(&item.tModified, &st);
            strText.Format(L"%04d-%02d-%02d %02d:%02d:%02d",
                st.wYear, st.wMonth, st.wDay,
                st.wHour, st.wMinute, st.wSecond);
            break;
        }

        return strText;
    }

    // 执行排序,注意 WINAPI 调用约定
    virtual void WINAPI Sort(int columnId, BOOL ascending)
    {
        m_nSortCol = columnId;
        m_bAscending = ascending;

        // 使用快速排序
        qsort_s(m_items.GetData(), m_items.GetCount(),
                sizeof(FileItemData), 
                CompareFunc, this);

        notifyDataSetChanged();
    }

    // 获取视图,注意 WINAPI 调用约定
    virtual void WINAPI getView(int position, SItemPanel* pItem, SXmlNode xmlTemplate)
    {
        if(pItem->GetChildrenCount() == 0)
        {
            pItem->InitFromXml(&xmlTemplate);
        }

        if(position >= 0 && position < m_items.GetCount())
        {
            const FileItemData &item = m_items[position];

            // 更新各个列的内容
            SWindow *pCol1 = pItem->FindChildByName(L"col1");
            if(pCol1) pCol1->SetWindowText(item.strName);

            SWindow *pCol2 = pItem->FindChildByName(L"col2");
            if(pCol2) pCol2->SetWindowText(FormatFileSize(item.nSize));

            SWindow *pCol3 = pItem->FindChildByName(L"col3");
            if(pCol3) 
            {
                SYSTEMTIME st;
                FileTimeToSystemTime(&item.tModified, &st);
                SStringT strDate;
                strDate.Format(_T("%04d-%02d-%02d %02d:%02d"),
                    st.wYear, st.wMonth, st.wDay,
                    st.wHour, st.wMinute);
                pCol3->SetWindowText(strDate);
            }

            SWindow *pCol4 = pItem->FindChildByName(L"col4");
            if(pCol4) pCol4->SetWindowText(item.strType);

            // 设置选中状态
            if(item.bSelected)
            {
                pItem->ModifyState(WndState_Check, 0);
            }
            else
            {
                pItem->ModifyState(0, WndState_Check);
            }
        }
    }

    static int __cdecl CompareFunc(void *context,
                                  const void *elem1,
                                  const void *elem2)
    {
        CAdvancedMcListAdapter *pThis = 
            (CAdvancedMcListAdapter*)context;
        const FileItemData *p1 = (const FileItemData*)elem1;
        const FileItemData *p2 = (const FileItemData*)elem2;

        int nResult = 0;

        switch(pThis->m_nSortCol)
        {
        case 0: // 按名称
            nResult = p1->strName.Compare(p2->strName);
            break;
        case 1: // 按大小
            nResult = p1->nSize > p2->nSize ? 1 : 
                     p1->nSize < p2->nSize ? -1 : 0;
            break;
        case 2: // 按类型
            nResult = p1->strType.Compare(p2->strType);
            break;
        case 3: // 按日期
            nResult = CompareFileTime(&p1->tModified,
                                    &p2->tModified);
            break;
        }

        return pThis->m_bAscending ? nResult : -nResult;
    }

    // 添加文件项
    void AddFile(const FileItemData &item)
    {
        m_items.Add(item);

        // 如果已经排序,则重新排序
        if(m_nSortCol >= 0)
        {
            Sort(m_nSortCol, m_bAscending);
        }
        else
        {
            notifyDataSetChanged();
        }
    }

    // 移除文件项
    void RemoveFile(int position)
    {
        if(position >= 0 && position < m_items.GetCount())
        {
            m_items.RemoveAt(position);
            notifyDataSetChanged();
        }
    }

    // 获取文件项数据
    const FileItemData* GetItemData(int position)
    {
        if(position >= 0 && position < m_items.GetCount())
            return &m_items[position];
        return NULL;
    }
};

这个示例展示了: 1. 如何实现复杂的数据结构 2. 如何处理文件相关信息 3. 如何实现高效的排序功能 4. 如何管理大量数据