C# 使用gdi32.dll DeleteObject处理Shell样式的拖放对象

C# 使用gdi32.dll DeleteObject处理Shell样式的拖放对象,c#,drag-and-drop,gdi,windows-shell,dispose,C#,Drag And Drop,Gdi,Windows Shell,Dispose,所以。。。我最近在.NET 2.0中开发了一个Winforms C#应用程序,它使用了伟大教程中描述的Shell样式的拖放: 这用于在拖动操作期间提供被拖动控件的半透明图像。但是,第二个功能是可以将文件放到该面板上,并根据文件执行某些操作 自定义控件必须能够从一个流布局面板拖动到另一个流布局面板中(这一切都已经起作用了,这里没有问题)。 当我在第一个面板中拖动控件并将其放入同一面板(即取消拖放操作)时,就会出现问题。如果随后将文件拖到面板上,则该文件的图像将替换为前一个控件的图像,即已拖动但未

所以。。。我最近在.NET 2.0中开发了一个Winforms C#应用程序,它使用了伟大教程中描述的Shell样式的拖放:

这用于在拖动操作期间提供被拖动控件的半透明图像。但是,第二个功能是可以将文件放到该面板上,并根据文件执行某些操作

自定义控件必须能够从一个流布局面板拖动到另一个流布局面板中(这一切都已经起作用了,这里没有问题)。 当我在第一个面板中拖动控件并将其放入同一面板(即取消拖放操作)时,就会出现问题。如果随后将文件拖到面板上,则该文件的图像将替换为前一个控件的图像,即已拖动但未删除的控件的图像(如果有意义的话?)

我认为这是由于图像处理不当造成的,看来我是对的。我试图使用gdi32.dll DeleteObject方法来处理创建的HBitmap,但此方法始终返回false

下面的代码显示了如何设置HBitmap对象,以及如何使用该对象创建拖动效果。如果在调用InitializeFromBitmap之前调用DisposeImage方法,则图像将按预期进行处理。但是如果在之后调用,调用将返回false,图像将保留在内存中。似乎有什么东西紧紧抓住HBitmap不放

private ShDragImage m_DragImageInfo = new ShDragImage();
    public void SetDragImage( System.Runtime.InteropServices.ComTypes.IDataObject theDataObject, Control theControl, System.Drawing.Point theCursorPosition )
    {
        theCursorPosition = theControl.PointToClient( theCursorPosition );
        int Width = theControl.Width;
        int Height = theControl.Height;

        // Ensure that the bitmap is disposed to prevent a memory leak
        IntPtr HBitmap = IntPtr.Zero;
        using ( Bitmap ControlImage = new Bitmap( Width, Height ) )
        {
            theControl.DrawToBitmap( ControlImage, new Rectangle( 0, 0, Width, Height ) );
            HBitmap = ControlImage.GetHbitmap();
            SetDragImage( theDataObject, HBitmap, theCursorPosition, ControlImage.Size );
        }
    }

    private void SetDragImage( System.Runtime.InteropServices.ComTypes.IDataObject theDataObject, IntPtr theHImage, System.Drawing.Point theCursorPosition, Size theImageSize )
    {
        m_DragImageInfo = new ShDragImage();

        Win32Size ImageSize;
        ImageSize.m_Width = theImageSize.Width;
        ImageSize.m_Height = theImageSize.Height;
        m_DragImageInfo.m_DragImageSize = ImageSize;

        Win32Point CursorPosition;
        CursorPosition.m_X = theCursorPosition.X;
        CursorPosition.m_Y = theCursorPosition.Y;

        m_DragImageInfo.m_CursorOffset = CursorPosition;
        m_DragImageInfo.m_ColorKey = Color.Magenta.ToArgb();
        m_DragImageInfo.m_DragImageHBitmap = theHImage;

        try
        {
            IDragSourceHelper sourceHelper = (IDragSourceHelper)new CDragDropHelper();
            sourceHelper.InitializeFromBitmap( ref m_DragImageInfo, theDataObject );
        }
        catch ( NotImplementedException theException )
        {
            DisposeImage();
            throw theException;
        }
    }

    public void DisposeImage()
    {
        CExternalFunctions.DeleteObject( m_DragImageInfo.m_DragImageHBitmap );
    }
另外,这里是用于存储数据的类。据我所见,在处理时,数据是以ClearStorage方法发布的

[ComVisible( true )]
public class CDataObject : System.Runtime.InteropServices.ComTypes.IDataObject, IDisposable
{
    private Dictionary<FORMATETC, STGMEDIUM> m_Storage;
    private EventHandler m_Delegate;

    public CDataObject()
    {
        m_Storage = new Dictionary<FORMATETC, STGMEDIUM>();
    }

