简体   繁体   English

移植WinForms拖放到WPF拖放

[英]Porting WinForms drag and drop to WPF drag and drop

I am porting my program from WinForms to WPF and have ran into some issues with the drag and drop. 我正在将我的程序从WinForms移植到WPF,并遇到了拖放问题。 It should allow for dragging from a TreeView (it is like a file explorer) to a textbox which opens the file. 它应该允许从TreeView(它就像文件浏览器)拖动到打开文件的文本框。 However, the WPF version acts like a copy-and-paste of the TreeViewItem 's header text automatically. 但是,WPF版本的作用类似于TreeViewItem的头文本的自动复制和粘贴。 I think I just have something mixed up? 我想我只是混淆了什么? Possibly the DataObject stuff. 可能是DataObject东西。

The fully functional, relevant WinForms code: 功能齐全,相关的WinForms代码:

private void treeView1_MouseMove(object sender, MouseEventArgs e)
{
    if (e.Button != MouseButtons.Left) return;
    TreeNode node = treeView1.GetNodeAt(e.Location);
    if (node != null) treeView1.DoDragDrop(node, DragDropEffects.Move);
}

textbox[i].DragDrop += (o, ee) =>
{
     if (ee.Data.GetDataPresent(typeof(TreeNode)))
     {
         TreeNode node = (TreeNode)ee.Data.GetData(typeof(TreeNode));   
         ((Textbox)o).Text = File.ReadAllLines(pathRoot + node.Parent.FullPath);
         ...

The WPF code that should do the same thing: 应该做同样事情的WPF代码:

private void TreeView_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    TreeViewItem item = e.Source as TreeViewItem;
    if (item != null)
    {
        DataObject dataObject = new DataObject();
        dataObject.SetData(DataFormats.StringFormat, GetFullPath(item));
        DragDrop.DoDragDrop(item, dataObject, DragDropEffects.Move);
    }
}

//textbox[i].PreviewDrop += textbox_Drop;
private void textbox_Drop(object sender, DragEventArgs e)
{
    TreeViewItem node = (TreeViewItem)e.Data.GetData(typeof(TreeViewItem)); //null?
    ((Textbox)sender).Text = ""; 
    //this is being executed BUT then the node's header text is being pasted
    //also, how do I access the DataObject I passed?
}

Problem: In my WPF version, I am setting the textbox's text to empty (as a test), which occurs, but afterwards the TreeViewItem's header text is being pasted which is not what I want. 问题:在我的WPF版本中,我将文本框的文本设置为空(作为测试),但是之后会粘贴TreeViewItem的标题文本,这不是我想要的。

Questions: What is the correct way to port this WinForms code to WPF? 问题:将此WinForms代码移植到WPF的正确方法是什么? Why is the text being pasted in the WPF version? 为什么文本被粘贴在WPF版本中? How do I prevent that? 我该如何预防呢? Am I using the correct events? 我使用的是正确的事件吗? How do I access the DataObject in textbox_Drop so that I can open the file like I did in the WinForms version? 如何在textbox_Drop访问DataObject ,以便我可以像在WinForms版本中那样打开文件? Why is TreeViewItem node always null in the WPF version? 为什么TreeViewItem节点在WPF版本中始终为空?

Ah, what the heck, I'll expand my comment to an answer: 啊,到底是什么,我会将我的评论扩展到答案:

The link to read, as mentioned, is this: http://msdn.microsoft.com/en-us/library/hh144798.aspx 如上所述,要阅读的链接是: http//msdn.microsoft.com/en-us/library/hh144798.aspx

Short story, the TextBox -derived controls already implement most of the "guts" for basic drag/drop operations, and it is recommended that you extend that rather than provide explicit DragEnter/DragOver/Drop handlers. 简而言之, TextBox派生的控件已经实现了大多数基本拖放操作的“胆量”,建议您扩展它而不是提供显式的DragEnter/DragOver/Drop处理程序。

Assuming a tree "data" structure like: 假设树“数据”结构如下:

public class TreeThing
{
   public string Description { get; set; }
   public string Path { get; set; }
}

The handlers might look something like this: 处理程序可能看起来像这样:

this.tb.AddHandler(UIElement.DragOverEvent, new DragEventHandler((sender, e) =>
    {
        e.Effects = !e.Data.GetDataPresent("treeThing") ? 
            DragDropEffects.None : 
            DragDropEffects.Copy;                    
    }), true);

this.tb.AddHandler(UIElement.DropEvent, new DragEventHandler((sender, e) =>
{
    if (e.Data.GetDataPresent("treeThing"))
    {
        var item = e.Data.GetData("treeThing") as TreeThing;
        if (item != null)
        {
            tb.Text = item.Path;
            // TODO: Actually open up the file here
        }
    }
}), true);

And just for giggles, here's a quick-and-dirty test app that is pure showboating in it's use of the Reactive Extensions (Rx) for the drag start stuff: 而且只是为了咯咯笑,这里是一个快速而肮脏的测试应用程序,它是纯粹的showboating,它使用Reactive Extensions(Rx)来实现拖动:

XAML: XAML:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TreeView x:Name="tree" Grid.Column="0" ItemsSource="{Binding TreeStuff}" DisplayMemberPath="Description"/>
        <TextBox x:Name="tb" Grid.Column="1" AllowDrop="True" Text="Drop here" Height="30"/>
    </Grid>
</Window>

Nasty code-behind (too lazy to MVVM this): 令人讨厌的代码隐藏(对MVVM来说太懒了):

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Reactive.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            TreeStuff = new ObservableCollection<TreeThing>()
                {
                    new TreeThing() { Description="file 1",  Path = @"c:\temp\test.txt" },
                    new TreeThing() { Description="file 2", Path = @"c:\temp\test2.txt" },
                    new TreeThing() { Description="file 3", Path = @"c:\temp\test3.txt" },
                };

            var dragStart = 
                from mouseDown in 
                    Observable.FromEventPattern<MouseButtonEventHandler, MouseEventArgs>(
                        h => tree.PreviewMouseDown += h, 
                        h => tree.PreviewMouseDown -= h)
                let startPosition = mouseDown.EventArgs.GetPosition(null)
                from mouseMove in 
                    Observable.FromEventPattern<MouseEventHandler, MouseEventArgs>(
                        h => tree.MouseMove += h, 
                        h => tree.MouseMove -= h)
                let mousePosition = mouseMove.EventArgs.GetPosition(null)
                let dragDiff = startPosition - mousePosition
                where mouseMove.EventArgs.LeftButton == MouseButtonState.Pressed && 
                    (Math.Abs(dragDiff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(dragDiff.Y) > SystemParameters.MinimumVerticalDragDistance)
                select mouseMove;

            dragStart.ObserveOnDispatcher().Subscribe(start =>
                {
                    var nodeSource = this.FindAncestor<TreeViewItem>(
                        (DependencyObject)start.EventArgs.OriginalSource);
                    var source = start.Sender as TreeView;
                    if (nodeSource == null || source == null)
                    {
                        return;
                    }
                    var data = (TreeThing)source
                        .ItemContainerGenerator
                        .ItemFromContainer(nodeSource);
                    DragDrop.DoDragDrop(nodeSource, new DataObject("treeThing", data), DragDropEffects.All);
                });

            this.tb.AddHandler(UIElement.DragOverEvent, new DragEventHandler((sender, e) =>
                {
                    e.Effects = !e.Data.GetDataPresent("treeThing") ? 
                        DragDropEffects.None : 
                        DragDropEffects.Copy;                    
                }), true);

            this.tb.AddHandler(UIElement.DropEvent, new DragEventHandler((sender, e) =>
            {
                if (e.Data.GetDataPresent("treeThing"))
                {
                    var item = e.Data.GetData("treeThing") as TreeThing;
                    if (item != null)
                    {
                        tb.Text = item.Path;
                       // TODO: Actually open up the file here
                    }
                }
            }), true);
            this.DataContext = this;
        }


        private T FindAncestor<T>(DependencyObject current)
            where T:DependencyObject
        {
            do
            {
                if (current is T)
                {
                    return (T)current;
                }
                current = VisualTreeHelper.GetParent(current);
            }
            while (current != null);
            return null;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public ObservableCollection<TreeThing> TreeStuff { get; set; }

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public class TreeThing
    {
        public string Description { get; set; }
        public string Path { get; set; }
    }
}

You've got more than one problem, enough to make this difficult. 你有不止一个问题,足以让这个问题变得困难。 First issue is that you got the drag object wrong, you are dragging a string but still checking for a TreeViewItem. 第一个问题是你的拖拽对象错误,你拖动一个字符串,但仍然检查TreeViewItem。 Just use the same approach as you used in Winforms, dragging the node. 只需使用与Winforms中使用的相同方法,拖动节点即可。 Second problem is that TextBox already implements D+D support and that gets in the way of your code. 第二个问题是TextBox已经实现了D + D支持,这会妨碍您的代码。 And the reason you saw the text show up after the drop. 你看到文字出现后丢失的原因。

Let's tackle the start of the drag first. 让我们首先解决阻力的开始。 You'll need to do a bit of extra work since the way you started the drag interferes with the normal usage of the TreeView, it gets very hard to select a node. 你需要做一些额外的工作,因为你开始拖动的方式干扰了TreeView的正常使用,很难选择一个节点。 Only start the drag when the mouse was moved far enough: 只有在鼠标移动到足够远时才开始拖动:

    private Point MouseDownPos;

    private void treeView1_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
        MouseDownPos = e.GetPosition(treeView1);
    }

    private void treeView1_PreviewMouseMove(object sender, MouseEventArgs e) {
        if (e.LeftButton == MouseButtonState.Released) return;
        var pos = e.GetPosition(treeView1);
        if (Math.Abs(pos.X - MouseDownPos.X) >= SystemParameters.MinimumHorizontalDragDistance ||
            Math.Abs(pos.Y - MouseDownPos.Y) >= SystemParameters.MinimumVerticalDragDistance) {
            TreeViewItem item = e.Source as TreeViewItem;
            if (item != null) DragDrop.DoDragDrop(item, item, DragDropEffects.Copy);
        }
    }

