zoukankan      html  css  js  c++  java
  • wtlExplorer研究

    wtlExplorer研究

    一直都想写个类似于window浏览器那样的程序。以前的想法就是手动的将几个磁盘加到根节点,然后通过FindFile来进行目录的列表和文件的列表。感觉还是没有找到真正正确的方法,昨天看wtlExplorer这个wtl库自带的例子时,发现他使用SHGetDesktopFolder等Shell函数来进行的,这个应该正儿八经的标准的做法了。因此,决定对这个例子进行仔细研究,希望通过这个例子能够将Shell的一些用法熟悉,特别是目录和文件处理相关的,第二点就是熟悉treeview和listview控件。最后将wtl进一步熟悉。

    第一天,IShellFolder接口

    先从IShellFolder这个接口开始吧,这个接口用来对windows的目录进行管理。在msdn中关于这个接口的详细说明。这个接口一般都是通过SHGetDesktopFolder这个shell函数来获得,这个函数用来获取桌面所对应的目录的IShellFolder接口。通过IShellFolder的 EnumObjects可以返回当前这个目录下所有的对象的ITEMIDLIST,有了这些idlist,就可以使用GetDisplayNameof来取得各个对象的名称,这样就可以显示在树形控件里。我先做了一个console的程序来测试一下各个函数的用法,具体程序如下:

    int _tmain(int argc, _TCHAR* argv[])

    {

         setlocale(LC_ALL, "chs"); //用来设置在控制台输出中文

         CComPtr<IShellFolder> spFolder;

         CComPtr<IEnumIDList> spEnumIDs;

         HRESULT hr = SHGetDesktopFolder(&spFolder);  //拿到桌面的IShellFolder接口

         ATLASSERT(SUCCEEDED(hr));

         hr = spFolder->EnumObjects(NULL, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &spEnumIDs);

         ATLASSERT(SUCCEEDED(hr));

     

         LPITEMIDLIST pIDs;   //直接定义一个指针,试验中刚开始用的是ITEMIDLIST,然后求地址,发现不对

         ULONG ulFetched;

         STRRET str = { STRRET_WSTR };

     

         hr = spEnumIDs->Next(1, &pIDs, &ulFetched);

         ATLASSERT(SUCCEEDED(hr));

         while(ulFetched == 1)

         {

             hr = spFolder->GetDisplayNameOf(pIDs, SHGDN_NORMAL, &str);

             ATLASSERT(SUCCEEDED(hr));

             printf("%ls\n", str.pOleStr);

             hr = spEnumIDs->Next(1, &pIDs, &ulFetched);

         }

         return 0;

    }

     

    第二集 TREEView控件和IShellFolder

    首先,在第一集中利用一个Console程序对IShellFolder接口进行了简单的熟悉,有了基础的了解,我尝试着在一个wtl程序中利用递归将目录树家在到一个treeview中,代码如下

    LRESULT OnFileNew(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)

         {

             CComPtr<IShellFolder> spFolder;

             HRESULT hr = SHGetDesktopFolder(&spFolder);

             ATLASSERT(SUCCEEDED(hr));

             if(SUCCEEDED(hr))

             {

                  CComPtr<IEnumIDList> spIdlst;

                  hr = spFolder->EnumObjects(m_hWnd, SHCONTF_FOLDERS , &spIdlst);

                  if(SUCCEEDED(hr))

                  {

                       LPITEMIDLIST idlst;

                       ULONG ulFetched;

                       while(spIdlst->Next(1, &idlst, &ulFetched) == S_OK && ulFetched ==1)

                       {

                           InsertTreeNode(spFolder, NULL, idlst);

                       }

                  }

             }

             return 0;

         }

     

         void InsertTreeNode(IShellFolder * pFolder, HTREEITEM hParentNode, LPITEMIDLIST lpChildObj)

         {

             iCnt ++;

             if(iCnt > 1000)    return ;  //如果不加这个限制,会有COM错误,我估计是树的节点太多了

             HRESULT hr;

             ULONG ulAtr;

             STRRET strDispName;

             hr = pFolder->GetDisplayNameOf(lpChildObj, SHGDN_NORMAL, &strDispName);

             hr = pFolder->GetAttributesOf(1, (LPCITEMIDLIST*)&lpChildObj, &ulAtr);

     

             if(ulAtr & (SFGAO_FOLDER | SFGAO_HASSUBFOLDER) )  // is folder

             {

             HTREEITEM hCurTreeNode = m_view.InsertItem(strDispName.pOleStr, hParentNode, NULL);

                  CComPtr<IShellFolder> spSubFolder;

                  hr = pFolder->BindToObject(lpChildObj, NULL, IID_IShellFolder, (void **)&spSubFolder);

                  ATLASSERT(SUCCEEDED(hr));

     

                  CComPtr< IEnumIDList> spEnumIds;

     

                  hr = spSubFolder->EnumObjects(m_hWnd, SHCONTF_FOLDERS , &spEnumIds);

     

                  ATLASSERT(SUCCEEDED(hr));

                  LPITEMIDLIST pids;

                  ULONG ulFetched;

     

                  while(spEnumIds->Next(1, &pids, &ulFetched) == NOERROR)

                  {

                       InsertTreeNode(spSubFolder, hCurTreeNode, pids);                

                  }   

             }

         }

     

    这是一段不成熟的代码。问题一,就是不断的递归,如果目录很多的话,就会报一个COM错误。这个问题我看了wtlexploer的代码,他每次只加了一层,由于在读取某个PIDL的属性的时候,可以获取到SFGAO_HASSUBFOLDER和SFGAO_FOLDER这个属性,这样就可以判断这个目录是不是一个目录。 问题二,GetAttributesOf的使用,这个函数在使用时,第三个参数要先初始化成我们需要查询的参数,然后再执行完后进行一下判断,我在刚开始使用时,没有初始化ULONG ulAtr,应该如下初始化:

             SFGAOF sf = SFGAO_FOLDER | SFGAO_HASSUBFOLDER;

             spFolder->GetAttributesOf(1, (LPCITEMIDLIST*)&pIDs, & sf);

             if(sf & (SFGAO_FOLDER | SFGAO_HASSUBFOLDER ))

                       ……….

    对于每次只打开当前节点的一层的这种做法,必须要相应节点的打开操作,这个操作对应的消息是TVN_ITEMEXPANDING,是一个Notify的消息,在wtl中使用如下方式来进行绑定

    NOTIFY_CODE_HANDLER(TVN_ITEMEXPANDING, OnTVItemExpanding)

     

    第三集 SplitterWindow

    为了实现类似于windows资源管理器的功能,必选先得学会怎么样进行双栏显示,左边那一栏显示树形结构,右面那一栏显示当前目录下的子项。在wtl中,这种风格使用CSplitterWindow来进行实现。

    1. 1.       PreTranslateMessage

    首先在mainFrm中定义一个CSplitterWindow的m_view变量,而不是使用向导所生成的View。这时编译会报错,提示CSplitterWindow么有实现PreTranslateMessage函数。向导生成的代码如下:   

    virtual BOOL PreTranslateMessage(MSG* pMsg)    {

             if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))

                  return TRUE;

             return m_view.PreTranslateMessage(pMsg);

         }

    在wtlExplore中,这个函数的定义如下;

         virtual BOOL PreTranslateMessage(MSG* pMsg)

         {

             return CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg);

         }

    要注意一下这两者的区别。

    1. 2.   创建树形控件和列表控件

    有了这个splitter窗体之后,就可以创建包含在这个窗体之间的树形控件和列表控件,以树形控件为例

    先在mainfrm中顶一个CTreeViewCtrlEx的成员变量。    CTreeViewCtrlEx m_tvFolders;

     

             m_tvFolders.Create(m_view, CWindow::rcDefault, NULL,

                  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |

                  TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS,

                  WS_EX_CLIENTEDGE);    

             m_view.SetSplitterPanes(m_tvFolders, NULL);

    第一行代码是创建树形控件,以splitterControl为父窗体;同时定义一些风格。第二句是splitter两个栏中的控件设置,我们只设置了左栏,右栏设置为空。

    按照同样的步骤我们可以建立一个CListViewCtrl控件

    1. 3.   CPaneContainer

    这个控件产生一个带有标题和关闭按钮的容器,在split中使用的方法和其他控件一样。代码如下

             m_leftPane.Create(m_view);

             m_tvFolders.Create(m_leftPane, CWindow::rcDefault, NULL,

                  WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN |

                  TVS_HASLINES | TVS_LINESATROOT | TVS_HASBUTTONS | TVS_SHOWSELALWAYS,

                  WS_EX_CLIENTEDGE);

            

             m_leftPane.SetClient(m_tvFolders);

    SetClient函数可以设置这个容器里所包含的子控件。

    使用SetTitle方法来设置显示的标题

     

    1. 4.   CSplitterWindow

    bool SetSinglePaneMode(int nPane = SPLIT_PANE_NONE)可以通过这个方法来设置分栏显示的栏位数量。

    第四集 TreeView中节点数据的添加, PIDL

    在Treeview中,节点状态TVIS_EXPANDEDONCE 表示这个节点至少被打开过一次。

    在WTLExplorer中,由于树形列表不是一次性加载,所以在每个节点中都保存了这个节点的相关信息,每个节点的数据定义为:

    typedef struct _TVItemData

    {

           _TVItemData()

           { }

          

           CComPtr<IShellFolder> spParentFolder;

          

           CShellItemIDList lpi;

           CShellItemIDList lpifq;

     

    } TVITEMDATA, *LPTVITEMDATA;

     

    系统中与PIDL有关的机构如下:

    typedef struct _ITEMIDLIST

        {

        SHITEMID mkid;

        } ITEMIDLIST;

     

    typedef struct _SHITEMID

        {

        USHORT cb;

        BYTE abID[ 1 ];

        } SHITEMID;

    一个PIDL就是一个pointer to an item identifier list,就是由SHITEMID所组成的一个列表。这个列表的末尾用一个cb为0的SHITEMID来表示。假设lpIDL是指向当前SHITEMID的一个指针,那lpIDL+(lpIDL->cb)就是指向下一个SHITEMID的地址了。

     

     

     

    这个结构的特点

     

     

     

     

     

     

     

     

    WM_NOTIFY消息

    Msdn里这个消息的功能如下:

    Sent by a common control to its parent window when an event has occurred or the control requires some information.

    这个消息是控件用来主动通知父窗体,他有个事件发生了。

    要使用这个消息,应该通过SendMessage来将消息发送给父窗体,具体用法可以参见msdn。

    lResult = SendMessage( (HWND)hWndControl , (UINT) WM_NOTIFY, (WPARAM) wParam, (LPARAM) lParam)

    其中第一个参数据msdn来说,应该是子空间的父控件的HWND。

    第四个参数lParam是一个指向NMHDR 结构的一个指针,这个结构中包含了notification code 和一些附加的信息。同时,具体该消息相关的控件ID也在这个结构NMHDR的hwndFrom和idFrom数据成员里。

    如果要在wtl中对该TreeView的SelChanged事件进行相应,可以用下面的宏

           NOTIFY_HANDLER(IDC_TREE, TVN_SELCHANGED, OnChange)

    还可以用

        NOTIFY_CODE_HANDLER(TVN_SELCHANGED, OnChange)

    总结。。

  • 相关阅读:
    机器学习的定义和分类
    选股
    mysql修改密码
    快速排序
    php的错误类型
    MySQL数据库优化
    库存超卖问题
    循环处理
    kafka安装配置
    JavaScript、jQuery杂记
  • 原文地址:https://www.cnblogs.com/kwliu/p/2080000.html
Copyright © 2011-2022 走看看