エクスプローラで項目をドラッグ中に表示される半透明のイメージを、ListViewコントロールで実現する方法について解説します。
ListViewコントロールはドラッグ中の半透明イメージを表示する機能をサポートしていませんが、Win32 APIにはこれを実現する機能が実装されています。よってListViewを継承したクラスを作成し、このAPIを内部から呼び出すようにします。Win32 Image List関数 | MSDN
C#からWin32 APIへアクセする方法は、C++のDLLをC#から利用する方法を参照してください。
public static class Win32ImageList { [DllImport( "comctl32.dll" )] public static extern bool ImageList_BeginDrag( IntPtr himlTrack, // イメージリストのハンドル int iTrack, // ドラッグするイメージの番号 int dxHotspot, // ドラッグ位置 (イメージ位置との相対座標) int dyHotspot // ); [DllImport( "comctl32.dll" )] public static extern bool ImageList_DragEnter( IntPtr hwndLock, // ドラッグするイメージの親となるウィンドウのハンドル int x, // ドラッグするイメージの表示位置 (ウィンドウ位置との相対座標) int y // ); [DllImport( "comctl32.dll" )] public static extern bool ImageList_DragLeave( IntPtr hwndLock // ドラッグするイメージの親となるウィンドウのハンドル ); [DllImport( "comctl32.dll" )] public static extern bool ImageList_DragMove( int x, // ドラッグするイメージの表示位置 (ウィンドウ位置との相対座標) int y // ); [DllImport( "comctl32.dll" )] public static extern void ImageList_EndDrag(); [DllImport( "user32.dll" )] public static extern IntPtr GetDesktopWindow(); }
ドラッグ開始時に呼び出されるOnItemDrag()メソッド内でドラッグするイメージを作成し、そのハンドルをImageList_BeginDragに与えることでドラッグを開始します。
ドラッグに使用できるイメージは256×256ドットに制限されるため、イメージに含める範囲をマウスポインタの周囲に限定しています。
protected override void OnItemDrag( ItemDragEventArgs e ) { base.OnItemDrag( e ); if( e.Button == MouseButtons.Left ) { Point mousePosition = PointToClient( Control.MousePosition ); // ドラッグするイメージのサイズを求める Rectangle imageRectangle = new Rectangle(); foreach( ListViewItem item in SelectedItems ) { if( imageRectangle.IsEmpty ) { imageRectangle = item.Bounds; } else { imageRectangle = Rectangle.Union( imageRectangle, item.Bounds ); } } Rectangle cursorNearRectangle = new Rectangle( mousePosition.X - 128, mousePosition.Y - 128, 256, 256 ); imageRectangle.Intersect( cursorNearRectangle ); // ドラッグするイメージを作成する Bitmap bitmap = new Bitmap( imageRectangle.Width, imageRectangle.Height ); Graphics graphics = Graphics.FromImage( bitmap ); foreach( ListViewItem item in SelectedItems ) { Point itemPosition = item.Position; itemPosition.Offset( -imageRectangle.X, -imageRectangle.Y ); using( Brush fontBrush = new SolidBrush( item.ForeColor ) ) { graphics.DrawString( item.Text, item.Font, fontBrush, itemPosition ); } } this.imageList.Images.Clear(); this.imageList.ImageSize = bitmap.Size; this.imageList.Images.Add( bitmap ); graphics.Dispose(); Point dragPoint = new Point( mousePosition.X - imageRectangle.X, mousePosition.Y - imageRectangle.Y ); Win32ImageList.ImageList_BeginDrag( this.imageList.Handle, 0, dragPoint.X, dragPoint.Y ); ListViewItem[] dragData = new ListViewItem[ SelectedItems.Count ]; SelectedItems.CopyTo( dragData, 0 ); DragDropEffects dragDropEffect = DoDragDrop( dragData, DragDropEffects.Move ); // ドラッグ後の処理を行う if( dragDropEffect == DragDropEffects.Move ) { BeginUpdate(); foreach( ListViewItem item in dragData ) { Items.Remove( item ); } EndUpdate(); Win32ImageList.ImageList_EndDrag(); } } }
コントロールの領域内にドラッグされたときに、ImageList_DragEnterを呼び出します。このときにデスクトップをイメージの親ウィンドウとすることで、コントロール外でもイメージを表示できるようになります。
protected override void OnDragEnter( DragEventArgs drgevent ) { base.OnDragEnter( drgevent ); IntPtr ownerWindow = Win32ImageList.GetDesktopWindow(); Win32ImageList.ImageList_DragEnter( ownerWindow, Cursor.Position.X, Cursor.Position.Y ); if( drgevent.Data.GetDataPresent( typeof( ListViewItem[] ) ) ) { drgevent.Effect = DragDropEffects.Move; } else { drgevent.Effect = DragDropEffects.None; } }
ドラッグ中のイメージの描画は、ImageList_DragMoveにより行います。コントロール外へドラッグされたときにも描画を実行するように、OnQueryContinueDragで処理しています。
protected override void OnQueryContinueDrag( QueryContinueDragEventArgs qcdevent ) { base.OnQueryContinueDrag( qcdevent ); Win32ImageList.ImageList_DragMove( Cursor.Position.X, Cursor.Position.Y ); const int MouseRightButton = 2; if( ( qcdevent.KeyState & MouseRightButton ) == MouseRightButton ) { qcdevent.Action = DragAction.Cancel; } }
ドロップされたときには、ImageList_DragLeaveによりイメージの表示を終了します。
protected override void OnDragDrop( DragEventArgs drgevent ) { base.OnDragDrop( drgevent ); IntPtr ownerWindow = Win32ImageList.GetDesktopWindow(); Win32ImageList.ImageList_DragLeave( ownerWindow ); if( drgevent.Data.GetDataPresent( typeof( ListViewItem[] ) ) ) { ListViewItem[] dragData = ( ListViewItem[] )drgevent.Data.GetData( typeof( ListViewItem[] ) ); foreach( ListViewItem listViewItem in dragData ) { Items.Add( ( ListViewItem )listViewItem.Clone() ); } Invalidate( false ); } }
Polyglot Image Searchによる利用例