How do I detect if both left and right buttons are pushed?
Asked Answered
G

6

3

I would like have three mouse actions over a control: left, right and BOTH.

I've got the left and right and am currently using the middle button for the third, but am curious how I could use the left and right buttons being pressed together, for those situations where the user has a mouse without a middle button. This would be handled in the OnMouseDown method of a custom control.

UPDATE After reviewing the suggested answers, I need to clarify that what I was attempting to do was to take action on the mouse click in the MouseDown event (actually OnMouseDown method of a control). Because it appears that .NET will always raise two MouseDown events when both the left and right buttons on the mouse are clicked (one for each button), I'm guessing the only way to do this would be either do some low level windows message management or to implement some sort of delayed execution of an action after MouseDown. In the end, it is just way simpler to use the middle mouse button.

Now, if the action took place on MouseUp, then Gary's or nos's suggestions would work well.

Any further insights on this problem would be appreciated. Thanks!

Genip answered 25/7, 2009 at 19:53 Comment(2)
Dude. Don't go there. UI standards exist for a good reason. Your usability will suffer. That sounds worse than the "Triple Click"Solano
@Solano Any decent Minesweeper clone needs both-clicking for chording. That's one example where it's useful.Heterodyne
G
0

Based on what I learned from the other answers, I was able to get this working. I post the solution here in case some else needs it.

I created a component, MouseDownManager, that I call during the MouseDown event. It tracks the button just pushed, determines what button we are waiting for in order to have a "both" buttons down event, then starts a timer to wait for that button. If within the allotted time, the correct button is pressed, the MouseDownManager raises the appropriate "both" button down event. Otherwise it raises the appropriate single button event. On the form, I'm handling the MouseDownManager's MouseDown event to take action on the translated mouse click.

I can now just drop this component on a form/control and I can react to a "both" click.

Thanks for the help in figuring this out.

/// <summary>
/// Manage mouse down event to enable using both buttons at once.
/// I.e. allowing the pressing of the right and left mouse
/// buttons together.
/// </summary>
public partial class MouseDownManager : Component
{

  /// <summary>
  /// Raised when a mouse down event is triggered by this
  /// component.
  /// </summary>
  public event EventHandler<MouseEventArgs> MouseDown;

  protected virtual void OnMouseDown( MouseEventArgs e )
  {
    if (this.MouseDown != null)
    {
      this.MouseDown( this, e );
    }
  }

  public MouseDownManager()
  { 
    //-- the timer was dropped on the designer, so it
    //   is initialized by InitializeComponent.
    InitializeComponent();
  }

  /// <summary>
  /// Defines the interval that the timer will wait for the other
  /// click, in milliseconds.
  /// </summary>
  public int BothClickInterval
  {
    get
    {
      return this.tmrBothInterval.Interval;
    }
    set
    {
      this.tmrBothInterval.Interval = value;
    }
  }

  private MouseButtons _virtualButton = MouseButtons.Middle;

  /// <summary>
  /// Defines the button that is sent when both buttons are
  /// pressed. This can be either a single button (like a middle
  /// button) or more than one button (like both the left and
  /// right buttons.
  /// </summary>
  public MouseButtons VirtualButton
  {
    get
    {
      return _virtualButton;
    }
    set
    {
      _virtualButton = value;
    }
  }

  private MouseEventArgs _originalArgs;

  /// <summary>
  /// Defines the original mouse event arguments that is associated
  /// with the original press.
  /// </summary>
  private MouseEventArgs OriginalArgs
  {
    get
    {
      return _originalArgs;
    }
    set
    {
      _originalArgs = value;
    }
  }

  private MouseButtons _waitButton = MouseButtons.None;

  /// <summary>
  /// Defines the button that we are waiting on, for there to be a
  /// both button click.
  /// </summary>
  private MouseButtons WaitButton
  {
    get
    {
      return _waitButton;
    }
    set
    {
      _waitButton = value;
    }
  }

