簡體   English   中英

移植WinForms拖放到WPF拖放

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

我正在將我的程序從WinForms移植到WPF,並遇到了拖放問題。 它應該允許從TreeView(它就像文件瀏覽器)拖動到打開文件的文本框。 但是,WPF版本的作用類似於TreeViewItem的頭文本的自動復制和粘貼。 我想我只是混淆了什么? 可能是DataObject東西。

功能齊全,相關的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版本中始終為空?

啊,到底是什么,我會將我的評論擴展到答案:

如上所述,要閱讀的鏈接是: 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);

而且只是為了咯咯笑,這里是一個快速而骯臟的測試應用程序,它是純粹的showboating,它使用Reactive Extensions(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; }
    }
}

你有不止一個問題,足以讓這個問題變得困難。 第一個問題是你的拖拽對象錯誤,你拖動一個字符串,但仍然檢查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事件處理程序,以避免TextBox中內置的默認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;
    }

問題 :在我的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
    }

我認為代碼片段應該回答您提出的大多數問題,除了這個問題:

為什么TreeViewItem節點在WPF版本中始終為空?

您在DragDrop事件中傳遞的DataObject不支持傳遞TreeViewItem 在你的代碼(和我的代碼)中,我們指定數據格式是DataFormats.StringFormat ,它不能轉換為TreeViewItem

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() )的drop部分,但最令人驚訝的是它甚至不需要。 如果你打開一個新的C#WPF項目,在其上放置一個TreeView和一個TextBox (更新XAML部分)並復制上面的代碼,你可以將文本從TreeView刪除到TextBox而不做任何其他事情! 文本將復制到TextBox而不考慮Drop handling

我使用的是正確的事件嗎?:我認為你正在使用正確的事件,但我認為你的代碼中有幾個問題。 我假設您已將樹視圖的DataContext設置為真實項目並使用綁定。

  1. 如何在textbox_Drop中訪問DataObject? - >為了獲取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方法。 盡量不要直接在drop事件的處理程序中設置Text。 將數據上下文設置為您的項目(到1. point中找到的項目),然后通過XAML指定綁定路徑。 (用於顯示)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM