------创建COleDropSource对象来控制放置
对于大多数应用程序,拖放功能的默认实现就已经足够了。但是,某些应用程序可能要求更改此标准行为。马上你会看到一个更改标准的拖放行为的例子,可以说明其实这是一件多么简单的事情,或许,你会得到一些自定义拖放的感性认识。
在我的工具条上,有一个按钮,资源ID号为:ID_DRAG_MY_OBJ,点击该按钮后,将开始拖放动作(注意,点击该按钮后已经了鼠标LButtonDown和LButtonUp两个动作,而不是按住鼠标左键不放),当鼠标在接受拖放的View中移动Move时,显示状态为拖放中,当在接受拖放的View中按下鼠标左键时,进行放置(Drop),被拖放的数据在该处放置。
这是一个不同于标准拖放的操作,该怎么完成这些动作呢?我们还是先来准备好需要的函数,一步一步从熟悉的默认拖放转变为自定义拖放。
首先,需要响应该按钮的函数,我把它放在CMainFrame中,于是CMainFrame.cpp就会增加一个函数OnDragMyObj()和一条消息映射ON_COMMAND(ID_DRAG_MY_OBJ, OnDragMyObj):
BEGIN_MESSAGE_MAP(CMainFrame,uiFoldTabFrame)
BEGIN_MESSAGE_MAP(CMainFrame,uiFoldTabFrame)
…
ON_COMMAND(ID_DRAG_MY_OBJ,OnDragMyObj)
…
END_MESSAGE_MAP()
…
void CMainFrame:: OnDragMyObj ()
{
//具体的实现将在后面逐渐清晰。
…
}
ON_COMMAND(ID_DRAG_MY_OBJ,OnDragMyObj)
…
END_MESSAGE_MAP()
…
void CMainFrame:: OnDragMyObj ()
{
//具体的实现将在后面逐渐清晰。
…
}
在MFC框架下,一个普通的拖放事怎么完成的?目的端,在接收拖放的窗体类中放置一个COleDropTarget对象,通过RegisterDragDropAPI来注册,实现该COleDropTarget的DragEnter、DragOvert、DragLeave、Drop等方法来处理接收窗体中不同鼠标状态下的拖动或放置,在窗体不需要再接收任何拖动过来的数据时使用RevokeDragDrop来解除注册。源端,生成一个COleDataSource 对象,调用该COleDataSource对象的DoDragDrop 方法开始拖放动作。这样就开始了一个拖放,我们还可以以一个RichEdit为例,来说明通常的拖放行为,请大家辛苦一点看下面这段英文:
1. Whenthe mouse moves over a selected area of text it's cursor shape changes to anarrow.
2. Whenthe left button is pressed, the selection is not removed. Instead an internalstate is set to indicate that a drag-drop operation might be about to start.
3. Whenthe mouse is first moved (and the internal state indicates that the left buttonis currently being held down inside a selected area of text), the drag and dropoperation starts.
4. Atthis point, OLE takes over and handles all further mouse messages until theoperation is complete.
5. However,if the left button is released and the mouse didn't move at all, it is customaryfor the RichEdit's selection to be cleared and the text-caret positioned underthe mouse.
接下来,我们来看看对于我们期望实现的例子,按照拖放一般做法有没有问题,如果有问题,问题在哪里?
为了准备被传送的数据,我们实现了一个简单的StringToHandle函数。这个函数的作用就是将一个普通的 char* string 转化为HGLOBAL 的形式,这样才能在OLE 中使用它。
HANDLE StringToHandle(char *szText, intnTextLen)
{
void *ptr;
// if text length is -1 then treat as a nul-terminated string
if(nTextLen == -1)
nTextLen = lstrlen(szText) + 1;
// allocate and lock a global memory buffer. Make it fixed
// data so we don't have to use GlobalLock
ptr = (void *)GlobalAlloc(GMEM_FIXED, nTextLen);
// copy the string into the buffer
memcpy(ptr, szText, nTextLen);
return ptr;
}
然后,我们来修改上面所述OnDragMyObj函数:
void CMainFrame:: OnDragMyObj ()
{
HGLOBAL hData = StringToHandle("Hello,World", -1);
if (hData != NULL)
{
COleDataSource ds;
ds.CacheGlobalData(CF_TEXT, hData);
DROPEFFECT de = ds.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE);
}
}
只需再去实现接收拖放窗体的DragEnter、DragOvert、DragLeave、Drop等方法,这些方法的实现我在这里就不深入讲解了,因为你在任何有关拖放的资料上都可以查到这几个函数的实现例子。好了,应该说关于拖放的代码已经完成了,但是它能正确工作吗?不能,至少不是按照我们的要求工作。
为了弄清楚问题出在哪里,其实COleDataSource是个大管家,它通过DoDragDrop来发出司令,DoDragDrop其实就是大管家手里的工作流程图,我们来看看COleDataSource::DoDragDrop的参数:
DROPEFFECT DoDragDrop(
DWORDdwEffects = DROPEFFECT_COPY|DROPEFFECT_MOVE|DROPEFFECT_LINK,
LPCRECTlpRectStartDrag = NULL,
COleDropSource*pDropSource = NULL
);
来看第三个参数pDropSource,用于指定相应的dropsource,这个dropsource就是COleDataSource手下的一个小工,将在DoDragDrop中被安排来做一些杂事.如果pDropSource为NULL,COleDropSource会作为默认的dropsource . 这正好是我们在上面代码中使用的情况,也就是说,大管家事先就有个人选COleDropSource, 如果找不到临时的小工,COleDropSource将会为我们做一些他会做的事情。
那,COleDropSource又是如何帮我们做事呢?这就要归功于QueryContinueDrag函数,我作了一点简化,剔出了不必要解释的变量。
SCODE COleDropSource::QueryContinueDrag(BOOLbEscapePressed, DWORD dwKeyState)
{
ASSERT_VALID(this);
// 检查是否按下Esc键或者是否按下鼠标右键 ?C 如果是,取消拖放
if (bEscapePressed || (dwKeyState & MK_RBUTTON) != 0)
{
m_bDragStarted = FALSE; // avoid unecessary cursor setting
return DRAGDROP_S_CANCEL;
}
//检查鼠标左键是否处于弹起状态,如果是,就结束拖放,并执行放置动作
if ((dwKeyState& MK_LBUTTON) == 0)
return m_bDragStarted ? DRAGDROP_S_DROP : DRAGDROP_S_CANCEL;
// 否则,继续由OLE监控鼠标状态…
return S_OK;
}
但是,为什么会出差错,COleDropSource做的事并不是我们想要的结果呢?原因在于COleDropSource其实是个脑袋不开窍的机器人,它做任何事情都一样,只是按照它会做的方式做,当鼠标左键按下时开始拖放,当鼠标左键弹起时结束拖放,并告诉大管家该进行放置了,而大管家在工作流程DoDragDrop中就会把这个命令传给下一个处理者COleDropTarget。
怎么办?既然COleDropSource脑袋不开窍,我们就只好自己给大管家出个小工,这个小工其实是和COleDropSource差不多的另外一个机器人,只是他知道按照我们的要求根据拖放中鼠标的状态来进行处理。
这个机器人就是CMyOleDropSource,
classCMyOleDropSource : public COleDropSource
{
public:
CMyOleDropSource();
virtual ~CMyOleDropSource();
virtual SCODE QueryContinueDrag(BOOL bEscapePressed, DWORD dwKeyState);
};
uiOleDropSource::uiOleDropSource()
{
}
uiOleDropSource::~uiOleDropSource()
{
}
SCODECMyOleDropSource ::QueryContinueDrag(BOOL bEscapePressed, DWORD dwKeyState)
{
ASSERT_VALID(this);
//检查是否按下Esc键或者是否按下鼠标右键 ?C 如果是,取消拖放
if (bEscapePressed || (dwKeyState & MK_RBUTTON) != 0)
{
m_bDragStarted = FALSE; // avoid unecessary cursor setting
return DRAGDROP_S_CANCEL;
}
//检查鼠标左键是否被按下,如果是,就结束拖放,并执行放置动作
if ((dwKeyState & MK_LBUTTON)!= 0)
return m_bDragStarted ? DRAGDROP_S_DROP : DRAGDROP_S_CANCEL;
//否则,继续由OLE监控鼠标状态…
return S_OK;
}
我们再对OnDragMyObj作下面的修改,就大功告成了。
void CMainFrame:: OnDragMyObj ()
{
HGLOBAL hData = StringToHandle("Hello,World", -1);
if (hData != NULL)
{
COleDataSource ds;
ds.CacheGlobalData(CF_TEXT, hData);
CMyOleDropSourcedropSource;
DROPEFFECT de = ds.DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE,0,&dropSource);
}
}
现在,你去实验,一定能够满足我们需要实现例子的要求