  /// <summary>
  /// Manage a mouse button being depressed.
  /// </summary>
  /// <remarks>
  /// This will instigate one of two actions.  If there is no
  /// current wait button, this will set the appropriate wait
  /// button (if the button pressed was the left button, then we
  /// are waiting on the right button) and start a timer. If the
  /// wait button is set, but this isn't that button, then the wait
  /// button is updated and the timer restarted.  Also, this will
  /// trigger the waiting event.  If it is the wait button, then
  /// the appropriate event is raised for a "both" button press.
  /// </remarks>
  public void ManageMouseDown( MouseEventArgs mouseArgs )
  {
    //-- Is the the button we are waiting for?
    if (mouseArgs.Button == this.WaitButton)
    {
      //-- Turn off timer.
      this.ClearTimer();

      //-- Create new mouse event args for our virtual event.
      MouseEventArgs bothArgs = new MouseEventArgs( this.VirtualButton
                                                  , mouseArgs.Clicks
                                                  , mouseArgs.X
                                                  , mouseArgs.Y
                                                  , mouseArgs.Delta );

      //-- Raise the mouse down event.
      this.OnMouseDown( bothArgs );
    }
    else
    {
      //-- Clear timer
      this.ClearTimer();

      //-- If we were waiting for a button, then
      //   fire the event for the original button.
      if (this.WaitButton != MouseButtons.None)
      {
        this.OnMouseDown( this.OriginalArgs );
      }

      //-- Cache the original mouse event args.
      MouseEventArgs newMouseArgs = new MouseEventArgs( mouseArgs.Button
                                                      , mouseArgs.Clicks
                                                      , mouseArgs.X
                                                      , mouseArgs.Y
                                                      , mouseArgs.Delta );
      this.OriginalArgs = newMouseArgs;

      //-- Reset to wait for the appropriate next button.
      switch (mouseArgs.Button)
      {
        case MouseButtons.Left:
          this.WaitButton = MouseButtons.Right;
          break;
        case MouseButtons.Right:
          this.WaitButton = MouseButtons.Left;
          break;
        default:
          this.WaitButton = MouseButtons.None;
          break;
      }

      //-- Start timer
      this.tmrBothInterval.Enabled = true;
    }
  }

  /// <summary>
  /// Raise the event for the button that was pressed initially
  /// and turn off the timer.
  /// </summary>
  private void tmrBothInterval_Tick( object sender, EventArgs e )
  {
    //-- Turn off the timer.
    this.tmrBothInterval.Enabled = false;

    //-- Based on the original button pressed, raise
    //   the appropriate mouse down event.
    this.OnMouseDown( this.OriginalArgs );

    //-- Clear timer.
    this.ClearTimer();
  }

  /// <summary>
  /// Clear the timer and associated variables so we aren't waiting
  /// for the second click.
  /// </summary>
  private void ClearTimer()
  {
    //-- Turn off the timer.
    this.tmrBothInterval.Enabled = false;

    //-- Clear the wait button.
    this.WaitButton = MouseButtons.None;

    //-- Clear the original args
    this.OriginalArgs = null;
  }
}

/// <summary>
/// Just the mouse code from the control needing the functionality.
/// </summary>
public class MyControl: Control
{
  /// <summary>
  /// Handle the mouse down event. This delegates the actual event
  /// to the MouseDownManager, so we can capture the situation
  /// where both buttons are clicked.
  /// </summary>
  protected override void OnMouseDown( MouseEventArgs e )
  {
    this.mdmMain.ManageMouseDown( e );
  }

  /// <summary>
  /// Process the mouse down event.
  /// </summary>
  private void mdmMain_MouseDown( object sender, MouseEventArgs e )
  {
    //-- Get the reported button state.
    MouseButtons mouseButton = e.Button;

    //-- Execute logic based on button pressed, which now can include
    //   both the left and right together if needed....
  }
}
Genip answered 27/4, 2010 at 16:50 Comment(0)
C
2

There's always the "do it yourself" approach:

Just remember the state of the button presses and release. In OnMouseDown you simply remember the button pressed, and in OnMouseUp just check what buttons were remembered, as well as clear the state for the button.

You need some logic to not do several actions when buttons are released. Something like

MouseButtons buttonPressed;
..

void OnMouseDown(MouseEventArgs e) 
{
   buttonPressed |= e.Button;
}


