Anzeigen der Miniaturansicht des Bildes mit dem Mauszeiger beim Ziehen

8

Ich habe eine kleine WPF-Anwendung, die ein Fenster mit einem Bildsteuerelement hat. Die Bildsteuerung zeigt ein Bild aus dem Dateisystem. Ich möchte, dass der Benutzer das Bild ziehen und auf den Desktop oder irgendwo hin ablegen kann, um es zu speichern. Es funktioniert gut.

Aber ich möchte ein kleines Bild-Miniaturbild zusammen mit dem Mauszeiger anzeigen, wenn der Benutzer es zieht. Genau wie wir ein Bild aus dem Windows-Datei-Explorer an eine andere Stelle ziehen. Wie erreicht man das?

Aktuelles Verhalten von Drag / Drop

Aktuelles Verhalten von Drag / Drop

Gewünschtes Verhalten

Gewünschtes Verhalten

Hier ist mein XAML-Code

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

Hier ist C # Code

   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
quelle
1
Vielleicht wirst du verwenden DragDrop.GiveFeedback. Überprüfen Sie diese stackoverflow.com/questions/4878004/…
Rao Hammas Hussain
@RaoHammasHussain Es wird versucht, den Mauszeiger zu ändern, das brauche ich nicht.
Riz
Nur eine Idee, vielleicht können Sie so etwas wie einen versteckten Container erstellen, der beim Ziehen angezeigt wird und dessen untergeordnetes aktuelles Bild gezogen wird und dessen Container dem Mauszeiger folgt.
Rao Hammas Hussain
@RaoHammasHussain Der versteckte Container hat das Problem, dass er im Fenster verbleibt. Wir können es nicht draußen zeigen, wenn die Maus das Fenster verlässt.
Riz
Ich
Rao Hammas Hussain

Antworten:

2

Offiziell sollten Sie die IDragSourceHelper- Oberfläche verwenden, um einer Drag & Drop-Operation eine Vorschau-Bitmap hinzuzufügen.

Leider verwendet diese Schnittstelle die IDataObject :: SetData-Methode, die von der .NET DataObject-Klasse nicht auf COM-Ebene implementiert wird, sondern nur auf .NET-Ebene.

Die Lösung besteht darin, ein von der Shell bereitgestelltes IDataObject für jedes Shell-Element (hier eine Datei) mithilfe der Funktion SHCreateItemFromParsingName und der Methode IShellItem :: BindToHandler wiederzuverwenden .

Beachten Sie, dass diese Funktionen automatisch Zwischenablageformate wie FileDrop hinzufügen. Wir müssen jedoch weiterhin IDragSourceHelper verwenden, um das Vorschaubild hinzuzufügen.

So können Sie es verwenden:

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

Und hier ist der Code:

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
quelle
@SimmonMourier Dies ist am nächsten an dem, was ich gesucht habe. Vielen Dank für Ihre Zeit.
Riz
0

Hier, versuchen Sie dies. Es "nimmt" ein rotes transparentes Quadrat um die Mausposition auf und "lässt" es fallen, wenn Sie erneut klicken.

In Wirklichkeit möchten Sie den Thread erstellen, der das Interop beim Klicken ausführt, und ihn stoppen (nicht abbrechen), wenn Sie ihn löschen.

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

Und Code dahinter:

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