[英]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
获取它。 另请注意,下面的方法与TreeView
的MouseMove 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);
}
}
我确实创建了基于文本而不是TreeViewItem
( e.Data.GetData(typeof(string)).ToString()
)的drop部分,但最令人惊讶的是它甚至不需要。 如果你打开一个新的C#WPF项目,在其上放置一个TreeView
和一个TextBox
(更新XAML部分)并复制上面的代码,你可以将文本从TreeView
删除到TextBox
而不做任何其他事情! 文本将复制到TextBox
而不考虑Drop handling
。
我使用的是正确的事件吗?:我认为你正在使用正确的事件,但我认为你的代码中有几个问题。 我假设您已将树视图的DataContext设置为真实项目并使用绑定。
如何在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); }
为什么文本被粘贴在WPF版本中? - >显示标题是因为(我假设)它就像你的项目上的tostring方法。 如果对于复杂项目未指定绑定,则执行ToString方法。 尽量不要直接在drop事件的处理程序中设置Text。 将数据上下文设置为您的项目(到1. point中找到的项目),然后通过XAML指定绑定路径。 (用于显示)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.