关于Duilib的扩展——“拖放”实现(一)

来源:互联网 发布:c语言编译器用什么编 编辑:程序博客网 时间:2024/05/22 05:02

          目前我们的Duilib是不支持拖拽的,想必大家已经知道什么是“拖拽”了,比如Windows文件浏览器,拖动文件到另一个窗口,实现复制或移动;再比如,QQ的好友列表,拖动,互换位置。

          所谓“拖拽“其实是两个动作,一个主动,一个被动。主动的叫”拖“,被动的叫”放“。那么支持”拖“动作的控件就叫做DropSource(拖放源),支持”放“动作的控件就叫做DropTarget(拖放目标)。DropSource和DropTarget可以在不同的进程中进行交互,它们的通信方式和”剪切板“是一样的,传递IDataObject对象。


         首先,我们来实现DropTarget拖放目标,Ole中有一个接口IDropTarget,就是拖放目标类,我们来实现它,就能让我们的控件支持”拖放源“往里面”拖“数据。但是有一个问题,IDropTarget是针对于窗口的,也就是说一个IDropTarget对象负责的是一个窗口。我们使用原生的Windows控件时,可以直接去实现一个IDropTarget接口,来管理这个控件,但是Duilib的控件都不是窗口,我们要怎么办?先不要着急,千里之行始于足下,我们先实现IDropTarget,先让我们的窗口支持“放”,也就是说,有东西拖入到我们窗口时,我们能捕获到这个东西是什么。巧妇难为无米之炊,控件依赖与窗口,控件支持拖放,必然要先让窗口支持,就像我们的控件捕获MouseMove事件一样,如果窗口没有WM_MOUSEMOVE消息,再怎么费劲也是一场空。好了废话不多说,上源码了。

class CDropTargetEx : public IDropTarget{public:CDropTargetEx(void);bool DragDropRegister(IDuiDropTarget* pDuiDropTarget,HWND hWnd,DWORD AcceptKeyState=MK_LBUTTON);bool DragDropRevoke(HWND hWnd);HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void **ppvObject);ULONG STDMETHODCALLTYPE AddRef();ULONG STDMETHODCALLTYPE Release();//进入HRESULT STDMETHODCALLTYPE DragEnter(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);//移动HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);//离开HRESULT STDMETHODCALLTYPE DragLeave();//释放HRESULT STDMETHODCALLTYPE Drop(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);private:~CDropTargetEx(void);HWND m_hWnd;IDropTargetHelper* m_piDropHelper;boolm_bUseDnDHelper;DWORD m_dAcceptKeyState;ULONG  m_lRefCount;};///////////////////////////////////////////////////////////////////////////////////////////////////////CDropTargetEx::CDropTargetEx(void){m_lRefCount = 1;// Create an instance of the shell DnD helper object.if ( SUCCEEDED( CoCreateInstance ( CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER,IID_IDropTargetHelper, (void**) &m_piDropHelper ) )){m_bUseDnDHelper = true;}}CDropTargetEx::~CDropTargetEx(void){if (m_piDropHelper){m_piDropHelper->Release();}m_bUseDnDHelper = false;m_lRefCount = 0;}bool CDropTargetEx::DragDropRegister(IDuiDropTarget* pDuiDropTarget,HWND hWnd,DWORD AcceptKeyState){if(!IsWindow(hWnd))return false;m_pDuiDropTarget = pDuiDropTarget;HRESULT s = ::RegisterDragDrop (hWnd,this);m_hWnd = hWnd;if(SUCCEEDED(s)){m_dAcceptKeyState = AcceptKeyState;return true;}else { return false; }}bool CDropTargetEx::DragDropRevoke(HWND hWnd){if(!IsWindow(hWnd))return false;HRESULT s = ::RevokeDragDrop(hWnd);   return SUCCEEDED(s);}HRESULT STDMETHODCALLTYPE CDropTargetEx::QueryInterface(REFIID riid, __RPC__deref_out void **ppvObject){static const QITAB qit[] ={QITABENT(CDropTargetEx, IDropTarget),{ 0 }};return QISearch(this, qit, riid, ppvObject);}ULONG STDMETHODCALLTYPE CDropTargetEx::AddRef(){return InterlockedIncrement(&m_lRefCount);}ULONG STDMETHODCALLTYPE CDropTargetEx::Release(){ULONG lRef = InterlockedDecrement(&m_lRefCount);if (0 == lRef){delete this;}return m_lRefCount;}//进入HRESULT STDMETHODCALLTYPE CDropTargetEx::DragEnter(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect){if ( m_bUseDnDHelper ){m_piDropHelper->DragEnter (m_hWnd, pDataObj, (LPPOINT)&pt, *pdwEffect );}return <span style="white-space:pre"></span>return S_OK;;}//移动HRESULT STDMETHODCALLTYPE CDropTargetEx::DragOver(DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect){if ( m_bUseDnDHelper ){m_piDropHelper->DragOver((LPPOINT)&pt, *pdwEffect);}return <span style="white-space:pre"></span>return S_OK;;}//离开HRESULT STDMETHODCALLTYPE CDropTargetEx::DragLeave(){if ( m_bUseDnDHelper ){m_piDropHelper->DragLeave();}return <span style="white-space:pre"></span>return S_OK;;}//释放HRESULT STDMETHODCALLTYPE CDropTargetEx::Drop(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect){m_piDropHelper->Drop ( pDataObj,  (LPPOINT)&pt, *pdwEffect );return <span style="white-space:pre"></span>return S_OK;;}