void OnMouseUp(MouseEventArgs e) 
{
  if(!doneAction) {
    if((buttonPressed & MouseButtons.Right == MouseButtons.Right 
       && buttonPressed & MouseButtons.Left == MouseButtons.Left)
       || buttonPressed & MouseButtons.Middle== MouseButtons.Middle) {
       DoMiddleAction();
       doneAction = true;
    } else if(check Right button , etc.) {
       .... 
    }
  }

  buttonpressed &= ~e.Button;
  if(buttonpressed == None)
      doneAction = false;

}
Combinative answered 25/7, 2009 at 20:52 Comment(3)
I am currently trying to handle the action on MouseDown, so this would get complicated (see comment on Gary Willoughby's answer). However, I'm going to revisit the code to see if I can do it in mouse up, where this would make sense. Thanks!Genip
I think this solution is the best. It uses the existing MouseEventArgs and e.Button to do what is needed. But your code could be made simpler. You don't need doneAction, because THIS OnMouseUp is the handler for all of the relevant mouseUp events. Simply use Select Case buttonPressed with a case statement for each type to handle: Case MouseButtons.Left, Case MouseButtons.Middle, MouseButtons.Left Or MouseButtons.Right, etc...Heterodyne
My above suggestion works for OnMouseDown also, using a similar Select Case statement.Heterodyne
Z
2

I would personally use the MouseUp and MouseDown events for a more cleaner way to handle it and to avoid interop. Basically this code uses a static class to hold the status of the two buttons and by checking that you can determine wether both are in fact down.

using System.Windows.Forms;

namespace WindowsFormsApplication1
{

    public static class MouseButtonStatus
    {
        static bool RightButton;
        static bool LeftButton;

        public static bool RightButtonPressed
        {
            get
            {
                return RightButton;
            }
            set
            {
                RightButton = value;
            }
        }

        public static bool LeftButtonPressed
        {
            get
            {
                return LeftButton;
            }
            set
            {
                LeftButton = value;
            }
        }


    }


    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        public void HandleButtons(bool LeftPressed, bool RightPressed)
        {
            if(LeftPressed && RightPressed)
            {
                //BOTH ARE PRESSED
            }
            else if(LeftPressed)
            {
                //LEFT IS PRESSED
            }
            else if(RightPressed)
            {
                //RIGHT IS PRESSED
            }
            else
            {
                //NONE ARE PRESSED
            }
        }

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left)
            {
                MouseButtonStatus.LeftButtonPressed = true;
            }
            if(e.Button == MouseButtons.Right)
            {
                MouseButtonStatus.RightButtonPressed = true;
            }

            HandleButtons(MouseButtonStatus.LeftButtonPressed, MouseButtonStatus.RightButtonPressed);
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            if(e.Button == MouseButtons.Left)
            {
                MouseButtonStatus.LeftButtonPressed = false;
            }
            if(e.Button == MouseButtons.Right)
            {
                MouseButtonStatus.RightButtonPressed = false;
            }

            HandleButtons(MouseButtonStatus.LeftButtonPressed, MouseButtonStatus.RightButtonPressed);
        }



    }
}
Zoo answered 25/7, 2009 at 21:42 Comment(4)
The problem with this approach is that .NET will send two mouse downs, one for the left and one for the right (depending on which is perceived to have been clicked first. This would work if I only needed to handle both, but if I need to handle left only, right only or both, then this gets real complicated, I'd think, as I'd have to setup some sort of timer to track the interval between the clicks and then I'd have to defer the action to take until I was certain it wasn't both buttons. This in the end still might be more reliable than Interop though.Genip
I've edited the code above to apply a solution to your problem. You just have to use a method to handle the clicks and update it each MouseUp and MouseDown. Check it out. :)Zoo
You're never going to get around the fact that when you press a button it fires an event. So if you want to handle a both button click you are going to have to handle first a right click event then a left click event. This is probably why no programs use a both button click for the UI.Zoo
I thought I might mention that I got the idea from MineSweeper. Clicking on both button is used to reveal all unflagged cells around the clicked one. I've also seen it in other games. So it is possible in windows, but maybe not exactly that way in .NET.Genip
M
1

Not sure if there's a native .Net way to do it, but if you're happy with P/Invoke you can use GetKeyState or GetAsyncKeyState like this:

[DllImport("user32.dll")]
public static extern short GetKeyState(int nVirtKey);

if (GetKeyState((int)Keys.LButton) < 0 && GetKeyState((int)Keys.RButton) < 0)
{
    // Both buttons are pressed.
}
Martineau answered 25/7, 2009 at 19:58 Comment(5)
Thanks. I'm not sure "happy" is the right word, but I think I can muster the courage to use it in this situation. :DGenip
See my answer to avoid p/invoke.Zoo
After some testing, this is turning out to not be very reliable. About 1 in 4 are caught when using this. Not sure why and there may be something I could do to improve that, but I have no idea what.Genip
Did you look up the difference between GetKeyState and GetAsyncKeyState? You'll have to use whichever is appropriate for what you're trying to do.Martineau
You need to take into account that the buttons won't go down exactly together. It's not enough to put a check in the OnLButtonDown handler whether the right button is also pressed - you need to also put a check in the OnRButtonDown handler to see whether the left button is pressed.Martineau
W
0

Wasn't "middle" the same as "left and right together"? At least that's what I remember from somewhere, but that was from way back when I had two button mice without scrollwheel buttons...

Wherefrom answered 25/7, 2009 at 20:1 Comment(2)
‎This is a flag on Xorg to emulate middle click on crappy laptops like mine (on X11, when you select text it gets copied automagically, and middle click also acts as paste, something I really miss when I use windows).Pamphlet
Funny, cos I remember it from Windows. Oh well.Wherefrom
G
0

Based on what I learned from the other answers, I was able to get this working. I post the solution here in case some else needs it.

I created a component, MouseDownManager, that I call during the MouseDown event. It tracks the button just pushed, determines what button we are waiting for in order to have a "both" buttons down event, then starts a timer to wait for that button. If within the allotted time, the correct button is pressed, the MouseDownManager raises the appropriate "both" button down event. Otherwise it raises the appropriate single button event. On the form, I'm handling the MouseDownManager's MouseDown event to take action on the translated mouse click.

I can now just drop this component on a form/control and I can react to a "both" click.

Thanks for the help in figuring this out.

/// <summary>
/// Manage mouse down event to enable using both buttons at once.
/// I.e. allowing the pressing of the right and left mouse
/// buttons together.
/// </summary>
public partial class MouseDownManager : Component
{

  /// <summary>
  /// Raised when a mouse down event is triggered by this
  /// component.
  /// </summary>
  public event EventHandler<MouseEventArgs> MouseDown;

  protected virtual void OnMouseDown( MouseEventArgs e )
  {
    if (this.MouseDown != null)
    {
      this.MouseDown( this, e );
    }
  }

  public MouseDownManager()
  { 
    //-- the timer was dropped on the designer, so it
    //   is initialized by InitializeComponent.
    InitializeComponent();
  }

  /// <summary>
  /// Defines the interval that the timer will wait for the other
  /// click, in milliseconds.
  /// </summary>
  public int BothClickInterval
  {
    get
    {
      return this.tmrBothInterval.Interval;
    }
    set
    {
      this.tmrBothInterval.Interval = value;
    }
  }

  private MouseButtons _virtualButton = MouseButtons.Middle;

  /// <summary>
  /// Defines the button that is sent when both buttons are
  /// pressed. This can be either a single button (like a middle
  /// button) or more than one button (like both the left and
  /// right buttons.
  /// </summary>
  public MouseButtons VirtualButton
  {
    get
    {
      return _virtualButton;
    }
    set
    {
      _virtualButton = value;
    }
  }

  private MouseEventArgs _originalArgs;

  /// <summary>
  /// Defines the original mouse event arguments that is associated
  /// with the original press.
  /// </summary>
  private MouseEventArgs OriginalArgs
  {
    get
    {
      return _originalArgs;
    }
    set
    {
      _originalArgs = value;
    }
  }

  private MouseButtons _waitButton = MouseButtons.None;

  /// <summary>
  /// Defines the button that we are waiting on, for there to be a
  /// both button click.
  /// </summary>
  private MouseButtons WaitButton
  {
    get
    {
      return _waitButton;
    }
    set
    {
      _waitButton = value;
    }
  }

  /// <summary>
  /// Manage a mouse button being depressed.
  /// </summary>
  /// <remarks>
  /// This will instigate one of two actions.  If there is no
  /// current wait button, this will set the appropriate wait
  /// button (if the button pressed was the left button, then we
  /// are waiting on the right button) and start a timer. If the
  /// wait button is set, but this isn't that button, then the wait
  /// button is updated and the timer restarted.  Also, this will
  /// trigger the waiting event.  If it is the wait button, then
  /// the appropriate event is raised for a "both" button press.
  /// </remarks>
  public void ManageMouseDown( MouseEventArgs mouseArgs )
  {
    //-- Is the the button we are waiting for?
    if (mouseArgs.Button == this.WaitButton)
    {
      //-- Turn off timer.
      this.ClearTimer();

      //-- Create new mouse event args for our virtual event.
      MouseEventArgs bothArgs = new MouseEventArgs( this.VirtualButton
                                                  , mouseArgs.Clicks
                                                  , mouseArgs.X
                                                  , mouseArgs.Y
                                                  , mouseArgs.Delta );

      //-- Raise the mouse down event.
      this.OnMouseDown( bothArgs );
    }
    else
    {
      //-- Clear timer
      this.ClearTimer();

      //-- If we were waiting for a button, then
      //   fire the event for the original button.
      if (this.WaitButton != MouseButtons.None)
      {
        this.OnMouseDown( this.OriginalArgs );
      }

      //-- Cache the original mouse event args.
      MouseEventArgs newMouseArgs = new MouseEventArgs( mouseArgs.Button
                                                      , mouseArgs.Clicks
                                                      , mouseArgs.X
                                                      , mouseArgs.Y
                                                      , mouseArgs.Delta );
      this.OriginalArgs = newMouseArgs;

      //-- Reset to wait for the appropriate next button.
      switch (mouseArgs.Button)
      {
        case MouseButtons.Left:
          this.WaitButton = MouseButtons.Right;
          break;
        case MouseButtons.Right:
          this.WaitButton = MouseButtons.Left;
          break;
        default:
          this.WaitButton = MouseButtons.None;
          break;
      }

      //-- Start timer
      this.tmrBothInterval.Enabled = true;
    }
  }

  /// <summary>
  /// Raise the event for the button that was pressed initially
  /// and turn off the timer.
  /// </summary>
  private void tmrBothInterval_Tick( object sender, EventArgs e )
  {
    //-- Turn off the timer.
    this.tmrBothInterval.Enabled = false;

    //-- Based on the original button pressed, raise
    //   the appropriate mouse down event.
    this.OnMouseDown( this.OriginalArgs );

    //-- Clear timer.
    this.ClearTimer();
  }

  /// <summary>
  /// Clear the timer and associated variables so we aren't waiting
  /// for the second click.
  /// </summary>
  private void ClearTimer()
  {
    //-- Turn off the timer.
    this.tmrBothInterval.Enabled = false;

    //-- Clear the wait button.
    this.WaitButton = MouseButtons.None;

    //-- Clear the original args
    this.OriginalArgs = null;
  }
}

/// <summary>
/// Just the mouse code from the control needing the functionality.
/// </summary>
public class MyControl: Control
{
  /// <summary>
  /// Handle the mouse down event. This delegates the actual event
  /// to the MouseDownManager, so we can capture the situation
  /// where both buttons are clicked.
  /// </summary>
  protected override void OnMouseDown( MouseEventArgs e )
  {
    this.mdmMain.ManageMouseDown( e );
  }

  /// <summary>
  /// Process the mouse down event.
  /// </summary>
  private void mdmMain_MouseDown( object sender, MouseEventArgs e )
  {
    //-- Get the reported button state.
    MouseButtons mouseButton = e.Button;

    //-- Execute logic based on button pressed, which now can include
    //   both the left and right together if needed....
  }
}
Genip answered 27/4, 2010 at 16:50 Comment(0)
B
0

Easy and Simple answer is here

bool midL,midR;

private void form1_MouseUp(object sender, MouseEventArgs e)
{
    midL = false;
    midR = false;
}
    
private void form1_MouseDown(object sender, MouseEventArgs e)
{
    if (!midL)
        midL = e.Button == MouseButtons.Left;

    if (!MidR)
        midR = e.Button == MouseButtons.Right;

    if (e.Button == MouseButtons.Middle || (midL && midR))
    {
        MessageBox.show("Middle button is pressed")
    }
}
Bradney answered 28/2, 2021 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.