ドラッグ中にマウスカーソルで画像のサムネイルを表示


8

Imageコントロール付きのウィンドウを持つ小さなWPFアプリケーションがあります。画像コントロールは、ファイルシステムからの画像を表示します。ユーザーが画像をドラッグしてデスクトップまたは任意の場所にドロップして保存できるようにしたい。正常に動作しています。

しかし、ユーザーがドラッグしたときに、マウスカーソルと共に小さな画像のサムネイルを表示したいと思います。Windowsファイルエクスプローラーから別の場所に画像をドラッグするのと同じです。それを達成する方法は?

ドラッグ/ドロップの現在の動作

ドラッグ/ドロップの現在の動作

望ましい行動

望ましい行動

これが私のXAMLコードです

<Grid>
   <Image x:Name="img" Height="100" Width="100" Margin="100,30,0,0"/>
</Grid>

ここにC#コードがあります

   public partial class MainWindow : Window
    {
        string imgPath;
        Point start;
        bool dragStart = false;

        public MainWindow()
        {
            InitializeComponent();
            imgPath = "C:\\Pictures\\flower.jpg";

            ImageSource imageSource = new BitmapImage(new Uri(imgPath));
            img.Source = imageSource;
            window.PreviewMouseMove += Window_PreviewMouseMove;
            window.PreviewMouseUp += Window_PreviewMouseUp;
            window.Closing += Window_Closing;
            img.PreviewMouseLeftButtonDown += Img_PreviewMouseLeftButtonDown;
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            window.PreviewMouseMove -= Window_PreviewMouseMove;
            window.PreviewMouseUp -= Window_PreviewMouseUp;
            window.Closing -= Window_Closing;
            img.PreviewMouseLeftButtonDown -= Img_PreviewMouseLeftButtonDown;
        }


        private void Window_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (!dragStart) return;
            if (e.LeftButton != MouseButtonState.Pressed)
            {
                dragStart = false; return;
            }

            Point mpos = e.GetPosition(null);
            Vector diff = this.start - mpos;

            if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance &&
                Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
            {
                string[] file = { imgPath };
                DataObject d = new DataObject();
                d.SetData(DataFormats.Text, file[0]);
                d.SetData(DataFormats.FileDrop, file);
                DragDrop.DoDragDrop(this, d, DragDropEffects.Copy);
            }
        }

        private void Img_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            this.start = e.GetPosition(null);
            dragStart = true;
        }

        private void Window_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            dragStart = false;
        }
    }

1
多分あなたが使用しますDragDrop.GiveFeedback。このstackoverflow.com/questions/4878004/…を
Rao Hammas Hussain

@RaoHammasHussainマウスカーソルを変更しようとしています。それは私が必要としていることではありません。
リズ

単にアイデアかもしれませんが、ドラッグ中に表示される非表示のコンテナーを作成し、子に現在ドラッグされているイメージを作成し、そのコンテナーをマウスカーソルに従うようにすることができます。
ラオハマスフセイン

@RaoHammasHussain非表示のコンテナはウィンドウ内に残るという問題があります。マウスがウィンドウを離れると、それを外に表示できなくなります。
リズ

...動作するはず何かを得たこの男試すstackoverflow.com/questions/1175870/...
ラオHammasフセイン

回答:


2

正式には、IDragSourceHelperインターフェイスを使用して、プレビュービットマップをドラッグ&ドロップ操作に追加します。

残念ながら、このインターフェイスは、.NET DataObjectクラスによってCOMレベルで実装されていないIDataObject :: SetDataメソッドを使用します。.NETレベルでのみです。

解決策は、SHCreateItemFromParsingName関数IShellItem :: BindToHandlerメソッドを使用して、シェルアイテム(ここではファイル)の代わりにシェルによって提供されるIDataObjectを再利用することです。

これらの関数はFileDropなどのクリップボード形式を自動的に追加しますが、プレビューイメージを追加するにはIDragSourceHelperを使用する必要があります。

これはあなたがそれを使う方法です:

...
// get IDataObject from the Shell so it can handle more formats
var dataObject = DataObjectUtilities.GetFileDataObject(imgPath);

// add the thumbnail to the data object
DataObjectUtilities.AddPreviewImage(dataObject, imgPath);

// start d&d
DragDrop.DoDragDrop(this, dataObject, DragDropEffects.All);
...

そしてここにコードがあります:

public static class DataObjectUtilities
{
    public static void AddPreviewImage(System.Runtime.InteropServices.ComTypes.IDataObject dataObject, string imgPath)
    {
        if (dataObject == null)
            throw new ArgumentNullException(nameof(dataObject));

        var ddh = (IDragSourceHelper)new DragDropHelper();
        var dragImage = new SHDRAGIMAGE();

        // note you should use a thumbnail here, not a full-sized image
        var thumbnail = new System.Drawing.Bitmap(imgPath);
        dragImage.sizeDragImage.cx = thumbnail.Width;
        dragImage.sizeDragImage.cy = thumbnail.Height;
        dragImage.hbmpDragImage = thumbnail.GetHbitmap();
        Marshal.ThrowExceptionForHR(ddh.InitializeFromBitmap(ref dragImage, dataObject));
    }

