17

簡略化した私のシナリオ: 従業員の行を含む ListView があり、各従業員の行には、給与を調整する「増加」および「減少」ボタンがあります。

私のプログラムで、従業員の行をダブルクリックすると、「この人を解雇する」ことを意味するとします。

問題は、「増加」をすばやくクリックしているときに、ListViewItem でダブルクリック イベントがトリガーされることです。当然のことながら、給料を上げているだけで人を解雇したくはありません。

他のすべてのイベントがどのように機能するかによると、イベントを設定することでこれを解決できると期待してHandled=trueいます。ただし、これは機能しません。WPF は、完全にリンクされていない 2 つの別個のダブルクリック イベントを生成するように思われます。

以下は、私の問題を再現するための最小限の例です。目に見えるコンポーネント:

<ListView>
    <ListViewItem MouseDoubleClick="ListViewItem_MouseDoubleClick">
            <Button MouseDoubleClick="Button_MouseDoubleClick"/>
    </ListViewItem>
</ListView>

そしてハンドラコード:

private void Button_MouseDoubleClick(object s, MouseButtonEventArgs e) {
    if (!e.Handled) MessageBox.Show("Button got unhandled doubleclick.");
    e.Handled = true;
}

private void ListViewItem_MouseDoubleClick(object s, MouseButtonEventArgs e) {
    if (!e.Handled) MessageBox.Show("ListViewItem got unhandled doubleclick.");
    e.Handled = true;
}

このプログラムを起動し、リストされたボタンをダブルクリックすると、両方のメッセージ ボックスが順番に表示されます。(また、この後ボタンが下の位置で動かなくなります。)

「修正」として、ListViewItem ハンドラーで、イベントに関連付けられたビジュアル ツリーを検査し、「どこかにボタンがある」ことを確認してイベントを破棄できますが、これは最後の手段です。そのようなクラッジをコーディングする前に、少なくとも問題を理解したいと思います。

WPFがこれを行う理由と、問題を回避するためのエレガントな慣用的な方法を知っている人はいますか?

4

7 に答える 7

10

MouseDoubleClickイベントはイベントの上に抽象化されたものであることがわかると思いますMouseDown。つまり、2 つMouseDownのイベントが連続して発生すると、そのMouseDoubleClickイベントも発生します。との両方にこのロジックがあるようButtonListViewItem見えるため、2 つの異なるMouseDoubleClickイベントが表示される理由が説明されます。

MSDNによると:

このルーティング イベントは、要素ツリーを介してバブリング ルートに従っているように見えますが、実際には、各 UIElement によって要素ツリーに沿って発生する直接ルーティング イベントです。MouseDoubleClick イベント ハンドラーで Handled プロパティを true に設定すると、ルートに沿って後続の MouseDoubleClick イベントが発生し、Handled が false に設定されます。

で処理MouseDownし、Buttonそれを処理済みに設定して、に伝播しないようにすることができListViewItemます。

これを自分で確認できればいいのですが、現時点では .NET を使用していません。

于 2011-06-08T14:12:00.493 に答える
8

MouseDoubleClickのMSDN ドキュメントには、MouseDoubleClick イベントがバブリングしないようにする方法が提案されています。

マウスのダブル クリックを処理するコントロールの作成者は、ClickCount が 2 の場合に MouseLeftButtonDown イベントを使用する必要があります。これにより、要素ツリー内の別の要素がイベントを処理する場合に、Handled の状態が適切に伝播されます。

したがって、ClickCount が 2 の場合は、MouseLeftButtonDown イベントを処理し、hanged を true に設定できます。しかし、ボタンではすでに MouseLeftButtonDown を処理しており、そのイベントを発生させないため、これは失敗します。

ただし、PreviewMouseLeftButtonDown イベントはまだあります。以下のように、ClickCount が 2 の場合に、ボタンでこれを使用して、handled を true に設定します。

 private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)   {
     if (e.ClickCount == 2)
         e.Handled = true;
 }
于 2011-06-13T02:31:17.330 に答える
4

エレガントでも慣用的でもないかもしれませんが、現在の回避策よりも気に入るかもしれません。

    int handledTimestamp = 0;

    private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        if (e.Timestamp != handledTimestamp)
        {
            System.Diagnostics.Debug.WriteLine("ListView at " + e.Timestamp);
            handledTimestamp = e.Timestamp;
        }
        e.Handled = true;
    }

    private void Button_MouseDoubleClick(object sender, MouseButtonEventArgs e)
    {
        if (e.Timestamp != handledTimestamp)
        {
            System.Diagnostics.Debug.WriteLine("Button at " + e.Timestamp);
            handledTimestamp = e.Timestamp;
        }
        e.Handled = true;
    }

奇妙なことに、これは設定しないと機能しませんe.Handled = truee.Handledボタンのハンドラーにブレークポイントまたはスリープを設定して配置しない場合、ListView のハンドラーで遅延が発生します。(明示的な遅延がなくても、それを破るのに十分な小さな遅延が発生します。) しかし、一度設定e.Handledすると、遅延の長さに関係なく、タイムスタンプは同じになります。これがなぜなのかはわかりません。また、これが信頼できる文書化された動作であるかどうかもわかりません。

