第二十九篇:使用SOUI的SMCListView控件


列表控件是客戶端應用最常用的控件之一。列表控件通常只負責顯示數據,最多通知一下APP列表行的選中狀態變化。

現在的UI經常要求程序猿在列表控件里不光顯示內容,還要能和用戶交互,顯示動畫等等,傳統的列表控件對於這樣的需求基本是無能為力了。

Android開發中很多界面都直接采用ListView實現,ListView中每一個Item中都可以容納其它控件,這樣的設計使得在表項中的交互和在主面板上交互一樣簡單。

雖然在列表項中容納其它控件並不是什么新的思想,考慮到列表中的數據量是不確定的,如果給每一個表項的分配一個容器窗口,系統的內存占用及效率都成問題。

還好Andriod開源,簡單看一下Android里ListView控件的源代碼就可以發現,Android實現的ListView關鍵在於容器窗口的重用。

借鑒Andriod ListView控件的思想,在SOUI中也實現了對應的ListView控件。但是ListView只有一列,顯示顯示復雜內容問題不大,但是不能調整列寬等,和一個ListCtrl的功能還是有些差距。

多列列表和單列表最大的區別在於多了一個表頭,核心的東西並沒有區別,經過近3天的編碼調試,終於完成了這個革命性的控件(至少我認為是Windows UI上革命性的)。

先看下效果:

SMCListView控件的使用:

XML配置:

要使用這個列表控件,首先應該在XML中定義該控件的位置及屬性,參考下面摘自demo的代碼:

          <mclistview name="mclv_test" colorBkgnd="#ffffff" pos="10,10,-10,-10" headerHeight="30">
            <header align="center"  sortSkin="skin_lcex_header_arrow" itemSkin="skin_lcex_header" itemSwapEnable="1" fixWidth="0" font="underline:0,adding:-3" sortHeader="1" colorBkgnd="#ffffff" >
              <items>
                <item width="480">軟件名稱</item>
                <item width="95">軟件評分</item>
                <item width="100">大小</item>
                <item width="100">安裝時間</item>
                <item width="100">使用頻率</item>
                <item width="100">操作</item>
              </items>
            </header>
            <template itemHeight="80" colorHover="#cccccc" colorSelected="#0000ff" id="30000">
              <window name="col1">
                <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">
                <ratingbar name="rating_score" starSkin="skin_star1" starNum="5" value="3.5" pos="10,16"  />
                <text name="txt_score" pos="15,36,50,-16" font="adding:-5"  >8.5分</text>
                <link pos="[5,36,@30,-16" cursor="hand" colorText="#1e78d5" href="www.163.com" font="adding:-5" >投票</link>
              </window>
              <window name="col3">
                <text name="txt_size" pos="0,26,-0,-26" font="adding:-4" align="center" >85.92M</text>
              </window>
              <window  name="col4">
                <text name="txt_installtime" pos="0,26,-0,-26" font="adding:-4" align="center" >2015-01-09</text>
              </window>
              <window name="col5">
                <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">
                <imgbtn animate="1"  pos="|-35,|-14" font="adding:-3" align="center" skin="skin_install" name="btn_uninstall">卸載</imgbtn>
              </window>            
            </template>

          </mclistview>

mclistview有一個屬性headerHeight,該屬性定義表頭的顯示高度。

節點下有一個header控件,用來定義表頭控件的樣式,都很簡單,自己看XML。

最關鍵的在於下面的template(模板)節點,該XML節點用來定義如何顯示列表項。

模板內樣式的定義其實並沒有特別的規定,因為最后如何解析這個模板是由APP決定的,但推薦使用上面的樣式:template節點下為每一列定義一個window節點,只需要指定一個name屬性(當然也可以指定其它的窗口屬性,布局屬性無效)。在該window節點下可以定義任意的其它控件。

代碼編寫:

和listview控件一樣,mclistview也需要XML和代碼配合才能正確顯示數據。

要使用mclistview,首先需要實現一個數據適配器(IMcAdapter,繼承自SListView中實現的IAdapter),還是先看demo中的實現:

class CTestMcAdapterFix : public SMcAdapterBase
{
public:
struct SOFTINFO
{
    wchar_t * pszSkinName;
    wchar_t * pszName;
    wchar_t * pszDesc;
    float     fScore;
    DWORD     dwSize;
    wchar_t * pszInstallTime;
    wchar_t * pszUseTime;
};

static SOFTINFO s_info[];

public:
    CTestMcAdapterFix()
    {

    }

