Перетаскивание объектов в стиле Shell с помощью gdi32.dll DeleteObject - PullRequest
2 голосов
/ 08 февраля 2012

Итак ... Недавно я разрабатывал приложение Winforms C # в .NET 2.0, которое использует перетаскивание в стиле оболочки, описанное в большом руководстве: http://blogs.msdn.com/b/adamroot/archive/2008/02/19/shell-style-drag-and-drop-in-net-wpf-and-winforms.aspx

Это было использовано дляполупрозрачное изображение перетаскиваемого элемента управления во время действия перетаскивания.Однако второстепенная функциональность заключается в том, что файлы могут быть перетащены на эту панель, и некоторые действия выполняются на основе файлов.

Пользовательский элемент управления должен иметь возможность перетаскивания из одной панели макета потока изаскочил в другой (это все уже работает, проблем тут нет).Моя проблема возникает, когда я перетаскиваю элемент управления на первой панели и помещаю его на ту же панель (т. Е. Отменяю действие удаления).Если файл затем перетаскивается на панель, изображение файла будет заменено изображением прежнего элемента управления, который был перетянут, но не отброшен (если это имеет смысл?)

Я полагалэто должно быть вызвано тем, что Изображение не было утилизировано должным образом, и кажется, что я был прав.Я пытаюсь использовать метод DeleteObject gdi32.dll для удаления созданного HBitmap, но этот метод всегда возвращает false.

Приведенный ниже код показывает, как задается объект HBitmap и используется для созданияэффект перетаскивания.Если метод DisposeImage вызывается до вызова InitializeFromBitmap, изображение будет располагаться, как и ожидалось.Но если он вызывается после, вызов вернет 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.

...