How to: 修改程序的拖拽行为

本文介绍如何利用Windows API Hook技术控制Word2003中的拖拽行为,包括禁止用户拖拽特定文本、限制拖拽范围及阻止特定区域接收拖拽内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者信息:

TAOWEN的闲言碎语
翻来覆去就那么一点事


最近在做的一个项目是一个Word 2003的插件。项目的一个需求是控制用户在Word中的拖拽行为。具体来说有三种:

1、用户完全不能把某些文字Drag起来

2、对于某些文字,可以Drag起来,但是不能Drop到除当前文档之外的任何地方

3、对于某些文字,任意东西都不能Drop于其上

说实话,我了解到这个是需求的时候, 第一反应就是,这可能吗?借用阿迪的广告语,Impossible is Nothing。的确,Windows之所以千疮百孔,在我看来很大程度上就是它提供了太多的可Hack的手段了。在实现这个可控的拖拽行为之前,已经通过SetWindowsHook控制了用户的鼠标和键盘(当然这种技术已经用烂了,我就不再炒冷饭了)。这次,我们使用Windows API Hook来达到这个目的。

如果不了解拖拽到底是怎么实现的,我们是不可能控制它的行为的。 我们要做的,其实就是找出标准的拖拽实现方式。然后在其中插一脚,把我们感兴趣的东西拦截下来,并篡改掉本来的输出结果。说实话,Hook的善恶就在一念之间。那么我们先来简单了解一下拖拽的流程:

Drag:

1、应用程序在用户用鼠标拖拽了一个物体之后,调用 DoDragDrop(dataObject, dropSource, okEffect, effects) 开始拖拽

2、Windows 回调 dropSource 的 QueryContinueDrag 来决定是不是继续Drag

Drop:

1、应用程序在初始化的时候,调用 RegisterDragDrop(hwnd, dropTarget)

2、当有物体拖拽进了 hwnd 所在的区域时,Windows 回调 dropTarget 的 DragEnter

3、当物体在 hwnd 所在区域内滑动时,Windows 回调 dropTarget 的 DragOver

4、当物体拖拽出 hwnd 所在区域时,Windows 回调 dropTarget 的 DragLeave

5、当拖拽的物体放下是,Windows 回调 dropTarget 的 Drop

所以对于我们来说,重点关注的就是两个API,两个Com Interface。分别是 Ole32 的 DoDragDrop 和 RegisterDragDrop。以及 Ole32 中的 IDropTarget, IDropSource。要实现开头所述的三种行为,我们只需要:

1、 用户完全不能把某些文字Drag起来:

拦截DoDragDrop的调用,如果dataObject或者用户当前选中的区域是受到保护的,就不调用Ole32的真实实现,直接返回0。

2、 对于某些文字,可以Drag起来,但是不能Drop到除当前文档之外的任何地方:

拦截DoDragDrop的调用,真实地去调用Ole32的真实实现,但是不直接使用原装的DropSource(包装之后再用)。因为我们要监听Windows对DropSource的回调,再“恰当”的时候篡QueryContinueDrag的返回值。从而使得用户无法Drop到当前文档之外的区域。

3、 对于某些文字,任意东西都不能Drop于其上:

拦截RegisterDragDrop的调用,确实使用Ole32的真实实现,但是也不直接使用原装的DropTarget(包装之后再用)。因为我们要监听Windows对DropTarget的回调。同样,在“恰当” 的时候篡改DragOver和Drop的返回值。

