Mostrar imagen en miniatura con el cursor del mouse mientras arrastra

8

Tengo una pequeña aplicación WPF que tiene una ventana con un control de imagen. El control de imagen muestra una imagen del sistema de archivos. Quiero que el usuario pueda arrastrar la imagen y soltarla en su escritorio o en cualquier lugar para guardarla. Funciona bien

Pero quiero mostrar una miniatura de imagen pequeña junto con el cursor del mouse cuando el usuario la arrastra. Al igual que arrastramos una imagen del explorador de archivos de Windows a otro lugar. ¿Cómo lograrlo?

Comportamiento actual de arrastrar / soltar

Comportamiento actual de arrastrar / soltar

Comportamiento deseado

Comportamiento deseado

Aquí está mi código XAML

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

Aquí está el código 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;
        }
    }
Riz
fuente
1
tal vez lo usarás DragDrop.GiveFeedback. Compruebe esto stackoverflow.com/questions/4878004/…
Rao Hammas Hussain
@RaoHammasHussain Está tratando de cambiar el cursor del mouse, eso no es lo que necesito.
Riz
solo una idea, tal vez pueda hacer algo como crear un contenedor oculto que aparezca mientras se arrastra y que la imagen arrastrada actual del niño y ese contenedor sigan el cursor del mouse.
Rao Hammas Hussain
@RaoHammasHussain El contenedor oculto tiene el problema de que permanecerá dentro de la ventana. No podemos mostrarlo afuera cuando el mouse sale de la ventana.
Riz
tengo algo que debería funcionar ... prueba este hombre stackoverflow.com/questions/1175870/…
Rao Hammas Hussain

Respuestas:

2

Oficialmente, se supone que debe utilizar la interfaz IDragSourceHelper para agregar un mapa de bits de vista previa a una operación de arrastrar y soltar.

Desafortunadamente, esta interfaz usa el método IDataObject :: SetData que no está implementado a nivel COM por la clase .NET DataObject, solo a nivel .NET.

La solución es reutilizar un IDataObject proporcionado por el Shell para cualquier elemento del Shell (aquí un archivo), utilizando la función SHCreateItemFromParsingName y el método IShellItem :: BindToHandler .

Tenga en cuenta que estas funciones agregan automáticamente formatos de portapapeles como FileDrop, pero aún tenemos que usar IDragSourceHelper para agregar la imagen de vista previa.

Así es como puedes usarlo:

...
// 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);
...

Y aquí está el código:

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);
    }
}
Simon Mourier
fuente
@SimmonMourier Esto es lo más parecido a lo que estaba buscando. Gracias por tu tiempo.
Riz
0

Aquí, prueba esto. "Recoge" un cuadrado rojo transparente alrededor de la posición del mouse y lo "suelta" cuando hace clic nuevamente.

En realidad, desearía crear el hilo haciendo la interoperabilidad al hacer clic y detenerlo (no abortarlo) cuando lo suelte.

<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>

Y código detrás:

[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;
    }
}
Adam Brown
fuente