Now the drop, you will need to implement the DragEnter, DragOver and Drop event handlers to avoid the default D+D support built into TextBox from getting in the way. 现在,您将需要实现DragEnter,DragOver和Drop事件处理程序,以避免TextBox中内置的默认D + D支持阻碍。 Setting the e.Handled property to true is necessary: 将e.Handled属性设置为true是必要的:

    private void textBox1_PreviewDragEnter(object sender, DragEventArgs e) {
        if (e.Data.GetDataPresent(typeof(TreeViewItem))) e.Effects = e.AllowedEffects;
        e.Handled = true;
    }

    private void textBox1_PreviewDrop(object sender, DragEventArgs e) {
        var item = (TreeViewItem)e.Data.GetData(typeof(TreeViewItem));
        textBox1.Text = item.Header.ToString();   // Replace this with your own code
        e.Handled = true;
    }

    private void textBox1_PreviewDragOver(object sender, DragEventArgs e) {
        e.Handled = true;
    }

Problem : In my WPF version, I am setting the textbox's text to empty (as a test), which occurs, but afterwards the TreeViewItem's header text is being pasted which is not what I want. 问题 :在我的WPF版本中,我将文本框的文本设置为空(作为测试),但是之后会粘贴TreeViewItem的标题文本,这不是我想要的。

