Windows窗体的明显滞后

时间:2018-03-06 20:35:11

标签: c# windows forms graphics

情况

作为一个爱好项目,我决定冒险模拟一个令人难以置信的基本现实。我的想法是创建一个简单的宇宙,其中内部的对象将根据位置或速度等内容进行交互。可以向对象添加力以使其移动。所以要展示这个宇宙'我必须学习窗口的形式。因此,我对这个主题的了解是有限的。我创建了一个计时器对象,以便在每个时间段之后,我会调用Universe的渲染来生成位图并将图片框设置为该位图。为了渲染宇宙,我只是将每个对象渲染到一个位图上,因此几乎可以创建一个对象拼贴。

为了测试这个,我创建了一个Universe,并添加了一个块。在每次更新图像时,我都会根据按键向块添加一个力。然而,我遇到的是该程序有很多视觉和延迟。有时它会非常顺利地运行,但几秒钟后它会冻结半秒钟,然后再次恢复工作。这有点引人注目,虽然不是很糟糕。无论如何,这是我想要解决的问题,并且/或者至少知道为什么这会成为更好的程序员。

我已经研究过修复,但没有成功。我在使用后包含了双缓冲区和处理图形和位图对象之类的东西,但这几乎没有效果。

Just the image of what the forms displays - nothing intersting

问题

造成这种“延迟”的原因是什么?我该怎么做才能解决它?

守则

class Block 
{
    ...

    public Tuple<float, float> Dimension { get; }
    public Tuple<float, float> Position { get; set; }

    ...

    public void Render(Bitmap bitmap)
    {
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            int x = bitmap.Width / 2;
            int y = bitmap.Height / 2;
            //g.DrawRectangle(Pens.Black, 0, 0, 500, 500);
            g.DrawRectangle(Pens.DarkCyan, Position.Item1 + x - Dimension.Item1 / 2, Position.Item2 + y - Dimension.Item2 / 2,
                Dimension.Item1, Dimension.Item2);
        }

    }

}
class Universe
{
    ...

    public List<IObject> Objects;
    public Tuple<int, int> Dimensions;

    ...

    public Bitmap RenderUniverse()
    {
        Bitmap bitmap = new Bitmap(Dimensions.Item1, Dimensions.Item2);
        foreach (var obj in Objects)
        {
            obj.Render(bitmap);
        }
        return bitmap;
    }

}

public partial class Form1 : Form
    {
        Universe Universe;
        const int SPF = 50; //MILLISECONDS TILL NEXT UPDATE
        public Form1()
        {
            Timer timer = new Timer();
            timer.Tick += Update;
            timer.Interval = SPF;
            timer.Start();
            ...
            InitializeComponent();
            DoubleBuffered = true;
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.ClientSize = new System.Drawing.Size(Universe.Dimensions.Item1, Universe.Dimensions.Item2);
            this.pictureBox1.Size = new System.Drawing.Size(Universe.Dimensions.Item1, Universe.Dimensions.Item2);
            this.pictureBox1.SizeMode = PictureBoxSizeMode.Normal;
        }

        public void Update(object o, EventArgs args)
        {
        ...
            if (pictureBox1.Equals(null))
                pictureBox1.Image.Dispose();
            pictureBox1.Image = Universe.RenderUniverse();
            pictureBox1.Refresh();

        }
    }

编辑:

我现在已经拿走了图片框并使用了OnPaint。但是,这还没有解决问题。

2 个答案:

答案 0 :(得分:0)

我怀疑你的代码试图清理旧图像:

if (pictureBox1.Equals(null))
    pictureBox1.Image.Dispose();
pictureBox1.Image = Universe.RenderUniverse();

(特别是第一行)没有按照你的想法做。您的评论表明您相信第一行:

  

检查pictureBox1是否已经有位图(即它是   我第一次渲染宇宙)

实际所做的是检查pictureBox1是否为null(它永远不会出现在您的代码流中)。

要解决此问题,请改为使用:

var oldImage = pictureBox1.Image;
pictureBox1.Image = Universe.RenderUniverse();
oldImage?.Dispose();

答案 1 :(得分:0)

我不会使用图片框,而是直接在表面上绘图。直接在表单上绘制或插入用作框架的控件(例如Panel)并在其上绘制。

你应该在Paint事件中绘制。让我们在表格上画画:

protected override void OnPaint(PaintEventArgs e)
{
    int x = ClientSize.Width / 2;
    int y = ClientSize.Height / 2;
    e.Graphics.DrawRectangle(
        Pens.DarkCyan,
        _currentBlock.Position.Item1 + x - _currentBlock.Dimension.Item1 / 2,
        _currentBlock.Position.Item2 + y - _currentBlock.Dimension.Item2 / 2,
        _currentBlock.Dimension.Item1,
        _currentBlock.Dimension.Item2);
}

然后,只要调用表单的Invalidate方法或您正在使用的控件,就可以在发生更改时触发绘图:

Invalidate();

这样做的好处是不会出现拥塞,因为如果Windows尚未绘制,它只会调用OnPaint

如果您想强制立即更新,请改为呼叫Refresh

Refresh();

完整示例(使用命名空间PointF中的SizeFSystem.Drawing结构而不是元组:

interface IObject
{
    SizeF Dimension { get; }
    PointF Position { get; set; }

    void Render(Graphics g, int x, int y);
}

class Block : IObject
{
    public PointF Position { get; set; }
    public SizeF Dimension { get; set; }

    public void Render(Graphics g, int x, int y)
    {
        g.DrawRectangle(
            Pens.DarkCyan,
            Position.X + x - Dimension.Width / 2,
            Position.Y + y - Dimension.Height / 2,
            Dimension.Width,
            Dimension.Height);
    }
}

public partial class frmPaintOnForm : Form
{
    private Timer _timer = new Timer();

    private IObject _currentObject;
    private List<IObject> _objects = new List<IObject> {
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(10, 0), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(20, 0), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(20,10), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(20,20), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(20,30), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF(10,30), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0,30), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0,20), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0,20), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(55,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(60,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(65,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(60,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(55,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,50) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,55) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,60) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,65) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,60) },
        new Block{ Position = new PointF( 0, 0), Dimension = new SizeF(50,55) },
    };
    private int _index;

    public frmPaintOnForm()
    {
        InitializeComponent();
        DoubleBuffered = true;
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

        _timer.Interval = 17;
        _timer.Tick += Timer_Tick;
        _timer.Start();
    }

    private void Timer_Tick(object sender, EventArgs e)
    {
        // Select the next object by cycling through the object list and trigger drawing
        _currentObject = _objects[_index];
        _index = (_index + 1) % _objects.Count;
        Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (_currentObject != null) {
            int x = ClientSize.Width / 2;
            int y = ClientSize.Height / 2;
            _currentObject.Render(e.Graphics, x, y);
        }
    }
}

请注意,您绝不会直接致电OnPaint。操作系统(Windows)负责这一点。通过调用Invalidate,您只需提示Windows,Windows决定何时以及是否将调用OnPaint。 Windows可以在许多情况下调用它:当窗体被打开或从最小化恢复时,当删除此窗体之上的另一个窗体时,在调整窗体大小时,当然,如果您通过调用{{1}请求它}或Invalidate。当悬停在任务栏上时,它也可以将其称为绘制形式的微缩图像。如果出现拥塞或表格不可见,Windows也可能会决定省略一些电话。