目前我们的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;
bool m_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,所以要解析这个数据。
————————————————
版权声明:本文为CSDN博主「Skilla」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Skilla/article/details/42377021