于 2011-06-13T01:54:28.550 に答える
4

この質問に対する明確な答えはなかったので、これは私が最終的に使用した回避策です。

protected override void ListViewItem_MouseDoubleClick(MouseButtonEventArgs e) {
    var originalSource = e.OriginalSource as System.Windows.Media.Visual;
    if (originalSource.IsDescendantOf(this)) {
        // Test for IsDescendantOf because other event handlers can have changed
        // the visual tree such that the actually clicked original source
        // component is no longer in the tree.
        // You may want to handle the "not" case differently, but for my
        // application's UI, this makes sense.
        for (System.Windows.DependencyObject depObj = originalSource;
             depObj != this;
             depObj = System.Windows.Media.VisualTreeHelper.GetParent(depObj))
        {
            if (depObj is System.Windows.Controls.Primitives.ButtonBase) return;
        }
    }

    MessageBox.Show("ListViewItem doubleclicked.");
}

ここでは、文書化の目的で、完全な名前空間でクラス名が不必要に型指定されています。

于 2011-06-10T11:45:32.360 に答える
2

Control.MouseDoubleClickはバブル イベントではなく、ダイレクト イベントです。

ビジュアル ツリーとルーティング イベントを参照するためのツールであるSnoopでこの質問を確認したところControl.MouseDoubleClick、'ListView' と 'ListBoxItem' のイベントが一度に発生することがわかりました。この Snoop ツールで確認できます。


まず、答えを見つけるために、 の両方のイベント引数MouseDoublClickが同じオブジェクトであることを確認する必要があります。これらは同じオブジェクトであると考えられます。それが本当なら、それはあなたの質問として非常に奇妙ですが、それらは同じインスタンスではありません. 次のコードで確認できます。

RoutedEventArgs _eventArg;
private void Button_MouseDoubleClick(object s, RoutedEventArgs e)
{
    if (!e.Handled) Debug.WriteLine("Button got unhandled doubleclick.");
    //e.Handled = true;
    _eventArg = e;
}

private void ListViewItem_MouseDoubleClick(object s, RoutedEventArgs e)
{
    if (!e.Handled) Debug.WriteLine("ListViewItem got unhandled doubleclick.");
    e.Handled = true;
    if (_eventArg != null)
    {
        var result = _eventArg.Equals(e);
        Debug.WriteLine(result);
    }
}

のイベント引数がMouseDoublClickどこかに新しく作成されたということですが、それがなぜなのかはよくわかりません。

より明確にするために、 のイベント引数を確認しましょうBottonBase.Click。同じインスタンスのチェックについては true を返します。

    <ListView>
        <ListViewItem ButtonBase.Click="ListViewItem_MouseDoubleClick">
            <Button Click="Button_MouseDoubleClick" Content="click"/>
        </ListViewItem>
    </ListView>

あなたが言ったように実行だけに集中すれば、多くの解決策があります。_eventArg上記のように、flag( ) を使用するのも良い選択だと思います。

于 2011-06-16T03:30:42.923 に答える
1

私はちょうどこの同じ問題を抱えていました。単純だが明白ではない解決策があります。

Control でダブルクリックを発生させる方法は次のとおりです....

private static void HandleDoubleClick(object sender, MouseButtonEventArgs e)
{
    if (e.ClickCount == 2)
    {
        Control control = (Control)sender;
        MouseButtonEventArgs mouseButtonEventArgs = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
        if (e.RoutedEvent == UIElement.PreviewMouseLeftButtonDownEvent || e.RoutedEvent == UIElement.PreviewMouseRightButtonDownEvent)
        {
            mouseButtonEventArgs.RoutedEvent = Control.PreviewMouseDoubleClickEvent;
            mouseButtonEventArgs.Source = e.OriginalSource;
            mouseButtonEventArgs.OverrideSource(e.Source);
            control.OnPreviewMouseDoubleClick(mouseButtonEventArgs);
        }
        else
        {
            mouseButtonEventArgs.RoutedEvent = Control.MouseDoubleClickEvent;
            mouseButtonEventArgs.Source = e.OriginalSource;
            mouseButtonEventArgs.OverrideSource(e.Source);
            control.OnMouseDoubleClick(mouseButtonEventArgs);
        }
        if (mouseButtonEventArgs.Handled)
        {
            e.Handled = true;
        }
    }
}

したがって、子コントロールのPreviewMouseDoubleClick設定を処理しても、親コントロールでは起動しません。e.Handled = trueMouseDoubleClick

于 2012-07-29T11:33:32.097 に答える
-1
  1. ダブルクリックイベントはユーザー設定に依存し、その遅延はコントロールパネルでカスタマイズされるため、ダブルクリックイベントの発生方法を簡単に変更することはできません。
  2. のボタンを押すことができるRepeatButtonをチェックアウトする必要があります。これを押すと、通常の順序で複数のクリックイベントが生成されます。
  3. イベントバブリングをカスタマイズする場合は、イベントの伝播をブロックできるプレビューイベントを検索する必要があります。WPFプレビューイベントとは何ですか?
于 2011-06-16T07:33:51.473 に答える