    private void ClearStorage()
    {
        foreach ( KeyValuePair<FORMATETC, STGMEDIUM> Pair in m_Storage )
        {
            STGMEDIUM Medium = Pair.Value;
            CExternalFunctions.ReleaseStgMedium( ref Medium );
        }
        m_Storage.Clear();
    }

    public void SetDelegate( EventHandler theDelegate )
    {
        m_Delegate = theDelegate;
    }

    public void Dispose()
    {
        Dispose( true );
    }

    private void Dispose( bool isDisposing )
    {
        if ( isDisposing )
        {
            ClearStorage();
        }
    }

    #region COM IDataObject Members

    #region COM constants

    private const int c_AdviseNotSupported = unchecked( (int)0x80040003 );

    private const int c_FormatEtc = unchecked( (int)0x80040064 );
    private const int c_TypeMismatch = unchecked( (int)0x80040069 );
    private const int c_WrongFormat = unchecked( (int)0x8004006A );

    #endregion // COM constants

    #region Unsupported functions

    public int DAdvise( ref FORMATETC pFormatetc, ADVF advf, IAdviseSink adviseSink, out int connection )
    {
        throw Marshal.GetExceptionForHR( c_AdviseNotSupported );
    }

    public void DUnadvise( int connection )
    {
        throw Marshal.GetExceptionForHR( c_AdviseNotSupported );
    }

    public int EnumDAdvise( out IEnumSTATDATA enumAdvise )
    {
        throw Marshal.GetExceptionForHR( c_AdviseNotSupported );
    }

    public int GetCanonicalFormatEtc( ref FORMATETC formatIn, out FORMATETC formatOut )
    {
        formatOut = formatIn;
        return c_FormatEtc;
    }

    public void GetDataHere( ref FORMATETC format, ref STGMEDIUM medium )
    {
        throw new NotSupportedException();
    }

    #endregion // Unsupported functions

    public IEnumFORMATETC EnumFormatEtc( DATADIR theDirection )
    {
        EnumFORMATETC EnumFormat = null;
        // We only support GET
        if ( theDirection == DATADIR.DATADIR_GET )
        {
            EnumFormat = new EnumFORMATETC( m_Storage );
        }
        else
        {
            throw new NotImplementedException( "EnumFormatEtc method is not implemented" );
        }

        return EnumFormat;
    }

    public void GetData( ref FORMATETC theFormat, out STGMEDIUM theMedium )
    {
        theMedium = new STGMEDIUM();
        foreach ( KeyValuePair<FORMATETC, STGMEDIUM> Pair in m_Storage )
        {
            if ( ( Pair.Key.tymed & theFormat.tymed ) > 0
                && Pair.Key.dwAspect == theFormat.dwAspect
                && Pair.Key.cfFormat == theFormat.cfFormat )
            {
                STGMEDIUM Medium = Pair.Value;
                theMedium = CopyMedium( ref Medium );
                break;
            }
        }
    }

    public int QueryGetData( ref FORMATETC format )
    {
        int ReturnValue;

        ReturnValue = c_TypeMismatch;

        // Try to locate the data
        // TODO: The ret, if not S_OK, is only relevant to the last item
        foreach ( FORMATETC FormatEtc in m_Storage.Keys )
        {
            if ( ( FormatEtc.tymed & format.tymed ) > 0 )
            {
                if ( FormatEtc.cfFormat == format.cfFormat )
                {
                    // Found it, return S_OK;
                    ReturnValue = 0;
                    break;
                }
                else
                {
                    // Found the medium type, but wrong format
                    ReturnValue = c_WrongFormat;
                }
            }
            else
            {
                // Mismatch on medium type
                ReturnValue = c_TypeMismatch;
            }
        }

        return ReturnValue;
    }

    public void SetData( ref FORMATETC theFormatIn, ref STGMEDIUM theMedium, bool theRelease )
    {
        // If the format exists in our storage, remove it prior to resetting it
        foreach ( FORMATETC FormatEtc in m_Storage.Keys )
        {
            if ( ( FormatEtc.tymed & theFormatIn.tymed ) > 0
                && FormatEtc.dwAspect == theFormatIn.dwAspect
                && FormatEtc.cfFormat == theFormatIn.cfFormat )
            {
                m_Storage.Remove( FormatEtc );
                break;
            }
        }

        // If release is true, we'll take ownership of the medium.
        // If not, we'll make a copy of it.
        STGMEDIUM Medium = theMedium;
        if ( !theRelease )
        {
            Medium = CopyMedium( ref theMedium );
        }

        m_Delegate( this, new EventArgs() );

        m_Storage.Add( theFormatIn, Medium );
    }