I think a parent UI element is handling (and therefore overriding) the Drop event so you're not getting the results you expect. 我认为父UI元素正在处理(并因此覆盖) Drop事件,因此您无法获得预期的结果。 As a matter of fact, when trying to recreate your issue, I couldn't even get my TextBox.Drop event to fire. 事实上,在尝试重新创建问题时,我甚至无法触发我的TextBox.Drop事件。 However, using the TextBox's PreviewDrop event, I was able to get what (I think) is your expected result. 但是,使用TextBox的PreviewDrop事件,我能够得到(我认为)您预期的结果。 Try this: 尝试这个:

    private void textBox1_PreviewDrop(object sender, DragEventArgs e)
    {
        TextBox tb = sender as TextBox;
        if (tb != null)
        {
            // If the DataObject contains string data, extract it.
            if (e.Data.GetDataPresent(DataFormats.StringFormat))
            {
                string fileName = e.Data.GetData(DataFormats.StringFormat) as string;
                using (StreamReader s = File.OpenText(fileName))
                {
                    ((TextBox)sender).Text = s.ReadToEnd();
                }
            }
        }
        e.Handled = true; //be sure to set this to true
    }

I think that code snippet should answer most of the questions you posed except for this one: 我认为代码片段应该回答您提出的大多数问题,除了这个问题:

