C#如何在鼠标按钮按下时循环执行

18

你能给我指点方向吗?我正在尝试在表单按钮按下时触发循环。

//pseudocode
While (button1 is pressed)
value1 += 1

当然,在释放按钮时停止循环


http://pmichaels.net/2015/01/01/using-a-repeatbutton-to-rapidly-increment-values/ - Paul Michaels
12个回答

30

为了避免使用线程,您可以在表单/控件上添加一个计时器组件,并在鼠标按下时启用它,在鼠标松开时禁用它。然后将通常放置在循环中的代码放在计时器的Tick事件中。如果您想使用System.Timers.Timer,则可以使用Timer.Elapsed事件代替。

示例(使用System.Timers.Timer):

using Timer = System.Timers.Timer;
using System.Timers;
using System.Windows.Forms;//WinForms example
private static Timer loopTimer;
private Button formButton;
public YourForm()
{ 
    //loop timer
    loopTimer = new Timer();
    loopTimer.Interval = 500;/interval in milliseconds
    loopTimer.Enabled = false;
    loopTimer.Elapsed += loopTimerEvent;
    loopTimer.AutoReset = true;
    //form button
    formButton.MouseDown += mouseDownEvent;
    formButton.MouseUp += mouseUpEvent;
}
private static void loopTimerEvent(Object source, ElapsedEventArgs e)
{
    //this does whatever you want to happen while clicking on the button
}
private static void mouseDownEvent(object sender, MouseEventArgs e)
{
    loopTimer.Enabled = true;
}
private static void mouseUpEvent(object sender, MouseEventArgs e)
{
    loopTimer.Enabled = false;
}

1
好的回答:提供了一个替代我的基于线程的解决方案。+1。 - Timwi
你很有可能想要检查一下(e.Button == MouseButtons.Left),否则你最终会在LeftClick、RightClick、MiddleClick或其他任何鼠标按钮上执行循环。通常情况下,按钮应该只响应左键单击。 - abelenky
Timer 的问题在于它不够准确,这是众所周知的。尝试将计时器设置为已知的毫秒 间隔,然后计算计时器的 ticks 数量并将其与秒表的 elapsed_milliseconds/interval 进行比较。你会发现得到的值有明显的差异。但如果 OP 不需要高精度,这是一个不错的解决方案。 - Gondil

11

您可以使用线程来进行计数,并在鼠标释放时停止线程。以下对我非常有效:

var b = new Button { Text = "Press me" };

int counter = 0;
Thread countThread = null;
bool stop = false;

b.MouseDown += (s, e) =>
{
    stop = false;
    counter = 0;
    countThread = new Thread(() =>
    {
        while (!stop)
        {
            counter++;
            Thread.Sleep(100);
        }
    });
    countThread.Start();
};

b.MouseUp += (s, e) =>
{
    stop = true;
    countThread.Join();
    MessageBox.Show(counter.ToString());
};

当然,如果你想要事件处理程序成为方法而不是lambda表达式,你将不得不把所有变量转换为字段。


8
    private void button1_MouseDown(object sender, MouseEventArgs e)
    {
        timer1.Enabled = true;
        timer1.Start();

    }

    private void button1_MouseUp(object sender, MouseEventArgs e)
    {
        timer1.Stop();
    }



    private void timer1_Tick(object sender, EventArgs e)
    {
        numericUpDown1.Value++;

    }

3

我受到这里阅读的启发,决定编写自己的按钮类,称为“重复按钮”。第一次单击后,它会等待500毫秒,然后每300毫秒重复一次,直到2秒钟,然后每100毫秒重复一次(即使用加速度)。

以下是代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;

/// <summary>
/// A repeating button class.
/// When the mouse is held down on the button it will first wait for FirstDelay milliseconds,
/// then press the button every LoSpeedWait milliseconds until LoHiChangeTime milliseconds,
/// then press the button every HiSpeedWait milliseconds
/// </summary>
public class RepeatingButton : Button
{
    /// <summary>
    /// Initializes a new instance of the <see cref="RepeatingButton"/> class.
    /// </summary>
    public RepeatingButton()
    {
        internalTimer = new Timer();
        internalTimer.Interval = FirstDelay;
        internalTimer.Tick += new EventHandler(internalTimer_Tick);
        this.MouseDown += new MouseEventHandler(RepeatingButton_MouseDown);
        this.MouseUp += new MouseEventHandler(RepeatingButton_MouseUp);
    }

    /// <summary>
    /// The delay before first repeat in milliseconds
    /// </summary>
    public int FirstDelay = 500;

    /// <summary>
    /// The delay in milliseconds between repeats before LoHiChangeTime
    /// </summary>
    public int LoSpeedWait = 300;

    /// <summary>
    /// The delay in milliseconds between repeats after LoHiChangeTime
    /// </summary>
    public int HiSpeedWait = 100;

    /// <summary>
    /// The changeover time between slow repeats and fast repeats in milliseconds
    /// </summary>
    public int LoHiChangeTime = 2000;

    private void RepeatingButton_MouseDown(object sender, MouseEventArgs e)
    {
        internalTimer.Tag = DateTime.Now;
        internalTimer.Start();
    }

    private void RepeatingButton_MouseUp(object sender, MouseEventArgs e)
    {
        internalTimer.Stop();
        internalTimer.Interval = FirstDelay;
    }

