简体   繁体   English

计时器中graphics.DrawLine上的“ System.ArgumentException参数无效”

[英]'System.ArgumentException parameter is not valid' at graphics.DrawLine in a timer

I am trying to make one of my classes draw a flashing red line on my window for 10 seconds, but I get the System.ArgumentException: parameter is not valid error at my graphics.DrawLine . 我试图让我的一个类在我的窗口上画一条闪烁的红线10秒钟,但是我得到了System.ArgumentException: parameter is not valid我的graphics.DrawLine System.ArgumentException: parameter is not valid错误。 Trying to find the problem, I've went as far as recreating it with the minimal parts included. 为了找到问题,我尝试使用最少的部分来重新创建它。 The red and dark functions, that draw the line work perfectly outside the timer's aTick event, but give the mentioned error while activated by it. 绘制线条的reddark功能在计时器的aTick事件之外可以完美地工作,但是在被其激活时会给出上述错误。 Others get this error when the graphics or the Pen object is invalid, but to me it appears that this isn't the case here. 当图形或Pen对象无效时,其他人则会收到此错误,但是在我看来,这里不是这种情况。

About my code: I've started programming quite recently, and I've heard only legends about databinding, and that it could've simplified my code, but curently it is out of my capabilities, so I've made the possibly quite crude, but otherwise working workaround of performing an action when a bool turns true (then turning it back false). 关于我的代码:我是最近才开始编程的,我只听说过有关数据绑定的传说,它可以简化我的代码,但是目前看来,这超出了我的能力,所以我使可能的想法变得很粗糙,但是在布尔值变为true(然后将其变为false)时执行操作的其他解决方法。 That's what I'm using to initiate the flashing and also to redraw my graphics at each tick of the timer. 这就是我用来开始闪烁以及在计时器的每个刻度上重绘我的图形的方式。 I also needed a second Redraw bool because when I tried to change the redraw true at the end of the aTick event, it said: Cannot use ref or out parameter 'redraw' inside an anonymous method, lambda expression, or query expression . 我还需要第二个Redraw bool,因为当我尝试在aTick事件结束时更改redraw true时,它说: Cannot use ref or out parameter 'redraw' inside an anonymous method, lambda expression, or query expression As you'll see I worked around it by adding that second bool, but it'd be great if you could also explain to me why is that happening and what's a better solution for it. 如您所见,我通过添加第二个bool来解决了这个问题,但是如果您还可以向我解释为什么会发生这种情况以及什么是更好的解决方案,那就太好了。

Here's the code of my Form: 这是我的表格的代码:

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

And Here's the code from the class from which I'm trying to draw the line: 这是我试图从中划出界线的类中的代码:

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

You are making things way too hard. 您使事情变得太困难了。 The basic diagnosis offered by the other answer is correct, but you got yourself into that situation by over-thinking the problem. 另一个答案提供的基本诊断是正确的,但您过分考虑问题会陷入困境。 Your new version is better, but still over-complicates things; 您的新版本更好,但仍然使事情复杂化。 it fails to use the modern async / await idiom, which can be used to write asynchronous code (like code that involves a timer) in a linear/synchronous way, and it still uses local methods for no apparent beneficial reason. 它无法使用现代的async / await惯用法,该惯用法可用于以线性/同步方式编写异步代码(如涉及计时器的代码),并且仍未使用本地方法,没有明显的有益原因。

Here is a version of your code that is IMHO is significantly simpler and better: 这是您的代码版本,恕我直言,它明显更简单更好:

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

The most complicated element of the above is logic I added to ensure flashing as close as possible to the apparently-desired 200ms interval. 上面最复杂的元素是我添加的逻辑,以确保闪烁尽可能接近看起来理想的200ms间隔。 Fact is, you can achieve nearly identical results even more simply if you're willing to allow for the flashing to wind up maybe dozens of milliseconds off (something no human user will ever notice): 事实是,如果您愿意允许闪烁结束大约几十毫秒(几乎没有人类用户会注意到),那么您甚至可以更简单地获得几乎相同的结果:

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

Either way, this is IMHO much better than using a Timer , making a whole new class to handle all the logic, and using local methods just to deal with drawing a line. 无论哪种方式,这都比使用Timer更好,恕我直言,这使一个全新的类可以处理所有逻辑,而使用局部方法只是为了处理一条线。 Of course, even if you decide you really want to use local methods and a separate class, the above can easily be refactored to accommodate that without the messiness of the Timer . 当然,即使您确定确实要使用本地方法和单独的类,也可以轻松地重构以上内容以容纳上述内容,而不会造成Timer的混乱。

Your code is using the Windows paint event in a way it is not intended to be used. 您的代码正在以某种不希望使用的方式使用Windows paint事件。

Although documentation may not mention it , the Graphics object that is passed as an argument to the Form.Paint event handler, is valid for the duration of a single event only. 尽管文档可能未提及 ,但作为参数传递给Form.Paint事件处理程序的Graphics对象仅在单个事件期间有效。 Your code passes it to a timer, which attempts to access and use it long after the event handler has exited. 您的代码将其传递给计时器,该计时器将在事件处理程序退出后很长时间尝试访问和使用它。

The second issue is the confusing use of the Redraw / redraw variables. 第二个问题是Redraw / redraw变量的用法混乱。 A paint region should not be invalidated from within its Paint event handler. 绘制区域不应在其Paint事件处理程序中失效。

Let the timer handle the state machine of the flashing, and have it call Invalidate . 让计时器处理闪烁的状态机,并使其调用Invalidate Then read the state from inside the Paint event handler and draw accordingly. 然后从Paint事件处理程序内部读取状态并进行相应绘制。 MSDN even has some useful examples for this. MSDN甚至为此提供了一些有用的示例

According to Hans Passant's diagnosis (e.Graphics can only be accessed by the function if you Invalidated at the exact moment) I managed to fix the problem after restructuring my program. 根据汉斯·帕桑特(Hans Passant)的诊断(例如,如果您在确切的时间失效,则只能通过该函数访问图形),我在重组程序后设法解决了该问题。 By making most of my class public ( :( ), I could put the timer in the Form and still access my class. Having the timer in the Form, I can Invalidate at each tick, allowing the use of e.Graphics at the right moment. Here's the new code: Form: 通过将我的大部分班级设为公开(:(),我可以将计时器放在窗体中并仍然访问我的班级。在窗体中使用计时器之后,我可以在每个刻度上使Invalid失效,从而允许在右侧使用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;
        }
    }
}

Class: 类:

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

Thank you for your help. 谢谢您的帮助。

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

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