    public static System.Runtime.InteropServices.ComTypes.IDataObject GetFileDataObject(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        Marshal.ThrowExceptionForHR(SHCreateItemFromParsingName(filePath, null, typeof(IShellItem).GUID, out var item));
        Marshal.ThrowExceptionForHR(item.BindToHandler(null, BHID_DataObject, typeof(System.Runtime.InteropServices.ComTypes.IDataObject).GUID, out var dataObject));
        return (System.Runtime.InteropServices.ComTypes.IDataObject)dataObject;
    }

    private static readonly Guid BHID_DataObject = new Guid("b8c0bd9f-ed24-455c-83e6-d5390c4fe8c4");

    [DllImport("shell32", CharSet = CharSet.Unicode)]
    private static extern int SHCreateItemFromParsingName(string path, System.Runtime.InteropServices.ComTypes.IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IShellItem ppv);

    [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IShellItem
    {
        [PreserveSig]
        int BindToHandler(System.Runtime.InteropServices.ComTypes.IBindCtx pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);

        // other methods are not defined, we don't need them
    }

    [ComImport, Guid("4657278a-411b-11d2-839a-00c04fd918d0")] // CLSID_DragDropHelper
    private class DragDropHelper
    {
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct SIZE
    {
        public int cx;
        public int cy;
    }

    // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/ns-shobjidl_core-shdragimage
    [StructLayout(LayoutKind.Sequential)]
    private struct SHDRAGIMAGE
    {
        public SIZE sizeDragImage;
        public POINT ptOffset;
        public IntPtr hbmpDragImage;
        public int crColorKey;
    }

    // https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-idragsourcehelper
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("DE5BF786-477A-11D2-839D-00C04FD918D0")]
    private interface IDragSourceHelper
    {
        [PreserveSig]
        int InitializeFromBitmap(ref SHDRAGIMAGE pshdi, System.Runtime.InteropServices.ComTypes.IDataObject pDataObject);

        [PreserveSig]
        int InitializeFromWindow(IntPtr hwnd, ref POINT ppt, System.Runtime.InteropServices.ComTypes.IDataObject pDataObject);
    }
}

@SimmonMourierこれは私が探していたものに最も近い。お時間をいただきありがとうございます。
リズ

0

ここで、これを試してください。マウスの位置の周りにある赤い透明な四角形を「ピッキング」し、もう一度クリックすると「ドロップ」します。

実際には、クリック時に相互運用を実行するスレッドを作成し、ドロップするときにスレッドを停止(中止しない)する必要があります。

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <Button x:Name="Clicker" Click="OnClick">Click Me!</Button>
        <Popup Placement="Absolute" PlacementRectangle="{Binding Placement}" AllowsTransparency="True" IsOpen="{Binding IsOpen}"
               MouseUp="Cancel">
            <Grid Margin="10" Background="#7fff0000">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="400"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="400"></RowDefinition>
                </Grid.RowDefinitions>
                <TextBlock>Hello!</TextBlock>
            </Grid>
        </Popup>
    </Grid>
</Window>

そしてコードビハインド:

[StructLayout(LayoutKind.Sequential)]
public struct InteropPoint
{
    public int X;
    public int Y;
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
    private bool isOpen;
    private int xPos;
    private int yPos;

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool GetCursorPos(ref InteropPoint lpPoint);

    private Thread t;

    public MainWindow()
    {
        InitializeComponent();

        t = new Thread(() =>
        {
            while (true)
            {
                //Logic
                InteropPoint p = new InteropPoint();
                GetCursorPos(ref p);

                //Update UI
                Dispatcher.BeginInvoke(new Action(() =>
                {
                    XPos = (int) p.X;
                    YPos = (int) p.Y;
                }));

                Thread.Sleep(10);
            }
        });

        t.Start();
    }

    protected override void OnClosing(CancelEventArgs e)
    {
        t.Abort();
    }

    private void OnClick(object sender, RoutedEventArgs e)
    {
        IsOpen = !IsOpen;
    }

    public int XPos
    {
        get => xPos;
        set
        {
            if (value == xPos) return;
            xPos = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(Placement));
        }
    }

    public int YPos
    {
        get => yPos;
        set
        {
            if (value == yPos) return;
            yPos = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(Placement));
        }
    }

    public bool IsOpen
    {
        get => isOpen;
        set
        {
            if (value == isOpen) return;
            isOpen = value;
            OnPropertyChanged();
        }
    }

    public Rect Placement => new Rect(XPos - 200, YPos - 200, 400, 400);

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    private void Cancel(object sender, MouseButtonEventArgs e)
    {
        IsOpen = false;
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.