繁体   English   中英

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

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

我试图让我的一个类在我的窗口上画一条闪烁的红线10秒钟,但是我得到了System.ArgumentException: parameter is not valid我的graphics.DrawLine System.ArgumentException: parameter is not valid错误。 为了找到问题,我尝试使用最少的部分来重新创建它。 绘制线条的reddark功能在计时器的aTick事件之外可以完美地工作,但是在被其激活时会给出上述错误。 当图形或Pen对象无效时,其他人则会收到此错误,但是在我看来,这里不是这种情况。

关于我的代码:我是最近才开始编程的,我只听说过有关数据绑定的传说,它可以简化我的代码,但是目前看来,这超出了我的能力,所以我使可能的想法变得很粗糙,但是在布尔值变为true(然后将其变为false)时执行操作的其他解决方法。 这就是我用来开始闪烁以及在计时器的每个刻度上重绘我的图形的方式。 我还需要第二个Redraw bool,因为当我尝试在aTick事件结束时更改redraw true时,它说: 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;
        }
    }
}

您使事情变得太困难了。 另一个答案提供的基本诊断是正确的,但您过分考虑问题会陷入困境。 您的新版本更好,但仍然使事情复杂化。 它无法使用现代的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 paint事件。

尽管文档可能未提及 ,但作为参数传递给Form.Paint事件处理程序的Graphics对象仅在单个事件期间有效。 您的代码将其传递给计时器,该计时器将在事件处理程序退出后很长时间尝试访问和使用它。

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

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

根据汉斯·帕桑特(Hans Passant)的诊断(例如,如果您在确切的时间失效,则只能通过该函数访问图形),我在重组程序后设法解决了该问题。 通过将我的大部分班级设为公开(:(),我可以将计时器放在窗体中并仍然访问我的班级。在窗体中使用计时器之后,我可以在每个刻度上使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;
        }
    }
}

类:

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

谢谢您的帮助。

暂无
暂无

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

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