Question

I need to be able to handle the double click and single click event on the WPF StackPanel. But there is no such thing as the StackPanel's DoubleClick Event. I want to do 2 different operations in these 2 EventHandlers.

Any idea how to do that?

Thank you

Was it helpful?

Solution

The best way is to right your own mouse button handler with a timeout - if the event is fired again within the timeout period, then fire your doubleclick message, otherwise call the single click handler. Here's some sample code (Edit: originally found here):

/// <summary>
/// For double clicks
/// </summary>
public class MouseClickManager {
    private event MouseButtonEventHandler _click;
    private event MouseButtonEventHandler _doubleClick;

    public event MouseButtonEventHandler Click {
        add { _click += value; }
        remove { _click -= value; }
    }

    public event MouseButtonEventHandler DoubleClick {
        add { _doubleClick += value; }
        remove { _doubleClick -= value; }
    }

    /// <summary>
    /// Gets or sets a value indicating whether this <see cref="MouseClickManager"/> is clicked.
    /// </summary>
    /// <value><c>true</c> if clicked; otherwise, <c>false</c>.</value>
    private bool Clicked { get; set; }

    /// <summary>
    /// Gets or sets the timeout.
    /// </summary>
    /// <value>The timeout.</value>
    public int DoubleClickTimeout { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="MouseClickManager"/> class.
    /// </summary>
    /// <param name="control">The control.</param>
    public MouseClickManager(int doubleClickTimeout) {
        this.Clicked = false;
        this.DoubleClickTimeout = doubleClickTimeout;
    }

    /// <summary>
    /// Handles the click.
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    public void HandleClick(object sender, MouseButtonEventArgs e) {
        lock (this) {
            if (this.Clicked) {
                this.Clicked = false;
                OnDoubleClick(sender, e);
            }
            else {
                this.Clicked = true;
                ParameterizedThreadStart threadStart = new ParameterizedThreadStart(ResetThread);
                Thread thread = new Thread(threadStart);
                thread.Start(e);
            }
        }
    }

    /// <summary>
    /// Resets the thread.
    /// </summary>
    /// <param name="state">The state.</param>
    private void ResetThread(object state) {
        Thread.Sleep(this.DoubleClickTimeout);

        lock (this) {
            if (this.Clicked) {
                this.Clicked = false;
                OnClick(this, (MouseButtonEventArgs)state);
            }
        }
    }

    /// <summary>
    /// Called when [click].
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private void OnClick(object sender, MouseButtonEventArgs e) {
        if (_click != null) {
            if (sender is Control) {
                (sender as Control).Dispatcher.BeginInvoke(_click, sender, e);
            }
        }
    }

    /// <summary>
    /// Called when [double click].
    /// </summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
    private void OnDoubleClick(object sender, MouseButtonEventArgs e) {
        if (_doubleClick != null) {
            _doubleClick(sender, e);
        }
    }
}

Then, in the control you want to receive the events:

MouseClickManager fMouseManager = new MouseClickManager(200);
fMouseManager.Click += new MouseButtonEventHandler(YourControl_Click); 
fMouseManager.DoubleClick += new MouseButtonEventHandler(YourControl_DoubleClick);

OTHER TIPS

 <StackPanel MouseDown="StackPanel_MouseDown">
   <!--stackpanel content-->
    <TextBlock>Hello</TextBlock>
</StackPanel>

Then in the event handler:

 private void StackPanel_MouseDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount >= 2)
        { 
            string hello; //only hit here on double click  
        }
    }

Should work. Note that single clicking in the StackPanel would hit the event (but fail the if check)

...years later. @MoominTroll's solution is perfectly acceptable. Another option is to wrap the stack panel in a content control that supports the double click event.

<ContentControl MouseDoubleClick="DoubleClickHandler" >
    <StackPanel>

    </StackPanel>
</ContentControl>

Another option is to add a MouseBinding to the InputBindings on the StackElement and then add a CommandBinding that gets activated by MouseBinding. On the whole this is a better practice than event based mechanisms because it avoids the memory leak issues caused by strong references. It also provides for separation of command logic from the representation.

That being said, its not as straight forward and attaching to the event makes for a great shortcut.

And it goes without saying, make your stackpanel background at least transparent or it won't be caught by the mouse click hit test when you click on the "background". Null backgrounds are skipped over by hit detection.

I had a similar problem (respond to single click event and do an additional work in case of double click event). I solved the problem this way:

1) define and declare some integer to hold last click timestamp

int lastClickTimestamp;

2) in Window_Loaded method initialize previously declared variable with some number greater than 200

lastClickTimestamp = 1000;

3) add mouse button handler to your stack panel

stackPanel.MouseLeftButtonUp += new MouseButtonEventHandler(stackPanel_MouseLeftButtonUp);

4) add following method

void stackPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        if (e.Timestamp - lastClickTimeStamp < 200)
        {
            //double click
        }
        lastClickTimeStamp = e.Timestamp;

        //single click
    }

This code is useless if you need to separately detect single click and double click event. That situation introduces a bit more complexity, but definitly can be solved.

Complete answer using WPF DispatcherTimer. We create the timer on demand and disconnect it when done in order to prevent resource clogging.

The C#:

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

public partial class MainWindow : Window
{
    DispatcherTimer dt;

    bool clear_timer()
    {
        if (dt == null)
            return false;
        dt.Tick -= _single_click;
        dt = null;
        return true;
    }

    private void _click(Object sender, MouseButtonEventArgs e)
    {
        if (clear_timer())
            Debug.Print("double click");
        else
            dt = new DispatcherTimer(
                        TimeSpan.FromMilliseconds(GetDoubleClickTime()),
                        DispatcherPriority.Normal,
                        _single_click,
                        Dispatcher);
    }

    void _single_click(Object sender, EventArgs e)
    {
        clear_timer();
        Debug.Print("single click");
    }

    public MainWindow() { InitializeComponent(); }

    [DllImport("user32.dll")]
    static extern uint GetDoubleClickTime();
 };

The XAML:

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Orientation="Horizontal"
                Background="AliceBlue"
                Width="100"
                Height="100"
                MouseLeftButtonDown="_click" />
</Window>

There is an easier solution to do this.

In StackPanel's event PreviewMouseLeftDown (for example), you can check if MouseButtonEventArgs.ClickCount property has a value of 2. 1 = Single click 2 = Double Click

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top