简体   繁体   English

C#如何在按住鼠标按钮时循环

[英]C# how to loop while mouse button is held down

Can you point me in the right direction?你能为我指出正确的方向吗? I'm trying to get a loop to trigger while the form button is depressed.我试图在按下表单按钮时触发一个循环。

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

And then of course stop looping when the button is released然后当然在按钮被释放时停止循环

To avoid using threads you can add a Timer component on your form/control and simply enable it on mouse down and disable it on mouse up.为了避免使用线程,您可以在表单/控件上添加一个Timer组件,只需在鼠标按下时启用它并在鼠标抬起时禁用它。 Then put the code you would normally put inside the loop in the Timer_Tick event.然后将您通常会放入循环中的代码放入 Timer_Tick 事件中。 If you want to use System.Timers.Timer you can use the Timer.Elapsed event instead.如果您想使用 System.Timers.Timer,您可以改用 Timer.Elapsed 事件。

Example (using System.Timers.Timer):示例(使用 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;
}

You could use a thread to do the counting, and stop the thread when the mouse is released.您可以使用线程进行计数,并在释放鼠标时停止线程。 The following has worked nicely for me:以下对我来说效果很好:

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());
};

Of course, if you want the event handlers to be methods rather than lambdas, you will have to turn all the variables into fields.当然,如果您希望事件处理程序是方法而不是 lambda,则必须将所有变量转换为字段。

    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++;

    }

I was inspired by what I read here and decided to write my own button class called a RepeatingButton.我受到我在这里读到的内容的启发,并决定编写我自己的按钮类,称为 RepeatingButton。 On first click it waits for 500ms, then repeats ever 300ms until 2s, then repeats every 100ms (ie it uses acceleration).第一次点击它等待 500 毫秒,然后重复 300 毫秒直到 2 秒,然后每 100 毫秒重复一次(即它使用加速度)。

Here is the code;这是代码;

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;
}

Anywhere you have a button, you can just replace it with a repeating button and it will just have all the new functionality built in.任何有按钮的地方,您都可以用重复按钮替换它,它就会内置所有新功能。

Enjoy!享受!

Sterren斯特伦

A recent article from Fabulous Adventures in Coding provides this narrative, which might help answer your question: Fabulous Adventures in Coding 最近的一篇文章提供了这种叙述,它可能有助于回答您的问题:

A surprising number of people have magical beliefs about how exactly applications respond to user inputs in Windows.很多人对应用程序如何准确响应 Windows 中的用户输入有着神奇的信念。 I assure you that it is not magic.我向你保证,这不是魔法。 The way that interactive user interfaces are built in Windows is quite straightforward.在 Windows 中构建交互式用户界面的方式非常简单。 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.在每个拥有 UI 线程的进程的核心位置,都有一个与这个非常相似的循环。 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".您可能希望将其翻译为“按下了 numlock 键”。 TranslateMessage does that. TranslateMessage 就是这样做的。 There might be some more specific procedure that deals with this message.可能有一些更具体的过程来处理此消息。 DispatchMessage passes the message along to the appropriate procedure. DispatchMessage 将消息传递给适当的过程。

I want to emphasize that this is not magic.我想强调的是,这不是魔术。 It's a while loop.这是一个while循环。 It runs like any other while loop in C that you've ever seen . 它的运行方式与您见过的任何其他 C 语言中的 while 循环一样 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?如果这些方法之一需要很长时间才能返回(通常 DispatchMessage 是长时间运行的方法,因为它是实际执行与消息相关的工作的方法),那么你猜怎么着? The UI doesn't fetch, translate or dispatch notifications from the operating system until such a time as it does return. UI 不会从操作系统获取、翻译或发送通知,直到它返回为止。

RepeatButton is perfect for that: RepeatButton非常适合:

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

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

Override the OnMouseDown() method in your form and then if the button you want is pressed, that would equal your loop.覆盖表单中的OnMouseDown()方法,然后如果您想要的按钮被按下,这将等于您的循环。 Example:例子:

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

It's not a loop in the traditional sense, but should work for what you need.这不是传统意义上的循环,但应该可以满足您的需求。

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

It's been several years since I posted this but someone upvoted it so it popped up in my notifications.自从我发布这篇文章已经好几年了,但有人对它进行了投票,所以它突然出现在我的通知中。 Now that I have a lot more experience lol, I thought I'd see if this simple problem is as simple as it sounds, and it was:现在我有了更多的经验,哈哈,我想我会看看这个简单的问题是否像听起来一样简单,它是:

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();
                });
            }
        });
    }
}

Based on Steztric's answer, an extension method with a few bug fixes and different options for the rate of increase.基于 Steztric 的回答,一种扩展方法,其中包含一些错误修复和不同的增长率选项。

/// <summary>
/// An extension method to add a repeat click feature to a button. Clicking and holding  on a button will cause it
/// to repeatedly fire. This is useful for up-down spinner buttons. Typically the longer the mouse is held, the
/// more quickly the click events are fired. There are different options when it comes to increasing the rate of
/// clicks:
/// 1) Exponential - this is the mode used in the NumericUpDown buttons. The first delay starts off around 650 ms
/// and each successive delay is multiplied by 75% of the current delay.
/// 2) Linear - the delay more slowly reaches the fastest repeat speed. Each successive delay subtracts a fixed
/// amount from the current delay. Decreases in delays occur half a second apart.
/// 3) Two Speed - this delay starts off at a slow speed, and then increases to a faster speed after a specified delay.
/// 4) Three Speed - the repeat speed can increase from slow, to medium, to fastest after a specified delay.
///
/// If repeating is added to a button that already has it, then it will be replaced with the new values.
/// </summary>
public static class RepeatingButtonEx {

    private static Hashtable ht = new Hashtable();
    private class Data {
        private static readonly System.Reflection.MethodInfo methodOnClick = null;
        static Data() {
            methodOnClick = typeof(Button).GetMethod("OnClick", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
        }

        public Button Button = null;
        private Timer Timer = new Timer();
        public double? GradientRate;
        public int? LinearGradient = null;
        public int FirstDelayMillis;
        public int FastestRepeatMillis;
        public int[] SwitchesMillis;
        public int[] SpeedsMillis;

        private DateTime lastEvent = DateTime.MinValue;
        private int millisCount = 0;
        private int currentSpeed = 0;
        private int waitSum = 0;

        public Data(Button button, double? gradientRate, int? linearGradient, int firstDelayMillis, int fastestRepeatMillis, int[] switchesMillis, int[] speedsMillis) {
            Button = button;
            GradientRate = gradientRate;
            LinearGradient = linearGradient;
            FirstDelayMillis = firstDelayMillis;
            FastestRepeatMillis = fastestRepeatMillis;
            SwitchesMillis = switchesMillis;
            SpeedsMillis = speedsMillis;
            Timer.Interval = firstDelayMillis;
            Timer.Tick += Timer_Tick;
            Button.MouseDown += Button_MouseDown;
            Button.MouseUp += Button_MouseUp;
            Button.MouseLeave += Button_MouseLeave;
        }

        void Button_MouseDown(object sender, MouseEventArgs e) {
            if (!Button.Enabled)
                return;

            lastEvent = DateTime.UtcNow;
            Timer.Start();
        }

        void Button_MouseUp(object sender, MouseEventArgs e) {
            Reset();
        }

        void Button_MouseLeave(object sender, EventArgs e) {
            Reset();
        }

        private void Reset() {
            Timer.Stop();
            Timer.Interval = FirstDelayMillis;
            millisCount = 0;
            currentSpeed = 0;
            waitSum = 0;
        }

        void Timer_Tick(object sender, EventArgs e) {
            if (!Button.Enabled) {
                Reset();
                return;
            }

            methodOnClick.Invoke(Button, new Object[] { EventArgs.Empty });
            //Button.PerformClick(); // if Button uses SetStyle(Selectable, false); then CanSelect is false, which prevents PerformClick from working.

            if (GradientRate.HasValue || LinearGradient.HasValue) {
                int millis = Timer.Interval;

                if (GradientRate.HasValue)
                    millis = (int) Math.Round(GradientRate.Value * millis);
                else if (LinearGradient.HasValue) {
                    DateTime now = DateTime.UtcNow;
                    var ts = now - lastEvent;
                    int ms = (int) ts.TotalMilliseconds;
                    millisCount += ms;
                    // only increase the rate every 500 milliseconds
                    // otherwise it appears too get to the maximum rate too quickly
                    if (millisCount >= 500) {
                        millis -= LinearGradient.Value;
                        millisCount -= 500;
                        lastEvent = now;
                    }
                }

                if (millis < FastestRepeatMillis)
                    millis = FastestRepeatMillis;

                Timer.Interval = millis;
            }
            else {
                if (currentSpeed < SpeedsMillis.Length) {
                    TimeSpan elapsed = DateTime.UtcNow - lastEvent; 
                    if (elapsed.TotalMilliseconds >= waitSum) {
                        waitSum += SwitchesMillis[currentSpeed];
                        Timer.Interval = SpeedsMillis[currentSpeed];
                        currentSpeed++;
                    }
                }
            }
        }

        public void Dispose() {
            Timer.Stop();
            Timer.Dispose();
            Button.MouseDown -= Button_MouseDown;
            Button.MouseUp -= Button_MouseUp;
            Button.MouseLeave -= Button_MouseLeave;
        }
    }