    private void internalTimer_Tick(object sender, EventArgs e)
    {
        this.OnClick(e);
        TimeSpan elapsed = DateTime.Now - ((DateTime)internalTimer.Tag);
        if (elapsed.TotalMilliseconds < LoHiChangeTime)
        {
            internalTimer.Interval = LoSpeedWait;
        }
        else
        {
            internalTimer.Interval = HiSpeedWait;
        }
    }

    private Timer internalTimer;
}

无论何处你有一个按钮,你只需要用重复按钮替换它,就可以拥有所有新功能。

享受吧!

Sterren


3

Fabulous Adventures in Coding的最新文章提供了以下叙述,可能有助于回答你的问题:

A surprising number of people have magical beliefs about how exactly applications respond to user inputs in Windows. I assure you that it is not magic. The way that interactive user interfaces are built in Windows is quite straightforward. When something happens, say, a mouse click on a button, the operating system makes a note of it. At some point, a process asks the operating system "did anything interesting happen recently?" and the operating system says "why yes, someone clicked this thing." The process then does whatever action is appropriate for that. What happens is up to the process; it can choose to ignore the click, handle it in its own special way, or tell the operating system "go ahead and do whatever the default is for that kind of event." All this is typically driven by some of the simplest code you'll ever see:

while(GetMessage(&msg, NULL, 0, 0) > 0) 
{ 
  TranslateMessage(&msg); 
  DispatchMessage(&msg); 
}

That's it. Somewhere in the heart of every process that has a UI thread is a loop that looks remarkably like this one. One call gets the next message. That message might be at too low a level for you; for example, it might say that a key with a particular keyboard code number was pressed. You might want that translated into "the numlock key was pressed". TranslateMessage does that. There might be some more specific procedure that deals with this message. DispatchMessage passes the message along to the appropriate procedure.

I want to emphasize that this is not magic. It's a while loop. It runs like any other while loop in C that you've ever seen. The loop repeatedly calls three methods, each of which reads or writes a buffer and takes some action before returning. If one of those methods takes a long time to return (typically DispatchMessage is the long-running one of course since it is the one actually doing the work associated with the message) then guess what? The UI doesn't fetch, translate or dispatch notifications from the operating system until such a time as it does return.


1
当然。问题的答案在哪里? - Timwi
3
@Timwi:答案是:“你的问题基于一个不准确的假设。” 我没有直接这样说,而是提供了这个机制的解释,我认为这会有助于澄清OP的思路。由于问题明确要求“能否指点我正确的方向?”,所以这似乎是一个适当的方式。当然,您可以持不同意见,但我感谢您解释您的推理。 - Daniel Pryden

1

在您的表单中覆盖OnMouseDown()方法,然后如果按下所需按钮,这将等于您的循环。示例:

protected override void OnMouseDown(MouseEventArgs e)
{
    if (e.Button == MouseButtons.Left)
    {
        // this is your loop
    }
}

这不是传统意义上的循环,但应该能满足你的需求。


1
这实际上不起作用。如果你按住按钮,它只会运行一次。然后当你松开按钮时,它会再次运行。 - Sinaesthetic

1
"

RepeatButton非常适合这种情况:

"
<RepeatButton Delay="1000" Interval="500" HorizontalAlignment="Left" Content="+" Click="IncreaseButton_Click"/>

private void IncreaseButton_Click(object sender, RoutedEventArgs e)
{
    value1++;
}

1

我发布这篇文章已经过去了几年,但有人点赞了它,所以它出现在了我的通知中。现在我有更多的经验了,哈哈,我想看看这个简单的问题是否像听起来那么简单,结果是:

public partial class Form1 : Form
{
    private bool _isRunning;

    public Form1()
    {
        InitializeComponent();
        txtValue.Text = @"0";

        btnTest.MouseDown += (sender, args) =>
        {
            _isRunning = true;
            Run();
        };

        btnTest.MouseUp += (sender, args) => _isRunning = false;
    }

    private void Run()
    {
        Task.Run(() =>
        {
            while (_isRunning)
            {
                var currentValue = long.Parse(txtValue.Text);
                currentValue++;
                txtValue.Invoke((MethodInvoker) delegate
                {
                    txtValue.Text = currentValue.ToString();
                });
            }
        });
    }
}

1
您需要处理窗体的MouseDown()事件,使用MouseEventArgs参数来确定按下了哪个按钮。

1
这个回答根本没有回答问题。循环在哪里? - Timwi
3
Timwi 在该帖子中发了帖子。他总是在其后留下一串踩票的记录。 - Hans Passant
1
@Bernard:汉斯在撒谎。我会给错误的答案投反对票,而不是那些“竞争”我的答案。(例如看看Doggett的回答。)关于你的评论,你不能只是添加一个循环到事件中,因为那样你就无法监听应该停止循环的事件了。 - Timwi
1
@Bernard:我也更愿意这样做,我过去也是这样做的,但是这个网站的社区普遍不鼓励这样做。看一下@Hans的反应就知道了。他对我变得非常愤怒,而这只是因为我指出了他回答中的错误,从而揭示了我给他点了踩。似乎大多数人在情感上无法应对自己的回答有缺陷的建议。 - Timwi
@Timwi:其实我并不介意发现我的答案是错误的或者可能不完全正确。我最终会学到新的东西。一个人无法知道所有的事情,即使他们认为自己可以。 - Bernard
显示剩余3条评论

0
你可以使用mouseMove事件并检查鼠标按钮是否按下,例如:
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        if(e.Button==MouseButtons.Left)
        {
        //your code here
        }
    }

这将导致无限递归,e 不会在方法内改变。 - NappingRabbit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接