创建客户区窗口,列表框之间项的拖拽操作

我写了一个小类库,其中包含一个类,CDragDropMgr,用这个类可以在自己的应用程序窗口间添加拖拽行为。我还写了一个测试程序,DDTest,示范了如何使用 CDragDropMgr 类(参见 Figure 2)。Figure 3 是程序运行的画面。DDTest 有两个列表框和一个编辑框。你可以将第一个列表框中的项目拖拽到第二个列表框,或者编辑框。此外,你还能在第二个列表框里通过拖拽重排项目。DDTest 就是使用 CDragDropMgr 来实现上述这些功能的。下面我首先示范如何使用 CDragDropMgr,然后在探讨它的工作原理。

  Figure 3 运行中的 DDTest

  为了使用拖拽管理器,首先要在主窗口或对话框中实例化 CDragDropMgr,然后用一个表对之进行初始化,就像下面的代码这样:


1

2

3

4

5

6

7

static DRAGDROPWND MyDragDropWindows[] = {

  { IDC_LIST1, DDW_SOURCE },

  { IDC_LIST2, DDW_SOURCE|DDW_TARGET },

  { IDC_EDIT1, DDW_TARGET },

  { 0, 0 },

};

m_ddm.Install(this, MyDragDropWindows);  

  我的专栏的爱好者们知道我编程的五大秘诀之一便是 一张表胜过一千行代码。表的形式比长长的一串过程代码更加简练、优雅、可靠和可维护。在本文的例子中,表告诉拖拽管理器哪个子窗口是拖拽操作的源和/或目标。每一个表入口都有一个子窗口ID以及一个 DDW_SOURCE 和 DDW_TARGET 的组合标志。在 DDTest 中,第一个列表框是源,第二个列表框既可以是源,也可以是目标,编辑框只能是目标。但是不管怎么样,不要忘了在表的末尾加上 NULL!

  一旦你用窗口表对拖拽管理器进行了初始化,下一步便是改写主窗口的 PreTranslateMessage 函数以便将消息传递给拖拽管理器:


1

2

3

4

5

BOOL CMyDlg::PreTranslateMessage(MSG* pMsg)

{

  return m_ddm.PreTranslateMessage(pMsg) ? TRUE :

   CDialog::PreTranslateMessage(pMsg);

}

  一切准备就绪,当用户试图从一个窗口到另一个窗口实施拖拽操作时,拖拽管理器便会察觉到并通知应用程序要做相应的处理。CDragDropMgr 可以发送四个消息/通知:WM_DD_DRAGENTER、WM_DD_DRAGOVER、WM_DD_ DRAGDROP 和 WM_DD_DRAGABORT。收到这些消息/通知后,做什么样的处理由你来决定。WM_DD_DRAGENTER 和 WM_DD_DRAGDROP 是拖拽操作必须要做的处理。其它两个可选。WM_DD_DRAGABORT 用来处理用户取消操作时的清除工作。WM_DD_ DRAGOVER 使你能够在用户实施拖拽操作而移动鼠标时进行连续不断的处理。DDTest 这样简单的程序不需要处理这些消息。

  当拖拽管理器发送 WM_DD_DRAGENTER 消息时,它在 LPARAM 中传递一个 DRAGDROPINFO 结构。你的任务是将 DRAGDROPINFO::data 指向一个包含你想要拖拽数据的 CDragDropData 实例,然后返回 TRUE。如果拖拽是不允许的(也许用户单击了列表框中的某个死区(dead area))则应该返回 FALSE,并不要设置 DRAGDROPINFO::data,CDragDropData 类似 COM 的 IDataObject,但要简单得多:它保存拟拽动的数据。CDragDropData 有三个虚拟函数:OnGetDragSize 获得一个拖拽图像的绑定矩形,OnDrawData 绘制拖拽图像,OnGetData 获取数据本身。我在我的库中提供了一个叫 CDragDropText 的类,它实现了这些函数,用来处理文本拖拽操作。它将文本保存在一个 CString 中。OnGetData 返回这个串,OnGetDragSize 计算该文本矩形,OnDrawData 则绘制该文本:


1

2

3

4

void CDragDropText::OnDrawData(CDC& dc, CRect& rc)

{

 dc.DrawText(m_text, &rc, DT_LEFT|DT_END_ELLIPSIS);

}   

  如果你想得到这个文本,你唯一需要调用的函数是 OnGetData,拖拽管理器需要时在其内部调用 OnGetDragSize 和 OnDrawData。

  那么所有这些工作是如何实现的呢?当 DDTest 收到 WM_DD_DRAGENTER 消息,它调用一个内部函数 GetLBItemUnderPt 来确定光标下是哪个列表框(如果有的话)。然后 DDTest 以这一项的文本作为数据创建一个 CDragDropText 对象并将 DRAGDROPINFO 中的 data 指针指向该对象:


