views:

904

answers:

3

Hi!

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

A: 

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);
Mark Pim
+1 - a billion times more comprehensive than my answer
MoominTroll
@MoominTroll haha, just happens that I've just implemented this in our app :D
Mark Pim
I must have read wrong... You're starting a new thread every time the mouse is clicked and it's not a double click? Wow... That's... hum... special.Please consider the solution below using "e.ClickCount >= 2".
Joce
+3  A: 
 <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)

MoominTroll
Not as performant as handling a double-click event, but fast enough when it's not there. +1
Randolpho
Yes I've tried that.. but I need to handle both Single and DoubleClick.. clickcount does not really help, because it seems that doubleclick == singleclick too.
PaN1C_Showt1Me
@PaN1C_Showt1Me How about doing 'if (e.ClickCount >= 2) { HandleDoubelClick(); } else if (e.ClickCount >= 1) {HandleSingleClick();}'
Joce
Well that's the same as Troll brought.. the problem was, that click count was never >= 2
PaN1C_Showt1Me
A: 

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.

Grant BlahaErath