    public void SetDataEx( string theFormat, object theData )
    {
        DataFormats.Format DataFormat = DataFormats.GetFormat( theFormat );

        // Initialize the format structure
        FORMATETC FormatETC = new FORMATETC();
        FormatETC.cfFormat = (short)DataFormat.Id;
        FormatETC.dwAspect = DVASPECT.DVASPECT_CONTENT;
        FormatETC.lindex = -1;
        FormatETC.ptd = IntPtr.Zero;

        // Try to discover the TYMED from the format and data
        TYMED Tymed = TYMED.TYMED_HGLOBAL;
        // If a TYMED was found, we can use the system DataObject
        // to convert our value for us.
        FormatETC.tymed = Tymed;

        // Set data on an empty DataObject instance
        DataObject DataObject = new DataObject();
        DataObject.SetData( theFormat, true, theData );

        // Now retrieve the data, using the COM interface.
        // This will perform a managed to unmanaged conversion for us.
        STGMEDIUM Medium;
        ( (System.Runtime.InteropServices.ComTypes.IDataObject)DataObject ).GetData( ref FormatETC, out Medium );
        try
        {
            // Now set the data on our data object
            SetData( ref FormatETC, ref Medium, true );
        }
        catch( Exception theException )
        {
            // Ensure the Medium is released if there are any problems
            CExternalFunctions.ReleaseStgMedium( ref Medium );
            throw theException;
        }
    }

    private STGMEDIUM CopyMedium( ref STGMEDIUM theMedium )
    {
        STGMEDIUM Medium = new STGMEDIUM();
        int Return = CExternalFunctions.CopyStgMedium( ref theMedium, ref Medium );
        if ( Return != 0 )
        {
            // If the copy operation fails, throw an exception using the HRESULT
            throw Marshal.GetExceptionForHR( Return );
        }

        return Medium;
    }

    #endregion

    [ComVisible( true )]
    private class EnumFORMATETC : IEnumFORMATETC
    {
        // Keep an array of the formats for enumeration
        private FORMATETC[] m_Formats;
        // The index of the next item
        private int m_CurrentIndex = 0;

        private const int c_OK = 0;
        private const int c_Failed = 1;

        internal EnumFORMATETC( Dictionary<FORMATETC, STGMEDIUM> storage )
        {
            // Get the formats from the list
            m_Formats = new FORMATETC[ storage.Count ];
            int Index = 0;
            foreach ( FORMATETC FormatEtc in storage.Keys )
            {
                m_Formats[ Index ] = FormatEtc;
                Index++;
            }
        }

        private EnumFORMATETC( FORMATETC[] theFormats )
        {
            // Get the formats as a copy of the array
            m_Formats = new FORMATETC[ theFormats.Length ];
            theFormats.CopyTo( this.m_Formats, 0 );
        }

        #region IEnumFORMATETC Members

        public void Clone( out IEnumFORMATETC theEnum )
        {
            EnumFORMATETC ReturnEnum = new EnumFORMATETC( m_Formats );
            ReturnEnum.m_CurrentIndex = m_CurrentIndex;
            theEnum = ReturnEnum;
        }

        public int Next( int theNumberOfElements, FORMATETC[] theRequestedFormats, int[] theNumberOfRequests )
        {
            // Start with zero fetched, in case we return early
            if ( theNumberOfRequests != null && theNumberOfRequests.Length > 0 )
            {
                theNumberOfRequests[ 0 ] = 0;
            }

            // This will count down as we fetch elements
            int ReturnCount = theNumberOfElements;

            int ReturnValue = c_OK;

            // Short circuit if they didn't request any elements, or didn't
            // provide room in the return array, or there are not more elements
            // to enumerate.
            if ( theNumberOfElements <= 0 || theRequestedFormats == null || m_CurrentIndex >= m_Formats.Length )
            {
                ReturnValue = c_Failed;
            }

            // If the number of requested elements is not one, then we must
            // be able to tell the caller how many elements were fetched.
            if ( ( theNumberOfRequests == null || theNumberOfRequests.Length < 1 ) && theNumberOfElements != 1 )
            {
                ReturnValue = c_Failed;
            }

            // If the number of elements in the return array is too small, we
            // throw. This is not a likely scenario, hence the exception.
            if ( theRequestedFormats.Length < theNumberOfElements )
            {
                throw new ArgumentException( "The number of elements in the return array is less than the number of elements requested" );
            }

            // Fetch the elements.
            for ( int i = 0; m_CurrentIndex < m_Formats.Length && ReturnCount > 0; i++ )
            {
                theRequestedFormats[ i ] = m_Formats[ m_CurrentIndex ];
                ReturnCount--;
                m_CurrentIndex++;
            }

            // Return the number of elements fetched
            if ( theNumberOfRequests != null && theNumberOfRequests.Length > 0 )
            {
                theNumberOfRequests[ 0 ] = theNumberOfElements - ReturnCount;
            }

            if ( ReturnCount != 0 )
            {
                ReturnValue = c_Failed;
            }

            return ReturnValue;
        }