Why is TreeViewItem node always null in the WPF version? 为什么TreeViewItem节点在WPF版本中始终为空?

The DataObject you are passing in the DragDrop event does not support passing a TreeViewItem . 您在DragDrop事件中传递的DataObject不支持传递TreeViewItem In your code (and mine) we specify that the data format will be DataFormats.StringFormat which cannot be cast to a TreeViewItem . 在你的代码(和我的代码)中,我们指定数据格式是DataFormats.StringFormat ,它不能转换为TreeViewItem

GetFullPath seems to be outputting a wrong value. GetFullPath似乎输出了错误的值。 What you want to drag/drop is the Header and you can get it directly from item . 您想要拖放的是Header ,您可以直接从item获取它。 Also bear in mind that the method below is associated with the MouseMove Event of the TreeView . 另请注意,下面的方法与TreeViewMouseMove Event相关联。

private void TreeView_MouseMove(object sender, MouseButtonEventArgs e)
{
    if (e.LeftButton != MouseButtonState.Pressed) return;
    TreeViewItem item = e.Source as TreeViewItem;
    if (item != null)
    {
        DataObject dataObject = new DataObject();
        dataObject.SetData(DataFormats.StringFormat, item.Header);
        DragDrop.DoDragDrop(item, dataObject, DragDropEffects.Move);
    }
}

I did create the drop part based on text rather than on the TreeViewItem ( e.Data.GetData(typeof(string)).ToString() ) but the most surprising thing is that it isn't even required. 我确实创建了基于文本而不是TreeViewIteme.Data.GetData(typeof(string)).ToString() )的drop部分,但最令人惊讶的是它甚至不需要。 If you open a new C# WPF project, put a TreeView and a TextBox on it (, update the XAML part) and copy the code above, you can drop text from the TreeView into the TextBox without doing anything else!! 如果你打开一个新的C#WPF项目,在其上放置一个TreeView和一个TextBox (更新XAML部分)并复制上面的代码,你可以将文本从TreeView删除到TextBox而不做任何其他事情! The text is copied into the TextBox without accounting for the Drop handling . 文本将复制到TextBox而不考虑Drop handling

Am I using the correct events?: I think you are using the correct events, but I think you have several problems in your code. 我使用的是正确的事件吗?:我认为你正在使用正确的事件,但我认为你的代码中有几个问题。 I assume you have set the DataContext of your treeview to the real items and you use binding. 我假设您已将树视图的DataContext设置为真实项目并使用绑定。

  1. How do I access the DataObject in textbox_Drop ? 如何在textbox_Drop中访问DataObject? --> For getting the DataObject you have to get the real item by recursion (other solutions possible) - >为了获取DataObject,你必须通过递归获得真实项目(其他解决方案可能)

     DependencyObject k = VisualTreeHelper.HitTest(tv_treeView, DagEventArgs.GetPosition(lv_treeView)).VisualHit; while (k != null) { if (k is TreeViewItem) { TreeViewItem treeNode = k as TreeViewItem; // Check if the context is your desired type if (treeNode.DataContext is YourType) { // save the item targetTreeViewItem = treeNode; return; } } else if (k == tv_treeview) { Console.WriteLine("Found treeview instance"); return; } // Get the parent item if no item from YourType was found k = VisualTreeHelper.GetParent(k); } 
  2. Why is the text being pasted in the WPF version? 为什么文本被粘贴在WPF版本中? --> The Header is displayed because (I assume) it is like the tostring method on your items. - >显示标题是因为(我假设)它就像你的项目上的tostring方法。 If for a complex item the binding is not specified, the ToString Method is executed. 如果对于复杂项目未指定绑定,则执行ToString方法。 Try not to set the Text directly in the handler of the drop event. 尽量不要直接在drop事件的处理程序中设置Text。 Set the data context to your item (to the item you found in 1. point) and then specify the binding path via XAML. 将数据上下文设置为您的项目(到1. point中找到的项目),然后通过XAML指定绑定路径。 (for displaying) (用于显示)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM