所以...我最近在.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。