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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计时器中的graphics.DrawLine中的“System.ArgumentException参数无效”相关的知识,希望对你有一定的参考价值。

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

关于我的代码:我最近开始编程,我只听说过关于数据绑定的传说,它可以简化我的代码,但实际上它不符合我的能力,所以我做了可能很粗糙的,但在bool变为true时执行动作的其他工作方法(然后将其变回false)。这就是我用来启动闪烁并在计时器的每个刻度处重绘我的图形。我还需要第二个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;
        }
    }
}
答案

你太过刻苦了。 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的混乱。

另一答案

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

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

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

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

另一答案

根据Hans Passant的诊断(e.Graphics只能通过函数访问,如果你在确切的时刻失效)我设法在重组我的程序后修复问题。通过将我的大部分课程公开(:(),我可以将计时器放在表格中并仍然访问我的班级。在表格中有计时器,我可以在每个刻度上无效,允许在右边使用e.Graphics这是新代码: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
    {
        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;
        }
    }
}

谢谢您的帮助。

以上是关于计时器中的graphics.DrawLine中的“System.ArgumentException参数无效”的主要内容,如果未能解决你的问题,请参考以下文章

C# 在带有 Graphics.DrawLine() 的 PictureBox 上绘制不适用于 Paint 事件

用 g.DrawLine 画一条简单的垂直线

如何画一个 X? [关闭]

winform c# groupBox 的边框颜色 怎么设?

如何设置GroupBox的边框有颜色或有凹凸感

winform如何画表格?