WPF StackPanel with Click AND DoubleClick

22,728

Solution 1

The best way is to write 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);

Solution 2

 <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)

Solution 3

...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>

Solution 4

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.

Share:
22,728
theSpyCry
Author by

theSpyCry

Updated on April 07, 2021

Comments

  • theSpyCry
    theSpyCry about 3 years

    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

  • Randolpho
    Randolpho over 14 years
    Not as performant as handling a double-click event, but fast enough when it's not there. +1
  • Mark Pim
    Mark Pim over 14 years
    @MoominTroll haha, just happens that I've just implemented this in our app :D
  • theSpyCry
    theSpyCry over 14 years
    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.
  • joce
    joce about 14 years
    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
    joce about 14 years
    @PaN1C_Showt1Me How about doing 'if (e.ClickCount >= 2) { HandleDoubelClick(); } else if (e.ClickCount >= 1) {HandleSingleClick();}'
  • theSpyCry
    theSpyCry about 14 years
    Well that's the same as Troll brought.. the problem was, that click count was never >= 2
  • Amged
    Amged over 12 years
    I don't know How can I assign this events to control?
  • gliderkite
    gliderkite over 11 years
    In general, avoid locking on a public type, or instances beyond your code's control. lock (this) is a problem if the instance can be accessed publicly.