WPF DoDragDrop 导致控件动画停止
这是场景(简化):我在窗口上有一个控件(比方说,一个矩形)。我挂钩了 MouseMove 事件以使其启动拖放操作。然后在 MouseDown 事件中,我让它动画化,向右移动 50 像素。但是,当我将鼠标按住矩形时,控件会移动大约一个像素,然后暂停。只有当我移动鼠标时动画才会继续。有谁知道为什么以及如何解决这个问题?多谢!!
这是重现此问题的源代码:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void rectangle1_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DragDrop.DoDragDrop(this, new DataObject(), DragDropEffects.Copy);
}
}
private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ThicknessAnimation animation = new ThicknessAnimation();
Thickness t = rectangle1.Margin;
t.Left += 50;
animation.To = t;
animation.Duration = new Duration(TimeSpan.FromSeconds(0.25));
rectangle1.BeginAnimation(Rectangle.MarginProperty, animation);
}
}
如果您想要 Window1.xaml:
<Window x:Class="DragDropHaltingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Rectangle Margin="12,12,0,0" Name="rectangle1" Stroke="Black" Fill="Blue" Height="29" HorizontalAlignment="Left" VerticalAlignment="Top" Width="31" MouseMove="rectangle1_MouseMove" MouseLeftButtonDown="rectangle1_MouseLeftButtonDown" />
</Grid>
Here's the scenario (simplified): I have a control (let's say, a Rectangle) on the Window. I hooked the MouseMove event to make it initiate a drag&drop. Then in the MouseDown event I let it animate, moving 50 pixels to the right. However, when I hold my mouse down on the Rectangle, the control moves about one pixel, and then pauses. Only when I move my mouse will the animation continue. Does anyone know why and how to solve this? Thanks a lot!!
Here's the source code to reproduce this problem:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void rectangle1_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DragDrop.DoDragDrop(this, new DataObject(), DragDropEffects.Copy);
}
}
private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ThicknessAnimation animation = new ThicknessAnimation();
Thickness t = rectangle1.Margin;
t.Left += 50;
animation.To = t;
animation.Duration = new Duration(TimeSpan.FromSeconds(0.25));
rectangle1.BeginAnimation(Rectangle.MarginProperty, animation);
}
}
In case you want Window1.xaml:
<Window x:Class="DragDropHaltingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Rectangle Margin="12,12,0,0" Name="rectangle1" Stroke="Black" Fill="Blue" Height="29" HorizontalAlignment="Left" VerticalAlignment="Top" Width="31" MouseMove="rectangle1_MouseMove" MouseLeftButtonDown="rectangle1_MouseLeftButtonDown" />
</Grid>
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我偶然发现了同样的问题;我的拖放源沿着动画路径移动。如果我将鼠标放置在拖动源的路径上并按住鼠标左键,则当源接触鼠标时动画将停止。当我释放鼠标按钮或移动鼠标时,动画将继续。 (有趣的是,如果我打开 Windows 任务管理器并且它定期刷新进程列表,动画也会继续!)
情况分析
据我了解,WPF 动画在
CompositionTarget.Rendering
事件中更新。在正常情况下,它会在每次屏幕刷新时触发,可能每秒 60 次。就我而言,当我的拖动源在鼠标下移动时,它会触发MouseMove
事件。在该事件处理程序中,我调用DragDrop.DoDragDrop
。此方法会阻塞 UI 线程,直到拖放完成。当 UI 线程进入DragDrop.DoDragDrop
时,不会触发CompositionTarget.Rendering
。从某种意义上说,触发事件的线程在拖放时被阻塞,这是可以理解的。但!如果您移动鼠标(也许其他类型的输入也可以实现此目的),则会触发MouseMove
事件。它在拖放操作中仍被阻止的同一个 UI 线程中触发。触发并处理MouseMove
后,CompositionTarget.Rendering
继续在仍被“阻止”的 UI 线程中定期触发。我通过比较下面两个堆栈跟踪得出了这一点。我根据我对堆栈帧含义的不理解对它们进行了分组。第一个堆栈跟踪取自我的
MouseMove
事件处理程序,同时没有活动的拖放操作。这是“通常情况”。System.Windows.Input.MouseEventArgs.InvokeEventHandler
:
System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
MS.Win32.UnsafeNativeMethods.DispatchMessage
System.Windows.Threading.Dispatcher.PushFrameImpl
:
System.Threading.ThreadHelper.ThreadStart
第二个堆栈跟踪是在拖放操作期间从我的
CompositionTarget.Rendering
事件处理程序中获取的。框架组 A、B 和 C 与上述相同。System.Windows.Media.MediaContext.RenderMessageHandlerCore
System.Windows.Media.MediaContext.AnimatedRenderMessageHandler
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
MS.Win32.UnsafeNativeMethods.DoDragDrop
:
System.Windows.DragDrop.DoDragDrop
因此,WPF在消息调度 (B) 内运行消息调度 (E)。这解释了为什么当在 UI 线程上调用
DragDrop.DoDragDrop
并阻止该线程时,我们仍然能够在同一线程上运行事件处理程序。我无法想象为什么内部消息调度没有从拖放操作开始连续运行以执行更新动画的常规CompositionTarget.Rendering
事件。可能的解决方法
触发额外的 MouseMove 事件
一种解决方法是在执行
DoDragDrop
时触发鼠标移动事件,如 VlaR 所建议的那样。他的链接似乎假设我们正在使用表单。对于 WPF,细节有点不同。按照 MSDN 上的 解决方案进行操作论坛(由原始海报发布?),这段代码可以解决问题。我修改了源代码,因此它应该可以在 32 位和 64 位中工作,并且还可以避免计时器在触发之前被 GC 的罕见情况。使用此方法而不是
DragDrop.DoDragDrop
将使动画继续进行而不停止。然而,它似乎并没有让DragDrop
更快地了解拖动正在进行中。因此,您可能会面临同时输入多个拖动操作的风险。调用代码应该像这样防范它:还有另一个故障。由于某些原因,在开头描述的情况下,只要按下鼠标左键,鼠标没有移动,并且放置源正在移动,鼠标光标就会在正常箭头光标和拖动光标之间闪烁光标。
仅当鼠标真正移动时才允许拖动
我尝试的另一个解决方案是禁止在开头描述的情况下开始拖放。为此,我连接了一些 Window 事件来更新状态变量,所有代码都可以压缩到这个初始化例程中。
调用代码将如下所示:
这解决了拖动开始的容易重现的情况,因为当按下鼠标左键时动画将拖动源移动到固定的鼠标光标上。然而,这并不能避免根本问题。如果您单击拖动源并且移动得很少以致于仅触发一个鼠标事件,则动画将停止。幸运的是,这种情况在实际操作中发生的可能性较小。此解决方案的优点是不直接使用 Win32 API,并且不必涉及后台线程。
从后台线程调用
DoDragDrop
不起作用。
DragDrop.DoDragDrop
必须从 UI 线程调用并会阻止它。调用使用调度程序调用DoDragDrop
的后台线程没有帮助。I stumbled upon the same problem like this; My drag'n'drop source moved along an animated path. If I positioned the mouse on the path of the drag source and kept the left mouse button pressed down, the animation would stop when the source touched the mouse. The animation would continue when I either released the mouse button or moved the mouse. (Interestingly, the animation would also continue if I had Windows Task Manager open and it did its periodical process list refresh!)
Analysis of the situation
To my understanding, WPF animations are updated in the
CompositionTarget.Rendering
event. In normal situations it is fired at every screen refresh, which might be 60 times a second. In my case, when my drag source moves under the mouse it fires theMouseMove
event. In that event handler, I callDragDrop.DoDragDrop
. This method blocks the UI thread until the drag'n'drop finishes. When the UI thread has enteredDragDrop.DoDragDrop
, thenCompositionTarget.Rendering
is not fired. It is understandable in the sense that the thread that fires events is blocked doing drag'n'drop. But! If you move the mouse (and maybe some other kind of input can also do the trick), then aMouseMove
event is fired. It is fired in the same UI thread that is still being blocked in a drag'n'drop operation. AfterMouseMove
is fired and handled,CompositionTarget.Rendering
continues firing regularly in the UI thread that is still being "blocked".I gathered this from the comparison of two stack traces below. I have grouped the stack frames based on my poor understanding of what they mean. The first stack trace is taken from my
MouseMove
event handler while no drag'n'drop operation was active. This is "the usual case".System.Windows.Input.MouseEventArgs.InvokeEventHandler
:
System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
MS.Win32.UnsafeNativeMethods.DispatchMessage
System.Windows.Threading.Dispatcher.PushFrameImpl
:
System.Threading.ThreadHelper.ThreadStart
The second stack trace is taken from my
CompositionTarget.Rendering
event handler during a drag'n'drop operation. Frame groups A, B, and C are identical to above.System.Windows.Media.MediaContext.RenderMessageHandlerCore
System.Windows.Media.MediaContext.AnimatedRenderMessageHandler
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
MS.Win32.UnsafeNativeMethods.DoDragDrop
:
System.Windows.DragDrop.DoDragDrop
So, WPF is running message dispatching (E) inside message dispatching (B). This explains how it is possible that while
DragDrop.DoDragDrop
is called on the UI thread and blocks the thread, we are still able to run event handlers on that same thread. I can't imagine why the internal message dispatching isn't run continuously from the beginning of the drag'n'drop operation to perform regularCompositionTarget.Rendering
events that would update animations.Possible workarounds
Fire an extra MouseMove event
One workaround is to trigger a mouse move event when
DoDragDrop
is executing, as VlaR suggests. His link seems to assume we're using Forms. For WPF, the details are a bit different. Following the solution on the MSDN forums (by the original poster?), this code does the trick. I modified the source so it should work both in 32 and 64 bits and also to avoid the rare chance that the timer is GC'd before it fires.Using this method instead of
DragDrop.DoDragDrop
will make the animation proceed without stopping. However, it seems that it doesn't makeDragDrop
understand any sooner that a drag is in progress. Therefore you risk entering multiple drag operations at once. The calling code should guard against it like this:There is another glitch, too. For some reason, in the situation described at the beginning, the mouse cursor will flicker between the normal arrow cursor and a drag cursor as long as the left mouse button is down, the mouse hasn't moved, and the drop source is traveling over the cursor.
Allow dragging only when the mouse really moves
Another solution I tried is to disallow drag'n'drop from starting in the case described at the beginning. For this, I hook up a few Window events to update a state variable all the code can be condensed into this initialization routine.
The calling code would then look something like this:
This works around the easily reproducible situation where dragging starts because an animation moved a drag source over a stationary mouse cursor while the left mouse button was down. This doesn't avoid the root problem, however. If you click on the drag source and move so little that just one mouse event is fired, animation will stop. Luckily this is less likely to happen in actual practice. This solution has the benefit of not using the Win32 API directly and not having to involve background threads.
Call
DoDragDrop
from a background threadDoesn't work.
DragDrop.DoDragDrop
must be called from the UI thread and will block it. It doesn't help to invoke a background thread that uses the dispatcher to invokeDoDragDrop
.