    ///<summary>The repeating speed becomes exponentially faster. This is the default behavior of the NumericUpDown control.</summary>
    ///<param name="button">The button to add the behavior.<param>
    ///<param name="firstDelayMillis">The delay before first repeat in milliseconds.</param>
    ///<param name="fastestRepeatMillis">The smallest delay allowed. Note: Masharling between the timer and the UI thread has an unavoidable limit of about 10 milliseconds.</param>
    ///<param name="gradientRate">The new interval is the current interval multiplied by the gradient rate.</param>
    public static void AddRepeatingExponential(this Button button, int firstDelayMillis = 500, int fastestRepeatMillis = 15, double gradientRate = 0.75) {
        AddRepeating(button, firstDelayMillis, fastestRepeatMillis, gradientRate, null, null, null);
    }

    ///<summary>The repeating speed becomes linearily faster.</param>
    ///<param name="button">The button to add the behavior.<param>
    ///<param name="firstDelayMillis">The delay before first repeat in milliseconds.</param>
    ///<param name="fastestRepeatMillis">The smallest delay allowed. Note: Masharling between the timer and the UI thread has an unavoidable limit of about 10 milliseconds.</param>
    ///<param name="linearGradient">If specified, the repeats gradually happen more quickly. The new interval is the current interval minus the linear gradient.</param>
    public static void AddRepeatingLinear(this Button button, int firstDelayMillis = 500, int fastestRepeatMillis = 50, int linearGradient = 25) {
        AddRepeating(button, firstDelayMillis, fastestRepeatMillis, null, linearGradient, null, null);
    }

    ///<summary>The repeating speed switches from the slow speed to the fastest speed after the specified amount of milliseconds.</summary>
    ///<param name="button">The button to add the behavior.<param>
    ///<param name="firstDelayMillis">The delay before first repeat in milliseconds.</param>
    ///<param name="fastestRepeatMillis">The smallest delay allowed. Note: Masharling between the timer and the UI thread has an unavoidable limit of about 10 milliseconds.</param>
    ///<param name="slowRepeatMillis">The delay in milliseconds between repeats when in the slow repeat state.</param>
    ///<param name="slowToFastestSwitchMillis">The delay in milliseconds before switching from the slow repeat speed to the fastest repeat speed.</param>
    public static void AddRepeatingTwoSpeed(this Button button, int firstDelayMillis = 500, int fastestRepeatMillis = 100, int slowRepeatMillis = 300, int slowToFastestSwitchMillis = 2000) {
        AddRepeating(button, firstDelayMillis, fastestRepeatMillis, null, null, new[] { slowRepeatMillis, fastestRepeatMillis }, new [] { slowToFastestSwitchMillis, 0 });
    }