1

2

3

4

5

6

7

8

9

// in CMyDlg::OnDragEnter

DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;

int item = GetLBItemUnderPt(...);

if (item>=0) {

 CString text = // get item text

 ddi.data = new CDragDropText(text);

 return TRUE;  // allow drag-drop

}

return FALSE; // nothing to drag   

  由 CDragDropMgr 来做剩余的工作。当用户拖拽它时在周边绘制文本并根据光标是否出于拖拽目的地上方而相应地改变鼠标光标。

  当用户松开鼠标,CDragDropMgr 便给应用程序发送一个 WM_DD_DRAGDROP 消息。暗示数据已经拖拽完成。对于 DDTest 而言,这意味着如果鼠标出于编辑框上方,则要设置编辑框中的文本,或者如果鼠标是在列表框上方,则要将文本添加到列表框中。在真正实现中,DDTest 稍显复杂,因为它可以让用户重新安排第二个列表框中的项目。DDTest 有代码可以察觉是否需要添加文本或修改列表框中文本的位置。具体细节就留给你来做了,OnDragDrop 实现的基本要点都是一样的:


1

2

3

4

5

// OnDragDrop handler

DRAGDROPINFO& ddi = *(DRAGDROPINFO*)lp;

void* data = ddi.data->OnGetData();

// do something with data

return 0;   

  以上都是关于文本的操作,如果要拖拽其它类型的数据怎么办呢?为此,你必须通过 CDragDropData 派生并改写三个基本函数来扩展我的库。例如,为了拖拽图像,你得派生一个 CDragDropImage 类,在这个类中,OnGetData 返回 BITMAP 或 CBitmap,OnGetDragSize 返回位图的尺寸,OnDrawData 调用 BltBit 或其它什么函数来绘制该位图。

  我已经示范了 CDragDropMgr 的使用方法,但它是如何工作的呢?基本思路很简单。CDragDropMgr::PreTranslateMessage 查找发送到拖拽源窗口之一的鼠标消息并发送相应的通知到你的应用程序主窗口。CDragDropMgr 实现了一个典型的具有三种状态的有限状态机:NONE、CAPTURED 和 DRAGGING。当用户按下鼠标键,CDragDropMgr 进入 CAPTURED 状态。当用户移动鼠标,则进入 DRAGGING 状态。具体细节简单直白。

  CDragDropMgr 使用 PreTranslateMessage 而不是子类化主窗口,因为它需要解释发送到可能的拖拽源窗口之一鼠标消息,该拖拽源窗口由前述的拖拽窗口表确定。MFC 的优点之一是它在主窗口中仅通过虚拟 PreTranslateMessage 方法便可以过滤所有子窗口消息。这使得 CDragDropMgr 可以仅在单一的函数中便可截获发送到任何潜在拖拽源窗口的鼠标消息,从而避免了必须子类化每一个窗口。当 CDragDropMgr::PreTranslateMessage 看到 WM_LBUTTONDOWN,它便查找该窗口句柄(HWND)以便检查它是否被列入源窗口表。如果它是一个源窗口,则进行拖拽初始化,否则忽略该消息。

  拖拽数据的机制是很简单直白的,甚至有些单调无趣,所以细节我就不再赘言。唯一一个亮点是 CDragDropData 使用 CImageList 来绘画。如果你实现自己的拖拽管理器,我鼓励你也这么做,CImageList 包含如下几个函数:BeginDrag、DragEnter、DragMove 和 EndDrag,用它们可以很快解决比特绘画问题,它们使用特有的光栅操作使图像呈半透明,从其以前位置擦除等等。

  没有 CImageList,这些绘制细节冗长乏味。有了它,CDragDropMgr 只要将拖拽图像绘制到图像列表位图一次即可。当用户初始化拖拽操作时,CDragDropMgr 通知主应用程序,该主应用程序将 DRAGDROPINFO::data 设置为一个 CDragDropData,正如我前面描述的那样。然后拖拽管理器调用 CDragDropData:: CreateDragImage (参见 Figure 4),它创建一个包含要绘制的图像列表。CreateDragImage 调用虚拟函数 CDragDropData::OnGetDragSize 来获取拖拽图像的尺寸,CDragDropData::OnDrawData 将数据绘制到图像列表的位图中。一旦完成了些工作,CDragDropMgr 调用图像列表函数绘制拖拽期间的图像。例如,每次用户移动鼠标,CDragDropMgr 都调用 CImageList::DragMove。还有比这更容易的吗?其优美之处在于这个代码完全是通用的。为了处理新的数据类型,你只要实现 OnGetDragSize 和 OnDrawData 即可。

本文示例代码或素材下载

时间: 2024-07-29 05:02:40

创建客户区窗口,列表框之间项的拖拽操作的相关文章

