このような同じ問題に出くわしました。ドラッグ アンド ドロップ ソースがアニメーション パスに沿って移動しました。ドラッグ ソースのパス上にマウスを配置し、マウスの左ボタンを押したままにすると、ソースがマウスに触れたときにアニメーションが停止します。マウス ボタンを離すか、マウスを移動すると、アニメーションが続行されます。(興味深いことに、Windows タスク マネージャーを開いて定期的なプロセス リストの更新を行った場合にも、アニメーションは続行されます!)
状況の分析
私の理解では、CompositionTarget.Rendering
イベントで WPF アニメーションが更新されます。通常の状況では、画面が更新されるたびに発生しますが、これは 1 秒間に 60 回になる可能性があります。私の場合、ドラッグ ソースがマウスの下に移動すると、MouseMove
イベントが発生します。そのイベント ハンドラーで、 を呼び出しますDragDrop.DoDragDrop
。このメソッドは、ドラッグ アンド ドロップが完了するまで UI スレッドをブロックします。UI スレッドが に入るDragDrop.DoDragDrop
と、CompositionTarget.Rendering
は起動されません。イベントを発生させるスレッドがドラッグ アンド ドロップでブロックされているという意味では理解できます。しかし!マウスを動かすと (また、他の種類の入力でもうまくいく可能性があります)、MouseMove
イベントが発生します。ドラッグ アンド ドロップ操作でまだブロックされているのと同じ UI スレッドで起動されます。MouseMove
発射されて処理された後、CompositionTarget.Rendering
まだ「ブロック」されている UI スレッドで定期的に起動し続けます。
以下の 2 つのスタック トレースの比較からこれを収集しました。スタック フレームの意味をよく理解していないため、スタック フレームをグループ化しました。最初のスタック トレースはMouseMove
、ドラッグ アンド ドロップ操作がアクティブになっていないときに、イベント ハンドラーから取得されます。これが「いつものケース」です。
- C) イベント固有の処理 (マウス イベント)
System.Windows.Input.MouseEventArgs.InvokeEventHandler
:
System.Windows.Input.InputManager.HitTestInvalidatedAsyncCallback
- B) メッセージディスパッチ
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
MS.Win32.UnsafeNativeMethods.DispatchMessage
- A) アプリケーションのメイン ループ
System.Windows.Threading.Dispatcher.PushFrameImpl
:
System.Threading.ThreadHelper.ThreadStart
2 番目のスタック トレースはCompositionTarget.Rendering
、ドラッグ アンド ドロップ操作中にイベント ハンドラーから取得されます。フレームグループ A、B、C は上記と同じです。
- F) イベント固有の処理 (レンダリング イベント)
System.Windows.Media.MediaContext.RenderMessageHandlerCore
System.Windows.Media.MediaContext.AnimatedRenderMessageHandler
- E) メッセージディスパッチ (B と同じ)
System.Windows.Threading.ExceptionWrapper.InternalRealCall
:
MS.Win32.HwndSubclass.SubclassWndProc
- D) ドラッグ アンド ドロップ (イベント ハンドラー内で開始)
MS.Win32.UnsafeNativeMethods.DoDragDrop
:
System.Windows.DragDrop.DoDragDrop
- C) イベント固有の処理 (マウス イベント)
- B) メッセージの発送
- A) アプリケーションのメインループ
したがって、WPF はメッセージ ディスパッチ (B) 内でメッセージ ディスパッチ (E) を実行しています。DragDrop.DoDragDrop
これは、UI スレッドで呼び出されてスレッドをブロックしている間、同じスレッドでイベント ハンドラーを実行できる可能性があることを説明しています。CompositionTarget.Rendering
アニメーションを更新する通常のイベントを実行するために、ドラッグ アンド ドロップ操作の最初から内部メッセージのディスパッチが継続的に実行されない理由が想像できません。
考えられる回避策
追加の MouseMove イベントを発生させる
DoDragDrop
1 つの回避策は、VlaR が示唆するように、実行時にマウス移動イベントをトリガーすることです。彼のリンクは、私たちが Forms を使用していることを前提としているようです。WPF の場合、詳細は少し異なります。MSDN フォーラムの解決策 (元の投稿者によるものですか?) に従って、このコードでうまくいきます。ソースを変更して、32 ビットと 64 ビットの両方で動作するようにし、タイマーが起動する前に GC されるというまれな可能性を回避しました。
[DllImport("user32.dll")]
private static extern void PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
private static Timer g_mouseMoveTimer;
public static DragDropEffects MyDoDragDrop(DependencyObject source, object data, DragDropEffects effects, Window callerWindow)
{
var callerHandle = new WindowInteropHelper(callerWindow).Handle;
var position = Mouse.GetPosition(callerWindow);
var WM_MOUSEMOVE = 0x0200u;
var MK_LBUTTON = new IntPtr(0x0001);
var lParam = (IntPtr)position.X + ((int)position.Y << (IntPtr.Size * 8 / 2));
if (g_mouseMoveTimer == null) {
g_mouseMoveTimer = new Timer { Interval = 1, AutoReset = false };
g_mouseMoveTimer.Elapsed += (sender, args) => PostMessage(callerHandle, WM_MOUSEMOVE, MK_LBUTTON, lParam);
}
g_mouseMoveTimer.Start();
return DragDrop.DoDragDrop(source, data, effects);
}
の代わりにこのメソッドを使用DragDrop.DoDragDrop
すると、アニメーションが停止せずに進行します。しかし、DragDrop
ドラッグが進行中であることはすぐにはわからないようです。したがって、一度に複数のドラッグ操作を入力する危険があります。呼び出しコードは、次のように保護する必要があります。
private bool _dragging;
source.MouseMove += (sender, args) => {
if (!_dragging && args.LeftButton == MouseButtonState.Pressed) {
_dragging = true;
MyDoDragDrop(source, data, effects, window);
_dragging = false;
}
}
また、別の不具合があります。何らかの理由で、最初に説明した状況で、マウスの左ボタンが押されていて、マウスが動かされておらず、ドロップ ソースが上を移動している限り、マウス カーソルは通常の矢印カーソルとドラッグ カーソルの間でちらつきます。カーソル。
マウスが実際に動いているときだけドラッグを許可する
私が試した別の解決策は、最初に説明したケースでドラッグアンドドロップを開始できないようにすることです。このために、状態変数を更新するためにいくつかの Window イベントを接続し、すべてのコードをこの初期化ルーチンに凝縮します。
/// <summary>
/// Returns a function that tells if drag'n'drop is allowed to start.
/// </summary>
public static Func<bool> PrepareForDragDrop(Window window)
{
var state = DragFilter.MustClick;
var clickPos = new Point();
window.MouseLeftButtonDown += (sender, args) => {
if (state != DragFilter.MustClick) return;
clickPos = Mouse.GetPosition(window);
state = DragFilter.MustMove;
};
window.MouseLeftButtonUp += (sender, args) => state = DragFilter.MustClick;
window.PreviewMouseMove += (sender, args) => {
if (state == DragFilter.MustMove && Mouse.GetPosition(window) != clickPos)
state = DragFilter.Ok;
};
window.MouseMove += (sender, args) => {
if (state == DragFilter.Ok)
state = DragFilter.MustClick;
};
return () => state == DragFilter.Ok;
}
呼び出しコードは次のようになります。
public MyWindow() {
var dragAllowed = PrepareForDragDrop(this);
source.MouseMove += (sender, args) => {
if (dragAllowed()) DragDrop.DoDragDrop(source, data, effects);
};
}
これは、マウスの左ボタンが押されている間に静止マウス カーソル上でアニメーションがドラッグ ソースを移動したためにドラッグが開始される、簡単に再現可能な状況を回避します。ただし、これは根本的な問題を回避するものではありません。ドラッグ ソースをクリックし、マウス イベントが 1 つしか発生しないほど少ししか動かさない場合、アニメーションは停止します。幸いなことに、これは実際には起こりそうにありません。このソリューションには、Win32 API を直接使用せず、バックグラウンド スレッドを関与させる必要がないという利点があります。
DoDragDrop
バックグラウンド スレッドからの呼び出し
うまくいきません。DragDrop.DoDragDrop
UI スレッドから呼び出す必要があり、ブロックされます。ディスパッチャーを使用して呼び出すバックグラウンド スレッドを呼び出すことは役に立ちませんDoDragDrop
。