IDropTarget继承自IUnkown,下面三个函数必然要实现,这是Com编程的根本,如果没有经验的话,去查一下资料

HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, __RPC__deref_out void **ppvObject);
ULONG STDMETHODCALLTYPE AddRef();
ULONG STDMETHODCALLTYPE Release();


这个函数才是关键

//进入
HRESULT STDMETHODCALLTYPE DragEnter(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);
//移动
HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);
//离开
HRESULT STDMETHODCALLTYPE DragLeave();
//释放
HRESULT STDMETHODCALLTYPE Drop(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);


bool DragDropRegister(IDuiDropTarget* pDuiDropTarget,HWND hWnd,DWORD AcceptKeyState=MK_LBUTTON);当我们调用这个函数后,那么hwnd所代表的窗口就已经支持“放”了。然后我们把它集成到Duilib,怎么集成进去可能是个比较抽象的问题。但是只要你对Duilib的源码熟悉的话,这一步还是非常简单的。

      

       为了照顾一下新手,我把Duilib界面库的扩展原则阐述一下,Duilib的Control设计的非常灵活,绝大多数都是虚函数,如果做控件的话,可以直接去派生,当然,这也也不是盲目地去继承,要根据需要,比如List有垂直布局的特性,所以派生自CVerticalLayoutUI,所以实现List你只要去关心如何去维护列表项和列表之间的关系就可以了,关于排版,滚动,翻页,size自适应等CVerticalLayoutUI已经全部帮我们实现了。假如我们先实现一个绘制RGBA数据的一个控件,比如我之前写过的wkeBrowser,直接继承CControlUI就可以了,我们直接重写PaintBkImage就可以了,利用这个传入hDC我们就可以随心所欲去绘制了,至于这个控件用在哪里我们根本不用关心,由于其先天特性,其父控件必然会自动去管理。当然这是有人问了,我现在这个控件里面添加东西怎么办,在wkeBrowser里面添加控件,你可能认为这是无法实现的,但是,我们只要轻轻地把wkeBrowser父类改成CContainerUI就可以了,其他的我们一概不用管,因为这个CContainerUI还是原来那个,我们只是偷偷地帮它画了一个网页作为背景。

    好了,介绍完控件,我们再回到正题。因为我们想让任何一个控件都实现拖拽,所以先把窗口实现拖拽是根本。Duilib的窗口管理者CPaintManagerUI,但凡和窗口有关的属性都会在CPaintManagerUI里面存有备案,为了不影响源码,我们的修改也要遵循源码本身的原则。所以让窗口实现拖拽,1.首先我们要做的就是给CPaintManagerUI加一个bool变量,bool m_bDropEnable;,当然可能有人要问了,弄个bool值做什么?很容易想到,任何事物都是对立的,这个窗口我们想实现拖拽,但是下一个窗口我们又不想要这个特性怎么办,所以弄一个bool作为标志,默认为false不支持,开启的话,则把它设置为true。2.然后再弄一个成员变量,就是我们刚才实现的CDropTargetEx,我们在这里保存CDropTargetEx的一个指针,CDropTargetEx*   m_pDropTarget;。在 CPaintManagerUI的构造里面创建这个对象m_pDropTarget = new CDropTargetEx;在析构里面调用m_pDropTarget->Release();去销毁,我就不解释了。3.在Init函数里面调用m_pDropTarget->DragDropRegister(this,GetPaintWindow());给窗口注册这个功能,当然不要忘了在PaintManager析构时调用m_pDropTarget->DragDropRevoke(GetPaintWindow());反注册。4.添加一个SetDropEnable函数给m_bDropEnable赋值,并在CDialogBulider的parse函数里面加入如下语句,以便于我们在xml里面开启这个功能。

else if( _tcscmp(pstrName, _T("dropenable")) == 0 ) {
pManager->SetDropEnable(_tcscmp(pstrValue, _T("true")) == 0);

       OK,大功告成,在我们的xml的Window标签上加入dropenable=“true”,就可以测试了,我们把文件拖到我们的窗口里面发现,效果出现了,一个可爱的图标出现在我们的窗口里。



   我们窗口已经是拖放目标了,下面这几个函数便是,拖放目标的响应函数,当鼠标拖住文件(或者其他可拖动的东西时)进入我们窗口时,DragEnter首先会相应,就像我们平时的MouseEnter;其次,就如窗口后在移动时,会调用DragOver函数,就像我们平时的MouseMove;这是如果在鼠标未松开是离开窗口的话,DragLeave便会响应,类似MouseLeave;如果鼠标在窗口里面松开了,就相当于刚才拖动的东西放在了我们的窗口里面,Drop函数便会响应

//进入
HRESULT STDMETHODCALLTYPE DragEnter(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);
//移动
HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);
//离开
HRESULT STDMETHODCALLTYPE DragLeave();
//释放
HRESULT STDMETHODCALLTYPE Drop(__RPC__in_opt IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, __RPC__inout DWORD *pdwEffect);


上面我们虽然为窗口开启了DropTarget的功能,但是仅仅看到了,仅仅是利用IDropTargetHelper为我们绘制了一个图标。我们目的根本不是这个,真正需要的是“拖”进来的数据,其实就是这个对象IDataObject *pDataObj,所以要解析这个数据。


       今天就到这里了,关于如何去获取IDataObject 里面的数据,会在下次介绍,有问题随时联系我(Skilla QQ:848861075)

3 0