计时器

时间:2017-12-29 17:26:22

标签: c# events graphics timer

我正在尝试让我的一个班级在我的窗口上画一条闪烁的红线,持续10秒,但我的System.ArgumentException: parameter is not valid出现graphics.DrawLine错误。试图找到问题,我已经尽可能地用最少的部分重新创建它。绘制线的reddark函数完全在计时器的aTick事件之外工作,但在由它激活时给出上述错误。当图形或Pen对象无效时,其他人会收到此错误,但对我来说,这似乎不是这种情况。

关于我的代码:我最近开始编程,而且我只听说过关于数据绑定的传说,并且它可以简化我的代码,但是它实际上是出于我的能力,所以我已经制作了可能相当粗糙,但在bool变为真时执行动作的其他工作方法(然后将其变回假)。这就是我用来启动闪烁并在计时器的每个刻度处重绘我的图形。我还需要第二个Redraw bool,因为当我尝试在redraw事件结束时更改aTick时,它说:Cannot use ref or out parameter 'redraw' inside an anonymous method, lambda expression, or query expression。正如你所看到的那样,我通过添加第二个bool来解决这个问题,但是如果你能向我解释为什么会发生这种情况以及它有什么更好的解决方案,那就太棒了。

以下是我的表格代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GrafikaTeszt
{
    public partial class Form1 : Form
    {

        bool flash = false; //can we draw the line?
        bool redraw = false;    //should we redraw?

        public Form1()
        {
            InitializeComponent();
        }

        Class1 classic = new Class1();

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (flash)
            {
                classic.makeitflash(e.Graphics, out redraw);

                if (redraw)
                {
                    Invalidate();
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            flash = true;
            Invalidate();
        }
    }
}

以下是我正在尝试绘制该行的类中的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;

namespace GrafikaTeszt
{
    class Class1
    {
        Timer clock;
        int ticks;

        public void makeitflash(Graphics g, out bool redraw)
        {
            redraw = false;
            bool Redraw = false;
            ticks = 0; 
            clock.Start();
            clock.Tick += new EventHandler(aTick);

            void aTick(object sender, EventArgs e)
            {
                if (ticks % 2 == 0)
                {
                    red();  //draw a red line
                }
                else
                {
                    dark();     //draw a darkred line
                }

                if (ticks == 20)
                {
                    clock.Stop();
                }
                ticks++;
                Redraw = true;
            }
            void red() { g.DrawLine(Pens.Red, 100, 100, 500, 500); }
            void dark() { g.DrawLine(Pens.DarkRed, 100, 100, 500, 500); }

            redraw = Redraw;
        }

        public Class1()
        {
            clock = new Timer();
            clock.Interval = 200;
        }
    }
}

3 个答案:

答案 0 :(得分:1)

你正在努力做事。 the other answer提供的基本诊断是正确的,但是你通过过度思考问题来了解自己。你的新版本更好,但仍然过于复杂;它无法使用现代async / await成语,它可用于以线性/同步方式编写异步代码(如涉及计时器的代码),并且它仍然使用本地方法显而易见的有益理由。

以下是您的代码版本,恕我直言更加简单:

public partial class Form1 : Form
{
    private Pen _currentPen = Pens.Black;

    public Form1()
    {
        InitializeComponent();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);

        e.Graphics.DrawLine(_currentPen, 100, 100, 500, 500);
    }

    private void button1_Click(object sender, EventArgs e)
    {
        // Ignore returned task...nothing more to do.
        var task = FlashLine(TimeSpan.FromMilliseconds(200), TimeSpan.FromSeconds(4));
    }

    private async Task FlashLine(TimeSpan interval, TimeSpan duration)
    {
        TimeSpan nextInterval = interval;
        Stopwatch sw = Stopwatch.StartNew();
        bool red = true;

        while (sw.Elapsed < duration)
        {
            TimeSpan wait = nextInterval - sw.Elapsed;

            // Just in case we got suspended long enough that the
            // next interval is already here
            if (wait > TimeSpan.Zero)
            {
                // "await" will suspend execution of this method, returning
                // control to the caller (i.e. freeing up the UI thread for
                // other UI activities). This method will resume execution
                // when the awaited task completed (in this case, a simple delay)
                await Task.Delay(wait);
            }

            _currentPen = red ? Pens.Red : Pens.Black;
            red = !red;
            Invalidate();

            // Just in case it the operation took too long and the initial next
            // interval time is still in the past. Use "do/while" to make sure
            // interval is always incremented at least once, because Task.Delay()
            // can occasionally return slightly (and imperceptibly) early and the
            // code in this example is so simple, that the nextInterval value might
            // still be later than the current time by the time execution reaches
            // this loop.
            do
            {
                nextInterval += interval;
            } while (nextInterval < sw.Elapsed);
        }

        _currentPen = Pens.Black;
        Invalidate();
    }
}