    virtual int getCount()
    {
        return 12340;
    }   

    SStringT getSizeText(DWORD dwSize)
    {
        int num1=dwSize/(1<<20);
        dwSize -= num1 *(1<<20);
        int num2 = dwSize*100/(1<<20);
        return SStringT().Format(_T("%d.%02dM"),num1,num2);
    }
    
    virtual void getView(int position, SWindow * pItem,pugi::xml_node xmlTemplate)
    {
        if(pItem->GetChildrenCount()==0)
        {
            pItem->InitFromXml(xmlTemplate);
        }
        int dataSize = 7;
        SOFTINFO *psi = s_info+position%dataSize;
        pItem->FindChildByName(L"img_icon")->SetAttribute(L"skin",psi->pszSkinName);
        pItem->FindChildByName(L"txt_name")->SetWindowText(S_CW2T(psi->pszName));
        pItem->FindChildByName(L"txt_desc")->SetWindowText(S_CW2T(psi->pszDesc));
        pItem->FindChildByName(L"txt_score")->SetWindowText(SStringT().Format(_T("%1.2f 分"),psi->fScore));
        pItem->FindChildByName(L"txt_installtime")->SetWindowText(S_CW2T(psi->pszInstallTime));
        pItem->FindChildByName(L"txt_usetime")->SetWindowText(S_CW2T(psi->pszUseTime));
        pItem->FindChildByName(L"txt_size")->SetWindowText(getSizeText(psi->dwSize));
        pItem->FindChildByName2<SRatingBar>(L"rating_score")->SetValue(psi->fScore/2);
        pItem->FindChildByName(L"txt_index")->SetWindowText(SStringT().Format(_T("第%d行"),position));
        
        SButton *pBtnUninstall = pItem->FindChildByName2<SButton>(L"btn_uninstall");
        pBtnUninstall->SetUserData(position);
        pBtnUninstall->GetEventSet()->subscribeEvent(EVT_CMD,Subscriber(&CTestMcAdapterFix::OnButtonClick,this));
    }

    bool OnButtonClick(EventArgs *pEvt)
    {
        SButton *pBtn = sobj_cast<SButton>(pEvt->sender);
        int iItem = pBtn->GetUserData();
        SMessageBox(NULL,SStringT().Format(_T("button of %d item was clicked"),iItem),_T("uninstall"),MB_OK);
        return true;
    }

    SStringW GetColumnName(int iCol) const{
        return SStringW().Format(L"col%d",iCol+1);
    }
    
    struct SORTCTX
    {
        int iCol;
        SHDSORTFLAG stFlag;
    };
    
    bool OnSort(int iCol,SHDSORTFLAG * stFlags,int nCols)
    {
        if(iCol==5) //最后一列“操作”不支持排序
            return false;
        
        SHDSORTFLAG stFlag = stFlags[iCol];
        switch(stFlag)
        {
            case ST_NULL:stFlag = ST_UP;break;
            case ST_DOWN:stFlag = ST_UP;break;
            case ST_UP:stFlag = ST_DOWN;break;
        }
        for(int i=0;i<nCols;i++)
        {
            stFlags[i]=ST_NULL;
        }
        stFlags[iCol]=stFlag;
        
        SORTCTX ctx={iCol,stFlag};
        qsort_s(s_info,7,sizeof(SOFTINFO),SortCmp,&ctx);
        return true;
    }
    
    static int __cdecl SortCmp(void *context,const void * p1,const void * p2)
    {
        SORTCTX *pctx = (SORTCTX*)context;
        const SOFTINFO *pSI1=(const SOFTINFO*)p1;
        const SOFTINFO *pSI2=(const SOFTINFO*)p2;
        int nRet =0;
        switch(pctx->iCol)
        {
            case 0://name
                nRet = wcscmp(pSI1->pszName,pSI2->pszName);
                break;
            case 1://score
                {
                    float fCmp = (pSI1->fScore - pSI2->fScore);
                    if(fabs(fCmp)<0.0000001) nRet = 0;
                    else if(fCmp>0.0f) nRet = 1;
                    else nRet = -1;
                }
                break;
            case 2://size
                nRet = (int)(pSI1->dwSize - pSI2->dwSize);
                break;
            case 3://install time
                nRet = wcscmp(pSI1->pszInstallTime,pSI2->pszInstallTime);
                break;
            case 4://user time
                nRet = wcscmp(pSI1->pszUseTime,pSI2->pszUseTime);
                break;

        }
        if(pctx->stFlag == ST_UP)
            nRet = -nRet;
        return nRet;
    }
};

