2013-07-16 85 views
5

我将我的程序从WinForms移植到WPF,并遇到了一些拖放问题。它应该允许从一个TreeView(它就像一个文件浏览器)拖到一个打开文件的文本框。但是,WPF版本的行为就像是自动复制并粘贴TreeViewItem的标题文本。我想我只是混合了一些东西?可能是DataObject的东西。移植WinForms拖放到WPF的拖放

全功能的,相关的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); 
     ... 

的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? 
} 

问题:在我的WPF的版本,我设置文本框的文本为空(作为测试),这发生,但之后TreeViewItem的标题文本被粘贴,这不是我想要的。

问题:将此WinForms代码移植到WPF的正确方法是什么?为什么文本被粘贴在WPF版本中?我如何防止这种情况发生?我使用正确的事件吗?我如何在textbox_Drop中访问DataObject,以便像我在WinForms版本中那样打开文件?为什么TreeViewItem节点在WPF版本中始终为空?

+0

这似乎是相关的:http://msdn.microsoft.com/en-us/library/hh144798.aspx编辑:这里最重要的一点是,'文本框'和它所有的亲属都有DragDrop的“默认”实现,并且建议你将它们拧紧,而不是旋转自己的。 – JerKimball

回答

1

问题:在我的WPF的版本,我设置文本框的文本为空(作为测试),其发生,但事后TreeViewItem的标题文本被粘贴这是不是我想要的。

我认为一个父UI元素正在处理(并因此覆盖)Drop事件,所以你没有得到你期望的结果。事实上,当试图重现你的问题时,我甚至无法让我的TextBox.Drop事件触发。但是,使用TextBox的PreviewDrop事件,我能够得到(我认为)是您的预期结果。试试这个:

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 
    } 

我认为代码段应回答你们中的大多数,除了这一个提出的问题:

为什么树型视图节点总是空的WPF的版本?

您传递给DragDrop事件的DataObject不支持传递TreeViewItem。在您的代码(和我的)中,我们指定数据格式为DataFormats.StringFormat,不能将其转换为TreeViewItem

+0

谢谢,我需要'e.Handled = true'并解决我如何检查'e.Data'。 –

-1

我使用正确的事件吗?: 我认为你使用的是正确的事件,但我认为你的代码有几个问题。 我假设你已经将树视图的DataContext设置为实际项目,并使用绑定。

  1. 如何访问textbox_Drop中的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. 为什么文本中的WPF版本粘贴? - > 显示标题是因为(我认为)它就像物品上的tostring方法。如果对于复杂的项目没有指定绑定,则执行ToString方法。 尽量不要在放置事件的处理程序中直接设置文本。将数据上下文设置为您的项目(指向您在1点找到的项目),然后通过XAML指定绑定路径。 (显示)

+0

这不回答我的问题,您复制和粘贴的代码没有帮助。 –

+0

我添加到我的回答你的问题。 – Tintenfiisch

+0

为什么我需要获得父母?我看不出代码是如何相关的,你可以将它应用于我的代码吗?我正在手动设置文本进行测试,正如您在我的工作WinForms代码中看到的那样。我应该可以让它工作,就像WPF一样。 –

0

GetFullPath似乎输出了一个错误的值。你想要拖/放的是Header,你可以直接从item得到它。同时请记住,以下方法与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); 
    } 
} 

我没有创建一个基于文本,而不是在TreeViewIteme.Data.GetData(typeof(string)).ToString())的下降一部分,但最令人惊讶的是,它甚至不要求。如果你打开一个新的C#WPF项目,在其上放一个TreeView和一个TextBox(更新XAML部分)并复制上面的代码,你可以将TreeView中的文本放入TextBox而不用做其他任何事情!文本被复制到TextBox中,而不会考虑Drop handling

4

啊,你到底,我会扩大我的评论一个答案:

阅读的链接,如所提到的,是这样的: http://msdn.microsoft.com/en-us/library/hh144798.aspx

的短篇小说中,TextBox来源的控制已经实现基本拖放操作的大部分“内核”,建议您扩展该操作,而不是提供明确的DragEnter/DragOver/Drop处理程序。

假设像一棵树“数据”的结构:

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

的处理器可能是这个样子:

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

和公正的笑声,这里有一个快速和肮脏的测试应用程序,在使用反应性扩展(Rx)作为拖拽开始的东西时是纯粹的展示:

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> 

讨厌的代码隐藏(懒得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; } 
    } 
} 
3

你有一个以上的问题,足以让这个困难。首先问题是拖动对象错误,你拖动一个字符串,但仍然检查TreeViewItem。只需使用与Winforms中使用的相同的方法,拖动节点即可。第二个问题是TextBox已经实现了D + D支持,并且阻碍了你的代码。你看到文本的原因在下降后出现。

让我们先解决拖动的开始。您需要做一些额外的工作,因为您开始拖动的方式会干扰TreeView的正常使用,所以选择节点非常困难。只有启动感动远远不够鼠标时的阻力:

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

现在的下降,你将需要实现的dragenter,的dragover和Drop事件处理程序,以避免默认d + d支持内置文本框进入我们的生活方式。在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; 
    }