上面最复杂的元素是我添加的逻辑,以确保闪烁尽可能接近显然所需的200ms间隔。事实上,如果您愿意允许闪光灯关闭几十毫秒(人类用户不会注意到的事情),您可以更简单地获得几乎相同的结果:

    private async Task FlashLine(TimeSpan interval, TimeSpan duration)
    {
        int iterations = (int)(duration.TotalSeconds / interval.TotalSeconds);
        bool red = true;

        while (iterations-- > 0)
        {
            await Task.Delay(interval);

            _currentPen = red ? Pens.Red : Pens.Black;
            red = !red;
            Invalidate();
        }

        _currentPen = Pens.Black;
        Invalidate();
    }

无论哪种方式,这都是恕我直言,比使用Timer要好得多,创建一个全新的类来处理所有逻辑,并使用本地方法来处理绘制线。当然,即使您决定使用本地方法和单独的类,上面的内容也可以很容易地进行重构,以适应Timer的混乱。

答案 1 :(得分:0)

您的代码正在以不打算使用的方式使用Windows绘图事件。

尽管documentation may not mention it,作为Graphics事件处理程序的参数传递的Form.Paint对象仅在单个事件的持续时间内有效。您的代码将其传递给计时器,计时器在事件处理程序退出后很久就会尝试访问和使用它。

第二个问题是混淆使用Redraw / redraw变量。不应在其Paint事件处理程序中使绘制区域无效。

让计时器处理闪烁的状态机,并让它调用Invalidate。然后从Paint事件处理程序中读取状态并相应地绘制。 MSDN甚至为此提供了一些有用的examples

答案 2 :(得分:0)

根据Hans Passant的诊断(e.Graphics只能通过函数访问,如果您在确切时刻无效)我在重建程序后设法修复了问题。通过将我的大部分课程公开(:(),我可以将计时器放在表格中并仍然访问我的班级。在表格中有计时器,我可以在每个刻度上无效,允许在右边使用e.Graphics这一刻是新代码: 形式:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace GrafikaTeszt
{
    public partial class Form1 : Form
    {
        Timer clock;
        Class1 classic;
        bool stop;

        public Form1()
        {
            InitializeComponent();
            clock = new Timer();
            clock.Interval = 200;
            clock.Tick += new EventHandler(ticked);
            classic = new Class1();
            stop = false;
        }

        void ticked(object sender, EventArgs e)
        {
            classic.ticks++;
            Invalidate();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            if (classic.flashing)
            {
                classic.draw(e.Graphics, out stop);
                if (stop)
                {
                    clock.Stop();
                    classic.flashing = false;
                    Invalidate();
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            clock.Start();
            classic.flashing = true;
            classic.ticks = 0;
        }
    }
}

类别:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;

namespace GrafikaTeszt
{
    class Class1
    {
        public int ticks;
        public bool flashing;

        public void draw(Graphics g, out bool stop)
        {
            stop = false;
            if (ticks % 2 == 0)
            {
                red();  //draw a red line
            }
            else
            {
                dark();     //draw a darkred line
            }

            if (ticks == 20)
            {
                stop = true;
            }

            void red() { g.DrawLine(Pens.Red, 100, 100, 500, 500); }
            void dark() { g.DrawLine(Pens.DarkRed, 100, 100, 500, 500); }
        }

        public Class1()
        {
            flashing = false;
        }
    }
}

感谢您的帮助。