CTestMcAdapterFix::SOFTINFO CTestMcAdapterFix::s_info[] =
{
    {
        L"skin_icon1",
        L"魯大師",
        L"魯大師是一款專業的硬件檢測,驅動安裝工具",
        5.4f,
        15*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon2",
        L"PhotoShop",
        L"強大的圖片處理工具",
        9.0f,
        150*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon3",
        L"QQ7.0",
        L"騰訊公司出品的即時聊天工具",
        8.0f,
        40*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon4",
        L"Visual Studio 2008",
        L"Microsoft公司的程序開發套件",
        9.0f,
        40*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon5",
        L"YY8",
        L"YY語音",
        9.0f,
        20*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon6",
        L"火狐瀏覽器",
        L"速度最快的瀏覽器",
        8.5f,
        35*(1<<20),
        L"2015-8-5",
        L"今天"
    },
    {
        L"skin_icon7",
        L"迅雷",
        L"迅雷下載軟件",
        7.3f,
        17*(1<<20),
        L"2015-8-5",
        L"今天"
    }
};

注意CTestMcAdapterFix::getView虛函數,上面提到的template會通過該函數的參數 pugi::xml_node xmlTemplate 傳遞過來。

在getView中,首先需要判斷表項容器的子窗口是不是已經被初始化過,如果沒有就執行InitFromXml如下:

        if(pItem->GetChildrenCount()==0)
        {
            pItem->InitFromXml(xmlTemplate);
        }

在子窗口初始化完成后,還需要從數據表中獲取對應項的數據填充到控件中顯示:

        int dataSize = 7;
        SOFTINFO *psi = s_info+position%dataSize;
        pItem->FindChildByName(L"img_icon")->SetAttribute(L"skin",psi->pszSkinName);
        pItem->FindChildByName(L"txt_name")->SetWindowText(S_CW2T(psi->pszName));
        pItem->FindChildByName(L"txt_desc")->SetWindowText(S_CW2T(psi->pszDesc));
        pItem->FindChildByName(L"txt_score")->SetWindowText(SStringT().Format(_T("%1.2f 分"),psi->fScore));
        pItem->FindChildByName(L"txt_installtime")->SetWindowText(S_CW2T(psi->pszInstallTime));
        pItem->FindChildByName(L"txt_usetime")->SetWindowText(S_CW2T(psi->pszUseTime));
        pItem->FindChildByName(L"txt_size")->SetWindowText(getSizeText(psi->dwSize));
        pItem->FindChildByName2<SRatingBar>(L"rating_score")->SetValue(psi->fScore/2);
        pItem->FindChildByName(L"txt_index")->SetWindowText(SStringT().Format(_T("第%d行"),position));

和主面板上的控件響應不同,要響應表項中控件的事件,沒有事件映射表可以使用,可能在IMcAdapter的實現中使用控件的GetEventSet()->subscribeEvent方法來響應:

        SButton *pBtnUninstall = pItem->FindChildByName2<SButton>(L"btn_uninstall");
        pBtnUninstall->SetUserData(position);
        pBtnUninstall->GetEventSet()->subscribeEvent(EVT_CMD,Subscriber(&CTestMcAdapterFix::OnButtonClick,this));

除了getView這個方法外,相對於IAdapter,IMcAdapter還需要實現另外兩個非常重要的方法:

    interface IMcAdapter : public IAdapter
    {
        //獲取列名
        virtual SStringW GetColumnName(int iCol) const PURE;
        //排序接口
        // int iCol:排序列
        // SHDSORTFLAG * stFlags [in, out]:當前列排序標志
        // int nCols:總列數,stFlags數組長度
        virtual bool OnSort(int iCol,SHDSORTFLAG * stFlags,int nCols) PURE;
    };

實現GetColumnName方法來獲取每一列對應的子窗口名稱,SMcListView通過它來確實template中的子窗口哪一個應該顯示在什么位置,返回在template中定義的子節點的name即可。

實現OnSort來處理表頭點擊事件,以確實如何對數據排序。

至此,這個超級列表控件的使用就完成了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM