2013-07-16 65 views
5

Tôi đang chuyển chương trình từ WinForms sang WPF và đã gặp phải một số vấn đề với việc kéo và thả. Nó cho phép kéo từ một TreeView (nó giống như một trình thám hiểm tệp) vào một hộp văn bản mở tệp. Tuy nhiên, phiên bản WPF hoạt động như tự động sao chép và dán văn bản đầu trang của TreeViewItem. Tôi nghĩ rằng tôi chỉ có một cái gì đó trộn lẫn? Có thể là công cụ DataObject.Kéo và thả WinForms để kéo và thả WPF

Các đầy đủ chức năng, phù hợp WinForms mã:

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

Mã WPF rằng nên làm điều tương tự:

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

Vấn đề: Trong phiên bản WPF của tôi, tôi đang thiết của textbox văn bản để trống (như một thử nghiệm), xảy ra, nhưng sau đó văn bản tiêu đề của TreeViewItem đang được dán mà không phải là những gì tôi muốn.

Câu hỏi: Cách chính xác để chuyển mã WinForms này sang WPF là gì? Tại sao văn bản được dán trong phiên bản WPF? Làm cách nào để ngăn chặn điều đó? Tôi có đang sử dụng các sự kiện chính xác không? Làm cách nào để truy cập vào DataObject trong textbox_Drop để tôi có thể mở tệp như tôi đã làm trong phiên bản WinForms? Tại sao nút TreeViewItem luôn rỗng trong phiên bản WPF?

+0

Điều này dường như có liên quan: http://msdn.microsoft.com/en-us/library/hh144798.aspx EDIT: Các bit quan trọng ở đây là 'TextBox 'và tất cả các thân nhân của nó có triển khai" mặc định "cho DragDrop, và nó được khuyến khích bạn chỉ cần gõ vào những thay vì quay của riêng bạn. – JerKimball

Trả lời

1

Vấn đề: Trong phiên bản WPF của tôi, tôi đang thiết lập văn bản của textbox để trống (như là một thử nghiệm), trong đó xảy ra, nhưng sau đó văn bản tiêu đề của TreeViewItem đang được dán đó là không phải những gì tôi muốn.

Tôi nghĩ phần tử giao diện người dùng gốc đang xử lý (và do đó ghi đè) sự kiện Drop để bạn không nhận được kết quả mong đợi. Như một vấn đề của thực tế, khi cố gắng để tái tạo vấn đề của bạn, tôi thậm chí không thể nhận được sự kiện TextBox.Drop của tôi để bắn. Tuy nhiên, bằng cách sử dụng sự kiện PreviewDrop của TextBox, tôi đã có thể nhận được những gì (tôi nghĩ) là kết quả mong đợi của bạn.Hãy thử điều này:

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 
    } 

Tôi nghĩ rằng đoạn mã nên trả lời hầu hết các câu hỏi mà bạn đặt ra trừ này một:

Tại sao TreeViewItem nút luôn null trong phiên bản WPF?

DataObject bạn đang chuyển trong sự kiện DragDrop không hỗ trợ chuyển số TreeViewItem. Trong mã của bạn (và của tôi), chúng tôi xác định rằng định dạng dữ liệu sẽ là DataFormats.StringFormat mà không thể được đúc thành TreeViewItem.

+0

Cảm ơn, tôi cần 'e.Handled = true' và để sửa cách tôi kiểm tra' e.Data'. –

-1

Tôi có sử dụng đúng sự kiện không ?: Tôi nghĩ bạn đang sử dụng đúng sự kiện, nhưng tôi nghĩ bạn có một số vấn đề trong mã của mình. Tôi giả sử bạn đã thiết lập DataContext của treeview của bạn để các mặt hàng thực tế và bạn sử dụng ràng buộc.

  1. Làm cách nào để truy cập DataObject trong textbox_Drop? -> Để nhận được DataObject bạn có để có được những mục thực bởi đệ quy (các giải pháp khác có thể)

    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. Tại sao đây là nội dung được dán trong phiên bản WPF? -> Tiêu đề được hiển thị vì (tôi giả định) nó giống như phương thức chuỗi trên các mục của bạn. Nếu đối với một mục phức tạp thì ràng buộc không được chỉ định, phương thức ToString được thực thi. Cố gắng không đặt Văn bản trực tiếp trong trình xử lý sự kiện thả. Đặt bối cảnh dữ liệu cho mục của bạn (đến mục bạn tìm thấy trong 1. điểm) và sau đó chỉ định đường dẫn ràng buộc qua XAML. (Để hiển thị)

+0

Điều này không trả lời các câu hỏi của tôi và mã mà bạn sao chép và dán không hữu ích. –

+0

Tôi đã thêm vào câu trả lời cho câu hỏi của bạn. – Tintenfiisch

+0

Tại sao tôi cần có cha mẹ? Tôi không thấy mã có liên quan như thế nào, bạn có thể áp dụng mã đó vào mã của tôi không? Tôi đang thiết lập văn bản theo cách thủ công để kiểm tra, như bạn có thể thấy trong mã WinForms đang hoạt động của tôi. Tôi sẽ có thể làm cho nó hoạt động giống nhau, chỉ trong WPF. –

0

GetFullPath dường như xuất ra một giá trị sai. Những gì bạn muốn kéo/thả là Header và bạn có thể lấy nó trực tiếp từ item. Cũng xin lưu ý rằng phương thức bên dưới được liên kết với số MouseMove Event của số TreeView.

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

tôi đã tạo ra một phần thả dựa trên văn bản chứ không phải là trên TreeViewItem (e.Data.GetData(typeof(string)).ToString()) nhưng điều đáng ngạc nhiên nhất là nó thậm chí không cần thiết. Nếu bạn mở một dự án C# WPF mới, hãy đặt TreeViewTextBox trên đó (cập nhật phần XAML) và sao chép mã ở trên, bạn có thể thả văn bản từ TreeView vào TextBox mà không làm bất kỳ điều gì khác !! Văn bản được sao chép vào TextBox mà không cần tính đến Drop handling.

4

Ah, cái quái gì, tôi sẽ mở rộng nhận xét của tôi đến một câu trả lời:

Các liên kết để đọc, như đã nói, là thế này: http://msdn.microsoft.com/en-us/library/hh144798.aspx

câu chuyện ngắn, các TextBox điều khiển -derived đã thực hiện hầu hết các "ruột" cho các thao tác kéo/thả cơ bản và bạn nên mở rộng thay vì cung cấp các trình xử lý rõ ràng DragEnter/DragOver/Drop.

Giả sử một cây "dữ liệu" cấu trúc như:

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

Việc xử lý có thể trông như thế này:

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

Và chỉ dành riêng cho tiếng cười khúc khích, đây là một ứng dụng thử nghiệm bẩn nhanh và rằng là tinh khiết showboating trong đó là sử dụng các phần mở rộng phản ứng (Rx) cho các công cụ kéo bắt đầu:

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 (quá lười biếng để MVVM này):

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

Bạn đã có nhiều hơn một vấn đề, đủ để làm cho điều này khó khăn. Vấn đề đầu tiên là bạn đã nhận được đối tượng kéo sai, bạn đang kéo một chuỗi nhưng vẫn đang kiểm tra một TreeViewItem. Chỉ cần sử dụng phương pháp tương tự như bạn đã sử dụng trong Winforms, kéo nút. Vấn đề thứ hai là TextBox đã thực hiện hỗ trợ D + D và điều đó sẽ cản trở mã của bạn. Và lý do bạn thấy văn bản xuất hiện sau khi thả.

Hãy giải quyết sự bắt đầu kéo đầu tiên. Bạn sẽ cần phải làm một chút công việc phụ kể từ khi bạn bắt đầu kéo cản trở việc sử dụng bình thường của TreeView, nó sẽ rất khó để chọn một nút. Chỉ bắt đầu kéo khi chuột được chuyển đủ xa:

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

Bây giờ thả, bạn sẽ cần phải thực hiện các xử lý sự kiện DragEnter, DragOver và thả để tránh mặc định D + D hỗ trợ xây dựng vào TextBox từ khi bước vào cách. Thiết lập thuộc tính e.Handled true là cần thiết:

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