那么问题就集中在了如何拦截Ole32的两个API上。这个问题很好解决,直接使EasyHook(http://www.codeplex.com/easyhook)库就行了。我阅读了这个库的所有源代码,有机会可以给大家讲讲Windows API Hook的原理,也挺有意思的。而且使用EasyHook,我们还可以做到远程注入(底层实现是CreateRemoteThread,老套但是可靠,关键是支持.NET)。这样,就不用受限于当前进程了。不过由于RegisterDragDrop一般在初始化的时候调用,所以最好使用EasyHook的CreateAndInject来做,要不然就时机太晚了。在本例中是一个Word 2003的插件,而且恰巧Word 2003是先加载AddIn再RegisterDragDrop,要不然也就Hook无门了。

下面是一些关键细节的源代码: 

安装DoDragDrop的钩子


using System;
using System.Runtime.InteropServices.ComTypes;
using EasyHook;

namespace xxx
{
    
public class RangeDragEvents : IDisposable
    {
        
private readonly WordApplication application;
        
private readonly RangeListener listener;
        
private readonly Ole32._DoDragDrop doDragDropHandler;
        
private readonly LocalHook doDragDropHook;

        
public RangeDragEvents(WordApplication application, RangeListener listener)
        {
            
this.application = application;
            
this.listener = listener;
            doDragDropHandler 
= HandleDoDragDrop;
            doDragDropHook 
= LocalHook.Create(LocalHook.GetProcAddress("ole32.dll""DoDragDrop"),
                                              doDragDropHandler, 
new object());
            doDragDropHook.ThreadACL.SetExclusiveACL(
new int[0]);
        }

        
private int HandleDoDragDrop(IDataObject rawDataObject, Ole32.DropSource originalDropSource, int allowedEffect,
                                     
int[] finalEffect)
        {
            Ole32.DropSource dropSource 
= originalDropSource;
            
switch (GetDragAction())
            {
                
case DragAction.JAILED:
                    dropSource 
= new JailDropSource(originalDropSource);
                    
break;
                
case DragAction.CANCELLED:
                    
return 0;
            }
            
return Ole32.DoDragDrop(rawDataObject, dropSource, allowedEffect, finalEffect);
        }

        
private DragAction GetDragAction()
        {
            WordSelection selection 
= application.Selection;
            
if (selection == null)
            {
                
return DragAction.PROCEEDED;
            }
            
using (var detached = selection.Detach())
            {
                var range 
= detached.Range;
                
if (range.Start == range.End)
                {
                    
return DragAction.PROCEEDED;
                }
                
return listener.HandleDrag(range);
            }
        }

        
public void Dispose()
        {
            doDragDropHook.Dispose();
        }
    }
}

安装RegisterDragDrop的钩子


using System;
using EasyHook;

namespace xxx
{
    
public class RangeDropEvents : IDisposable
    {
        
private readonly Ole32._RegisterDragDrop registerDragDropHandler;
        
private readonly LocalHook registerDragDropHook;
        
private readonly RangeListener listener;
        
private readonly WordApplication application;

        
public RangeDropEvents(WordApplication application, RangeListener listener)
        {
            
this.listener = listener;
            
this.application = application;
            registerDragDropHandler 
= HandleRegisterDragDrop;
            registerDragDropHook 
= LocalHook.Create(LocalHook.GetProcAddress("ole32.dll""RegisterDragDrop"),
                                                    registerDragDropHandler, 
new object());
            registerDragDropHook.ThreadACL.SetExclusiveACL(
new int[0]);
        }

        
private int HandleRegisterDragDrop(IntPtr hwnd, Ole32.DropTarget target)
        {
            
return Ole32.RegisterDragDrop(hwnd, new RangeAwareDropTarget(application, listener, target));
        }

        
public void Dispose()
        {
            registerDragDropHook.Dispose();
        }
    }
}

把Drag的物体“Jail”在当前文档中


using System;
using System.Drawing;
using System.Windows.Forms;
using log4net;

namespace xxx
{
    
public class JailDropSource : Ole32.DropSource
    {
        
private static readonly ILog LOGGER = LogManager.GetLogger(typeof (JailDropSource));
        
private const int DRAGDROP_S_DROP = 0x00040100;
        
private const int DRAGDROP_S_CANCEL = 0x00040101;
        
private readonly Ole32.DropSource dropSource;
        
private readonly IntPtr jailedIn;

        
public JailDropSource(Ole32.DropSource dropSource)
        {
            jailedIn 
= User32.GetForegroundWindow();
            
this.dropSource = dropSource;
        }

        
public int QueryContinueDrag(int escapePressed, int keyState)
        {
            
int result = dropSource.QueryContinueDrag(escapePressed, keyState);
            
if (result == DRAGDROP_S_DROP)
            {
                
if (!IsDropable())
                {
                    
return DRAGDROP_S_CANCEL;
                }
            }
            
return result;
        }

        
public int GiveFeedback(int effect)
        {
            
if (!IsDropable())
            {
                Cursor.Current 
= Cursors.No;
                
return 0;
            }
            
return dropSource.GiveFeedback(effect);
        }

        
private bool IsDropable()
        {
            
try
            {
                
if (jailedIn != User32.GetForegroundWindow())
                {
                    
return false;
                }
                User32.RECT rect 
= User32.GetWindowRect(jailedIn);
                var rectangle 
= new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
                
return rectangle.Contains(Cursor.Position);
            }
            
catch (Exception e)
            {
                LOGGER.Error(
"can not decide it is droppable or not, default to false", e);
                
return false;
            }
        }
    }
}

监听DropTarget的回调,拒绝某些场合下的Drop下来东西


using System.Windows.Forms;

namespace xxx
{
    
public class RangeAwareDropTarget : ProxyDropTarget
    {
        
private readonly RangeListener listener;
        
private readonly WordApplication application;

        
public RangeAwareDropTarget(WordApplication application, RangeListener listener, Ole32.DropTarget dropTarget)
            : 
base(dropTarget)
        {
            
this.listener = listener;
            
this.application = application;
        }

        
public override int DragOver(int keyState, long point, ref int effect)
        {
            var result 
= base.DragOver(keyState, point, ref effect);
            
if (ShouldHandle())
            {
                effect 
= (int) DragDropEffects.None;
            }
            
return result;
        }

        
public override int Drop(object dataObject, int keyState, long point, ref int effect)
        {
            
if (ShouldHandle())
            {
                DragLeave();
                
return 0;
            }
            
return base.Drop(dataObject, keyState, point, ref effect);
        }

        
private bool ShouldHandle()
        {
            var window 
= application.ActiveWindow;
            
if (window == null)
            {
                
return false;
            }
            
using (var detached = window.Detach())
            {
                var range 
= detached.RangeFromPoint(Cursor.Position.X, Cursor.Position.Y);
                
if (range == null)
                {
                    
return false;
                }
                
return listener.HandleChange(range, ChangeSource.Drop);
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值