WPF 使用鼠标和触摸与 Adorner 一起拖放
WPF Drag and Drop with Adorner using mouse and touch
我希望这是一个很好的问题,所以我会详细写下我想要实现的目标,我在互联网上找到的东西,并展示我到目前为止所做的事情以及我已经完成的事情尝试过。
我需要为我的应用程序添加拖放功能。我有要拖动到列表框项目的图像(基本上是控件)。
这是示例 UI:
这是我现在的用法:
如您所见,我可以将四张图片中的一张拖放到列表框项目上。
如果我将图像移到正确的目标(列表框图像)上,光标附近的图像就会消失并且一切正常,但是当我不将图像放在列表项上(我释放鼠标)时,该图像会保留在屏幕上。
我的解决方案基于 this 问题的答案,但我无法删除不需要的 window(光标附近的图像)
我的 XAML 看起来像这样:
<Window x:Class="DragDrop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Drag'n'Drop" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Height="300" Margin="0,10,10,0" VerticalAlignment="Top" Width="234" ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ProgressBar Height="20" Value="{Binding Points}" Margin="0,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Image HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-60000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="10"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="20"/>
<Image HorizontalAlignment="Left" Height="72" Margin="10,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-7000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="30"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="40"/>
</Grid>
</Window>
后面的代码:
public partial class MainWindow
{
private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return _people; }
}
public MainWindow()
{
InitializeComponent();
_people.Add(new Person() {Name = "Person1", Points = 10});
_people.Add(new Person() {Name = "Person2", Points = 0});
_people.Add(new Person() {Name = "Person3", Points = 40});
}
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as Image;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
private Window _dragdropWindow;
private void CreateDragDropWindow(Visual dragElement)
{
_dragdropWindow = new Window
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
AllowDrop = false,
Background = null,
IsHitTestVisible = false,
SizeToContent = SizeToContent.WidthAndHeight,
Topmost = true,
ShowInTaskbar = false
};
Rectangle r = new Rectangle
{
Width = ((FrameworkElement) dragElement).ActualWidth/2,
Height = ((FrameworkElement) dragElement).ActualHeight/2,
Fill = new VisualBrush(dragElement)
};
_dragdropWindow.Content = r;
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
_dragdropWindow.Show();
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
private void UIElement_OnPreviewGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
}
private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
{
//var droppedData = e.Data.GetData(typeof(Image)) as Image;
var droppedData = (Int32) e.Data.GetData(typeof (Int32));
var stackPanel = sender as StackPanel;
if (stackPanel != null)
{
var student = stackPanel.DataContext as Person;
//int targetIndex = _people.IndexOf(student);
if (student != null) student.Points += droppedData;
}
if (_dragdropWindow != null)
{
_dragdropWindow.Close();
_dragdropWindow = null;
}
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private int _points;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
if (_points >= 100)
{
_points -= 100;
Debug.WriteLine("100!");
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
我在互联网上发现我可以使用扩展 Adorner 的 class 并且我找到了一些示例:
- http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/ - (WaybackMachine archive link)
- http://www.zagstudio.com/blog/488#.VfiMSBHtmko
- http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx
- https://github.com/punker76/gong-wpf-dragdrop
但它们都展示了如何从集合 (ItemsControls) 中拖动项目。第三个 link 很有前途,但我无法根据需要采用它。
所以我的问题是:
- 当我取消拖动(MouseUp 或不正确的拖动目标)时,如何隐藏示例中的小图像 window
- 显示我使用 Adorner 以及如何在我的代码中使用它?我需要在开始拖放时显示 正确拖放图片时隐藏 或取消拖放目标不正确
我刚开始使用 WPF,所以请尝试理解我的挫败感 - 我花了过去两个晚上和晚上试图让它工作。
1) 修改您的 OnMouseTouchDown 处理程序以包括在开始拖动之前将 ContinueDragHandler 分配给拖动的项目,如下所示
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as FrameworkElement;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.AddQueryContinueDragHandler(draggedItem, DragContrinueHandler);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
以及处理程序本身:
public void DragContrinueHandler(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Continue && e.KeyStates != DragDropKeyStates.LeftMouseButton)
{
_dragdropWindow.Close();
}
}
2) 我认为创建一个新的 window 来在光标旁边显示图像是一个肮脏的黑客行为。有很多关于通过拖放使用装饰器的文章。尽管您的方法可行并且不需要大量代码。另一方面,装饰品可以。我认为你应该创建另一个问题,如果你按照某些教程失败,请提供代码示例和你采取的步骤
我希望这是一个很好的问题,所以我会详细写下我想要实现的目标,我在互联网上找到的东西,并展示我到目前为止所做的事情以及我已经完成的事情尝试过。
我需要为我的应用程序添加拖放功能。我有要拖动到列表框项目的图像(基本上是控件)。
这是示例 UI:
这是我现在的用法:
如您所见,我可以将四张图片中的一张拖放到列表框项目上。 如果我将图像移到正确的目标(列表框图像)上,光标附近的图像就会消失并且一切正常,但是当我不将图像放在列表项上(我释放鼠标)时,该图像会保留在屏幕上。
我的解决方案基于 this 问题的答案,但我无法删除不需要的 window(光标附近的图像)
我的 XAML 看起来像这样:
<Window x:Class="DragDrop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Drag'n'Drop" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox HorizontalAlignment="Right" HorizontalContentAlignment="Stretch" Height="300" Margin="0,10,10,0" VerticalAlignment="Top" Width="234" ItemsSource="{Binding People}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" AllowDrop="True" PreviewDrop="UIElement_OnPreviewDrop">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ProgressBar Height="20" Value="{Binding Points}" Margin="0,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Image HorizontalAlignment="Left" Height="72" Margin="10,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-60000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="10"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,10,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="20"/>
<Image HorizontalAlignment="Left" Height="72" Margin="10,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-7000-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="30"/>
<Image HorizontalAlignment="Left" Height="72" Margin="87,87,0,0" VerticalAlignment="Top" Width="72" Source="Images/coins-700-icon.png" RenderTransformOrigin="0.5,0.5"
PreviewMouseLeftButtonDown="OnMouseTouchDown"
PreviewTouchDown="OnMouseTouchDown"
PreviewGiveFeedback="UIElement_OnPreviewGiveFeedback" Tag="40"/>
</Grid>
</Window>
后面的代码:
public partial class MainWindow
{
private readonly ObservableCollection<Person> _people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return _people; }
}
public MainWindow()
{
InitializeComponent();
_people.Add(new Person() {Name = "Person1", Points = 10});
_people.Add(new Person() {Name = "Person2", Points = 0});
_people.Add(new Person() {Name = "Person3", Points = 40});
}
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as Image;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
private Window _dragdropWindow;
private void CreateDragDropWindow(Visual dragElement)
{
_dragdropWindow = new Window
{
WindowStyle = WindowStyle.None,
AllowsTransparency = true,
AllowDrop = false,
Background = null,
IsHitTestVisible = false,
SizeToContent = SizeToContent.WidthAndHeight,
Topmost = true,
ShowInTaskbar = false
};
Rectangle r = new Rectangle
{
Width = ((FrameworkElement) dragElement).ActualWidth/2,
Height = ((FrameworkElement) dragElement).ActualHeight/2,
Fill = new VisualBrush(dragElement)
};
_dragdropWindow.Content = r;
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
_dragdropWindow.Show();
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
private void UIElement_OnPreviewGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
_dragdropWindow.Left = w32Mouse.X;
_dragdropWindow.Top = w32Mouse.Y;
}
private void UIElement_OnPreviewDrop(object sender, DragEventArgs e)
{
//var droppedData = e.Data.GetData(typeof(Image)) as Image;
var droppedData = (Int32) e.Data.GetData(typeof (Int32));
var stackPanel = sender as StackPanel;
if (stackPanel != null)
{
var student = stackPanel.DataContext as Person;
//int targetIndex = _people.IndexOf(student);
if (student != null) student.Points += droppedData;
}
if (_dragdropWindow != null)
{
_dragdropWindow.Close();
_dragdropWindow = null;
}
}
}
public class Person : INotifyPropertyChanged
{
private string _name;
private int _points;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
public int Points
{
get { return _points; }
set
{
if (value == _points) return;
_points = value;
if (_points >= 100)
{
_points -= 100;
Debug.WriteLine("100!");
}
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
我在互联网上发现我可以使用扩展 Adorner 的 class 并且我找到了一些示例:
- http://nonocast.cn/adorner-in-wpf-part-5-drag-and-drop/ - (WaybackMachine archive link)
- http://www.zagstudio.com/blog/488#.VfiMSBHtmko
- http://www.infragistics.com/community/blogs/alex_fidanov/archive/2009/07/28/drag-amp-drop-with-datapresenter-family-controls.aspx
- https://github.com/punker76/gong-wpf-dragdrop
但它们都展示了如何从集合 (ItemsControls) 中拖动项目。第三个 link 很有前途,但我无法根据需要采用它。
所以我的问题是:
- 当我取消拖动(MouseUp 或不正确的拖动目标)时,如何隐藏示例中的小图像 window
- 显示我使用 Adorner 以及如何在我的代码中使用它?我需要在开始拖放时显示 正确拖放图片时隐藏 或取消拖放目标不正确
我刚开始使用 WPF,所以请尝试理解我的挫败感 - 我花了过去两个晚上和晚上试图让它工作。
1) 修改您的 OnMouseTouchDown 处理程序以包括在开始拖动之前将 ContinueDragHandler 分配给拖动的项目,如下所示
private void OnMouseTouchDown(object sender, InputEventArgs e)
{
var item = sender as FrameworkElement;
if (item == null) return;
var draggedItem = item;
var points = Convert.ToInt32(draggedItem.Tag);
CreateDragDropWindow(draggedItem);
System.Windows.DragDrop.AddQueryContinueDragHandler(draggedItem, DragContrinueHandler);
System.Windows.DragDrop.DoDragDrop(draggedItem, points, DragDropEffects.Move);
}
以及处理程序本身:
public void DragContrinueHandler(object sender, QueryContinueDragEventArgs e)
{
if (e.Action == DragAction.Continue && e.KeyStates != DragDropKeyStates.LeftMouseButton)
{
_dragdropWindow.Close();
}
}
2) 我认为创建一个新的 window 来在光标旁边显示图像是一个肮脏的黑客行为。有很多关于通过拖放使用装饰器的文章。尽管您的方法可行并且不需要大量代码。另一方面,装饰品可以。我认为你应该创建另一个问题,如果你按照某些教程失败,请提供代码示例和你采取的步骤