ドラッグ中に半透明のイメージを表示する方法

エクスプローラで項目をドラッグ中に表示される半透明のイメージを、ListViewコントロールで実現する方法について解説します。

ImageListのラッパークラスの作成

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();
}

ListViewを継承したクラスの作成

ドラッグ開始時に呼び出される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による利用例

参考

Microsoft Learnから検索