VC创建客户区窗口、列表框之间项的拖拽操作

创建客户区窗口 列表框之间项的拖拽操作 在发送绘画(paint)消息时,系统是如何识别某个窗口的客户区或非客户区?当我用 ::CreateWindow 创建窗口时,如何指定客户区矩形? 在创建窗口时不必指定客户区,当收到 WM_NCCALCSIZE 消息时才指定客户区.不管什么时候,只要 Windows 想知道窗口客户区的大小,它便会发送这个消息.在 MFC 中实现 OnNcCalcSize 处理例程.该处理函数有两个参数,从 WPARAM 和 LPARAM 转换而来: void OnNcCal

ExtJS5学习之Grid与Grid之间的数据拖拽

    拖拽是一个提升用户体验的一个特色功能,虽然不是必需的,但如果添加上此功能,必然就立马变得高大上了,有木有.Grid与Grid之间的数据拖拽是由gridviewdragdrop插件,官方源码已经内置了该插件,无须自己实现,进行一些简单配置就完事儿了.下面是示例代码:      Js代码   Ext.require([       'Ext.grid.*',       'Ext.data.*',       'Ext.dd.*'   ]);      Ext.define('DataObj

Jquery实现自定义窗口随意的拖拽

 点击一个按钮时,弹出一个自定义窗口,并且可以随意的拖拽,jquery也可以实现这样的功能,下面有个不错的示例,大家可以感受下 在网页上我们经常看到,当点击一个按钮时,弹出一个自定义窗口,并且可以随意的拖拽,从而改变其位置    使用jquery实现拖拽,则必须要jquery的文件了,实现步骤:    1.引入jquery文件    2.编写js脚本    具体代码:    html代码:   代码如下: <button id="show">显示</button>

Java 拖拽文件到文本框

Java中如何把文件拖拽到文本框呢? 先看一个例子:       核心代码: Java代码   /***       * 拖拽文件到文本框       * @param component       */       public void drag(final Component component)// 定义的拖拽方法       {           // panel表示要接受拖拽的控件           new DropTarget(component, DnDConstants.

Jquery实现自定义窗口随意的拖拽_jquery

在网页上我们经常看到,当点击一个按钮时,弹出一个自定义窗口,并且可以随意的拖拽,从而改变其位置 使用jquery实现拖拽,则必须要jquery的文件了,实现步骤: 1.引入jquery文件 2.编写js脚本 具体代码: html代码: 复制代码 代码如下: <button id="show">显示</button> <div class="win"> <div class="wTop"><p

vc++6.0mfc-vc++6.0如何实现点击组合框列表里的项响应相应消息?

问题描述 vc++6.0如何实现点击组合框列表里的项响应相应消息? 我想实现,在模态对话框编辑框里输入几个值,然后点击确定按钮将其添加到组合框列表中,当点击组合框列表里刚添加的项时响应相应消息(如:我在模态对话框四个编辑框中输入a.b.c.d,然后点击确定按钮,将a.b.c.d添加到组合框列表里,当点击组合框列表a时,提示你点击了a,当点击b时,提示你点击b--c,d,f,g也同上).我只是个新手,望大神们详解! 解决方案 CString str; int i = m_list->GetCurS

mfc listbox-MFC-LISTBOX列表框如何在程序中自选多项呢,不用鼠标点击选择

问题描述 MFC-LISTBOX列表框如何在程序中自选多项呢,不用鼠标点击选择 我setcuesel(0)为什么没有反应呢,我想点击某个按钮时自选固定的某几项,应该怎么写呢 解决方案 属性里有个,selectemode,你把它设置成multiextended就可以了

extjs 列表框(multiselect)的动态添加列表项的方法_extjs

因为它这个是创建时自动加载的ArrayStore(关键字是:data: ds),没有动态增加的示例,但我们的项目需要有三个列表框,并且后两个的内容要根据第一个列表框内容动态的加载,因此要在选择第一个列表框的内容时,动态填充后两个的内容.我比较佩服exjts的示例写作人员,这些应该在示例中体现的功能,他们都没有写到,包括之前的2.2版本的示例,网上也没有搜索到,害我找了一天如何动态控制列表数据的功能.首先说明一下,我的方法也不是官方的方法,只是自己灵机一动想到的,下面是动态增加列表项的方法. 在方

积累的VC编程小技巧之列表框

1.列表框中标题栏(Column)的添加 创建一个List Control,其ID为IDC_LIST,在其Styles属性项下的View项里选择Report.Align项里选择Top.Sort项里选择None. 然后在该List所在对话框的类(头文件)里创建ClistCtrl的一个对象m_list然后在.cpp文件的OnInitDialog()之类的函数里实现如下代码: CString strname[3]; strname[0]="Screen Name"; strname[1]=&