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

时间:2012-02-07 20:36:31

标签: c# drag-and-drop gdi windows-shell dispose

所以...我最近在.NET 2.0中开发了一个Winforms C#应用程序,该应用程序使用了这个伟大教程中描述的Shell Style拖放: http://blogs.msdn.com/b/adamroot/archive/2008/02/19/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx

这用于在拖动动作期间给出拖动控件的半透明图像。但是,辅助功能是可以将文件拖放到此面板上,并根据文件执行某些操作。

自定义控件必须能够从一个流布局面板拖放到另一个流布局面板中(这一切都已经有效,这里没有问题)。 当我在第一个面板中拖动控件并将其放入同一面板(即取消放置操作)时,会出现问题。如果一个文件被拖到面板上,文件的图像将被前一个控件的图像替换,一个被拖动但没有掉落的图像(如果这有意义吗?)

我认为这是由于图像没有被妥善处理造成的,而且看起来我是对的。我试图使用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
    }

有什么我错过的吗?有没有办法强制释放这个HBitmap,以便我可以正确处理它? 对此的任何帮助将不胜感激。

编辑: 真的很高兴在这个问题上获得任何形式的帮助。我已经尝试确保HDC被释放,这仍然导致对DeleteObject的调用返回false。

0 个答案:

没有答案