2013-07-16 112 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; 
    }