3

シナリオ (簡略化) は次のとおりです。ウィンドウにコントロール (たとえば、四角形) があります。MouseMove イベントをフックして、ドラッグ アンド ドロップを開始できるようにしました。次に、MouseDown イベントでアニメーション化して、50 ピクセル右に移動します。ただし、Rectangle 上でマウスを押したままにすると、コントロールが約 1 ピクセル移動してから一時停止します。マウスを動かしたときだけアニメーションが続きます。これを解決する理由と方法を知っている人はいますか?どうもありがとう!!

この問題を再現するソース コードは次のとおりです。

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>

4

1 に答える 1

3

このような同じ問題に出くわしました。ドラッグ アンド ドロップ ソースがアニメーション パスに沿って移動しました。ドラッグ ソースのパス上にマウスを配置し、マウスの左ボタンを押したままにすると、ソースがマウスに触れたときにアニメーションが停止します。マウス ボタンを離すか、マウスを移動すると、アニメーションが続行されます。(興味深いことに、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 イベントを発生させる

DoDragDrop1 つの回避策は、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.DoDragDropUI スレッドから呼び出す必要があり、ブロックされます。ディスパッチャーを使用して呼び出すバックグラウンド スレッドを呼び出すことは役に立ちませんDoDragDrop

于 2013-04-09T17:52:39.213 に答える