        public int Reset()
        {
            m_CurrentIndex = 0;
            return c_OK;
        }

        /// <summary>
        /// Skips the number of elements requested.
        /// </summary>
        /// <param name="celt">The number of elements to skip.</param>
        /// <returns>If there are not enough remaining elements to skip, returns S_FALSE. Otherwise, S_OK is returned.</returns>
        public int Skip( int theNumberOfElementsToSkip )
        {
            int Success = c_OK;
            if ( m_CurrentIndex + theNumberOfElementsToSkip > m_Formats.Length )
            {
                Success = c_Failed;
            }

            m_CurrentIndex += theNumberOfElementsToSkip;
            return Success;
        }

        #endregion
    }
[ComVisible(true)]
公共类CDATA对象:System.Runtime.InteropServices.ComTypes.IDataObject,IDisposable
{
专用字典存储;
私有事件处理程序m_委托;
公共对象()
{
m_存储=新字典();
}
私人仓库()
{
foreach(m_存储器中的KeyValuePair对)
{
STGMEDIUM Medium=Pair.Value;
CExternalFunctions.ReleaseStgMedium(参考介质);
}
m_Storage.Clear();
}
public void SetDelegate(EventHandler theDelegate)
{
m_Delegate=删除门;
}
公共空间处置()
{
处置(真实);
}
私有无效处置(bool isDisposing)
{
if(isDisposing)
{
清除存储();
}
}
#区域COM IDataObject成员
#区域COM常数
private const int c_advisionnotsupported=未选中((int)0x80040003);
私有常量int c_FormatEtc=unchecked((int)0x80040064);
私有常量int c_TypeMismatch=未选中((int)0x80040069);
私有常量int c_错误格式=未选中((int)0x8004006A);
#endregion//COM常量
#区域不支持的函数
公共int数据接收器(参考格式ETC pFormatetc、ADVF ADVF、IADVessink ADVISSINK、out int连接)
{
抛出Marshal.GetExceptionForHR(不支持c_Advised);
}
公共连接(内部连接)
{
抛出Marshal.GetExceptionForHR(不支持c_Advised);
}
公共int EnumDAdvise(out IEnumSTATDATA enumAdvise)
{
抛出Marshal.GetExceptionForHR(不支持c_Advised);
}
public int GetCanonicalFormatEtc(ref FORMATETC formatIn,out FORMATETC formatOut)
{
formatOut=formatIn;
返回c_格式等;
}
public void GetDataHere(ref FORMATETC格式,ref STGMEDIUM介质)
{
抛出新的NotSupportedException();
}
#endregion//不支持的函数
公共IEnumFORMATETC枚举FormatETC(数据目录方向)
{
EnumFORMATETC EnumFormat=null;
//我们只支持得到
if(theDirection==DATADIR.DATADIR\u GET)
{
EnumFormat=新的EnumFORMATETC(m_存储);
}
其他的
{
抛出新的NotImplementedException(“EnumFormatEtc方法未实现”);
}
返回枚举格式;
}
public void GetData(ref format等格式,out STGMEDIUM主题)
{
主题媒体=新的标准媒体();
foreach(m_存储器中的KeyValuePair对)
{
如果((Pair.Key.tymed和format.tymed)>0
&&Pair.Key.dwAspect==format.dwAspect
&&Pair.Key.cfFormat==format.cfFormat)
{
STGMEDIUM Medium=Pair.Value;
媒体=复制媒体(参考媒体);
打破
}
}
}
public int QueryGetData(ref FORMATETC格式)
{
返回值;
ReturnValue=c_类型不匹配;
//尝试定位数据
//TODO:ret(如果不是S_OK)仅与最后一项相关
foreach(m_Storage.Keys中的FORMATETC FORMATETC)
{
如果((FormatEtc.tymed和format.tymed)>0)
{
if(FormatEtc.cfFormat==format.cfFormat)
{
//找到它,返回S_OK;
返回值=0;
打破
}
其他的
{
//找到媒体类型,但格式错误
ReturnValue=c_错误格式;
}
}
其他的
{
//介质类型不匹配
ReturnValue=c_类型不匹配;
}
}
返回值;
}
公共void SetData(ref format等格式、ref STGMEDIUM主题、bool theRelease)
{
//如果存储中存在该格式,请在重置之前将其删除
foreach(m_Storage.Keys中的FORMATETC FORMATETC)
{
如果((FormatEtc.tymed和formatin.tymed)>0
&&FormatEtc.dwAspect==formatin.dwAspect
&&格式等