    ///<summary>The repeating speed switches from the slow to medium to fastest at speed switch interval specified.</summary>
    ///<param name="button">The button to add the behavior.<param>
    ///<param name="firstDelayMillis">The delay before first repeat in milliseconds.</param>
    ///<param name="fastestRepeatMillis">The smallest delay allowed. Note: Masharling between the timer and the UI thread has an unavoidable limit of about 10 milliseconds.</param>
    ///<param name="slowRepeatMillis">The delay in milliseconds between repeats when in the slow repeat state.</param>
    ///<param name="mediumRepeatMillis">The delay in milliseconds between repeats when in the medium repeat state.</param>
    ///<param name="speedSwitchMillis">The delay in milliseconds before switching from one speed state to the next speed state.</param>
    public static void AddRepeatingThreeSpeed(this Button button, int firstDelayMillis = 500, int fastestRepeatMillis = 75, int slowRepeatMillis = 300, int mediumRepeatMillis = 150, int speedSwitchMillis = 2000) {
        AddRepeating(button, firstDelayMillis, fastestRepeatMillis, null, null, new[] { slowRepeatMillis, mediumRepeatMillis, fastestRepeatMillis }, new [] { speedSwitchMillis, speedSwitchMillis, 0 });
    }

    private static void AddRepeating(this Button button, int firstDelayMillis, int fastestRepeatMillis, double? gradientRate, int? linearGradient, int[] speedsMillis, int[] switchesMillis) {
        Data d = (Data) ht[button];
        if (d != null)
            RemoveRepeating(button);

        d = new Data(button, gradientRate, linearGradient, firstDelayMillis, fastestRepeatMillis, switchesMillis, speedsMillis);
        ht[button] = d;
        button.Disposed += delegate {
            RemoveRepeating(button);
        };
    }

    ///<summary>Removes the repeating behavior from the button.</summary>
    public static void RemoveRepeating(this Button button) {
        Data d = (Data) ht[button];
        if (d == null)
            return;

        ht.Remove(button);
        d.Dispose();
    }
}

you could use the mouseMove Event and check if the mousebutton is held down like:您可以使用 mouseMove 事件并检查鼠标按钮是否被按下,例如:

private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
    {
        if(e.Button==MouseButtons.Left)
        {
        //your code here
        }
    }

Similar to Timwi's solution above except using async/await for asynchronous IO and lock for synchronization for some state...与上述 Timwi 的解决方案类似,除了使用async/await进行异步 IO 和lock用于某些状态的同步之外...

using System;
using System.Windows.Forms;
using System.Threading.Tasks;

namespace Foo {
    partial class Form1: Form {
        private static readonly object mousePressLock = new object();
        private bool mousePressed;
        private Task task;

        private async Task MouseAction(Action action) {
            while (true) {
                lock (mousePressLock) {
                    if (mousePressed)
                        action();
                    else
                        break;
                }
                await Task.Delay(100).ConfigureAwait(false);
            }
        }

        private void PnlTranslate_Paint(object sender, PaintEventArgs e) {
        }

        private void Up_MouseUp(object sender, MouseEventArgs e) {
            lock (mousePressLock) { mousePressed = false; }
            task.Wait();
        }

        private void Up_MouseDown(object sender, MouseEventArgs e) {
            lock (mousePressLock) { mousePressed = true; }
            int cnt = 0;
            task = MouseAction(() => {
                Console.WriteLine($"mouse up action {++cnt}");
            });
        }

        public Form1() {
            InitializeComponent();
            mousePressed = false;
            task = null;
        }
    }
}

Also, note the ConfigureAwait(false) call.另外,请注意ConfigureAwait(false)调用。 I ran into deadlock without that bc tasks were fighting to be on the same thread.我遇到了死锁,因为 bc 任务都在努力争取在同一线程上。 It was so annoying.太烦人了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM